[
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Discussion\n    url: https://github.com/nats-io/nack/discussions\n    about: Ideal for ideas, feedback, or longer form questions.\n  - name: Chat\n    url: https://slack.nats.io\n    about: Ideal for short, one-off questions, general conversation, and meeting other NATS users!\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/defect.yml",
    "content": "---\nname: Defect\ndescription: Report a defect, such as a bug or regression.\nlabels:\n  - defect\nbody:\n  - type: textarea\n    id: versions\n    attributes:\n      label: What version were you using?\n      description: Include the server version (`nats-server --version`) and any client versions when observing the issue.\n    validations:\n      required: true\n  - type: textarea\n    id: environment\n    attributes:\n      label: What environment was the server running in?\n      description: This pertains to the operating system, CPU architecture, and/or Docker image that was used.\n    validations:\n      required: true\n  - type: textarea\n    id: steps\n    attributes:\n      label: Is this defect reproducible?\n      description: Provide best-effort steps to showcase the defect.\n    validations:\n      required: true\n  - type: textarea\n    id: expected\n    attributes:\n      label: Given the capability you are leveraging, describe your expectation?\n      description: This may be the expected behavior or performance characteristics.\n    validations:\n      required: true\n  - type: textarea\n    id: actual\n    attributes:\n      label: Given the expectation, what is the defect you are observing?\n      description: This may be an unexpected behavior or regression in performance.\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/proposal.yml",
    "content": "---\nname: Proposal\ndescription: Propose an enhancement or new feature.\nlabels:\n  - proposal\nbody:\n  - type: textarea\n    id: usecase\n    attributes:\n      label: What motivated this proposal?\n      description: Describe the use case justifying this request.\n    validations:\n      required: true\n  - type: textarea\n    id: change\n    attributes:\n      label: What is the proposed change?\n      description: This could be a behavior change, enhanced API, or a branch new feature.\n    validations:\n      required: true\n  - type: textarea\n    id: benefits\n    attributes:\n      label: Who benefits from this change?\n      description: Describe how this not only benefits you.\n    validations:\n      required: false\n  - type: textarea\n    id: alternates\n    attributes:\n      label: What alternatives have you evaluated?\n      description: This could be using existing features or relying on an external dependency.\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # version updates: enabled\n  # security updates: enabled\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    cooldown:\n      default-days: 7\n  - package-ecosystem: docker\n    directory: /cicd\n    schedule:\n      interval: daily\n    cooldown:\n      default-days: 7\n\n  # version updates: disabled\n  # security updates: enabled\n  # https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates#overriding-the-default-behavior-with-a-configuration-file\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n    open-pull-requests-limit: 0\n    cooldown:\n      default-days: 7\n"
  },
  {
    "path": ".github/workflows/claude.yml",
    "content": "name: Claude Code\n\n# GITHUB_TOKEN needs contents:read and actions:read — required by\n# claude-code-action for restoring trusted config files from the base branch.\n# All other GitHub API access uses the App token.\npermissions:\n  contents: read\n  actions: read\n\non:\n  issue_comment:\n    types: [created]\n  pull_request_review_comment:\n    types: [created]\n  pull_request_target:\n    types: [opened, reopened]\n\njobs:\n  claude:\n    uses: synadia-io/ai-workflows/.github/workflows/claude.yml@v2\n    with:\n      gh_app_id: ${{ vars.CLAUDE_GH_APP_ID }}\n      checkout_mode: 'base'\n      review_focus: |\n        Additionally focus on:\n        - Kubernetes controller reconciliation correctness (idempotency, status updates, error handling)\n        - Proper use of controller-runtime patterns (watches, predicates, ownership references)\n        - Go error handling (wrapped errors, sentinel errors, no swallowed errors)\n    secrets:\n      claude_oauth_token: ${{ secrets.CLAUDE_OAUTH_TOKEN }}\n      gh_app_private_key: ${{ secrets.CLAUDE_GH_APP_PRIVATE_KEY }}\n"
  },
  {
    "path": ".github/workflows/deps-release-detect.yaml",
    "content": "name: Deps Release\n\non: 'pull_request'\n\npermissions:\n  contents: write\n\njobs:\n  detect:\n    name: Detect\n    runs-on: ubuntu-latest\n    if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n          persist-credentials: false\n\n      - name: Configure Git\n        run: |\n          git config user.name \"$GITHUB_ACTOR\"\n          git config user.email \"$GITHUB_ACTOR@users.noreply.github.com\"\n          git checkout -b \"$GITHUB_HEAD_REF\"\n\n      - name: Dependabot metadata\n        id: dependabot-metadata\n        uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0\n\n      - name: Install node\n        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\n        with:\n          node-version: 18\n      \n      - name: Install semver\n        run: |-\n          npm install -g semver\n\n      - name: Bump\n        run: |-\n          set -e\n          push=0\n          config='[\n            {\n              \"directory\": \"cicd\",\n              \"dependencyName\": \"alpine\"\n            }\n          ]'\n          deps_file=\"./cicd/tag-deps-version.txt\"\n\n          deps=\"${STEPS_DEPENDABOT_METADATA_OUTPUTS_UPDATED_DEPENDENCIES_JSON}\"\n\n          for i in $(seq 0 \"$((\"$(echo \"$config\" | jq length) - 1\"))\"); do\n            directory=\"$(echo \"$config\" | jq -r \".[$i].directory\")\"\n            dependencyName=\"$(echo \"$config\" | jq -r \".[$i].dependencyName\")\"\n            match=\"$(echo \"$deps\" | jq \".[] | select(.directory == \\\"/$directory\\\" and .dependencyName == \\\"$dependencyName\\\")\")\"\n            if [ -z \"$match\" ]; then\n              continue\n            fi\n\n            updateType=\"$(echo \"$match\" | jq -r \".updateType\")\"\n            prevVersion=\"$(echo \"$match\" | jq -r \".prevVersion\")\"\n            newVersion=\"$(echo \"$match\" | jq -r \".newVersion\")\"\n\n            echo \"directory        : $directory\"\n            echo \"dependencyName   : $dependencyName\"\n            echo \"updateType       : $updateType\"\n            echo \"prevVersion      : $prevVersion\"\n            echo \"newVersion       : $newVersion\"\n\n            tagPrevVersion=\"$(git ls-remote 2>/dev/null \\\n              | grep -oE 'refs/tags/v[0-9]+\\.[0-9]+\\.[0-9]+' \\\n              | cut -d'/' -f3 \\\n              | xargs semver \\\n              | tail -n 1)\"\n            \n            tagNewVersion=\"$(semver -i patch \"$tagPrevVersion\")\"\n              \n            echo \"$tagPrevVersion\" > \"$deps_file\"\n            echo \"$tagNewVersion\" >> \"$deps_file\"\n\n            git add \"$deps_file\"\n            if git commit -m \"bump dependency release to $tagNewVersion\"; then\n              push=1\n            fi\n          done\n\n          if [ \"$push\" = \"1\" ]; then\n            git push -u origin \"$GITHUB_HEAD_REF\"\n          fi\n        env:\n          STEPS_DEPENDABOT_METADATA_OUTPUTS_UPDATED_DEPENDENCIES_JSON: ${{ steps.dependabot-metadata.outputs.updated-dependencies-json }}\n"
  },
  {
    "path": ".github/workflows/deps-release-tag.yaml",
    "content": "name: Deps Release\n\non:\n  push:\n    branches:\n      - main\n\npermissions:\n  actions: write\n  contents: write\n\njobs:\n  tag:\n    name: Tag\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n          persist-credentials: false\n\n      - name: Configure Git\n        run: |\n          git config user.name \"$GITHUB_ACTOR\"\n          git config user.email \"$GITHUB_ACTOR@users.noreply.github.com\"\n\n      - id: tag\n        name: Determine tag\n        run: |\n          deps_file=\"./cicd/tag-deps-version.txt\"\n          old_version=\"$(head -n 1 \"$deps_file\")\"\n          old_ref_name=\"v$old_version\"\n          new_version=\"$(tail -n 1 \"$deps_file\")\"\n          new_ref_name=\"v$new_version\"\n\n          create=true\n          if [ \"$(git ls-remote origin \"refs/tags/$new_ref_name\" | wc -l)\" = \"1\" ]; then\n            create=false\n          fi\n\n          echo \"old-version=$old_version\" | tee -a \"$GITHUB_OUTPUT\"\n          echo \"old-ref-name=$old_ref_name\" | tee -a \"$GITHUB_OUTPUT\"\n          echo \"new-version=$new_version\" | tee -a \"$GITHUB_OUTPUT\"\n          echo \"new-ref-name=$new_ref_name\" | tee -a \"$GITHUB_OUTPUT\"\n          echo \"create=$create\" | tee -a \"$GITHUB_OUTPUT\"\n\n      - if: ${{ fromJSON(steps.tag.outputs.create) }}\n        name: Tag\n        run: |\n          commit=\"$(git rev-parse HEAD)\"\n          git fetch origin refs/tags/\"${STEPS_TAG_OUTPUTS_OLD_REF_NAME}\"\n          git checkout -b deps \"${STEPS_TAG_OUTPUTS_OLD_REF_NAME}\"\n          git restore --source=\"$commit\" ./cicd ./.github/workflows/release.yaml\n          git add ./cicd ./.github/workflows/release.yaml\n          if git commit -m \"bump dependency release to ${STEPS_TAG_OUTPUTS_NEW_VERSION}\"; then\n            git tag \"${STEPS_TAG_OUTPUTS_NEW_REF_NAME}\"\n            git push origin \"${STEPS_TAG_OUTPUTS_NEW_REF_NAME}\"\n          fi\n        env:\n          STEPS_TAG_OUTPUTS_OLD_REF_NAME: ${{ steps.tag.outputs.old-ref-name }}\n          STEPS_TAG_OUTPUTS_NEW_VERSION: ${{ steps.tag.outputs.new-version }}\n          STEPS_TAG_OUTPUTS_NEW_REF_NAME: ${{ steps.tag.outputs.new-ref-name }}\n\n      - if: ${{ fromJSON(steps.tag.outputs.create) }}\n        name: Trigger Release\n        run: gh workflow run release.yaml --ref \"${STEPS_TAG_OUTPUTS_NEW_REF_NAME}\"\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          STEPS_TAG_OUTPUTS_NEW_REF_NAME: ${{ steps.tag.outputs.new-ref-name }}\n"
  },
  {
    "path": ".github/workflows/e2e.yaml",
    "content": "name: e2e\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  e2e:\n    name: e2e\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n    steps:\n      - name: checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          persist-credentials: false\n\n      - name: install kuttl\n        run: |\n          curl -L https://github.com/kudobuilder/kuttl/releases/download/v0.24.0/kubectl-kuttl_0.24.0_linux_x86_64 -o /usr/local/bin/kubectl-kuttl\n          chmod +x /usr/local/bin/kubectl-kuttl\n\n      - name: create kind cluster\n        uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0\n        with:\n          install_only: true\n\n      - name: set up helm\n        uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1\n\n      - name: run e2e test\n        run: make test-e2e\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Release\non:\n  workflow_dispatch:\n  push:\n    tags:\n      - v[0-9]+.[0-9]+.[0-9]+\njobs:\n  release:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n    steps:\n    - name: Checkout Source\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        fetch-depth: 0\n        persist-credentials: false\n\n    - name: Setup Go\n      uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0\n      with:\n        go-version-file: go.mod\n        cache: false\n\n    - name: Setup QEMU\n      uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0\n\n    - name: Setup Docker Buildx\n      id: buildx\n      uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0\n\n    - name: Setup Docker Hub\n      uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n      with:\n        username: ${{ secrets.DOCKERHUB_USERNAME }}\n        password: ${{ secrets.DOCKERHUB_CLI_TOKEN }}\n\n    - name: Get Image Tags\n      id: tags\n      run: |\n        version=$(sed 's/^v//' <<< ${GITHUB_REF_NAME})\n        echo tags=\"latest,${version}\" >> $GITHUB_OUTPUT\n\n    - name: Build and Push\n      uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0\n      with:\n        source: .\n        files: docker-bake.hcl\n        push: true\n        set: goreleaser.args.GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}\n      env:\n        TAGS: \"${{ steps.tags.outputs.tags }}\"\n        REGISTRY: \"natsio\"\n\n    - name: Attach Release Files\n      run: gh release upload ${GITHUB_REF_NAME} deploy/crds.yml deploy/rbac.yml\n      env:\n        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "name: Test\non:\n  push:\n    paths-ignore:\n      - '**.md'\n  pull_request:\n    paths-ignore:\n      - '**.md'\n\njobs:\n  test:\n    name: Test\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n    steps:\n    - name: Checkout Source\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n\n    - name: Setup Go\n      id: setup-go\n      uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0\n      with:\n        go-version-file: go.mod\n\n    - name: Build\n      run: make build\n\n    - name: Test\n      run: make test\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\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/jetstream-controller.docker\n/jetstream-controller\n/nats-server-config-reloader\n/nats-server-config-reloader.docker\n/nats-boot-config\n/nats-boot-config.docker\n/tools\n/bin\n/.idea\n\n/kubeconfig\n\n# E2E test generated config\n/tests/nack.yaml\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "version: 2\nproject_name: nack\n\nrelease:\n  name_template: 'Release {{.Tag}}'\n  draft: true\n  skip_upload: true\n  github:\n    owner: nats-io\n    name: nack\n\nbuilds:\n  - id: jetstream-controller\n    main: ./cmd/jetstream-controller\n    binary: jetstream-controller\n    ldflags: &common_ldflags\n      - -s -w -X main.Version={{ if index .Env \"VERSION\" }}{{ .Env.VERSION }}{{ else }}{{ .Version }}{{ end }} -X main.GitInfo={{.ShortCommit}} -X main.BuildTime={{.Date}}\n    tags:\n      - timetzdata\n    env:\n      - CGO_ENABLED=0\n    goos:\n      - linux\n    goarch:\n      - amd64\n      - arm64\n      - arm\n    goarm:\n      - 6\n      - 7\n\n  - id: nats-boot-config\n    main: ./cmd/nats-boot-config\n    binary: nats-boot-config\n    ldflags: *common_ldflags\n    tags:\n      - timetzdata\n    env:\n      - CGO_ENABLED=0\n    goos:\n      - linux\n    goarch:\n      - amd64\n      - arm64\n      - arm\n    goarm:\n      - 6\n      - 7\n\n  - id: nats-server-config-reloader\n    main: ./cmd/nats-server-config-reloader\n    binary: nats-server-config-reloader\n    ldflags: *common_ldflags\n    tags:\n      - timetzdata\n    env:\n      - CGO_ENABLED=0\n    goos:\n      - linux\n    goarch:\n      - amd64\n      - arm64\n      - arm\n    goarm:\n      - 6\n      - 7\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nNACK (NATS Controllers for Kubernetes) is a Go-based Kubernetes operator that manages NATS JetStream resources (Streams, Consumers, Accounts, KeyValue, ObjectStore) via CRDs. It also includes a NATS server config reloader sidecar and a boot config init container.\n\n## Build & Test Commands\n\n```bash\nmake build                          # Build all binaries\nmake jetstream-controller           # Build main controller (with race detector)\nmake nats-server-config-reloader    # Build config reloader sidecar\nmake nats-boot-config               # Build boot config utility\nmake test                           # Run unit tests (go vet + envtest + go test)\nmake test-e2e                       # Run E2E tests with kuttl (requires kind)\nmake generate                       # Regenerate K8s clientset and deepcopy code\nmake clean                          # Remove built binaries\n```\n\nRun a single test package:\n```bash\ngo test -race -cover -count=1 -timeout 30s ./internal/controller/...\ngo test -race -cover -count=1 -timeout 30s ./controllers/jetstream/...\n```\n\nRun a single test:\n```bash\ngo test -race -count=1 -timeout 30s -run TestMyFunction ./internal/controller/...\n```\n\nFormat code: `go fmt ./...`\n\n## Architecture\n\n### Two Controller Modes\n\nThe `jetstream-controller` binary runs in one of two modes:\n\n- **Legacy mode** (default): Event-driven queue processing using custom informer factories. Supports only Stream and Consumer. Code in `controllers/jetstream/`.\n- **Control-loop mode** (`--control-loop`): Full controller-runtime reconciliation loop. Supports all resource types (Stream, Consumer, Account, KeyValue, ObjectStore). Code in `internal/controller/`.\n\nEntry point: `cmd/jetstream-controller/main.go` — the `--control-loop` flag selects which mode to run.\n\n### CRD Types\n\nAll CRDs are API version `jetstream.nats.io/v1beta2`, defined in `pkg/jetstream/apis/jetstream/v1beta2/`:\n- `streamtypes.go`, `consumertypes.go`, `accounttypes.go`, `keyvaluetypes.go`, `objectstoretypes.go`\n\nGenerated client code lives in `pkg/jetstream/generated/` — do not edit manually, run `make generate`.\n\n### Controller Patterns\n\nControllers follow standard Kubernetes operator patterns:\n- **Finalizers** for safe deletion cleanup (defined in `internal/controller/types.go`)\n- **Status conditions** (Ready/Errored) tracked on each resource\n- **State annotations** for reconciliation state tracking (Ready, Reconciling, Errored, Finalizing)\n- **Idempotent reconciliation** — operations must be safe to retry\n- **Owner references** for parent-child relationships (e.g., Consumer → Stream)\n\n### Other Components\n\n- `pkg/natsreloader/` — watches config files and sends SIGHUP to reload NATS server\n- `pkg/bootconfig/` — init container for node-level network config\n\n## Key Dependencies\n\n- `sigs.k8s.io/controller-runtime` — Kubernetes controller framework (control-loop mode)\n- `k8s.io/client-go` — Kubernetes client (legacy mode)\n- `github.com/nats-io/nats.go` — NATS client\n- `github.com/nats-io/jsm.go` — JetStream management\n\n## Review Focus Areas\n\nWhen reviewing changes, pay attention to:\n- Kubernetes controller reconciliation correctness (idempotency, status updates, error handling)\n- Proper use of controller-runtime patterns (watches, predicates, ownership references)\n- Go error handling (wrapped errors, sentinel errors, no swallowed errors)\n\n## Local Development\n\n```bash\n# Build and run against a local kubeconfig\nmake jetstream-controller\n./jetstream-controller -kubeconfig ~/.kube/config -s nats://localhost:4222\n\n# Start a local JetStream-enabled NATS server\nnats-server -DV -js\n\n# Increase log verbosity (klog flags)\n./jetstream-controller -kubeconfig ~/.kube/config -s nats://localhost:4222 -v=10\n```\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "> [!WARNING]\n> This contribution guide is work in progress and is meant to be a location where more developers can contribute.\n\n# Development\nThe codebase is currently fragmented into the refactored solution using today's standards for creating controllers (when\nusing the `--control-loop` argument) and the old variant. The old variant is found in `controllers` directory, while the\nnew code is found in `internal`.\n\n## E2E testing\nYou may run the entire e2e suite with the accompanying updated image using:\n```bash\nmake test-e2e\n```\n\nThis command will:\n1. Build a local Docker image with your changes\n2. Run the full test suite in **legacy controller mode** (using `controllers/` implementation)\n3. Run the full test suite in **control-loop mode** (using `internal/controller/` implementation with `--control-loop` flag)\n\nThis ensures both controller implementations are tested with your changes.\n\n**Requirements:**\n- `kind` must be installed (install via `make install-kind`)\n- `kubectl-kuttl` must be installed (install via `kubectl krew install kuttl`)\n\n# CRD Updates\n\n## Generating types\n```bash\nmake generate\n```\nwill update the generated go structs after having updated the types.\n\n## CRD and docs\nCRD updates & accompanying documentation is currently updated manually.\nTODO to automate this.\n\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.\n"
  },
  {
    "path": "Makefile",
    "content": "export GO111MODULE := on\n\nSHELL=/usr/bin/env bash\n\nENVTEST_K8S_VERSION = 1.32.0\n\nnow := $(shell date -u +%Y-%m-%dT%H:%M:%S%z)\ngitBranch := $(shell git rev-parse --abbrev-ref HEAD)\ngitCommit := $(shell git rev-parse --short HEAD)\nrepoDirty := $(shell git diff --quiet || echo \"-dirty\")\n\nVERSION ?= version-not-set\nlinkerVars := -X main.BuildTime=$(now) -X main.GitInfo=$(gitBranch)-$(gitCommit)$(repoDirty) -X main.Version=$(VERSION)\ndrepo ?= natsio\n\njetstreamSrc := $(shell find cmd/jetstream-controller pkg/jetstream internal/controller controllers/jetstream -name \"*.go\") pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go\n\nconfigReloaderSrc := $(shell find cmd/nats-server-config-reloader/ pkg/natsreloader/ -name \"*.go\")\n\nbootConfigSrc := $(shell find cmd/nats-boot-config/ pkg/bootconfig/ -name \"*.go\")\n\n# You might override this so as to use a more recent version, to update old\n# generated imports, and so migrate away from old import paths and get back to\n# a consistent import tree.\ncodeGeneratorDir ?=\n\ndefault:\n\t# Try these (read Makefile for more recipes):\n\t#   make jetstream-controller\n\t#   make nats-server-config-reloader\n\t#   make nats-boot-config\n\ngenerate: fetch-modules pkg/k8scodegen/file-header.txt\n\trm -rf pkg/jetstream/generated\n\tD=\"$(codeGeneratorDir)\"; : \"$${D:=`go list -m -f '{{.Dir}}' k8s.io/code-generator`}\"; \\\n\tsource \"$$D/kube_codegen.sh\" ; \\\n\tkube::codegen::gen_helpers \\\n\t  --boilerplate pkg/k8scodegen/file-header.txt \\\n\t  pkg/jetstream/apis; \\\n\tkube::codegen::gen_client \\\n\t\t--with-watch \\\n\t\t--with-applyconfig \\\n\t\t--boilerplate pkg/k8scodegen/file-header.txt \\\n\t\t--output-dir pkg/jetstream/generated \\\n\t\t--output-pkg github.com/nats-io/nack/pkg/jetstream/generated \\\n\t\t--one-input-api jetstream/v1beta2 \\\n\t\tpkg/jetstream/apis\n\njetstream-controller: $(jetstreamSrc)\n\tgo build -race -o $@ \\\n\t\t-ldflags \"$(linkerVars)\" \\\n\t\tgithub.com/nats-io/nack/cmd/jetstream-controller\n\njetstream-controller.docker: $(jetstreamSrc)\n\tCGO_ENABLED=0 GOOS=linux go build -o $@ \\\n\t\t-ldflags \"-s -w $(linkerVars)\" \\\n\t\t-tags timetzdata \\\n\t\tgithub.com/nats-io/nack/cmd/jetstream-controller\n\n.PHONY: jetstream-controller-docker\njetstream-controller-docker:\nifneq ($(ver),)\n\tREGISTRY=\"$(drepo)\" \\\n\tTAGS=\"$(ver)\" \\\n\tdocker buildx bake --load \\\n\t\t--set goreleaser.args.VERSION=$(ver) \\\n\t\tjetstream-controller\nelse\n\t# Missing version, try this.\n\t# make jetstream-controller-docker ver=1.2.3\n\texit 1\nendif\n\n.PHONY: jetstream-controller-dockerx\njetstream-controller-dockerx:\nifneq ($(ver),)\n\t# Ensure 'docker buildx ls' shows correct platforms.\n\tREGISTRY=\"$(drepo)\" \\\n\tTAGS=\"$(ver)\" \\\n\tPUSH=true \\\n\tdocker buildx bake --push \\\n\t\t--set goreleaser.args.VERSION=$(ver) \\\n\t\tjetstream-controller\nelse\n\t# Missing version, try this.\n\t# make jetstream-controller-dockerx ver=1.2.3\n\texit 1\nendif\n\nnats-server-config-reloader: $(configReloaderSrc)\n\tgo build -race -o $@ \\\n\t\t-ldflags \"$(linkerVars)\" \\\n\t\tgithub.com/nats-io/nack/cmd/nats-server-config-reloader\n\nnats-server-config-reloader.docker: $(configReloaderSrc)\n\tCGO_ENABLED=0 GOOS=linux go build -o $@ \\\n\t\t-ldflags \"-s -w $(linkerVars)\" \\\n\t\t-tags timetzdata \\\n\t\tgithub.com/nats-io/nack/cmd/nats-server-config-reloader\n\n.PHONY: nats-server-config-reloader-docker\nnats-server-config-reloader-docker:\nifneq ($(ver),)\n\tREGISTRY=\"$(drepo)\" \\\n\tTAGS=\"$(ver)\" \\\n\tdocker buildx bake --load \\\n\t\t--set goreleaser.args.VERSION=$(ver) \\\n\t\tnats-server-config-reloader\nelse\n\t# Missing version, try this.\n\t# make nats-server-config-reloader-docker ver=1.2.3\n\texit 1\nendif\n\n.PHONY: nats-server-config-reloader-dockerx\nnats-server-config-reloader-dockerx:\nifneq ($(ver),)\n\t# Ensure 'docker buildx ls' shows correct platforms.\n\tREGISTRY=\"$(drepo)\" \\\n\tTAGS=\"$(ver)\" \\\n\tPUSH=true \\\n\tdocker buildx bake --push \\\n\t\t--set goreleaser.args.VERSION=$(ver) \\\n\t\tnats-server-config-reloader\nelse\n\t# Missing version, try this.\n\t# make nats-server-config-reloader-dockerx ver=1.2.3\n\texit 1\nendif\n\nnats-boot-config: $(bootConfigSrc)\n\tgo build -race -o $@ \\\n\t\t-ldflags \"$(linkerVars)\" \\\n\t\tgithub.com/nats-io/nack/cmd/nats-boot-config\n\nnats-boot-config.docker: $(bootConfigSrc)\n\tCGO_ENABLED=0 GOOS=linux go build -o $@ \\\n\t\t-ldflags \"-s -w $(linkerVars)\" \\\n\t\t-tags timetzdata \\\n\t\tgithub.com/nats-io/nack/cmd/nats-boot-config\n\n.PHONY: nats-boot-config-docker\nnats-boot-config-docker:\nifneq ($(ver),)\n\tREGISTRY=\"$(drepo)\" \\\n\tTAGS=\"$(ver)\" \\\n\tdocker buildx bake --load \\\n\t\t--set goreleaser.args.VERSION=$(ver) \\\n\t\tnats-boot-config\nelse\n\t# Missing version, try this.\n\t# make nats-boot-config-docker ver=1.2.3\n\texit 1\nendif\n\n.PHONY: nats-boot-config-dockerx\nnats-boot-config-dockerx:\nifneq ($(ver),)\n\t# Ensure 'docker buildx ls' shows correct platforms.\n\tREGISTRY=\"$(drepo)\" \\\n\tTAGS=\"$(ver)\" \\\n\tPUSH=true \\\n\tdocker buildx bake --push \\\n\t\t--set goreleaser.args.VERSION=$(ver) \\\n\t\tnats-boot-config\nelse\n\t# Missing version, try this.\n\t# make nats-boot-config-dockerx ver=1.2.3\n\texit 1\nendif\n\n.PHONY: fetch-modules\n# This will error if we have removed some code to be regenerated, so we instead silence it and force success\nfetch-modules:\n\tgo list -f '{{with .Module}}{{end}}' all >/dev/null 2>&1 || true\n\n.PHONY: build\nbuild: jetstream-controller nats-server-config-reloader nats-boot-config\n\n# Setup envtest tools based on a operator-sdk project makefile\nLOCALBIN ?= $(shell pwd)/bin\n$(LOCALBIN):\n\tmkdir -p $(LOCALBIN)\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 (ideally with version)\n# $2 - package url which can be installed\n# $3 - specific version of package\ndefine go-install-tool\n@[ -f $(1) ] || { \\\nset -e; \\\npackage=$(2)@$(3) ;\\\necho \"Downloading $${package}\" ;\\\nGOBIN=$(LOCALBIN) go install $${package} ;\\\nmv \"$$(echo \"$(1)\" | sed \"s/-$(3)$$//\")\" $(1) ;\\\n}\nendef\n\nENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION)\nENVTEST_VERSION ?= release-0.20\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: test\ntest: envtest\n\tgo vet ./controllers/... ./pkg/natsreloader/... ./internal/controller/...\n\t$(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path ## Get k8s binaries\n\tgo test -race -cover -count=1 -timeout 30s ./controllers/... ./pkg/natsreloader/... ./internal/controller/...\n\n.PHONY: test-e2e\ntest-e2e:\n\t@echo \"Running e2e tests with kuttl...\"\n\t@command -v kubectl-kuttl >/dev/null 2>&1 || { echo \"kuttl not installed. Install: kubectl krew install kuttl\"; exit 1; }\n\tkind delete cluster || true\n\tkind create cluster\n\tdocker build -t nack:test -f tests/Dockerfile .\n\tkind load docker-image nack:test\n\t@echo \"\\n=== Testing LEGACY controller mode ===\"\n\tcp tests/nack-legacy.yaml tests/nack.yaml && kubectl kuttl test\n\t@echo \"\\n=== Testing CONTROL-LOOP controller mode ===\"\n\tcp tests/nack-control-loop.yaml tests/nack.yaml && kubectl kuttl test\n\trm -f tests/nack.yaml\n\n.PHONY: clean\nclean:\n\trm -f jetstream-controller jetstream-controller.docker \\\n\t\tnats-server-config-reloader nats-server-config-reloader.docker \\\n\t\tnats-boot-config nats-boot-config.docker\n\n.PHONY: install-kind\ninstall-kind:\n\tgo install sigs.k8s.io/kind@v0.30.0\n"
  },
  {
    "path": "README.md",
    "content": "<img width=\"800\" alt=\"nack-large\" src=\"https://user-images.githubusercontent.com/26195/92535603-71ad9a80-f1ec-11ea-8959-cdc22b31b84a.png\">\n\n[![License Apache 2](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)\n[![Release Badge](https://github.com/nats-io/nack/actions/workflows/release.yaml/badge.svg)](https://github.com/nats-io/nack/actions/workflows/release.yaml)\n[![E2E Badge](https://github.com/nats-io/nack/actions/workflows/e2e.yaml/badge.svg)](https://github.com/nats-io/nack/actions/workflows/e2e.yaml)\n[![TEST Badge](https://github.com/nats-io/nack/actions/workflows/test.yaml/badge.svg)](https://github.com/nats-io/nack/actions/workflows/test.yaml)\n\n[NATS](https://nats.io) Controllers for Kubernetes (NACK)\n\n## Table of Contents\n\n- [JetStream Controller](#jetstream-controller)\n  - [Controller Modes](#controller-modes)\n  - [Getting Started](#getting-started)\n  - [Managing Multiple NATS Systems and Accounts](#managing-multiple-nats-systems-and-accounts)\n  - [Creating NATS Resources](#creating-nats-resources)\n  - [Getting Started with Accounts](#getting-started-with-accounts)\n  - [Local Development](#local-development)\n- [NATS Server Config Reloader](#nats-server-config-reloader)\n- [NATS Boot Config](#nats-boot-config)\n\n## JetStream Controller\n\nThe JetStream controllers allows you to manage [NATS JetStream](https://docs.nats.io/nats-concepts/jetstream) resources via Kubernetes CRDs.\n\n### Controller Modes\n\nNACK supports two controller modes with different capabilities:\n\n| Mode | Streams | Consumers | Key/Value | Object Store | Accounts |\n|------|---------|-----------|-----------|--------------|----------|\n| **Legacy (default)** | ✅ | ✅ | ❌ | ❌ | ❌ |\n| **Control-loop** (`--control-loop`) | ✅ | ✅ | ✅ | ✅ | ✅ |\n\n> **Important**: Key/Value stores and Object stores are **only supported in control-loop mode**. If you create KeyValue or ObjectStore resources without enabling control-loop mode, they will not be reconciled.\n\nResources managed by NACK controllers are expected to _exclusively_ be managed by NACK, and configuration state will be enforced if mutated by an external client.\n\n## [API Reference](docs/api.md)\n\nThe API reference documents all available CRD fields for Streams, Consumers, KeyValue, ObjectStore, and Account resources.\n\n### Getting started\n\nInstall with Helm:\n\n```sh\nhelm repo add nats https://nats-io.github.io/k8s/helm/charts/\nhelm repo update\n\nhelm upgrade --install nats nats/nats \\\n  --set config.jetstream.enabled=true \\\n  --set config.jetstream.memoryStore.enabled=true \\\n  --set config.cluster.enabled=true --wait\n\nhelm upgrade --install nack nats/nack \\\n  --set jetstream.nats.url=nats://nats.default.svc.cluster.local:4222 --wait\n```\n\n#### (Optional) Enable Experimental `controller-runtime` Controllers\n\n> **Note**: The updated controllers will more reliably enforce resource state. If migrating from an older version of NACK, as long as all NATS resources are in-sync with NACK resources no modifications are expected.\n>\n> The `jetstream-controller` logs will contain a diff of any changes the controller has made.\n\n```sh\nhelm upgrade nack nats/nack \\\n  --set jetstream.nats.url=nats://nats.default.svc.cluster.local:4222 \\\n  --set jetstream.additionalArgs={--control-loop} --wait\n```\n\n### Managing Multiple NATS Systems and Accounts\n\nThere are several approaches for managing multiple NATS Systems with NACK within one Kubernetes cluster. These options are not mutually exclusive.\n\n#### 1. Run Multiple Namespaced Controllers\nYou can run multiple NACK controllers on the same Kubernetes cluster. Add `--set config.namespaced=true` to your install flags or set `namespaced: true` in your `values.yaml`. When set, the controller will only reconcile resources within its own namespace.\n\n```sh\nhelm upgrade --install nack nats/nack \\\n  --create-namespace --namespace nats \\\n  --set namespaced=true \\\n  --set jetstream.nats.url=nats://nats.nats.svc.cluster.local:4222 --wait\n```\n\n#### 2. Use the Accounts Resource\nThe Accounts resource acts as a connection config for other resources. You may define multiple accounts for the same, or for distinct, NATS Systems.\n\n```yaml\n---\napiVersion: jetstream.nats.io/v1beta2\nkind: Account\nmetadata:\n  name: a\nspec:\n  name: a\n  creds:\n    secret:\n      name: account-a-creds\n  servers:\n    - nats://nats.nats-a.svc.cluster.local\n---\napiVersion: jetstream.nats.io/v1beta2\nkind: Account\nmetadata:\n  name: b\nspec:\n  name: b\n  creds:\n    secret:\n      name: account-b-creds\n  servers:\n    - nats://nats.nats-b.svc.cluster.local\n---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: foo-a\nspec:\n  name: foo\n  subjects: [\"foo\", \"foo.>\"]\n  storage: file\n  replicas: 3\n  maxAge: 1h\n  account: a\n---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: foo-b\nspec:\n  name: foo\n  subjects: [\"foo\", \"foo.>\"]\n  storage: file\n  replicas: 3\n  maxAge: 1h\n  account: b\n```\n\nThe above manifests will define two Account resources, each pulling credentials from a Kubernetes secret. Account `a` is configured to use the NATS Cluster in namespace `nats-a` and Account `b` is configured to use the NATS Cluster in namespace `nats-b`. The NATS clusters do not need to be in Kubernetes, this is just an example.\n\nThis will also create an identical stream, `foo`, in each cluster. **Note:** The resource names, `foo-a` and `foo-b`, must be distinct to not conflict as Kubernetes resources, but the stream names themselves are both `foo`.\n\nSee more details in the [Getting Started with Accounts](#getting-started-with-accounts) section.\n\n#### 3. Define Connection Config in the CRD Manifest\nYou may define some connection options within the resource manifests directly. If not running in the newer `--control-loop` mode, set `--crd-connect`.\n\nIf running with `--control-loop`, resource-level connection config will always override any global config.\n\n> **Note**: The `--crd-connect` flag is not required if running with `--control-loop`.\n\n```sh\nhelm upgrade nack nats/nack \\\n  --set jetstream.additionalArgs={--crd-connect} --wait\n```\n\n#### Example Stream:\n```yaml\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: bar\nspec:\n  name: bar\n  subjects: [\"bar\", \"bar.>\"]\n  storage: file\n  replicas: 3\n  maxAge: 1h\n  servers:\n    - nats://nats.nats.svc.cluster.local:4222\n```\n\n### Creating NATS Resources\n\nLet's create a stream and a couple of consumers:\n\n```yaml\n---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: mystream\nspec:\n  name: mystream\n  subjects: [\"orders.*\"]\n  storage: memory\n  maxAge: 1h\n---\napiVersion: jetstream.nats.io/v1beta2\nkind: Consumer\nmetadata:\n  name: my-push-consumer\nspec:\n  streamName: mystream\n  durableName: my-push-consumer\n  deliverSubject: my-push-consumer.orders\n  deliverPolicy: last\n  ackPolicy: none\n  replayPolicy: instant\n---\napiVersion: jetstream.nats.io/v1beta2\nkind: Consumer\nmetadata:\n  name: my-pull-consumer\nspec:\n  streamName: mystream\n  durableName: my-pull-consumer\n  deliverPolicy: all\n  filterSubject: orders.received\n  maxDeliver: 20\n  ackPolicy: explicit\n---\n# Note: KeyValue requires control-loop mode to be enabled\napiVersion: jetstream.nats.io/v1beta2\nkind: KeyValue\nmetadata:\n  name: my-key-value\nspec:\n  bucket: my-key-value\n  history: 20\n  storage: file\n  maxBytes: 2048\n  compression: true\n---\n# Note: ObjectStore requires control-loop mode to be enabled\napiVersion: jetstream.nats.io/v1beta2\nkind: ObjectStore\nmetadata:\n  name: my-object-store\nspec:\n  bucket: my-object-store\n  storage: file\n  replicas: 1\n  maxBytes: 536870912 # 512 MB\n  compression: true\n```\n\n```sh\n# Create a stream.\n$ kubectl apply -f https://raw.githubusercontent.com/nats-io/nack/main/deploy/examples/stream.yml\n\n# Check if it was successfully created.\n$ kubectl get streams\nNAME       STATE     STREAM NAME   SUBJECTS\nmystream   Ready     mystream      [orders.*]\n\n# Create a push-based consumer\n$ kubectl apply -f https://raw.githubusercontent.com/nats-io/nack/main/deploy/examples/consumer_push.yml\n\n# Create a pull based consumer\n$ kubectl apply -f https://raw.githubusercontent.com/nats-io/nack/main/deploy/examples/consumer_pull.yml\n\n# Check if they were successfully created.\n$ kubectl get consumers\nNAME               STATE     STREAM     CONSUMER           ACK POLICY\nmy-pull-consumer   Ready     mystream   my-pull-consumer   explicit\nmy-push-consumer   Ready     mystream   my-push-consumer   none\n\n# If you end up in an Errored state, run kubectl describe for more info.\n#     kubectl describe streams mystream\n#     kubectl describe consumers my-pull-consumer\n```\n\nNow we're ready to use Streams and Consumers. Let's start off with writing some\ndata into `mystream`.\n\n```sh\n# Run nats-box that includes the NATS management utilities, and exec into it.\n$ kubectl exec -it deployment/nats-box -- /bin/sh -l\n\n# Publish a couple of messages from nats-box\nnats-box:~$ nats pub orders.received \"order 1\"\nnats-box:~$ nats pub orders.received \"order 2\"\n```\n\nFirst, we'll read the data using a pull-based consumer.\n\nFrom the above `my-pull-consumer` Consumer CRD, we have set the filterSubject\nof `orders.received`. You can double check with the following command:\n\n```sh\n$ kubectl get consumer my-pull-consumer -o jsonpath={.spec.filterSubject}\norders.received\n```\n\nSo that's the subject my-pull-consumer will pull messages from.\n\n```sh\n# Pull first message.\nnats-box:~$ nats consumer next mystream my-pull-consumer\n--- subject: orders.received / delivered: 1 / stream seq: 1 / consumer seq: 1\n\norder 1\n\nAcknowledged message\n\n# Pull next message.\nnats-box:~$ nats consumer next mystream my-pull-consumer\n--- subject: orders.received / delivered: 1 / stream seq: 2 / consumer seq: 2\n\norder 2\n\nAcknowledged message\n```\n\nNext, let's read data using a push-based consumer.\n\nFrom the above `my-push-consumer` Consumer CRD, we have set the deliverSubject\nof `my-push-consumer.orders`, as you can confirm with the following command:\n\n```sh\n$ kubectl get consumer my-push-consumer -o jsonpath={.spec.deliverSubject}\nmy-push-consumer.orders\n```\n\nSo pushed messages will arrive on that subject. This time all messages arrive automatically.\n\n```sh\nnats-box:~$ nats sub my-push-consumer.orders\n17:57:24 Subscribing on my-push-consumer.orders\n[#1] Received JetStream message: consumer: mystream > my-push-consumer / subject: orders.received /\ndelivered: 1 / consumer seq: 1 / stream seq: 1 / ack: false\norder 1\n\n[#2] Received JetStream message: consumer: mystream > my-push-consumer / subject: orders.received /\ndelivered: 1 / consumer seq: 2 / stream seq: 2 / ack: false\norder 2\n```\n\n### Getting Started with Accounts\n\nYou can create an Account resource with the following CRD. The Account resource\ncan be used to specify server and TLS information.\n\n> **Note** The `Account` resource does not create or manage NATS accounts. It functions as a connection and authentication config for the managed resources.\n\nThe [nsc](https://docs.nats.io/using-nats/nats-tools/nsc/basics#creating-an-operator-account-and-user) tool can be used to manage your NATS account configuration on the server-side. You can find more details about NATS decentralized auth in the [docs](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/auth_intro/jwt).\n\n```yaml\n---\napiVersion: jetstream.nats.io/v1beta2\nkind: Account\nmetadata:\n  name: a\nspec:\n  name: a\n  servers:\n    - nats://nats:4222\n  tls:\n    secret:\n      name: nack-a-tls\n    ca: \"ca.crt\"\n    cert: \"tls.crt\"\n    key: \"tls.key\"\n```\n\nYou can then link an Account to a Stream so that the Stream uses the Account\ninformation for its creation.\n\n```yaml\n---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: foo\nspec:\n  name: foo\n  subjects: [\"foo\", \"foo.>\"]\n  storage: file\n  replicas: 1\n  account: a # <-- Create stream using account A information\n```\n\nThe following is an example of how to get Accounts working with a custom NATS\nServer URL and TLS certificates.\n\n```sh\n# Install cert-manager\nkubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.17.0/cert-manager.yaml\n\n# Install TLS certs\ncd examples/secure\n\n# Install certificate issuer\nkubectl apply -f issuer.yaml\n\n# Install account A cert\nkubectl apply -f nack-a-client-tls.yaml\n\n# Install server cert\nkubectl apply -f server-tls.yaml\n\n# Install nats-box cert\nkubectl apply -f client-tls.yaml\n\n# Install NATS cluster\nhelm upgrade --install -f nats-helm.yaml nats nats/nats\n\n# Verify pods are healthy\nkubectl get pods\n\n# Install JetStream Controller from nack\nhelm upgrade --install nack nats/nack --set jetstream.enabled=true\n\n# Verify pods are healthy\nkubectl get pods\n\n# Create account A resource\nkubectl apply -f nack/nats-account-a.yaml\n\n# Create stream using account A\nkubectl apply -f nack/nats-stream-foo-a.yaml\n\n# Create consumer using account A\nkubectl apply -f nack/nats-consumer-bar-a.yaml\n```\n\nAfter Accounts, Streams, and Consumers are created, let's log into the nats-box\ncontainer to run the management CLI.\n\n```sh\n# Get container shell\nkubectl exec -it deployment/nats-box -- /bin/sh -l\n```\n\nThere should now be some Streams available, verify with `nats` command.\n\n```sh\n# List streams\nnats stream ls\n```\n\nYou can now publish messages on a Stream.\n\n```sh\n# Push message\nnats pub foo hi\n```\n\nAnd pull messages from a Consumer.\n\n```sh\n# Pull message\nnats consumer next foo bar\n```\n\n### Local Development\n\n```sh\n# First, build the jetstream controller.\nmake jetstream-controller\n\n# Next, run the controller like this\n./jetstream-controller -kubeconfig ~/.kube/config -s nats://localhost:4222\n\n# Pro tip: jetstream-controller uses klog just like kubectl or kube-apiserver.\n# This means you can change the verbosity of logs with the -v flag.\n#\n# For example, this prints raw HTTP requests and responses.\n#     ./jetstream-controller -v=10\n\n# You'll probably want to start a local Jetstream-enabled NATS server, unless\n# you use a public one.\nnats-server -DV -js\n```\n\nBuild Docker image\n\n```sh\nmake jetstream-controller-docker ver=1.2.3\n```\n\n## NATS Server Config Reloader\n\nThis is a sidecar that you can use to automatically reload your NATS Server\nconfiguration file.\n\n### Installing with Helm\n\nFor more information see the\n[Chart repo](https://github.com/nats-io/k8s/tree/main/helm/charts/nats).\n\n```sh\nhelm repo add nats https://nats-io.github.io/k8s/helm/charts/\nhelm upgrade --install nats nats/nats\n```\n\n### Configuring\n\n```yaml\nreloader:\n  enabled: true\n  image: natsio/nats-server-config-reloader:0.16.1\n  pullPolicy: IfNotPresent\n```\n\n### Local Development\n\n```sh\n# First, build the config reloader.\nmake nats-server-config-reloader\n\n# Next, run the reloader like this\n./nats-server-config-reloader\n```\n\nBuild Docker image\n\n```sh\nmake nats-server-config-reloader-docker ver=1.2.3\n```\n\n## NATS Boot Config\n\nA helper utility used during NATS server pod initialization to generate and manage boot-time configuration.\n\n### Installing with Helm\n\nFor more information see the\n[Chart repo](https://github.com/nats-io/k8s/tree/main/helm/charts/nats).\n\n```sh\nhelm repo add nats https://nats-io.github.io/k8s/helm/charts/\nhelm upgrade --install nats nats/nats\n```\n\n### Configuring\n\n```yaml\nbootconfig:\n  image: natsio/nats-boot-config:0.16.1\n  pullPolicy: IfNotPresent\n```\n\n### Local Development\n\n```sh\n# First, build the project.\nmake nats-boot-config\n\n# Next, run the project like this\n./nats-boot-config\n```\n\nBuild Docker image\n\n```sh\nmake nats-boot-config-docker ver=1.2.3\n```\n"
  },
  {
    "path": "cicd/Dockerfile",
    "content": "#syntax=docker/dockerfile:1.13\nARG GO_APP\n\nFROM alpine:3.23.3 AS deps\n\nARG GO_APP\nARG GORELEASER_DIST_DIR=/go/src/dist\n\nARG TARGETOS\nARG TARGETARCH\nARG TARGETVARIANT\n\nRUN mkdir -p /go/bin /go/src ${GORELEASER_DIST_DIR}\n\nCOPY --from=build ${GORELEASER_DIST_DIR}/ ${GORELEASER_DIST_DIR}\n\nRUN <<EOT\n  set -e \n  apk add --no-cache ca-certificates jq\n  cd ${GORELEASER_DIST_DIR}/..\n\n  if [[ ${TARGETARCH} == \"arm\" ]]; then VARIANT=$(echo ${TARGETVARIANT} | sed 's/^v//'); fi\n  BIN_PATH=$(jq -r \".[] |select(.type   == \\\"Binary\\\" and \\\n                                .name   == \\\"${GO_APP}\\\" and \\\n                                .goos   == \\\"${TARGETOS}\\\" and \\\n                                .goarch == \\\"${TARGETARCH}\\\" and \\\n                                (.goarm == \\\"${VARIANT}\\\" or .goarm == null)) | .path\" < /go/src/dist/artifacts.json)\n  cp ${BIN_PATH} /go/bin\nEOT\n\nFROM alpine:3.23.3\n\nARG GO_APP\nENV GO_APP ${GO_APP}\n\nCOPY --from=deps --chmod=755 /go/bin/${GO_APP} /usr/local/bin/${GO_APP}\n\nCOPY --from=deps /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n\nCOPY --from=assets entrypoint.sh /entrypoint.sh\n\nRUN ln -s /usr/local/bin/${GO_APP} /${GO_APP} && chmod +x /entrypoint.sh\n\nENTRYPOINT [\"/entrypoint.sh\"]\n"
  },
  {
    "path": "cicd/Dockerfile_goreleaser",
    "content": "#syntax=docker/dockerfile:1.13\nFROM --platform=$BUILDPLATFORM golang:1.25.6-bookworm AS build\n\n\nRUN <<EOT\n    set -e\n\n    echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' > /etc/apt/sources.list.d/goreleaser.list\n    apt-get update\n    apt-get install -y goreleaser\n    rm -rf /var/lib/apt/lists/*\nEOT\n\nFROM build\n\nARG CI\nARG PUSH\nARG GITHUB_TOKEN\nARG TAGS\nARG VERSION\n\nCOPY --from=src . /go/src\n\nRUN <<EOT\n  set -e\n  cd /go/src\n  FLAGS=\"--clean\"\n  if [ -z ${GITHUB_TOKEN} ]; then\n    if [ ${CI} != \"true\" ]; then FLAGS=\"${FLAGS} --skip=validate\"; fi\n    if [ ${PUSH} != \"true\" ]; then FLAGS=\"${FLAGS} --single-target\"; fi\n    goreleaser build ${FLAGS}\n  else\n    goreleaser release ${FLAGS}\n  fi\nEOT\n"
  },
  {
    "path": "cicd/assets/entrypoint.sh",
    "content": "#!/bin/sh\nexec \"/${GO_APP}\" \"$@\"\n"
  },
  {
    "path": "cicd/tag-deps-version.txt",
    "content": "0.21.0\n0.21.1\n"
  },
  {
    "path": "cmd/jetstream-controller/main.go",
    "content": "// Copyright 2020-2023 The NATS 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//\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\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/nats-io/nack/controllers/jetstream\"\n\t\"github.com/nats-io/nack/internal/controller\"\n\tv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tclientset \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\t\"k8s.io/client-go/kubernetes\"\n\tclientgoscheme \"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\tklog \"k8s.io/klog/v2\"\n\t\"sigs.k8s.io/controller-runtime/pkg/cache\"\n\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\"\n)\n\nvar (\n\tBuildTime = \"build-time-not-set\"\n\tGitInfo   = \"gitinfo-not-set\"\n\tVersion   = \"not-set\"\n)\n\nfunc main() {\n\tif err := run(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Error: %s\\n\", err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc run() error {\n\tklog.InitFlags(nil)\n\t// Opt into the new klog behavior so that -stderrthreshold is honored even\n\t// when -logtostderr=true (the default).\n\t// Ref: kubernetes/klog#212, kubernetes/klog#432\n\t_ = flag.Set(\"legacy_stderr_threshold_behavior\", \"false\")\n\t_ = flag.Set(\"stderrthreshold\", \"INFO\")\n\n\t// Explicitly register controller-runtime flags\n\tctrl.RegisterFlags(nil)\n\n\tnamespace := flag.String(\"namespace\", v1.NamespaceAll, \"Restrict to a namespace\")\n\tversion := flag.Bool(\"version\", false, \"Print the version and exit\")\n\tcreds := flag.String(\"creds\", \"\", \"NATS Credentials\")\n\tnkey := flag.String(\"nkey\", \"\", \"NATS NKey\")\n\tcert := flag.String(\"tlscert\", \"\", \"NATS TLS public certificate\")\n\tkey := flag.String(\"tlskey\", \"\", \"NATS TLS private key\")\n\tca := flag.String(\"tlsca\", \"\", \"NATS TLS certificate authority chain\")\n\ttlsfirst := flag.Bool(\"tlsfirst\", false, \"If enabled, forces explicit TLS without waiting for Server INFO\")\n\tserver := flag.String(\"s\", \"\", \"NATS Server URL\")\n\tcrdConnect := flag.Bool(\"crd-connect\", false, \"If true, then NATS connections will be made from CRD config, not global config. Ignored if running with control loop, CRD options will always override global config\")\n\tcleanupPeriod := flag.Duration(\"cleanup-period\", 30*time.Second, \"Period to run object cleanup\")\n\treadOnly := flag.Bool(\"read-only\", false, \"Starts the controller without causing changes to the NATS resources\")\n\tcacheDir := flag.String(\"cache-dir\", \"\", \"Directory to store cached credential and TLS files\")\n\tcontrolLoop := flag.Bool(\"control-loop\", false, \"Experimental: Run controller with a full reconciliation control loop\")\n\tcontrolLoopSyncInterval := flag.Duration(\"sync-interval\", time.Minute, \"Interval to perform scheduled reconcile\")\n\thealthProbeBindAddress := flag.String(\"health-probe-bind-address\", \":8081\", \"The address the probe endpoint binds to\")\n\n\tflag.Parse()\n\n\tif *version {\n\t\tfmt.Printf(\"%s version %s (%s), built %s\\n\", os.Args[0], Version, GitInfo, BuildTime)\n\t\treturn nil\n\t}\n\n\tif *server == \"\" && !*crdConnect {\n\t\treturn errors.New(\"NATS Server URL is required\")\n\t}\n\n\tconfig, err := ctrl.GetConfig()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"get kubernetes rest config: %w\", err)\n\t}\n\n\tif *controlLoop {\n\t\tklog.Warning(\"Starting JetStream controller in experimental control loop mode\")\n\n\t\tnatsCfg := &controller.NatsConfig{\n\t\t\tClientName:  \"jetstream-controller\",\n\t\t\tCredentials: *creds,\n\t\t\tNKey:        *nkey,\n\t\t\tServerURL:   *server,\n\t\t\tCAs:         []string{},\n\t\t\tCertificate: *cert,\n\t\t\tKey:         *key,\n\t\t\tTLSFirst:    *tlsfirst,\n\t\t}\n\n\t\tif *ca != \"\" {\n\t\t\tnatsCfg.CAs = []string{*ca}\n\t\t}\n\n\t\tcontrollerCfg := &controller.Config{\n\t\t\tReadOnly:               *readOnly,\n\t\t\tNamespace:              *namespace,\n\t\t\tCacheDir:               *cacheDir,\n\t\t\tRequeueInterval:        *controlLoopSyncInterval,\n\t\t\tHealthProbeBindAddress: *healthProbeBindAddress,\n\t\t}\n\n\t\treturn runControlLoop(config, natsCfg, controllerCfg)\n\t}\n\n\t// K8S API Client.\n\tkc, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// JetStream CRDs client.\n\tjc, err := clientset.NewForConfig(config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tctrl := jetstream.NewController(jetstream.Options{\n\t\t// FIXME: Move context to be param from Run\n\t\t// to avoid keeping state in options.\n\t\tCtx:             ctx,\n\t\tNATSCredentials: *creds,\n\t\tNATSNKey:        *nkey,\n\t\tNATSServerURL:   *server,\n\t\tNATSCA:          *ca,\n\t\tNATSCertificate: *cert,\n\t\tNATSKey:         *key,\n\t\tNATSTLSFirst:    *tlsfirst,\n\t\tKubeIface:       kc,\n\t\tJetstreamIface:  jc,\n\t\tNamespace:       *namespace,\n\t\tCRDConnect:      *crdConnect,\n\t\tCleanupPeriod:   *cleanupPeriod,\n\t\tReadOnly:        *readOnly,\n\t})\n\n\tklog.Infof(\"Starting %s v%s...\", os.Args[0], Version)\n\tklog.Infof(\"Running in LEGACY mode\")\n\tif *readOnly {\n\t\tklog.Infof(\"Running in read-only mode: JetStream state in server will not be changed\")\n\t}\n\tgo handleSignals(cancel)\n\treturn ctrl.Run()\n}\n\nfunc runControlLoop(config *rest.Config, natsCfg *controller.NatsConfig, controllerCfg *controller.Config) error {\n\t// Setup scheme\n\tscheme := runtime.NewScheme()\n\tutilruntime.Must(clientgoscheme.AddToScheme(scheme))\n\tutilruntime.Must(v1beta2.AddToScheme(scheme))\n\n\tlog.SetLogger(klog.NewKlogr())\n\n\tctrlOpts := ctrl.Options{\n\t\tScheme:                 scheme,\n\t\tLogger:                 log.Log,\n\t\tHealthProbeBindAddress: controllerCfg.HealthProbeBindAddress,\n\t}\n\n\tif controllerCfg.Namespace != \"\" {\n\t\tctrlOpts.Cache = cache.Options{\n\t\t\tDefaultNamespaces: map[string]cache.Config{\n\t\t\t\tcontrollerCfg.Namespace: {},\n\t\t\t},\n\t\t}\n\t}\n\n\tmgr, err := ctrl.NewManager(config, ctrlOpts)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to start manager: %w\", err)\n\t}\n\n\tif controllerCfg.CacheDir == \"\" {\n\t\tcacheDir, err := os.MkdirTemp(\".\", \"nack\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"create cache dir: %w\", err)\n\t\t}\n\t\tdefer os.RemoveAll(cacheDir)\n\t\tcacheDir, err = filepath.Abs(cacheDir)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"get absolute cache dir: %w\", err)\n\t\t}\n\t\tcontrollerCfg.CacheDir = cacheDir\n\t} else {\n\t\tif _, err := os.Stat(controllerCfg.CacheDir); os.IsNotExist(err) {\n\t\t\terr = os.MkdirAll(controllerCfg.CacheDir, 0o755)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"create cache dir: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\terr = controller.RegisterAll(mgr, natsCfg, controllerCfg)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"register jetstream controllers: %w\", err)\n\t}\n\n\tif err := mgr.AddHealthzCheck(\"healthz\", healthz.Ping); err != nil {\n\t\treturn fmt.Errorf(\"unable to set up health check: %w\", err)\n\t}\n\tif err := mgr.AddReadyzCheck(\"readyz\", healthz.Ping); err != nil {\n\t\treturn fmt.Errorf(\"unable to set up ready check: %w\", err)\n\t}\n\n\tklog.Info(\"starting manager\")\n\tklog.Infof(\"Running in CONTROL-LOOP mode\")\n\tif controllerCfg.ReadOnly {\n\t\tklog.Infof(\"Running in read-only mode: JetStream state in server will not be changed\")\n\t}\n\treturn mgr.Start(ctrl.SetupSignalHandler())\n}\n\nfunc handleSignals(cancel context.CancelFunc) {\n\tsigc := make(chan os.Signal, 2)\n\tsignal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)\n\n\tfor sig := range sigc {\n\t\tswitch sig {\n\t\tcase syscall.SIGINT:\n\t\t\tos.Exit(130)\n\t\tcase syscall.SIGTERM:\n\t\t\tcancel()\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/nats-boot-config/main.go",
    "content": "// Copyright 2018 The NATS 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//\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\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/nats-io/nack/pkg/bootconfig\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tBuildTime = \"build-time-not-set\"\n\tGitInfo   = \"gitinfo-not-set\"\n\tVersion   = \"version-not-set\"\n)\n\nfunc main() {\n\tfs := flag.NewFlagSet(\"nats-pod-bootconfig\", flag.ExitOnError)\n\tflag.Usage = func() {\n\t\tfmt.Fprintf(os.Stderr, \"Usage: nats-pod-bootconfig [options...]\\n\\n\")\n\t\tfs.PrintDefaults()\n\t\tfmt.Fprintf(os.Stderr, \"\\n\")\n\t}\n\n\tvar opts *bootconfig.Options = &bootconfig.Options{}\n\tvar showHelp, showVersion bool\n\tfs.BoolVar(&showHelp, \"h\", false, \"Show help\")\n\tfs.BoolVar(&showVersion, \"v\", false, \"Show version\")\n\tfs.StringVar(&opts.ClientAdvertiseFileName, \"f\", \"client_advertise.conf\", \"File name where the client advertise address will be written into\")\n\tfs.StringVar(&opts.GatewayAdvertiseFileName, \"gf\", \"gateway_advertise.conf\", \"File name where the gateway advertise address will be written into\")\n\tfs.StringVar(&opts.TargetTag, \"t\", \"nats.io/node-external-ip\", \"Tag that will be looked up from a node\")\n\tfs.Parse(os.Args[1:])\n\n\tswitch {\n\tcase showHelp:\n\t\tflag.Usage()\n\t\tos.Exit(0)\n\tcase showVersion:\n\t\tfmt.Fprintf(os.Stderr, \"NATS Server Pod boot config v%s (%s, %s)\\n\", Version, GitInfo, BuildTime)\n\t\tos.Exit(0)\n\t}\n\tif os.Getenv(\"DEBUG\") == \"true\" {\n\t\tlog.SetLevel(log.DebugLevel)\n\t}\n\tformatter := &log.TextFormatter{\n\t\tFullTimestamp: true,\n\t}\n\tlog.SetFormatter(formatter)\n\n\tcontroller := bootconfig.NewController(opts)\n\tlog.Infof(\"Starting NATS Server boot config v%s (%s, %s)\", Version, GitInfo, BuildTime)\n\terr := controller.Run(context.Background())\n\tif err != nil && err != context.Canceled {\n\t\tlog.Fatalf(err.Error())\n\t}\n}\n"
  },
  {
    "path": "cmd/nats-server-config-reloader/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/nats-io/nack/pkg/natsreloader\"\n)\n\nvar (\n\tBuildTime = \"build-time-not-set\"\n\tGitInfo   = \"gitinfo-not-set\"\n\tVersion   = \"version-not-set\"\n)\n\n// StringSet is a wrapper for []string to allow using it with the flags package.\ntype StringSet []string\n\nfunc (s *StringSet) String() string {\n\treturn strings.Join([]string(*s), \", \")\n}\n\n// Set appends the value provided to the list of strings.\nfunc (s *StringSet) Set(val string) error {\n\t*s = append(*s, val)\n\treturn nil\n}\n\nfunc main() {\n\tfs := flag.NewFlagSet(\"nats-server-config-reloader\", flag.ExitOnError)\n\tflag.Usage = func() {\n\t\tfmt.Fprintf(os.Stderr, \"Usage: nats-server-config-reloader [options...]\\n\\n\")\n\t\tfs.PrintDefaults()\n\t\tfmt.Fprintf(os.Stderr, \"\\n\")\n\t}\n\n\t// Help and version\n\tvar (\n\t\tshowHelp          bool\n\t\tshowVersion       bool\n\t\tfileSet           StringSet\n\t\tcustomSignal      int\n\t\tforcePoll         bool\n\t\tpollInterval      time.Duration\n\t\tmaxWatcherRetries int\n\t)\n\n\tnconfig := &natsreloader.Config{}\n\tfs.BoolVar(&showHelp, \"h\", false, \"Show help\")\n\tfs.BoolVar(&showHelp, \"help\", false, \"Show help\")\n\tfs.BoolVar(&showVersion, \"v\", false, \"Show version\")\n\tfs.BoolVar(&showVersion, \"version\", false, \"Show version\")\n\n\tfs.StringVar(&nconfig.PidFile, \"P\", \"/var/run/nats/nats.pid\", \"NATS Server Pid File\")\n\tfs.StringVar(&nconfig.PidFile, \"pid\", \"/var/run/nats/nats.pid\", \"NATS Server Pid File\")\n\tfs.Var(&fileSet, \"c\", \"NATS Server Config File (may be repeated to specify more than one)\")\n\tfs.Var(&fileSet, \"config\", \"NATS Server Config File (may be repeated to specify more than one)\")\n\tfs.IntVar(&nconfig.MaxRetries, \"max-retries\", 30, \"Max attempts to trigger reload\")\n\tfs.IntVar(&nconfig.RetryWaitSecs, \"retry-wait-secs\", 4, \"Time to back off when reloading fails before retrying\")\n\tfs.IntVar(&customSignal, \"signal\", 1, \"Signal to send to the NATS Server process (default SIGHUP 1)\")\n\tfs.BoolVar(&forcePoll, \"force-poll\", false, \"Force polling mode instead of inotify file watching\")\n\tfs.DurationVar(&pollInterval, \"poll-interval\", 5*time.Second, \"Polling interval when using polling mode (default 5s)\")\n\tfs.IntVar(&maxWatcherRetries, \"max-watcher-retries\", 3, \"Max retries for creating inotify watcher on transient failures\")\n\n\tfs.Parse(os.Args[1:])\n\n\tnconfig.WatchedFiles = fileSet\n\tif len(fileSet) == 0 {\n\t\tnconfig.WatchedFiles = []string{\"/etc/nats-config/nats.conf\"}\n\t}\n\tnconfig.Signal = syscall.Signal(customSignal)\n\tnconfig.ForcePoll = forcePoll\n\tnconfig.PollInterval = pollInterval\n\tnconfig.MaxWatcherRetries = maxWatcherRetries\n\n\tswitch {\n\tcase showHelp:\n\t\tflag.Usage()\n\t\tos.Exit(0)\n\tcase showVersion:\n\t\tfmt.Fprintf(os.Stderr, \"NATS Server Config Reloader v%s (%s, %s)\\n\", Version, GitInfo, BuildTime)\n\t\tos.Exit(0)\n\t}\n\tr, err := natsreloader.NewReloader(nconfig)\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"Error: %s\\n\", err)\n\t\tos.Exit(1)\n\t}\n\n\t// Signal handling.\n\tgo func() {\n\t\tc := make(chan os.Signal, 1)\n\t\tsignal.Notify(c, syscall.SIGINT, syscall.SIGTERM)\n\n\t\tfor sig := range c {\n\t\t\tlog.Printf(\"Trapped \\\"%v\\\" signal\\n\", sig)\n\t\t\tswitch sig {\n\t\t\tcase syscall.SIGINT:\n\t\t\t\tlog.Println(\"Exiting...\")\n\t\t\t\tos.Exit(0)\n\t\t\t\treturn\n\t\t\tcase syscall.SIGTERM:\n\t\t\t\tr.Stop()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\tlog.Printf(\"Starting NATS Server Reloader v%s\\n\", Version)\n\terr = r.Run(context.Background())\n\tif err != nil && err != context.Canceled {\n\t\tfmt.Fprintf(os.Stderr, \"Error: %s\\n\", err.Error())\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "controllers/jetstream/conn_pool.go",
    "content": "package jetstream\n\nimport (\n\t\"crypto/sha256\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/nats-io/nats.go\"\n\t\"github.com/sirupsen/logrus\"\n\t\"golang.org/x/sync/singleflight\"\n)\n\ntype natsContext struct {\n\tName        string   `json:\"name\"`\n\tURL         string   `json:\"url\"`\n\tJWT         string   `json:\"jwt\"`\n\tSeed        string   `json:\"seed\"`\n\tCredentials string   `json:\"credential\"`\n\tNkey        string   `json:\"nkey\"`\n\tToken       string   `json:\"token\"`\n\tUsername    string   `json:\"username\"`\n\tPassword    string   `json:\"password\"`\n\tTLSCAs      []string `json:\"tls_ca\"`\n\tTLSCert     string   `json:\"tls_cert\"`\n\tTLSKey      string   `json:\"tls_key\"`\n}\n\nfunc (c *natsContext) copy() *natsContext {\n\tif c == nil {\n\t\treturn nil\n\t}\n\tcp := *c\n\treturn &cp\n}\n\nfunc (c *natsContext) hash() (string, error) {\n\tb, err := json.Marshal(c)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error marshaling context to json: %v\", err)\n\t}\n\tif c.Nkey != \"\" {\n\t\tfb, err := os.ReadFile(c.Nkey)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"error opening nkey file %s: %v\", c.Nkey, err)\n\t\t}\n\t\tb = append(b, fb...)\n\t}\n\tif c.Credentials != \"\" {\n\t\tfb, err := os.ReadFile(c.Credentials)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"error opening creds file %s: %v\", c.Credentials, err)\n\t\t}\n\t\tb = append(b, fb...)\n\t}\n\tif len(c.TLSCAs) > 0 {\n\t\tfor _, cert := range c.TLSCAs {\n\t\t\tfb, err := os.ReadFile(cert)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"error opening ca file %s: %v\", cert, err)\n\t\t\t}\n\t\t\tb = append(b, fb...)\n\t\t}\n\t}\n\tif c.TLSCert != \"\" {\n\t\tfb, err := os.ReadFile(c.TLSCert)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"error opening cert file %s: %v\", c.TLSCert, err)\n\t\t}\n\t\tb = append(b, fb...)\n\t}\n\tif c.TLSKey != \"\" {\n\t\tfb, err := os.ReadFile(c.TLSKey)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"error opening key file %s: %v\", c.TLSKey, err)\n\t\t}\n\t\tb = append(b, fb...)\n\t}\n\thash := sha256.New()\n\thash.Write(b)\n\treturn fmt.Sprintf(\"%x\", hash.Sum(nil)), nil\n}\n\ntype natsContextDefaults struct {\n\tName      string\n\tURL       string\n\tTLSCAs    []string\n\tTLSCert   string\n\tTLSKey    string\n\tTLSConfig *tls.Config\n}\n\ntype pooledNatsConn struct {\n\tnc     *nats.Conn\n\tcp     *natsConnPool\n\tkey    string\n\tcount  uint64\n\tclosed bool\n}\n\nfunc (pc *pooledNatsConn) ReturnToPool() {\n\tpc.cp.Lock()\n\tpc.count--\n\tif pc.count == 0 {\n\t\tif pooledConn, ok := pc.cp.cache[pc.key]; ok && pc == pooledConn {\n\t\t\tdelete(pc.cp.cache, pc.key)\n\t\t}\n\t\tpc.closed = true\n\t\tpc.cp.Unlock()\n\t\tpc.nc.Close()\n\t\treturn\n\t}\n\tpc.cp.Unlock()\n}\n\ntype natsConnPool struct {\n\tsync.Mutex\n\tcache        map[string]*pooledNatsConn\n\tlogger       *logrus.Logger\n\tgroup        *singleflight.Group\n\tnatsDefaults *natsContextDefaults\n\tnatsOpts     []nats.Option\n}\n\nfunc newNatsConnPool(logger *logrus.Logger, natsDefaults *natsContextDefaults, natsOpts []nats.Option) *natsConnPool {\n\treturn &natsConnPool{\n\t\tcache:        map[string]*pooledNatsConn{},\n\t\tgroup:        &singleflight.Group{},\n\t\tlogger:       logger,\n\t\tnatsDefaults: natsDefaults,\n\t\tnatsOpts:     natsOpts,\n\t}\n}\n\nconst getPooledConnMaxTries = 10\n\n// Get returns a *pooledNatsConn\nfunc (cp *natsConnPool) Get(cfg *natsContext) (*pooledNatsConn, error) {\n\tif cfg == nil {\n\t\treturn nil, fmt.Errorf(\"nats context must not be nil\")\n\t}\n\n\t// copy cfg\n\tcfg = cfg.copy()\n\n\t// set defaults\n\tif cfg.Name == \"\" {\n\t\tcfg.Name = cp.natsDefaults.Name\n\t}\n\tif cfg.URL == \"\" {\n\t\tcfg.URL = cp.natsDefaults.URL\n\t}\n\tif len(cfg.TLSCAs) == 0 {\n\t\tcfg.TLSCAs = cp.natsDefaults.TLSCAs\n\t}\n\tif cfg.TLSCert == \"\" {\n\t\tcfg.TLSCert = cp.natsDefaults.TLSCert\n\t}\n\tif cfg.TLSKey == \"\" {\n\t\tcfg.TLSKey = cp.natsDefaults.TLSKey\n\t}\n\n\t// get hash\n\tkey, err := cfg.hash()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor i := 0; i < getPooledConnMaxTries; i++ {\n\t\tconnection, err := cp.getPooledConn(key, cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcp.Lock()\n\t\tif connection.closed {\n\t\t\t// ReturnToPool closed this while lock not held, try again\n\t\t\tcp.Unlock()\n\t\t\tcontinue\n\t\t}\n\n\t\t// increment count out of the pool\n\t\tconnection.count++\n\t\tcp.Unlock()\n\t\treturn connection, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"failed to get pooled connection after %d attempts\", getPooledConnMaxTries)\n}\n\n// getPooledConn gets or establishes a *pooledNatsConn in a singleflight group, but does not increment its count\nfunc (cp *natsConnPool) getPooledConn(key string, cfg *natsContext) (*pooledNatsConn, error) {\n\tconn, err, _ := cp.group.Do(key, func() (interface{}, error) {\n\t\tcp.Lock()\n\t\tpooledConn, ok := cp.cache[key]\n\t\tif ok && pooledConn.nc.IsConnected() {\n\t\t\tcp.Unlock()\n\t\t\treturn pooledConn, nil\n\t\t}\n\t\tcp.Unlock()\n\n\t\topts := cp.natsOpts\n\t\topts = append(opts, func(options *nats.Options) error {\n\t\t\tif cfg.Name != \"\" {\n\t\t\t\toptions.Name = cfg.Name\n\t\t\t}\n\t\t\tif cfg.Token != \"\" {\n\t\t\t\toptions.Token = cfg.Token\n\t\t\t}\n\t\t\tif cfg.Username != \"\" {\n\t\t\t\toptions.User = cfg.Username\n\t\t\t}\n\t\t\tif cfg.Password != \"\" {\n\t\t\t\toptions.Password = cfg.Password\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\n\t\tif cfg.JWT != \"\" && cfg.Seed != \"\" {\n\t\t\topts = append(opts, nats.UserJWTAndSeed(cfg.JWT, cfg.Seed))\n\t\t}\n\n\t\tif cfg.Nkey != \"\" {\n\t\t\topt, err := nats.NkeyOptionFromSeed(cfg.Nkey)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"unable to load nkey: %v\", err)\n\t\t\t}\n\t\t\topts = append(opts, opt)\n\t\t}\n\n\t\tif cfg.Credentials != \"\" {\n\t\t\topts = append(opts, nats.UserCredentials(cfg.Credentials))\n\t\t}\n\n\t\tif len(cfg.TLSCAs) > 0 {\n\t\t\topts = append(opts, nats.RootCAs(cfg.TLSCAs...))\n\t\t}\n\n\t\tif cfg.TLSCert != \"\" && cfg.TLSKey != \"\" {\n\t\t\topts = append(opts, nats.ClientCert(cfg.TLSCert, cfg.TLSKey))\n\t\t}\n\n\t\tnc, err := nats.Connect(cfg.URL, opts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcp.logger.Infof(\"%s connected to NATS Deployment: %s\", cfg.Name, nc.ConnectedAddr())\n\n\t\tconnection := &pooledNatsConn{\n\t\t\tnc:  nc,\n\t\t\tcp:  cp,\n\t\t\tkey: key,\n\t\t}\n\n\t\tcp.Lock()\n\t\tcp.cache[key] = connection\n\t\tcp.Unlock()\n\n\t\treturn connection, err\n\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconnection, ok := conn.(*pooledNatsConn)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"not a pooledNatsConn\")\n\t}\n\treturn connection, nil\n}\n"
  },
  {
    "path": "controllers/jetstream/conn_pool_test.go",
    "content": "package jetstream\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/nats-io/nats.go\"\n\n\tnatsservertest \"github.com/nats-io/nats-server/v2/test\"\n\t\"github.com/sirupsen/logrus\"\n\ttestifyAssert \"github.com/stretchr/testify/assert\"\n)\n\nfunc TestConnPool(t *testing.T) {\n\tt.Parallel()\n\n\ts := natsservertest.RunRandClientPortServer()\n\tdefer s.Shutdown()\n\to1 := &natsContext{\n\t\tName: \"Client 1\",\n\t}\n\to2 := &natsContext{\n\t\tName: \"Client 1\",\n\t}\n\to3 := &natsContext{\n\t\tName: \"Client 2\",\n\t}\n\n\tnatsDefaults := &natsContextDefaults{\n\t\tURL: s.ClientURL(),\n\t}\n\tnatsOptions := []nats.Option{\n\t\tnats.MaxReconnects(10240),\n\t}\n\tcp := newNatsConnPool(logrus.New(), natsDefaults, natsOptions)\n\n\tvar c1, c2, c3 *pooledNatsConn\n\tvar c1e, c2e, c3e error\n\twg := &sync.WaitGroup{}\n\twg.Add(3)\n\tgo func() {\n\t\tc1, c1e = cp.Get(o1)\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\tc2, c2e = cp.Get(o2)\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\tc3, c3e = cp.Get(o3)\n\t\twg.Done()\n\t}()\n\twg.Wait()\n\n\tassert := testifyAssert.New(t)\n\tif assert.NoError(c1e) && assert.NoError(c2e) {\n\t\tassert.Same(c1, c2)\n\t}\n\tif assert.NoError(c3e) {\n\t\tassert.NotSame(c1, c3)\n\t\tassert.NotSame(c2, c3)\n\t}\n\n\tc1.ReturnToPool()\n\tc3.ReturnToPool()\n\ttime.Sleep(1 * time.Second)\n\tassert.False(c1.nc.IsClosed())\n\tassert.False(c2.nc.IsClosed())\n\tassert.True(c3.nc.IsClosed())\n\n\tc4, c4e := cp.Get(o1)\n\tif assert.NoError(c4e) {\n\t\tassert.Same(c2, c4)\n\t}\n\n\tc2.ReturnToPool()\n\tc4.ReturnToPool()\n\ttime.Sleep(1 * time.Second)\n\tassert.True(c1.nc.IsClosed())\n\tassert.True(c2.nc.IsClosed())\n\tassert.True(c4.nc.IsClosed())\n\n\tc5, c5e := cp.Get(o1)\n\tif assert.NoError(c5e) {\n\t\tassert.NotSame(c1, c5)\n\t}\n\n\tc5.ReturnToPool()\n\ttime.Sleep(1 * time.Second)\n\tassert.True(c5.nc.IsClosed())\n}\n"
  },
  {
    "path": "controllers/jetstream/consumer.go",
    "content": "package jetstream\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/nats-io/jsm.go\"\n\tjsmapi \"github.com/nats-io/jsm.go/api\"\n\tapis \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\ttyped \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2\"\n\tk8sapi \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/util/retry\"\n\tklog \"k8s.io/klog/v2\"\n)\n\nfunc (c *Controller) runConsumerQueue() {\n\tfor {\n\t\tprocessQueueNext(c.cnsQueue, c.RealJSMC, c.processConsumer)\n\t}\n}\n\nfunc (c *Controller) processConsumer(ns, name string, jsmClient jsmClientFunc) (err error) {\n\tcns, err := c.cnsLister.Consumers(ns).Get(name)\n\tif err != nil && k8serrors.IsNotFound(err) {\n\t\treturn nil\n\t} else if err != nil {\n\t\treturn err\n\t}\n\n\treturn c.processConsumerObject(cns, jsmClient)\n}\n\nfunc (c *Controller) processConsumerObject(cns *apis.Consumer, jsm jsmClientFunc) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to process consumer: %w\", err)\n\t\t}\n\t}()\n\n\tns := cns.Namespace\n\tspec := cns.Spec\n\tifc := c.ji.Consumers(ns)\n\n\tacc, err := c.getAccountOverrides(spec.Account, ns)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif err == nil {\n\t\t\treturn\n\t\t}\n\n\t\tif _, serr := setConsumerErrored(c.ctx, cns, ifc, err); serr != nil {\n\t\t\terr = fmt.Errorf(\"%s: %w\", err, serr)\n\t\t}\n\t}()\n\n\ttype operator func(ctx context.Context, c jsmClient, spec apis.ConsumerSpec) (err error)\n\n\tnatsClientUtil := func(op operator) error {\n\t\treturn c.runWithJsmc(jsm, acc, &jsmcSpecOverrides{\n\t\t\tservers: spec.Servers,\n\t\t\ttls:     spec.TLS,\n\t\t\tcreds:   spec.Creds,\n\t\t\tnkey:    spec.Nkey,\n\t\t}, cns, func(jsmc jsmClient) error {\n\t\t\treturn op(c.ctx, jsmc, spec)\n\t\t})\n\t}\n\n\tdeleteOK := cns.GetDeletionTimestamp() != nil\n\tnewGeneration := cns.Generation != cns.Status.ObservedGeneration\n\tconsumerOK := true\n\terr = natsClientUtil(consumerExists)\n\tvar apierr jsmapi.ApiError\n\tif errors.As(err, &apierr) && apierr.NotFoundError() {\n\t\tconsumerOK = false\n\t} else if err != nil {\n\t\treturn err\n\t}\n\tupdateOK := (consumerOK && !deleteOK && newGeneration)\n\tcreateOK := (!consumerOK && !deleteOK) || (!updateOK && !deleteOK && newGeneration)\n\n\tswitch {\n\tcase createOK:\n\t\tc.normalEvent(cns, \"Creating\",\n\t\t\tfmt.Sprintf(\"Creating consumer %q on stream %q\", spec.DurableName, spec.StreamName))\n\t\tif err := natsClientUtil(createConsumer); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif _, err := setConsumerOK(c.ctx, cns, ifc); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.normalEvent(cns, \"Created\",\n\t\t\tfmt.Sprintf(\"Created consumer %q on stream %q\", spec.DurableName, spec.StreamName))\n\tcase updateOK:\n\t\tif cns.Spec.PreventUpdate {\n\t\t\tc.normalEvent(cns, \"SkipUpdate\", fmt.Sprintf(\"Skip updating consumer %q on stream %q\", spec.DurableName, spec.StreamName))\n\t\t\tif _, err := setConsumerOK(c.ctx, cns, ifc); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\tc.normalEvent(cns, \"Updating\", fmt.Sprintf(\"Updating consumer %q on stream %q\", spec.DurableName, spec.StreamName))\n\t\tif err := natsClientUtil(updateConsumer); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif _, err := setConsumerOK(c.ctx, cns, ifc); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.normalEvent(cns, \"Updated\", fmt.Sprintf(\"Updated consumer %q on stream %q\", spec.DurableName, spec.StreamName))\n\tcase deleteOK:\n\t\tif cns.Spec.PreventDelete {\n\t\t\tc.normalEvent(cns, \"SkipDelete\", fmt.Sprintf(\"Skip deleting consumer %q on stream %q\", spec.DurableName, spec.StreamName))\n\t\t\tif _, err := setConsumerOK(c.ctx, cns, ifc); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\tc.normalEvent(cns, \"Deleting\", fmt.Sprintf(\"Deleting consumer %q on stream %q\", spec.DurableName, spec.StreamName))\n\t\tif err := natsClientUtil(deleteConsumer); err != nil {\n\t\t\treturn err\n\t\t}\n\tdefault:\n\t\tc.normalEvent(cns, \"Noop\", fmt.Sprintf(\"Nothing done for consumer %q (prevent-delete=%v, prevent-update=%v)\",\n\t\t\tspec.DurableName, spec.PreventDelete, spec.PreventUpdate,\n\t\t))\n\t\tif _, err := setConsumerOK(c.ctx, cns, ifc); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc consumerExists(ctx context.Context, c jsmClient, spec apis.ConsumerSpec) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to check if consumer exists: %w\", err)\n\t\t}\n\t}()\n\n\t_, err = c.LoadConsumer(ctx, spec.StreamName, spec.DurableName)\n\treturn err\n}\n\nfunc createConsumer(ctx context.Context, c jsmClient, spec apis.ConsumerSpec) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to create consumer %q on stream %q: %w\", spec.DurableName, spec.StreamName, err)\n\t\t}\n\t}()\n\n\topts, err := consumerSpecToOpts(spec)\n\tif err != nil {\n\t\treturn\n\t}\n\t_, err = c.NewConsumer(ctx, spec.StreamName, opts)\n\treturn\n}\n\nfunc updateConsumer(ctx context.Context, c jsmClient, spec apis.ConsumerSpec) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to update consumer %q on stream %q: %w\", spec.DurableName, spec.StreamName, err)\n\t\t}\n\t}()\n\n\tjs, err := c.LoadConsumer(ctx, spec.StreamName, spec.DurableName)\n\tif err != nil {\n\t\treturn\n\t}\n\n\topts, err := consumerSpecToOpts(spec)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = js.UpdateConfiguration(opts...)\n\treturn\n}\n\nfunc consumerSpecToOpts(spec apis.ConsumerSpec) ([]jsm.ConsumerOption, error) {\n\topts := []jsm.ConsumerOption{\n\t\tjsm.DurableName(spec.DurableName),\n\t\tjsm.DeliverySubject(spec.DeliverSubject),\n\t\tjsm.RateLimitBitsPerSecond(uint64(spec.RateLimitBps)),\n\t\tjsm.MaxAckPending(uint(spec.MaxAckPending)),\n\t\tjsm.ConsumerDescription(spec.Description),\n\t\tjsm.DeliverGroup(spec.DeliverGroup),\n\t\tjsm.MaxWaiting(uint(spec.MaxWaiting)),\n\t\tjsm.MaxRequestBatch(uint(spec.MaxRequestBatch)),\n\t\tjsm.MaxRequestMaxBytes(spec.MaxRequestMaxBytes),\n\t\tjsm.ConsumerOverrideReplicas(spec.Replicas),\n\t}\n\n\tif spec.FilterSubject != \"\" && len(spec.FilterSubjects) > 0 {\n\t\treturn nil, fmt.Errorf(\"cannot specify both FilterSubject and FilterSubjects\")\n\t}\n\n\tif spec.FilterSubject != \"\" {\n\t\topts = append(opts, jsm.FilterStreamBySubject(spec.FilterSubject))\n\t} else if len(spec.FilterSubjects) > 0 {\n\t\topts = append(opts, jsm.FilterStreamBySubject(spec.FilterSubjects...))\n\t}\n\n\tswitch spec.DeliverPolicy {\n\tcase \"all\":\n\t\topts = append(opts, jsm.DeliverAllAvailable())\n\tcase \"last\":\n\t\topts = append(opts, jsm.StartWithLastReceived())\n\tcase \"new\":\n\t\topts = append(opts, jsm.StartWithNextReceived())\n\tcase \"byStartSequence\":\n\t\topts = append(opts, jsm.StartAtSequence(uint64(spec.OptStartSeq)))\n\tcase \"byStartTime\":\n\t\tif spec.OptStartTime == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"'optStartTime' is required for deliver policy 'byStartTime'\")\n\t\t}\n\t\tt, err := time.Parse(time.RFC3339, spec.OptStartTime)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\topts = append(opts, jsm.StartAtTime(t))\n\tcase \"lastPerSubject\":\n\t\topts = append(opts, jsm.DeliverLastPerSubject())\n\tcase \"\":\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid value for 'deliverPolicy': '%s'. Must be one of 'all', 'last', 'new', 'lastPerSubject', 'byStartSequence', 'byStartTime'\", spec.DeliverPolicy)\n\t}\n\n\tswitch spec.AckPolicy {\n\tcase \"none\":\n\t\topts = append(opts, jsm.AcknowledgeNone())\n\tcase \"all\":\n\t\topts = append(opts, jsm.AcknowledgeAll())\n\tcase \"explicit\":\n\t\topts = append(opts, jsm.AcknowledgeExplicit())\n\tcase \"\":\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid value for 'ackPolicy': '%s'. Must be one of 'none', 'all', 'explicit'\", spec.AckPolicy)\n\t}\n\n\tif spec.AckWait != \"\" {\n\t\td, err := time.ParseDuration(spec.AckWait)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\topts = append(opts, jsm.AckWait(d))\n\t}\n\n\tswitch spec.ReplayPolicy {\n\tcase \"instant\":\n\t\topts = append(opts, jsm.ReplayInstantly())\n\tcase \"original\":\n\t\topts = append(opts, jsm.ReplayAsReceived())\n\tcase \"\":\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid value for 'replayPolicy': '%s'. Must be one of 'instant', 'original'\", spec.ReplayPolicy)\n\t}\n\n\tif spec.SampleFreq != \"\" {\n\t\tn, err := strconv.Atoi(spec.SampleFreq)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\topts = append(opts, jsm.SamplePercent(n))\n\t}\n\n\tif spec.FlowControl {\n\t\topts = append(opts, jsm.PushFlowControl())\n\t}\n\n\tif spec.HeartbeatInterval != \"\" {\n\t\td, err := time.ParseDuration(spec.HeartbeatInterval)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\topts = append(opts, jsm.IdleHeartbeat(d))\n\t}\n\n\tif len(spec.BackOff) > 0 {\n\t\tbackoffs := make([]time.Duration, 0)\n\t\tfor _, backoff := range spec.BackOff {\n\t\t\tdur, err := time.ParseDuration(backoff)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tbackoffs = append(backoffs, dur)\n\t\t}\n\t\topts = append(opts, jsm.BackoffIntervals(backoffs...))\n\t}\n\n\tif spec.HeadersOnly {\n\t\topts = append(opts, jsm.DeliverHeadersOnly())\n\t} else {\n\t\topts = append(opts, jsm.DeliverBodies())\n\t}\n\n\tif spec.MaxRequestExpires != \"\" {\n\t\tdur, err := time.ParseDuration(spec.MaxRequestExpires)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\topts = append(opts, jsm.MaxRequestExpires(dur))\n\t}\n\n\tif spec.MemStorage {\n\t\topts = append(opts, jsm.ConsumerOverrideMemoryStorage())\n\t}\n\n\tif spec.MaxDeliver != 0 {\n\t\topts = append(opts, jsm.MaxDeliveryAttempts(spec.MaxDeliver))\n\t}\n\n\tif spec.Metadata != nil {\n\t\topts = append(opts, jsm.ConsumerMetadata(spec.Metadata))\n\t}\n\n\tif spec.InactiveThreshold != \"\" {\n\t\tdur, err := time.ParseDuration(spec.InactiveThreshold)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\topts = append(opts, jsm.InactiveThreshold(dur))\n\t}\n\n\t// Handle PauseUntil for pausing consumer\n\tif spec.PauseUntil != \"\" {\n\t\tt, err := time.Parse(time.RFC3339, spec.PauseUntil)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid pauseUntil time: %w\", err)\n\t\t}\n\t\topts = append(opts, jsm.PauseUntil(t))\n\t}\n\n\t// Handle PriorityPolicy with PriorityGroups and PinnedTTL\n\tswitch spec.PriorityPolicy {\n\tcase \"\", \"none\":\n\t\t// Default is none, no need to set\n\tcase \"pinned_client\":\n\t\tif spec.PinnedTTL != \"\" {\n\t\t\tdur, err := time.ParseDuration(spec.PinnedTTL)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid pinnedTTL duration: %w\", err)\n\t\t\t}\n\t\t\topts = append(opts, jsm.PinnedClientPriorityGroups(dur, spec.PriorityGroups...))\n\t\t}\n\tcase \"overflow\":\n\t\topts = append(opts, jsm.OverflowPriorityGroups(spec.PriorityGroups...))\n\tcase \"prioritized\":\n\t\topts = append(opts, jsm.PrioritizedPriorityGroups(spec.PriorityGroups...))\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid priority policy: %s\", spec.PriorityPolicy)\n\t}\n\n\treturn opts, nil\n}\n\nfunc deleteConsumer(ctx context.Context, c jsmClient, spec apis.ConsumerSpec) (err error) {\n\tstream, consumer := spec.StreamName, spec.DurableName\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to delete consumer %q on stream %q: %w\", consumer, stream, err)\n\t\t}\n\t}()\n\n\tif spec.PreventDelete {\n\t\tklog.Infof(\"Consumer %q is configured to preventDelete on stream %q:\", stream, consumer)\n\t\treturn nil\n\t}\n\n\tvar apierr jsmapi.ApiError\n\tcn, err := c.LoadConsumer(ctx, stream, consumer)\n\tif errors.As(err, &apierr) && apierr.NotFoundError() {\n\t\treturn nil\n\t} else if err != nil {\n\t\treturn err\n\t}\n\n\treturn cn.Delete()\n}\n\nfunc setConsumerOK(ctx context.Context, s *apis.Consumer, i typed.ConsumerInterface) (*apis.Consumer, error) {\n\tsc := s.DeepCopy()\n\n\tsc.Status.ObservedGeneration = s.Generation\n\tsc.Status.Conditions = UpsertCondition(sc.Status.Conditions, apis.Condition{\n\t\tType:               readyCondType,\n\t\tStatus:             k8sapi.ConditionTrue,\n\t\tLastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),\n\t\tReason:             \"Created\",\n\t\tMessage:            \"Consumer successfully created\",\n\t})\n\n\tvar res *apis.Consumer\n\terr := retry.RetryOnConflict(retry.DefaultRetry, func() error {\n\t\tvar err error\n\t\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\t\tdefer cancel()\n\t\tres, err = i.UpdateStatus(ctx, sc, k8smeta.UpdateOptions{})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set consumer %q status: %w\", s.Spec.DurableName, err)\n\t\t}\n\t\treturn nil\n\t})\n\treturn res, err\n}\n\nfunc setConsumerErrored(ctx context.Context, s *apis.Consumer, sif typed.ConsumerInterface, err error) (*apis.Consumer, error) {\n\tif err == nil {\n\t\treturn s, nil\n\t}\n\n\tsc := s.DeepCopy()\n\tsc.Status.Conditions = UpsertCondition(sc.Status.Conditions, apis.Condition{\n\t\tType:               readyCondType,\n\t\tStatus:             k8sapi.ConditionFalse,\n\t\tLastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),\n\t\tReason:             \"Errored\",\n\t\tMessage:            err.Error(),\n\t})\n\n\tvar res *apis.Consumer\n\terr = retry.RetryOnConflict(retry.DefaultRetry, func() error {\n\t\tvar err error\n\t\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\t\tdefer cancel()\n\t\tres, err = sif.UpdateStatus(ctx, sc, k8smeta.UpdateOptions{})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set consumer errored status: %w\", err)\n\t\t}\n\t\treturn nil\n\t})\n\treturn res, err\n}\n"
  },
  {
    "path": "controllers/jetstream/consumer_test.go",
    "content": "package jetstream\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tjsmapi \"github.com/nats-io/jsm.go/api\"\n\tapis \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tclientsetfake \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/fake\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tk8sclientsetfake \"k8s.io/client-go/kubernetes/fake\"\n\tk8stesting \"k8s.io/client-go/testing\"\n\t\"k8s.io/client-go/tools/record\"\n)\n\nfunc TestProcessConsumer(t *testing.T) {\n\tt.Parallel()\n\n\tupdateObject := func(a k8stesting.Action) (handled bool, o runtime.Object, err error) {\n\t\tua, ok := a.(k8stesting.UpdateAction)\n\t\tif !ok {\n\t\t\treturn false, nil, nil\n\t\t}\n\n\t\treturn true, ua.GetObject(), nil\n\t}\n\n\tt.Run(\"create consumer\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tjc := clientsetfake.NewSimpleClientset()\n\t\twantEvents := 2\n\t\trec := record.NewFakeRecorder(wantEvents)\n\t\tctrl := NewController(Options{\n\t\t\tCtx:            context.Background(),\n\t\t\tKubeIface:      k8sclientsetfake.NewSimpleClientset(),\n\t\t\tJetstreamIface: jc,\n\t\t\tRecorder:       rec,\n\t\t})\n\n\t\tns, name := \"default\", \"my-consumer\"\n\n\t\tinformer := ctrl.informerFactory.Jetstream().V1beta2().Consumers()\n\t\terr := informer.Informer().GetStore().Add(&apis.Consumer{\n\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\tNamespace:  ns,\n\t\t\t\tName:       name,\n\t\t\t\tGeneration: 1,\n\t\t\t},\n\t\t\tSpec: apis.ConsumerSpec{\n\t\t\t\tDurableName:       name,\n\t\t\t\tDeliverPolicy:     \"byStartTime\",\n\t\t\t\tOptStartTime:      time.Now().Format(time.RFC3339),\n\t\t\t\tAckPolicy:         \"explicit\",\n\t\t\t\tAckWait:           \"1m\",\n\t\t\t\tReplayPolicy:      \"original\",\n\t\t\t\tSampleFreq:        \"50\",\n\t\t\t\tHeartbeatInterval: \"30s\",\n\t\t\t\tBackOff:           []string{\"500ms\", \"1s\"},\n\t\t\t\tHeadersOnly:       true,\n\t\t\t\tMaxRequestExpires: \"5m\",\n\t\t\t\tMemStorage:        true,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tjc.PrependReactor(\"update\", \"consumers\", updateObject)\n\n\t\tnotFoundErr := jsmapi.ApiError{Code: 404}\n\t\tjsmc := &mockJsmClient{\n\t\t\tloadConsumerErr: notFoundErr,\n\t\t\tnewConsumerErr:  nil,\n\t\t\tnewConsumer:     &mockConsumer{},\n\t\t}\n\t\tif err := ctrl.processConsumer(ns, name, func(n *natsContext) (jsmClient, error) {\n\t\t\treturn jsmc, nil\n\t\t}); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif got := len(rec.Events); got != wantEvents {\n\t\t\tt.Error(\"unexpected number of events\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, wantEvents)\n\t\t}\n\n\t\tfor i := 0; i < len(rec.Events); i++ {\n\t\t\tgotEvent := <-rec.Events\n\t\t\tif !strings.Contains(gotEvent, \"Creat\") {\n\t\t\t\tt.Error(\"unexpected event\")\n\t\t\t\tt.Fatalf(\"got=%s; want=%s\", gotEvent, \"Creating/Created...\")\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"create consumer, invalid configuration\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tjc := clientsetfake.NewSimpleClientset()\n\t\twantEvents := 1\n\t\trec := record.NewFakeRecorder(wantEvents)\n\t\tctrl := NewController(Options{\n\t\t\tCtx:            context.Background(),\n\t\t\tKubeIface:      k8sclientsetfake.NewSimpleClientset(),\n\t\t\tJetstreamIface: jc,\n\t\t\tRecorder:       rec,\n\t\t})\n\n\t\tns, name := \"default\", \"my-consumer\"\n\n\t\tinformer := ctrl.informerFactory.Jetstream().V1beta2().Consumers()\n\t\terr := informer.Informer().GetStore().Add(&apis.Consumer{\n\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\tNamespace:  ns,\n\t\t\t\tName:       name,\n\t\t\t\tGeneration: 1,\n\t\t\t},\n\t\t\tSpec: apis.ConsumerSpec{\n\t\t\t\tDurableName:   name,\n\t\t\t\tDeliverPolicy: \"invalid\",\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tjc.PrependReactor(\"update\", \"consumers\", updateObject)\n\n\t\tnotFoundErr := jsmapi.ApiError{Code: 404}\n\t\tjsmc := &mockJsmClient{\n\t\t\tloadConsumerErr: notFoundErr,\n\t\t\tnewConsumerErr:  nil,\n\t\t\tnewConsumer:     &mockConsumer{},\n\t\t}\n\t\tif err := ctrl.processConsumer(ns, name, testWrapJSMC(jsmc)); err == nil || !strings.Contains(err.Error(), `failed to create consumer \"my-consumer\" on stream `) {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif got := len(rec.Events); got != wantEvents {\n\t\t\tt.Error(\"unexpected number of events\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, wantEvents)\n\t\t}\n\n\t\tgotEvent := <-rec.Events\n\t\tif !strings.Contains(gotEvent, \"Creating\") {\n\t\t\tt.Error(\"unexpected event\")\n\t\t\tt.Fatalf(\"got=%s; want=%s\", gotEvent, \"Creating...\")\n\t\t}\n\t})\n\n\tt.Run(\"update consumer\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tjc := clientsetfake.NewSimpleClientset()\n\t\twantEvents := 2\n\t\trec := record.NewFakeRecorder(wantEvents)\n\t\tctrl := NewController(Options{\n\t\t\tCtx:            context.Background(),\n\t\t\tKubeIface:      k8sclientsetfake.NewSimpleClientset(),\n\t\t\tJetstreamIface: jc,\n\t\t\tRecorder:       rec,\n\t\t})\n\n\t\tns, name := \"default\", \"my-consumer\"\n\n\t\tinformer := ctrl.informerFactory.Jetstream().V1beta2().Consumers()\n\t\terr := informer.Informer().GetStore().Add(&apis.Consumer{\n\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\tNamespace:  ns,\n\t\t\t\tName:       name,\n\t\t\t\tGeneration: 2,\n\t\t\t},\n\t\t\tSpec: apis.ConsumerSpec{\n\t\t\t\tDurableName: name,\n\t\t\t},\n\t\t\tStatus: apis.Status{\n\t\t\t\tObservedGeneration: 1,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tjc.PrependReactor(\"update\", \"consumers\", updateObject)\n\n\t\tjsmc := &mockJsmClient{\n\t\t\tloadConsumerErr: nil,\n\t\t\tloadConsumer:    &mockConsumer{},\n\t\t}\n\t\tif err := ctrl.processConsumer(ns, name, testWrapJSMC(jsmc)); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif got := len(rec.Events); got != wantEvents {\n\t\t\tt.Error(\"unexpected number of events\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, wantEvents)\n\t\t}\n\n\t\tfor i := 0; i < len(rec.Events); i++ {\n\t\t\tgotEvent := <-rec.Events\n\t\t\tif !strings.Contains(gotEvent, \"Updat\") {\n\t\t\t\tt.Error(\"unexpected event\")\n\t\t\t\tt.Fatalf(\"got=%s; want=%s\", gotEvent, \"Updating/Updated...\")\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"delete consumer\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tjc := clientsetfake.NewSimpleClientset()\n\t\twantEvents := 1\n\t\trec := record.NewFakeRecorder(wantEvents)\n\t\tctrl := NewController(Options{\n\t\t\tCtx:            context.Background(),\n\t\t\tKubeIface:      k8sclientsetfake.NewSimpleClientset(),\n\t\t\tJetstreamIface: jc,\n\t\t\tRecorder:       rec,\n\t\t})\n\n\t\tts := k8smeta.Unix(1600216923, 0)\n\t\tns, name := \"default\", \"my-consumer\"\n\n\t\tinformer := ctrl.informerFactory.Jetstream().V1beta2().Consumers()\n\t\terr := informer.Informer().GetStore().Add(&apis.Consumer{\n\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\tNamespace:         ns,\n\t\t\t\tName:              name,\n\t\t\t\tDeletionTimestamp: &ts,\n\t\t\t},\n\t\t\tSpec: apis.ConsumerSpec{\n\t\t\t\tDurableName: name,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tjc.PrependReactor(\"update\", \"consumers\", updateObject)\n\n\t\tjsmc := &mockJsmClient{\n\t\t\tloadConsumerErr: nil,\n\t\t\tloadConsumer:    &mockConsumer{},\n\t\t}\n\t\tif err := ctrl.processConsumer(ns, name, testWrapJSMC(jsmc)); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif got := len(rec.Events); got != wantEvents {\n\t\t\tt.Error(\"unexpected number of events\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, wantEvents)\n\t\t}\n\n\t\tgotEvent := <-rec.Events\n\t\tif !strings.Contains(gotEvent, \"Deleting\") {\n\t\t\tt.Error(\"unexpected event\")\n\t\t\tt.Fatalf(\"got=%s; want=%s\", gotEvent, \"Deleting...\")\n\t\t}\n\t})\n\n\tt.Run(\"process error\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tjc := clientsetfake.NewSimpleClientset()\n\t\twantEvents := 1\n\t\trec := record.NewFakeRecorder(wantEvents)\n\t\tctrl := NewController(Options{\n\t\t\tCtx:            context.Background(),\n\t\t\tKubeIface:      k8sclientsetfake.NewSimpleClientset(),\n\t\t\tJetstreamIface: jc,\n\t\t\tRecorder:       rec,\n\t\t})\n\n\t\tns, name := \"default\", \"my-consumer\"\n\n\t\tinformer := ctrl.informerFactory.Jetstream().V1beta2().Consumers()\n\t\terr := informer.Informer().GetStore().Add(&apis.Consumer{\n\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\tNamespace:  ns,\n\t\t\t\tName:       name,\n\t\t\t\tGeneration: 1,\n\t\t\t},\n\t\t\tSpec: apis.ConsumerSpec{\n\t\t\t\tDurableName: name,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tjc.PrependReactor(\"update\", \"consumers\", func(a k8stesting.Action) (handled bool, o runtime.Object, err error) {\n\t\t\tua, ok := a.(k8stesting.UpdateAction)\n\t\t\tif !ok {\n\t\t\t\treturn false, nil, nil\n\t\t\t}\n\t\t\tobj := ua.GetObject()\n\n\t\t\tstr, ok := obj.(*apis.Consumer)\n\t\t\tif !ok {\n\t\t\t\tt.Error(\"unexpected object type\")\n\t\t\t\tt.Fatalf(\"got=%T; want=%T\", obj, &apis.Consumer{})\n\t\t\t}\n\n\t\t\tif got, want := len(str.Status.Conditions), 1; got != want {\n\t\t\t\tt.Error(\"unexpected number of conditions\")\n\t\t\t\tt.Fatalf(\"got=%d; want=%d\", got, want)\n\t\t\t}\n\t\t\tif got, want := str.Status.Conditions[0].Reason, \"Errored\"; got != want {\n\t\t\t\tt.Error(\"unexpected condition reason\")\n\t\t\t\tt.Fatalf(\"got=%s; want=%s\", got, want)\n\t\t\t}\n\n\t\t\treturn true, obj, nil\n\t\t})\n\n\t\tjsmc := &mockJsmClient{\n\t\t\tloadConsumerErr: errors.New(\"failed to load consumer\"),\n\t\t}\n\t\tif err := ctrl.processConsumer(ns, name, testWrapJSMC(jsmc)); err == nil {\n\t\t\tt.Fatal(\"unexpected success\")\n\t\t}\n\t})\n}\n\nfunc TestConsumerSpecToOpts(t *testing.T) {\n\ttests := map[string]struct {\n\t\tname     string\n\t\tgiven    apis.ConsumerSpec\n\t\texpected jsmapi.ConsumerConfig\n\t\terrCheck func(t *testing.T, err error)\n\t}{\n\t\t\"valid consumer spec\": {\n\t\t\tgiven: apis.ConsumerSpec{\n\t\t\t\tDurableName:       \"my-consumer\",\n\t\t\t\tDeliverPolicy:     \"byStartSequence\",\n\t\t\t\tOptStartSeq:       10,\n\t\t\t\tAckPolicy:         \"explicit\",\n\t\t\t\tAckWait:           \"1m\",\n\t\t\t\tReplayPolicy:      \"original\",\n\t\t\t\tSampleFreq:        \"50\",\n\t\t\t\tHeartbeatInterval: \"30s\",\n\t\t\t\tBackOff:           []string{\"500ms\", \"1s\"},\n\t\t\t\tHeadersOnly:       true,\n\t\t\t\tMaxRequestExpires: \"5m\",\n\t\t\t\tMemStorage:        true,\n\t\t\t},\n\t\t\texpected: jsmapi.ConsumerConfig{\n\t\t\t\tAckPolicy:         jsmapi.AckExplicit,\n\t\t\t\tAckWait:           1 * time.Minute,\n\t\t\t\tDeliverPolicy:     jsmapi.DeliverByStartSequence,\n\t\t\t\tDurable:           \"my-consumer\",\n\t\t\t\tHeartbeat:         30 * time.Second,\n\t\t\t\tBackOff:           []time.Duration{500 * time.Millisecond, 1 * time.Second},\n\t\t\t\tOptStartSeq:       10,\n\t\t\t\tReplayPolicy:      jsmapi.ReplayOriginal,\n\t\t\t\tSampleFrequency:   \"50%\",\n\t\t\t\tHeadersOnly:       true,\n\t\t\t\tMaxRequestExpires: 5 * time.Minute,\n\t\t\t\tMemoryStorage:     true,\n\t\t\t},\n\t\t},\n\t\t\"valid consumer spec, defaults only\": {\n\t\t\tgiven: apis.ConsumerSpec{\n\t\t\t\tDurableName: \"my-consumer\",\n\t\t\t},\n\t\t\texpected: jsmapi.ConsumerConfig{\n\t\t\t\tDurable: \"my-consumer\",\n\t\t\t},\n\t\t},\n\t\t\"invalid deliver policy value\": {\n\t\t\tgiven: apis.ConsumerSpec{\n\t\t\t\tDurableName:   \"my-consumer\",\n\t\t\t\tDeliverPolicy: \"invalid\",\n\t\t\t},\n\t\t\terrCheck: func(t *testing.T, err error) {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.Contains(t, err.Error(), \"invalid value for 'deliverPolicy': 'invalid'\")\n\t\t\t},\n\t\t},\n\t\t\"missing start time for deliver policy byStartTime\": {\n\t\t\tgiven: apis.ConsumerSpec{\n\t\t\t\tDurableName:   \"my-consumer\",\n\t\t\t\tDeliverPolicy: \"byStartTime\",\n\t\t\t},\n\t\t\terrCheck: func(t *testing.T, err error) {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.Contains(t, err.Error(), \"'optStartTime' is required for deliver policy 'byStartTime'\")\n\t\t\t},\n\t\t},\n\t\t\"deliver policy lastPerSubject\": {\n\t\t\tgiven: apis.ConsumerSpec{\n\t\t\t\tDurableName:   \"my-consumer\",\n\t\t\t\tDeliverPolicy: \"lastPerSubject\",\n\t\t\t},\n\t\t\texpected: jsmapi.ConsumerConfig{\n\t\t\t\tDurable:       \"my-consumer\",\n\t\t\t\tDeliverPolicy: jsmapi.DeliverLastPerSubject,\n\t\t\t},\n\t\t},\n\t\t\"invalid ack policy\": {\n\t\t\tgiven: apis.ConsumerSpec{\n\t\t\t\tDurableName: \"my-consumer\",\n\t\t\t\tAckPolicy:   \"invalid\",\n\t\t\t},\n\t\t\terrCheck: func(t *testing.T, err error) {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.Contains(t, err.Error(), \"invalid value for 'ackPolicy': 'invalid'\")\n\t\t\t},\n\t\t},\n\t\t\"invalid replay policy\": {\n\t\t\tgiven: apis.ConsumerSpec{\n\t\t\t\tDurableName:  \"my-consumer\",\n\t\t\t\tReplayPolicy: \"invalid\",\n\t\t\t},\n\t\t\terrCheck: func(t *testing.T, err error) {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\trequire.Contains(t, err.Error(), \"invalid value for 'replayPolicy': 'invalid'\")\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, test := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tres, err := consumerSpecToOpts(test.given)\n\t\t\tif test.errCheck != nil {\n\t\t\t\ttest.errCheck(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tvar config jsmapi.ConsumerConfig\n\t\t\tfor _, opt := range res {\n\t\t\t\terr := opt(&config)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t\tassert.Equal(t, test.expected, config)\n\t\t})\n\t}\n}\n\nfunc testWrapJSMC(jsm jsmClient) jsmClientFunc {\n\treturn func(n *natsContext) (jsmClient, error) {\n\t\treturn jsm, nil\n\t}\n}\n"
  },
  {
    "path": "controllers/jetstream/controller.go",
    "content": "// Copyright 2020-2022 The NATS 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//\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\npackage jetstream\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/nats-io/jsm.go\"\n\tjsmapi \"github.com/nats-io/jsm.go/api\"\n\t\"github.com/nats-io/nats.go\"\n\t\"github.com/sirupsen/logrus\"\n\n\tapis \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tclientset \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned\"\n\tscheme \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme\"\n\ttyped \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2\"\n\tinformers \"github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions\"\n\tlisters \"github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2\"\n\n\tk8sapi \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/equality\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"k8s.io/client-go/kubernetes\"\n\tk8sscheme \"k8s.io/client-go/kubernetes/scheme\"\n\tk8styped \"k8s.io/client-go/kubernetes/typed/core/v1\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\t\"k8s.io/client-go/tools/cache\"\n\t\"k8s.io/client-go/tools/record\"\n\t\"k8s.io/client-go/util/workqueue\"\n\tklog \"k8s.io/klog/v2\"\n)\n\nconst (\n\t// maxQueueRetries is the max times an item will be retried. An item will\n\t// be pulled maxQueueRetries+1 times from the queue. On pull number\n\t// maxQueueRetries+1, if it fails again, it won't be retried.\n\tmaxQueueRetries = 10\n\n\t// readyCondType is the Ready condition type.\n\treadyCondType = \"Ready\"\n)\n\ntype Options struct {\n\tCtx context.Context\n\n\tKubeIface      kubernetes.Interface\n\tJetstreamIface clientset.Interface\n\n\tNATSClientName  string\n\tNATSCredentials string\n\tNATSNKey        string\n\tNATSServerURL   string\n\n\tNATSCA          string\n\tNATSCertificate string\n\tNATSKey         string\n\n\tNATSTLSFirst bool\n\n\tNamespace     string\n\tCRDConnect    bool\n\tCleanupPeriod time.Duration\n\tReadOnly      bool\n\n\tRecorder record.EventRecorder\n}\n\ntype Controller struct {\n\tctx      context.Context\n\topts     Options\n\tconnPool *natsConnPool\n\n\tki              k8styped.CoreV1Interface\n\tji              typed.JetstreamV1beta2Interface\n\tinformerFactory informers.SharedInformerFactory\n\trec             record.EventRecorder\n\n\tstrLister listers.StreamLister\n\tstrSynced cache.InformerSynced\n\tstrQueue  workqueue.TypedRateLimitingInterface[any]\n\n\tcnsLister listers.ConsumerLister\n\tcnsSynced cache.InformerSynced\n\tcnsQueue  workqueue.TypedRateLimitingInterface[any]\n\n\taccLister listers.AccountLister\n\n\t// Informers for unsupported resources (KeyValue and ObjectStore)\n\t// These are only used to emit warnings in legacy mode\n\tkvLister listers.KeyValueLister\n\tkvSynced cache.InformerSynced\n\n\tosLister listers.ObjectStoreLister\n\tosSynced cache.InformerSynced\n\n\t// cacheDir is where the downloaded TLS certs from the server\n\t// will be stored temporarily.\n\tcacheDir string\n}\n\nfunc NewController(opt Options) *Controller {\n\tresyncPeriod := 30 * time.Second\n\tinformerFactory := informers.NewSharedInformerFactoryWithOptions(opt.JetstreamIface, resyncPeriod, informers.WithNamespace(opt.Namespace))\n\n\tstreamInformer := informerFactory.Jetstream().V1beta2().Streams()\n\tconsumerInformer := informerFactory.Jetstream().V1beta2().Consumers()\n\taccountInformer := informerFactory.Jetstream().V1beta2().Accounts()\n\tkeyValueInformer := informerFactory.Jetstream().V1beta2().KeyValues()\n\tobjectStoreInformer := informerFactory.Jetstream().V1beta2().ObjectStores()\n\n\tif opt.Recorder == nil {\n\t\tutilruntime.Must(scheme.AddToScheme(k8sscheme.Scheme))\n\t\teventBroadcaster := record.NewBroadcaster()\n\t\teventBroadcaster.StartLogging(klog.Infof)\n\t\teventBroadcaster.StartRecordingToSink(&k8styped.EventSinkImpl{\n\t\t\tInterface: opt.KubeIface.CoreV1().Events(\"\"),\n\t\t})\n\n\t\topt.Recorder = eventBroadcaster.NewRecorder(k8sscheme.Scheme, k8sapi.EventSource{\n\t\t\tComponent: \"jetstream-controller\",\n\t\t})\n\t}\n\n\tif opt.NATSClientName == \"\" {\n\t\topt.NATSClientName = \"jetstream-controller\"\n\t}\n\n\tji := opt.JetstreamIface.JetstreamV1beta2()\n\tstreamQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any](), \"Streams\")\n\tconsumerQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any](), \"Consumers\")\n\n\tstreamInformer.Informer().AddEventHandler(\n\t\teventHandlers(\n\t\t\tstreamQueue,\n\t\t),\n\t)\n\n\tconsumerInformer.Informer().AddEventHandler(\n\t\teventHandlers(\n\t\t\tconsumerQueue,\n\t\t),\n\t)\n\n\t// Add warning handlers for unsupported resources in legacy mode\n\tkeyValueInformer.Informer().AddEventHandler(\n\t\tcache.ResourceEventHandlerFuncs{\n\t\t\tAddFunc: func(obj interface{}) {\n\t\t\t\tif kv, ok := obj.(*apis.KeyValue); ok {\n\t\t\t\t\tklog.Warningf(\"KeyValue resource %s/%s detected but not supported in legacy mode. The NATS KV bucket will NOT be created. Enable --control-loop mode to use KeyValue resources.\", kv.Namespace, kv.Name)\n\t\t\t\t\topt.Recorder.Event(kv, k8sapi.EventTypeWarning, \"NotSupported\", \"KeyValue resources require --control-loop mode. The NATS KV bucket will NOT be created.\")\n\t\t\t\t}\n\t\t\t},\n\t\t\tUpdateFunc: func(oldObj, newObj interface{}) {\n\t\t\t\tif kv, ok := newObj.(*apis.KeyValue); ok {\n\t\t\t\t\tklog.Warningf(\"KeyValue resource %s/%s updated but not supported in legacy mode. Changes will NOT be applied to NATS. Enable --control-loop mode to use KeyValue resources.\", kv.Namespace, kv.Name)\n\t\t\t\t\topt.Recorder.Event(kv, k8sapi.EventTypeWarning, \"NotSupported\", \"KeyValue resources require --control-loop mode. Updates will NOT be applied.\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t)\n\n\tobjectStoreInformer.Informer().AddEventHandler(\n\t\tcache.ResourceEventHandlerFuncs{\n\t\t\tAddFunc: func(obj interface{}) {\n\t\t\t\tif os, ok := obj.(*apis.ObjectStore); ok {\n\t\t\t\t\tklog.Warningf(\"ObjectStore resource %s/%s detected but not supported in legacy mode. The NATS object store will NOT be created. Enable --control-loop mode to use ObjectStore resources.\", os.Namespace, os.Name)\n\t\t\t\t\topt.Recorder.Event(os, k8sapi.EventTypeWarning, \"NotSupported\", \"ObjectStore resources require --control-loop mode. The NATS object store will NOT be created.\")\n\t\t\t\t}\n\t\t\t},\n\t\t\tUpdateFunc: func(oldObj, newObj interface{}) {\n\t\t\t\tif os, ok := newObj.(*apis.ObjectStore); ok {\n\t\t\t\t\tklog.Warningf(\"ObjectStore resource %s/%s updated but not supported in legacy mode. Changes will NOT be applied to NATS. Enable --control-loop mode to use ObjectStore resources.\", os.Namespace, os.Name)\n\t\t\t\t\topt.Recorder.Event(os, k8sapi.EventTypeWarning, \"NotSupported\", \"ObjectStore resources require --control-loop mode. Updates will NOT be applied.\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t)\n\n\tcacheDir, err := os.MkdirTemp(\".\", \"nack\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer os.RemoveAll(cacheDir)\n\n\treturn &Controller{\n\t\tctx:  opt.Ctx,\n\t\topts: opt,\n\n\t\tki:              opt.KubeIface.CoreV1(),\n\t\tji:              ji,\n\t\tinformerFactory: informerFactory,\n\t\trec:             opt.Recorder,\n\n\t\tstrLister: streamInformer.Lister(),\n\t\tstrSynced: streamInformer.Informer().HasSynced,\n\t\tstrQueue:  streamQueue,\n\n\t\tcnsLister: consumerInformer.Lister(),\n\t\tcnsSynced: consumerInformer.Informer().HasSynced,\n\t\tcnsQueue:  consumerQueue,\n\n\t\taccLister: accountInformer.Lister(),\n\n\t\tkvLister: keyValueInformer.Lister(),\n\t\tkvSynced: keyValueInformer.Informer().HasSynced,\n\n\t\tosLister: objectStoreInformer.Lister(),\n\t\tosSynced: objectStoreInformer.Informer().HasSynced,\n\n\t\tcacheDir: cacheDir,\n\t}\n}\n\nfunc (c *Controller) Run() error {\n\t// Connect to NATS.\n\topts := make([]nats.Option, 0)\n\t// Always attempt to have a connection to NATS.\n\topts = append(opts, nats.MaxReconnects(-1))\n\tif c.opts.NATSTLSFirst {\n\t\topts = append(opts, nats.TLSHandshakeFirst())\n\t}\n\tnatsCtxDefaults := &natsContextDefaults{Name: c.opts.NATSClientName}\n\tif !c.opts.CRDConnect {\n\t\t// Use JWT/NKEYS based credentials if present.\n\t\tif c.opts.NATSCredentials != \"\" {\n\t\t\topts = append(opts, nats.UserCredentials(c.opts.NATSCredentials))\n\t\t} else if c.opts.NATSNKey != \"\" {\n\t\t\topt, err := nats.NkeyOptionFromSeed(c.opts.NATSNKey)\n\t\t\tif err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\topts = append(opts, opt)\n\t\t}\n\n\t\tif c.opts.NATSCertificate != \"\" && c.opts.NATSKey != \"\" {\n\t\t\tnatsCtxDefaults.TLSCert = c.opts.NATSCertificate\n\t\t\tnatsCtxDefaults.TLSKey = c.opts.NATSKey\n\t\t}\n\n\t\tif c.opts.NATSCA != \"\" {\n\t\t\tnatsCtxDefaults.TLSCAs = []string{c.opts.NATSCA}\n\t\t}\n\t\tnatsCtxDefaults.URL = c.opts.NATSServerURL\n\t\tncp := newNatsConnPool(logrus.New(), natsCtxDefaults, opts)\n\t\tpooledNc, err := ncp.Get(&natsContext{})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to connect to nats: %w\", err)\n\t\t}\n\t\tpooledNc.ReturnToPool()\n\t\tc.connPool = ncp\n\t} else {\n\t\tc.connPool = newNatsConnPool(logrus.New(), natsCtxDefaults, opts)\n\t}\n\n\tdefer utilruntime.HandleCrash()\n\n\tdefer c.strQueue.ShutDown()\n\tdefer c.cnsQueue.ShutDown()\n\n\tc.informerFactory.Start(c.ctx.Done())\n\n\tif !cache.WaitForCacheSync(c.ctx.Done(), c.strSynced) {\n\t\treturn fmt.Errorf(\"failed to wait for stream cache sync\")\n\t}\n\tif !cache.WaitForCacheSync(c.ctx.Done(), c.cnsSynced) {\n\t\treturn fmt.Errorf(\"failed to wait for consumer cache sync\")\n\t}\n\t// Also wait for KeyValue and ObjectStore caches to sync so we can emit warnings\n\tif !cache.WaitForCacheSync(c.ctx.Done(), c.kvSynced) {\n\t\treturn fmt.Errorf(\"failed to wait for keyvalue cache sync\")\n\t}\n\tif !cache.WaitForCacheSync(c.ctx.Done(), c.osSynced) {\n\t\treturn fmt.Errorf(\"failed to wait for objectstore cache sync\")\n\t}\n\n\tgo wait.Until(c.runStreamQueue, time.Second, c.ctx.Done())\n\tgo wait.Until(c.runConsumerQueue, time.Second, c.ctx.Done())\n\tgo c.cleanupStreams()\n\tgo c.cleanupConsumers()\n\n\t<-c.ctx.Done()\n\n\t// Gracefully shutdown.\n\treturn nil\n}\n\n// RealJSMC creates a new JSM client from pooled nats connections\n// Providing a blank string for servers, defaults to c.opts.NATSServerUrls\n// call deferred jsmC.Close() on returned instance to return the nats connection to pool\nfunc (c *Controller) RealJSMC(cfg *natsContext) (jsmClient, error) {\n\tif cfg == nil {\n\t\tcfg = &natsContext{}\n\t}\n\tpooledNc, err := c.connPool.Get(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tjm, err := jsm.New(pooledNc.nc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tjsmc := &realJsmClient{pooledNc: pooledNc, jm: jm}\n\treturn jsmc, nil\n}\n\nfunc selectMissingStreamsFromList(prev, cur map[string]*apis.Stream) []*apis.Stream {\n\tvar deleted []*apis.Stream\n\tfor name, ps := range prev {\n\t\tif _, ok := cur[name]; !ok {\n\t\t\tdeleted = append(deleted, ps)\n\t\t}\n\t}\n\treturn deleted\n}\n\nfunc streamsMap(ss []*apis.Stream) map[string]*apis.Stream {\n\tm := make(map[string]*apis.Stream)\n\tfor _, s := range ss {\n\t\tm[fmt.Sprintf(\"%s/%s\", s.Namespace, s.Name)] = s\n\t}\n\treturn m\n}\n\nfunc (c *Controller) cleanupStreams() error {\n\tif c.opts.ReadOnly {\n\t\treturn nil\n\t}\n\ttick := time.NewTicker(c.opts.CleanupPeriod)\n\tdefer tick.Stop()\n\n\t// Track the Stream CRDs that may have been created.\n\tvar prevStreams map[string]*apis.Stream\n\tfor {\n\t\tselect {\n\t\tcase <-c.ctx.Done():\n\t\t\treturn c.ctx.Err()\n\t\tcase <-tick.C:\n\t\t\tstreams, err := c.strLister.List(labels.Everything())\n\t\t\tif err != nil {\n\t\t\t\tklog.Infof(\"failed to list streams for cleanup: %s\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsm := streamsMap(streams)\n\t\t\tmissing := selectMissingStreamsFromList(prevStreams, sm)\n\t\t\tfor _, s := range missing {\n\t\t\t\t// A stream that we were tracking but that for some reason\n\t\t\t\t// was not part of the latest list shared by informer.\n\t\t\t\t// Need to double check whether the stream is present before\n\t\t\t\t// considering deletion.\n\t\t\t\tklog.Infof(\"stream %s/%s might be missing, looking it up...\", s.Namespace, s.Name)\n\t\t\t\tctx, done := context.WithTimeout(context.Background(), 10*time.Second)\n\t\t\t\tdefer done()\n\t\t\t\t_, err := c.ji.Streams(s.Namespace).Get(ctx, s.Name, k8smeta.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tif k8serrors.IsNotFound(err) {\n\t\t\t\t\t\tklog.Infof(\"stream %s/%s was not found anymore, deleting from JetStream\", s.Namespace, s.Name)\n\t\t\t\t\t\tt := k8smeta.NewTime(time.Now())\n\t\t\t\t\t\ts.DeletionTimestamp = &t\n\t\t\t\t\t\tif err := c.processStreamObject(s, c.RealJSMC); err != nil && !k8serrors.IsNotFound(err) {\n\t\t\t\t\t\t\tklog.Infof(\"failed to delete stream %s/%s: %s\", s.Namespace, s.Name, err)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tklog.Infof(\"deleted stream %s/%s from JetStream\", s.Namespace, s.Name)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tklog.Warningf(\"error looking up stream %s/%s\", s.Namespace, s.Name)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tklog.Infof(\"found stream %s/%s, no further action needed\", s.Namespace, s.Name)\n\t\t\t\t}\n\t\t\t}\n\t\t\tprevStreams = sm\n\t\t}\n\t}\n}\n\nfunc selectMissingConsumersFromList(prev, cur map[string]*apis.Consumer) []*apis.Consumer {\n\tvar deleted []*apis.Consumer\n\tfor name, ps := range prev {\n\t\tif _, ok := cur[name]; !ok {\n\t\t\tdeleted = append(deleted, ps)\n\t\t}\n\t}\n\treturn deleted\n}\n\nfunc consumerMap(cs []*apis.Consumer) map[string]*apis.Consumer {\n\tm := make(map[string]*apis.Consumer)\n\tfor _, c := range cs {\n\t\tm[fmt.Sprintf(\"%s/%s\", c.Namespace, c.Name)] = c\n\t}\n\treturn m\n}\n\nfunc (c *Controller) cleanupConsumers() error {\n\tif c.opts.ReadOnly {\n\t\treturn nil\n\t}\n\ttick := time.NewTicker(c.opts.CleanupPeriod)\n\tdefer tick.Stop()\n\n\t// Track consumers that may have been deleted.\n\tvar prevConsumers map[string]*apis.Consumer\n\tfor {\n\t\tselect {\n\t\tcase <-c.ctx.Done():\n\t\t\treturn c.ctx.Err()\n\t\tcase <-tick.C:\n\t\t\tconsumers, err := c.cnsLister.List(labels.Everything())\n\t\t\tif err != nil {\n\t\t\t\tklog.Infof(\"failed to list consumers for cleanup: %s\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcm := consumerMap(consumers)\n\t\t\tmissing := selectMissingConsumersFromList(prevConsumers, cm)\n\t\t\tfor _, cns := range missing {\n\t\t\t\t// A consumer that we were tracking but that for some reason\n\t\t\t\t// was not part of the latest list shared by informer.\n\t\t\t\t// Need to double check whether the consumer is present before\n\t\t\t\t// considering deletion.\n\t\t\t\tklog.Infof(\"consumer %s/%s might be missing, looking it up...\", cns.Namespace, cns.Name)\n\t\t\t\tctx, done := context.WithTimeout(context.Background(), 10*time.Second)\n\t\t\t\tdefer done()\n\t\t\t\t_, err := c.ji.Consumers(cns.Namespace).Get(ctx, cns.Name, k8smeta.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tif k8serrors.IsNotFound(err) {\n\t\t\t\t\t\tklog.Infof(\"consumer %s/%s was not found anymore, deleting from JetStream\", cns.Namespace, cns.Name)\n\t\t\t\t\t\tt := k8smeta.NewTime(time.Now())\n\t\t\t\t\t\tcns.DeletionTimestamp = &t\n\t\t\t\t\t\tif err := c.processConsumerObject(cns, c.RealJSMC); err != nil && !k8serrors.IsNotFound(err) {\n\t\t\t\t\t\t\tklog.Infof(\"failed to delete consumer %s/%s: %s\", cns.Namespace, cns.Name, err)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tklog.Infof(\"deleted consumer %s/%s from JetStream\", cns.Namespace, cns.Name)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tklog.Warningf(\"error looking up consumer %s/%s\", cns.Namespace, cns.Name)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tklog.Infof(\"found consumer %s/%s, no further action needed\", cns.Namespace, cns.Name)\n\t\t\t\t}\n\t\t\t}\n\t\t\tprevConsumers = cm\n\t\t}\n\t}\n}\n\nfunc (c *Controller) normalEvent(o runtime.Object, reason, message string) {\n\tif c.rec != nil {\n\t\tc.rec.Event(o, k8sapi.EventTypeNormal, reason, message)\n\t}\n}\n\nfunc (c *Controller) warningEvent(o runtime.Object, reason, message string) {\n\tif c.rec != nil {\n\t\tc.rec.Event(o, k8sapi.EventTypeWarning, reason, message)\n\t}\n}\n\ntype accountOverrides struct {\n\tremoteClientCert string\n\tremoteClientKey  string\n\tremoteRootCA     string\n\tservers          []string\n\tuserCreds        string\n\tnkey             string\n\tuser             string\n\tpassword         string\n\ttoken            string\n}\n\nfunc (c *Controller) getAccountOverrides(account string, ns string) (*accountOverrides, error) {\n\toverrides := &accountOverrides{}\n\n\tif account == \"\" || !c.opts.CRDConnect {\n\t\treturn overrides, nil\n\t}\n\n\t// Lookup the account using the REST client.\n\tctx, done := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer done()\n\tacc, err := c.ji.Accounts(ns).Get(ctx, account, k8smeta.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\toverrides.servers = acc.Spec.Servers\n\n\t// Lookup the TLS secrets\n\tif acc.Spec.TLS != nil && acc.Spec.TLS.Secret != nil {\n\t\tsecretName := acc.Spec.TLS.Secret.Name\n\t\tsecret, err := c.ki.Secrets(ns).Get(c.ctx, secretName, k8smeta.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Write this to the cacheDir.\n\t\taccDir := filepath.Join(c.cacheDir, ns, account)\n\t\tif err := os.MkdirAll(accDir, 0o755); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar certData, keyData []byte\n\t\tvar certPath, keyPath string\n\n\t\tfor k, v := range secret.Data {\n\t\t\tswitch k {\n\t\t\tcase acc.Spec.TLS.ClientCert:\n\t\t\t\tcertPath = filepath.Join(accDir, k)\n\t\t\t\tcertData = v\n\t\t\tcase acc.Spec.TLS.ClientKey:\n\t\t\t\tkeyPath = filepath.Join(accDir, k)\n\t\t\t\tkeyData = v\n\t\t\tcase acc.Spec.TLS.RootCAs:\n\t\t\t\toverrides.remoteRootCA = filepath.Join(accDir, k)\n\t\t\t\tif err := os.WriteFile(overrides.remoteRootCA, v, 0o644); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif certData != nil && keyData != nil {\n\t\t\toverrides.remoteClientCert = certPath\n\t\t\toverrides.remoteClientKey = keyPath\n\n\t\t\tif err := os.WriteFile(certPath, certData, 0o644); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif err := os.WriteFile(keyPath, keyData, 0o644); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\t// Lookup the UserCredentials.\n\tif acc.Spec.Creds != nil && acc.Spec.Creds.Secret != nil {\n\t\tsecretName := acc.Spec.Creds.Secret.Name\n\t\tsecret, err := c.ki.Secrets(ns).Get(c.ctx, secretName, k8smeta.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Write the user credentials to the cache dir.\n\t\taccDir := filepath.Join(c.cacheDir, ns, account)\n\t\tif err := os.MkdirAll(accDir, 0o755); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif credsBytes, ok := secret.Data[acc.Spec.Creds.File]; ok {\n\t\t\toverrides.userCreds = filepath.Join(accDir, acc.Spec.Creds.File)\n\t\t\tif err := os.WriteFile(overrides.userCreds, credsBytes, 0o644); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\t// Lookup the NKey seed.\n\tif acc.Spec.NKey != nil && acc.Spec.NKey.Secret != nil {\n\t\tsecretName := acc.Spec.NKey.Secret.Name\n\t\tsecret, err := c.ki.Secrets(ns).Get(c.ctx, secretName, k8smeta.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif nkeyBytes, ok := secret.Data[acc.Spec.NKey.Seed]; ok {\n\t\t\toverrides.nkey = string(nkeyBytes)\n\t\t}\n\t}\n\n\t// Lookup the Token.\n\tif acc.Spec.Token != nil {\n\t\tsecretName := acc.Spec.Token.Secret.Name\n\t\tsecret, err := c.ki.Secrets(ns).Get(c.ctx, secretName, k8smeta.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif token, ok := secret.Data[acc.Spec.Token.Token]; ok {\n\t\t\toverrides.token = string(token)\n\t\t}\n\t}\n\n\t// Lookup the User.\n\tif acc.Spec.User != nil {\n\t\tsecretName := acc.Spec.User.Secret.Name\n\t\tsecret, err := c.ki.Secrets(ns).Get(c.ctx, secretName, k8smeta.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tuserBytes := secret.Data[acc.Spec.User.User]\n\t\tpasswordBytes := secret.Data[acc.Spec.User.Password]\n\t\tif userBytes != nil && passwordBytes != nil {\n\t\t\toverrides.user = string(userBytes)\n\t\t\toverrides.password = string(passwordBytes)\n\t\t}\n\t}\n\n\treturn overrides, nil\n}\n\ntype jsmcSpecOverrides struct {\n\tservers []string\n\ttls     *apis.TLS\n\tcreds   string\n\tnkey    string\n}\n\nfunc (c *Controller) runWithJsmc(jsm jsmClientFunc, acc *accountOverrides, spec *jsmcSpecOverrides, o runtime.Object, op func(jsmClient) error) error {\n\tif !c.opts.CRDConnect {\n\t\tjsmc, err := jsm(&natsContext{})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn op(jsmc)\n\t}\n\n\t// Create a new client\n\tnatsCtx := &natsContext{}\n\t// Use JWT/NKEYS/user-password/token based credentials if present.\n\tif spec.creds != \"\" {\n\t\tnatsCtx.Credentials = spec.creds\n\t} else if spec.nkey != \"\" {\n\t\tnatsCtx.Nkey = spec.nkey\n\t}\n\tif spec.tls != nil {\n\t\tif spec.tls.ClientCert != \"\" && spec.tls.ClientKey != \"\" {\n\t\t\tnatsCtx.TLSCert = spec.tls.ClientCert\n\t\t\tnatsCtx.TLSKey = spec.tls.ClientKey\n\t\t}\n\t}\n\n\t// Use fetched secrets for the account and server if defined.\n\tif acc.remoteClientCert != \"\" && acc.remoteClientKey != \"\" {\n\t\tnatsCtx.TLSCert = acc.remoteClientCert\n\t\tnatsCtx.TLSKey = acc.remoteClientKey\n\t}\n\tif acc.remoteRootCA != \"\" {\n\t\tnatsCtx.TLSCAs = []string{acc.remoteRootCA}\n\t}\n\tif acc.userCreds != \"\" {\n\t\tnatsCtx.Credentials = acc.userCreds\n\t} else if acc.nkey != \"\" {\n\t\tnatsCtx.Nkey = acc.nkey\n\t}\n\n\tif acc.user != \"\" && acc.password != \"\" {\n\t\tnatsCtx.Username = acc.user\n\t\tnatsCtx.Password = acc.password\n\t} else if acc.token != \"\" {\n\t\tnatsCtx.Token = acc.token\n\t}\n\n\tif spec.tls != nil && len(spec.tls.RootCAs) > 0 {\n\t\tnatsCtx.TLSCAs = spec.tls.RootCAs\n\t}\n\n\tnatsServers := strings.Join(append(spec.servers, acc.servers...), \",\")\n\tnatsCtx.URL = natsServers\n\tc.normalEvent(o, \"Connecting\", \"Connecting to new nats-servers\")\n\tjsmc, err := jsm(natsCtx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to connect to nats-servers(%s): %w\", natsServers, err)\n\t}\n\n\tdefer jsmc.Close()\n\n\treturn op(jsmc)\n}\n\nfunc splitNamespaceName(item interface{}) (ns string, name string, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to split namespace-name: %w\", err)\n\t\t}\n\t}()\n\n\tkey, ok := item.(string)\n\tif !ok {\n\t\treturn \"\", \"\", fmt.Errorf(\"unexpected type: got=%T, want=%T\", item, key)\n\t}\n\n\tns, name, err = cache.SplitMetaNamespaceKey(key)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\treturn ns, name, nil\n}\n\nfunc getStorageType(s string) (jsmapi.StorageType, error) {\n\tswitch s {\n\tcase strings.ToLower(jsmapi.FileStorage.String()):\n\t\treturn jsmapi.FileStorage, nil\n\tcase strings.ToLower(jsmapi.MemoryStorage.String()):\n\t\treturn jsmapi.MemoryStorage, nil\n\tdefault:\n\t\treturn 0, fmt.Errorf(\"invalid jetstream storage option: %s\", s)\n\t}\n}\n\nfunc enqueueWork(q workqueue.TypedRateLimitingInterface[any], item interface{}) (err error) {\n\tkey, err := cache.MetaNamespaceKeyFunc(item)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to enqueue work: %w\", err)\n\t}\n\n\tq.Add(key)\n\treturn nil\n}\n\ntype (\n\tjsmClientFunc func(*natsContext) (jsmClient, error)\n\tprocessorFunc func(ns, name string, jmsClient jsmClientFunc) error\n)\n\nfunc processQueueNext(q workqueue.TypedRateLimitingInterface[any], jmsClient jsmClientFunc, process processorFunc) {\n\titem, shutdown := q.Get()\n\tif shutdown {\n\t\treturn\n\t}\n\tdefer q.Done(item)\n\n\tns, name, err := splitNamespaceName(item)\n\tif err != nil {\n\t\t// Probably junk, clean it up.\n\t\tutilruntime.HandleError(err)\n\t\tq.Forget(item)\n\t\treturn\n\t}\n\n\terr = process(ns, name, jmsClient)\n\tif err == nil {\n\t\t// Item processed successfully, don't requeue.\n\t\tq.Forget(item)\n\t\treturn\n\t}\n\n\tutilruntime.HandleError(err)\n\n\tif q.NumRequeues(item) < maxQueueRetries {\n\t\t// Failed to process item, try again.\n\t\tq.AddRateLimited(item)\n\t\treturn\n\t}\n\n\t// If we haven't been able to recover by this point, then just stop.\n\t// The user should have enough info in kubectl describe to debug.\n\tq.Forget(item)\n}\n\nfunc UpsertCondition(cs []apis.Condition, next apis.Condition) []apis.Condition {\n\tfor i := 0; i < len(cs); i++ {\n\t\tif cs[i].Type != next.Type {\n\t\t\tcontinue\n\t\t}\n\n\t\tcs[i] = next\n\t\treturn cs\n\t}\n\n\treturn append(cs, next)\n}\n\nfunc shouldEnqueue(prevObj, nextObj interface{}) bool {\n\ttype crd interface {\n\t\tGetDeletionTimestamp() *k8smeta.Time\n\t\tGetSpec() interface{}\n\t}\n\n\tprev, ok := prevObj.(crd)\n\tif !ok {\n\t\treturn false\n\t}\n\n\tnext, ok := nextObj.(crd)\n\tif !ok {\n\t\treturn false\n\t}\n\n\tmarkedDelete := next.GetDeletionTimestamp() != nil\n\tspecChanged := !equality.Semantic.DeepEqual(prev.GetSpec(), next.GetSpec())\n\n\treturn markedDelete || specChanged\n}\n\nfunc eventHandlers(q workqueue.TypedRateLimitingInterface[any]) cache.ResourceEventHandlerFuncs {\n\treturn cache.ResourceEventHandlerFuncs{\n\t\tAddFunc: func(obj interface{}) {\n\t\t\tif err := enqueueWork(q, obj); err != nil {\n\t\t\t\tutilruntime.HandleError(err)\n\t\t\t}\n\t\t},\n\t\tUpdateFunc: func(prev, next interface{}) {\n\t\t\tif !shouldEnqueue(prev, next) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err := enqueueWork(q, next); err != nil {\n\t\t\t\tutilruntime.HandleError(err)\n\t\t\t}\n\t\t},\n\t\tDeleteFunc: func(obj interface{}) {\n\t\t\tif err := enqueueWork(q, obj); err != nil {\n\t\t\t\tutilruntime.HandleError(err)\n\t\t\t}\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "controllers/jetstream/controller_test.go",
    "content": "package jetstream\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\tjsmapi \"github.com/nats-io/jsm.go/api\"\n\tapis \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\n\tk8sapis \"k8s.io/api/core/v1\"\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\t\"k8s.io/client-go/util/workqueue\"\n)\n\nfunc TestMain(m *testing.M) {\n\t// Disable error logs.\n\tutilruntime.ErrorHandlers = []utilruntime.ErrorHandler{\n\t\tfunc(ctx context.Context, err error, msg string, args ...any) {},\n\t}\n\n\tos.Exit(m.Run())\n}\n\nfunc TestGetStorageType(t *testing.T) {\n\tt.Parallel()\n\n\tcases := []struct {\n\t\tstorage string\n\n\t\twantType jsmapi.StorageType\n\t\twantErr  bool\n\t}{\n\t\t{storage: \"memory\", wantType: jsmapi.MemoryStorage},\n\t\t{storage: \"file\", wantType: jsmapi.FileStorage},\n\t\t{storage: \"junk\", wantErr: true},\n\t}\n\tfor _, c := range cases {\n\t\tc := c\n\t\tt.Run(c.storage, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tgot, err := getStorageType(c.storage)\n\t\t\tif err != nil && !c.wantErr {\n\t\t\t\tt.Error(\"unexpected error\")\n\t\t\t\tt.Fatalf(\"got=%s; want=nil\", err)\n\t\t\t} else if err == nil && c.wantErr {\n\t\t\t\tt.Error(\"unexpected success\")\n\t\t\t\tt.Fatalf(\"got=nil; want=err\")\n\t\t\t}\n\n\t\t\tif got != c.wantType {\n\t\t\t\tt.Error(\"unexpected storage type\")\n\t\t\t\tt.Fatalf(\"got=%v; want=%v\", got, c.wantType)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestEnqueueWork(t *testing.T) {\n\tt.Parallel()\n\n\tlimiter := workqueue.DefaultTypedControllerRateLimiter[any]()\n\tq := workqueue.NewNamedRateLimitingQueue(limiter, \"StreamsTest\")\n\tdefer q.ShutDown()\n\n\ts := &apis.Stream{\n\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\tNamespace: \"default\",\n\t\t\tName:      \"my-stream\",\n\t\t},\n\t}\n\n\tif err := enqueueWork(q, s); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif got, want := q.Len(), 1; got != want {\n\t\tt.Error(\"unexpected queue length\")\n\t\tt.Fatalf(\"got=%d; want=%d\", got, want)\n\t}\n\n\twantItem := fmt.Sprintf(\"%s/%s\", s.Namespace, s.Name)\n\tgotItem, _ := q.Get()\n\tif gotItem != wantItem {\n\t\tt.Error(\"unexpected queue item\")\n\t\tt.Fatalf(\"got=%s; want=%s\", gotItem, wantItem)\n\t}\n}\n\nfunc TestProcessQueueNext(t *testing.T) {\n\tt.Parallel()\n\n\tt.Run(\"bad item key\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tlimiter := workqueue.DefaultTypedControllerRateLimiter[any]()\n\t\tq := workqueue.NewNamedRateLimitingQueue(limiter, \"StreamsTest\")\n\t\tdefer q.ShutDown()\n\n\t\tkey := \"this/is/a/bad/key\"\n\t\tq.Add(key)\n\n\t\tprocessQueueNext(q, testWrapJSMC(&mockJsmClient{}), func(ns, name string, c jsmClientFunc) error {\n\t\t\treturn nil\n\t\t})\n\n\t\tif got, want := q.Len(), 0; got != want {\n\t\t\tt.Error(\"unexpected number of items in queue\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, want)\n\t\t}\n\n\t\tif got, want := q.NumRequeues(key), 0; got != want {\n\t\t\tt.Error(\"unexpected number of requeues\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, want)\n\t\t}\n\t})\n\n\tt.Run(\"process error\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tlimiter := workqueue.DefaultTypedControllerRateLimiter[any]()\n\t\tq := workqueue.NewNamedRateLimitingQueue(limiter, \"StreamsTest\")\n\t\tdefer q.ShutDown()\n\n\t\tns, name := \"default\", \"mystream\"\n\t\tkey := fmt.Sprintf(\"%s/%s\", ns, name)\n\t\tq.Add(key)\n\n\t\tmaxGets := maxQueueRetries + 1\n\t\tnumRequeues := -1\n\t\tfor i := 0; i < maxGets; i++ {\n\t\t\tif i == maxGets-1 {\n\t\t\t\tnumRequeues = q.NumRequeues(key)\n\t\t\t}\n\n\t\t\tprocessQueueNext(q, testWrapJSMC(&mockJsmClient{}), func(ns, name string, c jsmClientFunc) error {\n\t\t\t\treturn fmt.Errorf(\"processing error\")\n\t\t\t})\n\t\t}\n\n\t\tif got, want := q.Len(), 0; got != want {\n\t\t\tt.Error(\"unexpected number of items in queue\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, want)\n\t\t}\n\n\t\tif got, want := numRequeues, 10; got != want {\n\t\t\tt.Error(\"unexpected number of requeues\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, want)\n\t\t}\n\t})\n\n\tt.Run(\"process ok\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tlimiter := workqueue.DefaultTypedControllerRateLimiter[any]()\n\t\tq := workqueue.NewNamedRateLimitingQueue(limiter, \"StreamsTest\")\n\t\tdefer q.ShutDown()\n\n\t\tns, name := \"default\", \"mystream\"\n\t\tkey := fmt.Sprintf(\"%s/%s\", ns, name)\n\t\tq.Add(key)\n\n\t\tnumRequeues := q.NumRequeues(key)\n\t\tprocessQueueNext(q, testWrapJSMC(&mockJsmClient{}), func(ns, name string, c jsmClientFunc) error {\n\t\t\treturn nil\n\t\t})\n\n\t\tif got, want := q.Len(), 0; got != want {\n\t\t\tt.Error(\"unexpected number of items in queue\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, want)\n\t\t}\n\n\t\tif got, want := numRequeues, 0; got != want {\n\t\t\tt.Error(\"unexpected number of requeues\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, want)\n\t\t}\n\t})\n}\n\nfunc TestUpsertCondition(t *testing.T) {\n\tt.Parallel()\n\n\tvar cs []apis.Condition\n\n\tcs = UpsertCondition(cs, apis.Condition{\n\t\tType:               readyCondType,\n\t\tStatus:             k8sapis.ConditionTrue,\n\t\tLastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),\n\t\tReason:             \"Synced\",\n\t\tMessage:            \"Stream is synced with spec\",\n\t})\n\tif got, want := len(cs), 1; got != want {\n\t\tt.Error(\"unexpected len conditions\")\n\t\tt.Fatalf(\"got=%d; want=%d\", got, want)\n\t}\n\tif got, want := cs[0].Reason, \"Synced\"; got != want {\n\t\tt.Error(\"unexpected reason\")\n\t\tt.Fatalf(\"got=%s; want=%s\", got, want)\n\t}\n\n\tcs = UpsertCondition(cs, apis.Condition{\n\t\tType:               readyCondType,\n\t\tStatus:             k8sapis.ConditionFalse,\n\t\tLastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),\n\t\tReason:             \"Errored\",\n\t\tMessage:            \"invalid foo\",\n\t})\n\tif got, want := len(cs), 1; got != want {\n\t\tt.Error(\"unexpected len conditions\")\n\t\tt.Fatalf(\"got=%d; want=%d\", got, want)\n\t}\n\tif got, want := cs[0].Reason, \"Errored\"; got != want {\n\t\tt.Error(\"unexpected reason\")\n\t\tt.Fatalf(\"got=%s; want=%s\", got, want)\n\t}\n\n\tcs = UpsertCondition(cs, apis.Condition{\n\t\tType:               \"Foo\",\n\t\tStatus:             k8sapis.ConditionTrue,\n\t\tLastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),\n\t\tReason:             \"Bar\",\n\t\tMessage:            \"bar ok\",\n\t})\n\tif got, want := len(cs), 2; got != want {\n\t\tt.Error(\"unexpected len conditions\")\n\t\tt.Fatalf(\"got=%d; want=%d\", got, want)\n\t}\n\tif got, want := cs[1].Reason, \"Bar\"; got != want {\n\t\tt.Error(\"unexpected reason\")\n\t\tt.Fatalf(\"got=%s; want=%s\", got, want)\n\t}\n}\n\nfunc TestShouldEnqueue(t *testing.T) {\n\tt.Parallel()\n\n\tts := k8smeta.NewTime(time.Now())\n\n\tcases := []struct {\n\t\tname string\n\t\tprev interface{}\n\t\tnext interface{}\n\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"stream deleted\",\n\t\t\tprev: &apis.Stream{\n\t\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tName:      \"obj-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tnext: &apis.Stream{\n\t\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\t\tNamespace:         \"default\",\n\t\t\t\t\tName:              \"obj-name\",\n\t\t\t\t\tDeletionTimestamp: &ts,\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"stream spec changed\",\n\t\t\tprev: &apis.Stream{\n\t\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tName:      \"obj-name\",\n\t\t\t\t},\n\t\t\t\tSpec: apis.StreamSpec{\n\t\t\t\t\tName: \"foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tnext: &apis.Stream{\n\t\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tName:      \"obj-name\",\n\t\t\t\t},\n\t\t\t\tSpec: apis.StreamSpec{\n\t\t\t\t\tName: \"bar\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"consumer deleted\",\n\t\t\tprev: &apis.Consumer{\n\t\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tName:      \"obj-name\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tnext: &apis.Consumer{\n\t\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\t\tNamespace:         \"default\",\n\t\t\t\t\tName:              \"obj-name\",\n\t\t\t\t\tDeletionTimestamp: &ts,\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"consumer spec changed\",\n\t\t\tprev: &apis.Consumer{\n\t\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tName:      \"obj-name\",\n\t\t\t\t},\n\t\t\t\tSpec: apis.ConsumerSpec{\n\t\t\t\t\tDurableName: \"foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tnext: &apis.Consumer{\n\t\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tName:      \"obj-name\",\n\t\t\t\t},\n\t\t\t\tSpec: apis.ConsumerSpec{\n\t\t\t\t\tDurableName: \"bar\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tc := c\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tgot := shouldEnqueue(c.prev, c.next)\n\t\t\tif got != c.want {\n\t\t\t\tt.Fatalf(\"got=%t; want=%t\", got, c.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "controllers/jetstream/jsmclient.go",
    "content": "package jetstream\n\nimport (\n\t\"context\"\n\n\t\"github.com/nats-io/jsm.go\"\n\tjsmapi \"github.com/nats-io/jsm.go/api\"\n\t\"github.com/nats-io/nats.go\"\n\t\"github.com/sirupsen/logrus\"\n)\n\ntype jsmClient interface {\n\tConnect(servers string, opts ...nats.Option) error\n\tClose()\n\n\tLoadStream(ctx context.Context, name string) (jsmStream, error)\n\tNewStream(ctx context.Context, name string, opts []jsm.StreamOption) (jsmStream, error)\n\n\tLoadConsumer(ctx context.Context, stream, consumer string) (jsmConsumer, error)\n\tNewConsumer(ctx context.Context, stream string, opts []jsm.ConsumerOption) (jsmConsumer, error)\n}\n\ntype jsmStream interface {\n\tUpdateConfiguration(cnf jsmapi.StreamConfig, opts ...jsm.StreamOption) error\n\tDelete() error\n}\n\ntype jsmConsumer interface {\n\tUpdateConfiguration(opts ...jsm.ConsumerOption) error\n\tDelete() error\n}\n\ntype realJsmClient struct {\n\tpooledNc *pooledNatsConn\n\tjm       *jsm.Manager\n}\n\nfunc (c *realJsmClient) Connect(servers string, opts ...nats.Option) error {\n\tconnPool := newNatsConnPool(logrus.New(), &natsContextDefaults{URL: servers}, opts)\n\tpooledNc, err := connPool.Get(&natsContext{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.pooledNc = pooledNc\n\n\tm, err := jsm.New(pooledNc.nc)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.jm = m\n\n\treturn nil\n}\n\nfunc (c *realJsmClient) Close() {\n\tc.pooledNc.ReturnToPool()\n}\n\nfunc (c *realJsmClient) LoadStream(_ context.Context, name string) (jsmStream, error) {\n\treturn c.jm.LoadStream(name)\n}\n\nfunc (c *realJsmClient) NewStream(_ context.Context, name string, opts []jsm.StreamOption) (jsmStream, error) {\n\treturn c.jm.NewStream(name, opts...)\n}\n\nfunc (c *realJsmClient) LoadConsumer(_ context.Context, stream, consumer string) (jsmConsumer, error) {\n\treturn c.jm.LoadConsumer(stream, consumer)\n}\n\nfunc (c *realJsmClient) NewConsumer(_ context.Context, stream string, opts []jsm.ConsumerOption) (jsmConsumer, error) {\n\treturn c.jm.NewConsumer(stream, opts...)\n}\n"
  },
  {
    "path": "controllers/jetstream/jsmclient_test.go",
    "content": "package jetstream\n\nimport (\n\t\"context\"\n\n\t\"github.com/nats-io/jsm.go\"\n\tjsmapi \"github.com/nats-io/jsm.go/api\"\n\t\"github.com/nats-io/nats.go\"\n)\n\ntype mockStream struct {\n\tdeleteErr            error\n\tcapturedConfig       *jsmapi.StreamConfig\n\tupdateConfigCallback func(cnf jsmapi.StreamConfig) error\n}\n\nfunc (m *mockStream) UpdateConfiguration(cnf jsmapi.StreamConfig, opts ...jsm.StreamOption) error {\n\tif m.capturedConfig != nil {\n\t\t*m.capturedConfig = cnf\n\t}\n\tif m.updateConfigCallback != nil {\n\t\treturn m.updateConfigCallback(cnf)\n\t}\n\treturn nil\n}\n\nfunc (m *mockStream) Delete() error {\n\treturn m.deleteErr\n}\n\ntype mockConsumer struct {\n\tdeleteErr error\n}\n\nfunc (m *mockConsumer) UpdateConfiguration(opts ...jsm.ConsumerOption) error {\n\treturn nil\n}\n\nfunc (m *mockConsumer) Delete() error {\n\treturn m.deleteErr\n}\n\ntype mockJsmClient struct {\n\tconnectErr error\n\n\tloadStream    jsmStream\n\tloadStreamErr error\n\tnewStream     jsmStream\n\tnewStreamErr  error\n\n\tloadConsumer    jsmConsumer\n\tloadConsumerErr error\n\tnewConsumer     jsmConsumer\n\tnewConsumerErr  error\n}\n\nfunc (c *mockJsmClient) Connect(servers string, opts ...nats.Option) error {\n\treturn c.connectErr\n}\n\nfunc (c *mockJsmClient) Close() {}\n\nfunc (c *mockJsmClient) LoadStream(ctx context.Context, name string) (jsmStream, error) {\n\treturn c.loadStream, c.loadStreamErr\n}\n\nfunc (c *mockJsmClient) NewStream(ctx context.Context, name string, opt []jsm.StreamOption) (jsmStream, error) {\n\treturn c.newStream, c.newStreamErr\n}\n\nfunc (c *mockJsmClient) LoadConsumer(ctx context.Context, stream, consumer string) (jsmConsumer, error) {\n\treturn c.loadConsumer, c.loadConsumerErr\n}\n\nfunc (c *mockJsmClient) NewConsumer(ctx context.Context, stream string, opts []jsm.ConsumerOption) (jsmConsumer, error) {\n\treturn c.newConsumer, c.newConsumerErr\n}\n"
  },
  {
    "path": "controllers/jetstream/stream.go",
    "content": "// Copyright 2020 The NATS 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//\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\npackage jetstream\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\tjsm \"github.com/nats-io/jsm.go\"\n\tjsmapi \"github.com/nats-io/jsm.go/api\"\n\tapis \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\ttyped \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2\"\n\tk8sapi \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/util/retry\"\n\tklog \"k8s.io/klog/v2\"\n)\n\nfunc (c *Controller) runStreamQueue() {\n\tfor {\n\t\tprocessQueueNext(c.strQueue, c.RealJSMC, c.processStream)\n\t}\n}\n\nfunc (c *Controller) processStream(ns, name string, jsm jsmClientFunc) (err error) {\n\tstr, err := c.strLister.Streams(ns).Get(name)\n\tif err != nil && k8serrors.IsNotFound(err) {\n\t\treturn nil\n\t} else if err != nil {\n\t\treturn err\n\t}\n\n\treturn c.processStreamObject(str, jsm)\n}\n\nfunc (c *Controller) processStreamObject(str *apis.Stream, jsm jsmClientFunc) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to process stream: %w\", err)\n\t\t}\n\t}()\n\n\tspec := str.Spec\n\tifc := c.ji.Streams(str.Namespace)\n\tns := str.Namespace\n\treadOnly := c.opts.ReadOnly\n\n\tacc, err := c.getAccountOverrides(spec.Account, ns)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif err == nil {\n\t\t\treturn\n\t\t}\n\n\t\tif _, serr := setStreamErrored(c.ctx, str, ifc, err); serr != nil {\n\t\t\terr = fmt.Errorf(\"%s: %w\", err, serr)\n\t\t}\n\t}()\n\n\ttype operator func(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err error)\n\n\tnatsClientUtil := func(op operator) error {\n\t\treturn c.runWithJsmc(jsm, acc, &jsmcSpecOverrides{\n\t\t\tservers: spec.Servers,\n\t\t\ttls:     spec.TLS,\n\t\t\tcreds:   spec.Creds,\n\t\t\tnkey:    spec.Nkey,\n\t\t}, str, func(jsmc jsmClient) error {\n\t\t\treturn op(c.ctx, jsmc, spec)\n\t\t})\n\t}\n\n\tdeleteOK := str.GetDeletionTimestamp() != nil\n\tnewGeneration := str.Generation != str.Status.ObservedGeneration\n\tstrOK := true\n\terr = natsClientUtil(streamExists)\n\tvar apierr jsmapi.ApiError\n\tif errors.As(err, &apierr) && apierr.NotFoundError() {\n\t\tstrOK = false\n\t} else if err != nil {\n\t\treturn err\n\t}\n\tupdateOK := (strOK && !deleteOK && newGeneration)\n\tcreateOK := (!strOK && !deleteOK) || (!updateOK && !deleteOK && newGeneration)\n\n\tswitch {\n\tcase createOK:\n\t\tif readOnly {\n\t\t\tc.normalEvent(str, \"SkipCreate\", fmt.Sprintf(\"Skip creating stream %q\", spec.Name))\n\t\t\treturn nil\n\t\t}\n\t\tc.normalEvent(str, \"Creating\", fmt.Sprintf(\"Creating stream %q\", spec.Name))\n\t\tif err := natsClientUtil(createStream); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif _, err := setStreamOK(c.ctx, str, ifc); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.normalEvent(str, \"Created\", fmt.Sprintf(\"Created stream %q\", spec.Name))\n\tcase updateOK:\n\t\tif str.Spec.PreventUpdate || readOnly {\n\t\t\tc.normalEvent(str, \"SkipUpdate\", fmt.Sprintf(\"Skip updating stream %q\", spec.Name))\n\t\t\tif _, err := setStreamOK(c.ctx, str, ifc); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\tc.normalEvent(str, \"Updating\", fmt.Sprintf(\"Updating stream %q\", spec.Name))\n\t\tif err := natsClientUtil(updateStream); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif _, err := setStreamOK(c.ctx, str, ifc); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.normalEvent(str, \"Updated\", fmt.Sprintf(\"Updated stream %q\", spec.Name))\n\t\treturn nil\n\tcase deleteOK:\n\t\tif str.Spec.PreventDelete || readOnly {\n\t\t\tc.normalEvent(str, \"SkipDelete\", fmt.Sprintf(\"Skip deleting stream %q\", spec.Name))\n\t\t\tif _, err := setStreamOK(c.ctx, str, ifc); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\tc.normalEvent(str, \"Deleting\", fmt.Sprintf(\"Deleting stream %q\", spec.Name))\n\t\tif err := natsClientUtil(deleteStream); err != nil {\n\t\t\treturn err\n\t\t}\n\tdefault:\n\t\tc.normalEvent(str, \"Noop\", fmt.Sprintf(\"Nothing done for stream %q (prevent-delete=%v, prevent-update=%v)\",\n\t\t\tspec.Name, spec.PreventDelete, spec.PreventUpdate,\n\t\t))\n\t\t// Noop events only update the status of the CRD.\n\t\tif _, err := setStreamOK(c.ctx, str, ifc); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc streamExists(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to check if stream exists: %w\", err)\n\t\t}\n\t}()\n\n\t_, err = c.LoadStream(ctx, spec.Name)\n\treturn err\n}\n\nfunc createStream(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to create stream %q: %w\", spec.Name, err)\n\t\t}\n\t}()\n\n\tmaxAge, err := getDurationFromString(spec.MaxAge)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tduplicates, err := getDuplicates(spec.DuplicateWindow)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\topts := []jsm.StreamOption{\n\t\tjsm.Subjects(spec.Subjects...),\n\t\tjsm.MaxConsumers(spec.MaxConsumers),\n\t\tjsm.MaxMessageSize(int32(spec.MaxMsgSize)),\n\t\tjsm.MaxMessages(int64(spec.MaxMsgs)),\n\t\tjsm.Replicas(spec.Replicas),\n\t\tjsm.DuplicateWindow(duplicates),\n\t\tjsm.MaxAge(maxAge),\n\t\tjsm.MaxBytes(int64(spec.MaxBytes)),\n\t}\n\n\tswitch spec.Retention {\n\tcase \"limits\":\n\t\topts = append(opts, jsm.LimitsRetention())\n\tcase \"interest\":\n\t\topts = append(opts, jsm.InterestRetention())\n\tcase \"workqueue\":\n\t\topts = append(opts, jsm.WorkQueueRetention())\n\t}\n\n\tswitch spec.Storage {\n\tcase \"file\":\n\t\topts = append(opts, jsm.FileStorage())\n\tcase \"memory\":\n\t\topts = append(opts, jsm.MemoryStorage())\n\t}\n\n\tswitch spec.Discard {\n\tcase \"old\":\n\t\topts = append(opts, jsm.DiscardOld())\n\tcase \"new\":\n\t\topts = append(opts, jsm.DiscardNew())\n\t}\n\n\tswitch spec.Compression {\n\tcase \"s2\":\n\t\topts = append(opts, jsm.Compression(jsmapi.S2Compression))\n\tcase \"none\":\n\t\topts = append(opts, jsm.Compression(jsmapi.NoCompression))\n\t}\n\n\tif spec.NoAck {\n\t\topts = append(opts, jsm.NoAck())\n\t}\n\n\tif spec.Description != \"\" {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.Description = spec.Description\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tif spec.MaxMsgsPerSubject > 0 {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.MaxMsgsPer = int64(spec.MaxMsgsPerSubject)\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tif spec.Mirror != nil {\n\t\tss, err := getStreamSource(spec.Mirror)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.Mirror = ss\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tif spec.Placement != nil {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.Placement = &jsmapi.Placement{\n\t\t\t\tCluster: spec.Placement.Cluster,\n\t\t\t\tTags:    spec.Placement.Tags,\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tvar srcs []*jsmapi.StreamSource\n\tfor _, ss := range spec.Sources {\n\t\tjss, err := getStreamSource(ss)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsrcs = append(srcs, jss)\n\t}\n\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\to.Sources = srcs\n\t\treturn nil\n\t})\n\n\tif spec.RePublish != nil {\n\t\topts = append(opts, jsm.Republish(&jsmapi.RePublish{\n\t\t\tSource:      spec.RePublish.Source,\n\t\t\tDestination: spec.RePublish.Destination,\n\t\t}))\n\t}\n\n\tif spec.SubjectTransform != nil {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.SubjectTransform = &jsmapi.SubjectTransformConfig{\n\t\t\t\tSource:      spec.SubjectTransform.Source,\n\t\t\t\tDestination: spec.SubjectTransform.Dest,\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tif spec.AllowDirect {\n\t\topts = append(opts, jsm.AllowDirect())\n\t}\n\n\tif spec.AllowRollup {\n\t\topts = append(opts, jsm.AllowRollup())\n\t}\n\n\tif spec.DenyDelete {\n\t\topts = append(opts, jsm.DenyDelete())\n\t}\n\n\tif spec.DenyPurge {\n\t\topts = append(opts, jsm.DenyPurge())\n\t}\n\n\tif spec.DiscardPerSubject {\n\t\topts = append(opts, jsm.DiscardNewPerSubject())\n\t}\n\n\tif spec.FirstSequence != 0 {\n\t\topts = append(opts, jsm.FirstSequence(spec.FirstSequence))\n\t}\n\n\tif spec.Metadata != nil {\n\t\topts = append(opts, jsm.StreamMetadata(spec.Metadata))\n\t}\n\n\tif spec.AllowMsgTTL {\n\t\topts = append(opts, jsm.AllowMsgTTL())\n\t}\n\n\tif spec.SubjectDeleteMarkerTTL != \"\" {\n\t\td, err := time.ParseDuration(spec.SubjectDeleteMarkerTTL)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"parse subject delete marker TTL: %w\", err)\n\t\t}\n\t\topts = append(opts, jsm.SubjectDeleteMarkerTTL(d))\n\t}\n\n\tif spec.AllowMsgCounter {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.AllowMsgCounter = true\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tif spec.AllowAtomicPublish {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.AllowAtomicPublish = true\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tif spec.AllowMsgSchedules {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.AllowMsgSchedules = true\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tif spec.PersistMode == \"async\" {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.PersistMode = jsmapi.AsyncPersistMode\n\t\t\treturn nil\n\t\t})\n\t} else if spec.PersistMode == \"default\" {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.PersistMode = jsmapi.DefaultPersistMode\n\t\t\treturn nil\n\t\t})\n\t}\n\n\t_, err = c.NewStream(ctx, spec.Name, opts)\n\treturn err\n}\n\nfunc updateStream(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to update stream %q: %w\", spec.Name, err)\n\t\t}\n\t}()\n\n\tjs, err := c.LoadStream(ctx, spec.Name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmaxAge, err := getDurationFromString(spec.MaxAge)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsubjectDeleteMarkerTTL, err := getDurationFromString(spec.SubjectDeleteMarkerTTL)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tretention := getRetention(spec.Retention)\n\tstorage := getStorage(spec.Storage)\n\tdiscard := getDiscard(spec.Discard)\n\n\tduplicates, err := getDuplicates(spec.DuplicateWindow)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar subjectTransform *jsmapi.SubjectTransformConfig\n\tif spec.SubjectTransform != nil {\n\t\tsubjectTransform = &jsmapi.SubjectTransformConfig{\n\t\t\tSource:      spec.SubjectTransform.Source,\n\t\t\tDestination: spec.SubjectTransform.Dest,\n\t\t}\n\t}\n\n\tconfig := jsmapi.StreamConfig{\n\t\tName:                   spec.Name,\n\t\tDescription:            spec.Description,\n\t\tRetention:              retention,\n\t\tSubjects:               spec.Subjects,\n\t\tMaxConsumers:           spec.MaxConsumers,\n\t\tMaxMsgs:                int64(spec.MaxMsgs),\n\t\tMaxBytes:               int64(spec.MaxBytes),\n\t\tMaxMsgsPer:             int64(spec.MaxMsgsPerSubject),\n\t\tMaxAge:                 maxAge,\n\t\tMaxMsgSize:             int32(spec.MaxMsgSize),\n\t\tStorage:                storage,\n\t\tDiscard:                discard,\n\t\tDiscardNewPer:          spec.DiscardPerSubject,\n\t\tReplicas:               spec.Replicas,\n\t\tNoAck:                  spec.NoAck,\n\t\tDuplicates:             duplicates,\n\t\tAllowDirect:            spec.AllowDirect,\n\t\tDenyDelete:             spec.DenyDelete,\n\t\tDenyPurge:              spec.DenyPurge,\n\t\tRollupAllowed:          spec.AllowRollup,\n\t\tFirstSeq:               spec.FirstSequence,\n\t\tSubjectTransform:       subjectTransform,\n\t\tAllowMsgTTL:            spec.AllowMsgTTL,\n\t\tSubjectDeleteMarkerTTL: subjectDeleteMarkerTTL,\n\t\tAllowMsgCounter:        spec.AllowMsgCounter,\n\t\tAllowAtomicPublish:     spec.AllowAtomicPublish,\n\t\tAllowMsgSchedules:      spec.AllowMsgSchedules,\n\t}\n\tif spec.RePublish != nil {\n\t\tconfig.RePublish = &jsmapi.RePublish{\n\t\t\tSource:      spec.RePublish.Source,\n\t\t\tDestination: spec.RePublish.Destination,\n\t\t\tHeadersOnly: spec.RePublish.HeadersOnly,\n\t\t}\n\t}\n\tif spec.Mirror != nil {\n\t\tss, err := getStreamSource(spec.Mirror)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tconfig.Mirror = ss\n\t}\n\tconfig.Sources = make([]*jsmapi.StreamSource, len(spec.Sources))\n\tfor i, ss := range spec.Sources {\n\t\tjss, err := getStreamSource(ss)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tconfig.Sources[i] = jss\n\t}\n\n\tif spec.Placement != nil {\n\t\tconfig.Placement = &jsmapi.Placement{\n\t\t\tCluster: spec.Placement.Cluster,\n\t\t\tTags:    spec.Placement.Tags,\n\t\t}\n\t}\n\n\tif spec.Metadata != nil {\n\t\tconfig.Metadata = spec.Metadata\n\t}\n\n\tswitch spec.Compression {\n\tcase \"s2\":\n\t\tconfig.Compression = jsmapi.S2Compression\n\tcase \"none\":\n\t\tconfig.Compression = jsmapi.NoCompression\n\t}\n\n\t// Handle PersistMode\n\tif spec.PersistMode == \"async\" {\n\t\tconfig.PersistMode = jsmapi.AsyncPersistMode\n\t} else if spec.PersistMode == \"default\" {\n\t\tconfig.PersistMode = jsmapi.DefaultPersistMode\n\t}\n\n\treturn js.UpdateConfiguration(config)\n}\n\nfunc deleteStream(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err error) {\n\tname := spec.Name\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to delete stream %q: %w\", name, err)\n\t\t}\n\t}()\n\n\tif spec.PreventDelete {\n\t\tklog.Infof(\"Stream %q is configured to preventDelete:\\n\", name)\n\t\treturn nil\n\t}\n\n\tvar apierr jsmapi.ApiError\n\tstr, err := c.LoadStream(ctx, name)\n\tif errors.As(err, &apierr) && apierr.NotFoundError() {\n\t\treturn nil\n\t} else if err != nil {\n\t\treturn err\n\t}\n\n\treturn str.Delete()\n}\n\nfunc setStreamErrored(ctx context.Context, s *apis.Stream, sif typed.StreamInterface, err error) (*apis.Stream, error) {\n\tif err == nil {\n\t\treturn s, nil\n\t}\n\n\tsc := s.DeepCopy()\n\tsc.Status.Conditions = UpsertCondition(sc.Status.Conditions, apis.Condition{\n\t\tType:               readyCondType,\n\t\tStatus:             k8sapi.ConditionFalse,\n\t\tLastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),\n\t\tReason:             \"Errored\",\n\t\tMessage:            err.Error(),\n\t})\n\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\n\tvar res *apis.Stream\n\terr = retry.RetryOnConflict(retry.DefaultRetry, func() error {\n\t\tvar err error\n\t\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\t\tdefer cancel()\n\t\tres, err = sif.UpdateStatus(ctx, sc, k8smeta.UpdateOptions{})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set stream errored status: %w\", err)\n\t\t}\n\t\treturn nil\n\t})\n\treturn res, err\n}\n\nfunc setStreamOK(ctx context.Context, s *apis.Stream, i typed.StreamInterface) (*apis.Stream, error) {\n\tsc := s.DeepCopy()\n\n\tsc.Status.ObservedGeneration = s.Generation\n\tsc.Status.Conditions = UpsertCondition(sc.Status.Conditions, apis.Condition{\n\t\tType:               readyCondType,\n\t\tStatus:             k8sapi.ConditionTrue,\n\t\tLastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),\n\t\tReason:             \"Created\",\n\t\tMessage:            \"Stream successfully created\",\n\t})\n\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\n\tvar res *apis.Stream\n\terr := retry.RetryOnConflict(retry.DefaultRetry, func() error {\n\t\tvar err error\n\t\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\t\tdefer cancel()\n\t\tres, err = i.UpdateStatus(ctx, sc, k8smeta.UpdateOptions{})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to set stream %q status: %w\", s.Spec.Name, err)\n\t\t}\n\t\treturn nil\n\t})\n\treturn res, err\n}\n\nfunc getDurationFromString(v string) (time.Duration, error) {\n\tif v == \"\" {\n\t\treturn time.Duration(0), nil\n\t}\n\n\treturn time.ParseDuration(v)\n}\n\nfunc getRetention(v string) jsmapi.RetentionPolicy {\n\tretention := jsmapi.LimitsPolicy\n\tswitch v {\n\tcase \"interest\":\n\t\tretention = jsmapi.InterestPolicy\n\tcase \"workqueue\":\n\t\tretention = jsmapi.WorkQueuePolicy\n\t}\n\treturn retention\n}\n\nfunc getStorage(v string) jsmapi.StorageType {\n\tstorage := jsmapi.MemoryStorage\n\tswitch v {\n\tcase \"file\":\n\t\tstorage = jsmapi.FileStorage\n\t}\n\treturn storage\n}\n\nfunc getDiscard(v string) jsmapi.DiscardPolicy {\n\tdiscard := jsmapi.DiscardOld\n\tswitch v {\n\tcase \"new\":\n\t\tdiscard = jsmapi.DiscardNew\n\t}\n\treturn discard\n}\n\nfunc getDuplicates(v string) (time.Duration, error) {\n\tif v == \"\" {\n\t\treturn time.Duration(0), nil\n\t}\n\n\treturn time.ParseDuration(v)\n}\n\nfunc getStreamSource(ss *apis.StreamSource) (*jsmapi.StreamSource, error) {\n\tjss := &jsmapi.StreamSource{\n\t\tName:          ss.Name,\n\t\tFilterSubject: ss.FilterSubject,\n\t}\n\n\tif ss.OptStartSeq > 0 {\n\t\tjss.OptStartSeq = uint64(ss.OptStartSeq)\n\t} else if ss.OptStartTime != \"\" {\n\t\tt, err := time.Parse(time.RFC3339, ss.OptStartTime)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjss.OptStartTime = &t\n\t}\n\n\tif ss.ExternalAPIPrefix != \"\" || ss.ExternalDeliverPrefix != \"\" {\n\t\tjss.External = &jsmapi.ExternalStream{\n\t\t\tApiPrefix:     ss.ExternalAPIPrefix,\n\t\t\tDeliverPrefix: ss.ExternalDeliverPrefix,\n\t\t}\n\t}\n\n\tfor _, transform := range ss.SubjectTransforms {\n\t\tjss.SubjectTransforms = append(jss.SubjectTransforms, jsmapi.SubjectTransformConfig{\n\t\t\tSource:      transform.Source,\n\t\t\tDestination: transform.Dest,\n\t\t})\n\t}\n\n\treturn jss, nil\n}\n"
  },
  {
    "path": "controllers/jetstream/stream_test.go",
    "content": "package jetstream\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\tjsmapi \"github.com/nats-io/jsm.go/api\"\n\n\tapis \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tclientsetfake \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/fake\"\n\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tk8sclientsetfake \"k8s.io/client-go/kubernetes/fake\"\n\tk8stesting \"k8s.io/client-go/testing\"\n\t\"k8s.io/client-go/tools/record\"\n)\n\nfunc TestProcessStream(t *testing.T) {\n\tt.Parallel()\n\n\tupdateObject := func(a k8stesting.Action) (handled bool, o runtime.Object, err error) {\n\t\tua, ok := a.(k8stesting.UpdateAction)\n\t\tif !ok {\n\t\t\treturn false, nil, nil\n\t\t}\n\n\t\treturn true, ua.GetObject(), nil\n\t}\n\n\tt.Run(\"create stream\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tjc := clientsetfake.NewSimpleClientset()\n\t\twantEvents := 2\n\t\trec := record.NewFakeRecorder(wantEvents)\n\t\tctrl := NewController(Options{\n\t\t\tCtx:            context.Background(),\n\t\t\tKubeIface:      k8sclientsetfake.NewSimpleClientset(),\n\t\t\tJetstreamIface: jc,\n\t\t\tRecorder:       rec,\n\t\t})\n\n\t\tns, name := \"default\", \"my-stream\"\n\n\t\tinformer := ctrl.informerFactory.Jetstream().V1beta2().Streams()\n\t\terr := informer.Informer().GetStore().Add(&apis.Stream{\n\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\tNamespace:  ns,\n\t\t\t\tName:       name,\n\t\t\t\tGeneration: 1,\n\t\t\t},\n\t\t\tSpec: apis.StreamSpec{\n\t\t\t\tName:    name,\n\t\t\t\tMaxAge:  \"1h\",\n\t\t\t\tStorage: \"memory\",\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tjc.PrependReactor(\"update\", \"streams\", updateObject)\n\n\t\tnotFoundErr := jsmapi.ApiError{Code: 404}\n\t\tjsmc := &mockJsmClient{\n\t\t\tloadStreamErr: notFoundErr,\n\t\t}\n\t\tif err := ctrl.processStream(ns, name, testWrapJSMC(jsmc)); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif got := len(rec.Events); got != wantEvents {\n\t\t\tt.Error(\"unexpected number of events\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, wantEvents)\n\t\t}\n\n\t\t<-rec.Events\n\t\t<-rec.Events\n\t\tfor i := 0; i < len(rec.Events); i++ {\n\t\t\tgotEvent := <-rec.Events\n\t\t\tif !strings.Contains(gotEvent, \"Creat\") {\n\t\t\t\tt.Error(\"unexpected event\")\n\t\t\t\tt.Fatalf(\"got=%s; want=%s\", gotEvent, \"Creating/Created...\")\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"update stream\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tjc := clientsetfake.NewSimpleClientset()\n\t\twantEvents := 2\n\t\trec := record.NewFakeRecorder(wantEvents)\n\t\tctrl := NewController(Options{\n\t\t\tCtx:            context.Background(),\n\t\t\tKubeIface:      k8sclientsetfake.NewSimpleClientset(),\n\t\t\tJetstreamIface: jc,\n\t\t\tRecorder:       rec,\n\t\t})\n\n\t\tns, name := \"default\", \"my-stream\"\n\n\t\tinformer := ctrl.informerFactory.Jetstream().V1beta2().Streams()\n\t\terr := informer.Informer().GetStore().Add(&apis.Stream{\n\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\tNamespace:  ns,\n\t\t\t\tName:       name,\n\t\t\t\tGeneration: 2,\n\t\t\t},\n\t\t\tSpec: apis.StreamSpec{\n\t\t\t\tName:              name,\n\t\t\t\tMaxAge:            \"1h\",\n\t\t\t\tStorage:           \"memory\",\n\t\t\t\tAllowMsgSchedules: true,\n\t\t\t},\n\t\t\tStatus: apis.Status{\n\t\t\t\tObservedGeneration: 1,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tjc.PrependReactor(\"update\", \"streams\", updateObject)\n\n\t\t// Capture the config that gets passed to UpdateConfiguration\n\t\tvar capturedConfig jsmapi.StreamConfig\n\t\tjsmc := &mockJsmClient{\n\t\t\tloadStreamErr: nil,\n\t\t\tloadStream: &mockStream{\n\t\t\t\tcapturedConfig: &capturedConfig,\n\t\t\t},\n\t\t}\n\t\tif err := ctrl.processStream(ns, name, testWrapJSMC(jsmc)); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// Verify that AllowMsgSchedules was set in the config\n\t\tif !capturedConfig.AllowMsgSchedules {\n\t\t\tt.Errorf(\"AllowMsgSchedules not set in stream config during update: got=%v, want=true\", capturedConfig.AllowMsgSchedules)\n\t\t}\n\n\t\tif got := len(rec.Events); got != wantEvents {\n\t\t\tt.Error(\"unexpected number of events\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, wantEvents)\n\t\t}\n\n\t\tfor i := 0; i < len(rec.Events); i++ {\n\t\t\tgotEvent := <-rec.Events\n\t\t\tif !strings.Contains(gotEvent, \"Updat\") {\n\t\t\t\tt.Error(\"unexpected event\")\n\t\t\t\tt.Fatalf(\"got=%s; want=%s\", gotEvent, \"Updating/Updated...\")\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"delete stream\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tjc := clientsetfake.NewSimpleClientset()\n\t\twantEvents := 1\n\t\trec := record.NewFakeRecorder(wantEvents)\n\t\tctrl := NewController(Options{\n\t\t\tCtx:            context.Background(),\n\t\t\tKubeIface:      k8sclientsetfake.NewSimpleClientset(),\n\t\t\tJetstreamIface: jc,\n\t\t\tRecorder:       rec,\n\t\t})\n\n\t\tts := k8smeta.Unix(1600216923, 0)\n\t\tns, name := \"default\", \"my-stream\"\n\n\t\tinformer := ctrl.informerFactory.Jetstream().V1beta2().Streams()\n\t\terr := informer.Informer().GetStore().Add(&apis.Stream{\n\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\tNamespace:         ns,\n\t\t\t\tName:              name,\n\t\t\t\tGeneration:        2,\n\t\t\t\tDeletionTimestamp: &ts,\n\t\t\t},\n\t\t\tSpec: apis.StreamSpec{\n\t\t\t\tName:    name,\n\t\t\t\tMaxAge:  \"1h\",\n\t\t\t\tStorage: \"memory\",\n\t\t\t},\n\t\t\tStatus: apis.Status{\n\t\t\t\tObservedGeneration: 1,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tjc.PrependReactor(\"update\", \"streams\", updateObject)\n\n\t\tjsmc := &mockJsmClient{\n\t\t\tloadStreamErr: nil,\n\t\t\tloadStream:    &mockStream{},\n\t\t}\n\t\tif err := ctrl.processStream(ns, name, testWrapJSMC(jsmc)); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif got := len(rec.Events); got != wantEvents {\n\t\t\tt.Error(\"unexpected number of events\")\n\t\t\tt.Fatalf(\"got=%d; want=%d\", got, wantEvents)\n\t\t}\n\n\t\tfor i := 0; i < len(rec.Events); i++ {\n\t\t\tgotEvent := <-rec.Events\n\t\t\tif !strings.Contains(gotEvent, \"Delet\") {\n\t\t\t\tt.Error(\"unexpected event\")\n\t\t\t\tt.Fatalf(\"got=%s; want=%s\", gotEvent, \"Deleting/Deleted...\")\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"process error\", func(t *testing.T) {\n\t\tt.Parallel()\n\n\t\tjc := clientsetfake.NewSimpleClientset()\n\t\twantEvents := 4\n\t\trec := record.NewFakeRecorder(wantEvents)\n\t\tctrl := NewController(Options{\n\t\t\tCtx:            context.Background(),\n\t\t\tKubeIface:      k8sclientsetfake.NewSimpleClientset(),\n\t\t\tJetstreamIface: jc,\n\t\t\tRecorder:       rec,\n\t\t})\n\n\t\tns, name := \"default\", \"my-stream\"\n\n\t\tinformer := ctrl.informerFactory.Jetstream().V1beta2().Streams()\n\t\terr := informer.Informer().GetStore().Add(&apis.Stream{\n\t\t\tObjectMeta: k8smeta.ObjectMeta{\n\t\t\t\tNamespace:  ns,\n\t\t\t\tName:       name,\n\t\t\t\tGeneration: 1,\n\t\t\t},\n\t\t\tSpec: apis.StreamSpec{\n\t\t\t\tName:    name,\n\t\t\t\tMaxAge:  \"1h\",\n\t\t\t\tStorage: \"memory\",\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tjc.PrependReactor(\"update\", \"streams\", func(a k8stesting.Action) (handled bool, o runtime.Object, err error) {\n\t\t\tua, ok := a.(k8stesting.UpdateAction)\n\t\t\tif !ok {\n\t\t\t\treturn false, nil, nil\n\t\t\t}\n\t\t\tobj := ua.GetObject()\n\n\t\t\tstr, ok := obj.(*apis.Stream)\n\t\t\tif !ok {\n\t\t\t\tt.Error(\"unexpected object type\")\n\t\t\t\tt.Fatalf(\"got=%T; want=%T\", obj, &apis.Stream{})\n\t\t\t}\n\n\t\t\tif got, want := len(str.Status.Conditions), 1; got != want {\n\t\t\t\tt.Error(\"unexpected number of conditions\")\n\t\t\t\tt.Fatalf(\"got=%d; want=%d\", got, want)\n\t\t\t}\n\t\t\tif got, want := str.Status.Conditions[0].Reason, \"Errored\"; got != want {\n\t\t\t\tt.Error(\"unexpected condition reason\")\n\t\t\t\tt.Fatalf(\"got=%s; want=%s\", got, want)\n\t\t\t}\n\n\t\t\treturn true, obj, nil\n\t\t})\n\n\t\tjsmc := &mockJsmClient{\n\t\t\tloadStreamErr: errors.New(\"failed to load stream\"),\n\t\t}\n\t\tif err := ctrl.processStream(ns, name, testWrapJSMC(jsmc)); err == nil {\n\t\t\tt.Fatal(\"unexpected success\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "dependencies.md",
    "content": "# External Dependencies\n\nThis file lists the dependencies used in this repository.\n\n| Dependency                           | License      |\n|--------------------------------------|--------------|\n| github.com/fsnotify/fsnotify         | BSD-3-Clause |\n| github.com/nats-io/jsm.go            | Apache-2.0   |\n| github.com/nats-io/nats.go           | Apache-2.0   |\n| github.com/sirupsen/logrus           | MIT          |\n| github.com/stretchr/testify          | MIT          |\n| k8s.io/api                           | Apache-2.0   |\n| k8s.io/apimachinery                  | Apache-2.0   |\n| k8s.io/client-go                     | Apache-2.0   |\n| k8s.io/code-generator                | Apache-2.0   |\n| k8s.io/klog/v2                       | Apache-2.0   |\n| sigs.k8s.io/structured-merge-diff/v4 | Apache-2.0   |\n"
  },
  {
    "path": "deploy/crds.yml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: streams.jetstream.nats.io\nspec:\n  group: jetstream.nats.io\n  scope: Namespaced\n  names:\n    kind: Stream\n    singular: stream\n    plural: streams\n  versions:\n  - name: v1beta2\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              name:\n                description: A unique name for the Stream.\n                type: string\n                pattern: '^[^.*>]*$'\n                minLength: 1\n              description:\n                description: The description of the stream.\n                type: string\n              subjects:\n                description: A list of subjects to consume, supports wildcards.\n                type: array\n                minLength: 1\n                items:\n                  type: string\n                  minLength: 1\n              retention:\n                description: How messages are retained in the Stream, once this is exceeded old messages are removed.\n                type: string\n                enum:\n                - limits\n                - interest\n                - workqueue\n                default: limits\n              maxConsumers:\n                description: How many Consumers can be defined for a given Stream. -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              maxMsgs:\n                description: How many messages may be in a Stream, oldest messages will be removed if the Stream exceeds this size. -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              maxBytes:\n                description: How big the Stream may be, when the combined stream size exceeds this old messages are removed. -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              discard:\n                description: When a Stream reach it's limits either old messages are deleted or new ones are denied.\n                type: string\n                enum:\n                - old\n                - new\n                default: old\n              discardPerSubject:\n                description: Applies discard policy on a per-subject basis. Requires discard policy 'new' and 'maxMsgs' to be set.\n                type: boolean\n                default: false\n              maxAge:\n                description: Maximum age of any message in the stream, expressed in Go's time.Duration format. Empty for unlimited.\n                type: string\n                default: ''\n              maxMsgsPerSubject:\n                description: The maximum number of messages per subject.\n                type: integer\n                default: 0\n              maxMsgSize:\n                description: The largest message that will be accepted by the Stream. -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              storage:\n                description: The storage backend to use for the Stream.\n                type: string\n                enum:\n                - file\n                - memory\n                default: memory\n              replicas:\n                description: How many replicas to keep for each message.\n                type: integer\n                minimum: 1\n                default: 1\n              noAck:\n                description: Disables acknowledging messages that are received by the Stream.\n                type: boolean\n                default: false\n              duplicateWindow:\n                description: The duration window to track duplicate messages for.\n                type: string\n              placement:\n                description: A stream's placement.\n                type: object\n                properties:\n                  cluster:\n                    type: string\n                  tags:\n                    type: array\n                    items:\n                      type: string\n              mirror:\n                description: A stream mirror.\n                type: object\n                properties:\n                  name:\n                    type: string\n                  optStartSeq:\n                    type: integer\n                  optStartTime:\n                    description: Time format must be RFC3339.\n                    type: string\n                  filterSubject:\n                    type: string\n                  externalApiPrefix:\n                    type: string\n                  externalDeliverPrefix:\n                    type: string\n                  subjectTransforms:\n                    description: List of subject transforms for this mirror.\n                    type: array\n                    items:\n                      description: A subject transform pair.\n                      type: object\n                      properties:\n                        source:\n                          description: Source subject.\n                          type: string\n                        dest:\n                          description: Destination subject.\n                          type: string\n              sources:\n                description: A stream's sources.\n                type: array\n                items:\n                  type: object\n                  properties:\n                    name:\n                      type: string\n                    optStartSeq:\n                      type: integer\n                    optStartTime:\n                      description: Time format must be RFC3339.\n                      type: string\n                    filterSubject:\n                      type: string\n                    externalApiPrefix:\n                      type: string\n                    externalDeliverPrefix:\n                      type: string\n                    subjectTransforms:\n                      description: List of subject transforms for this mirror.\n                      type: array\n                      items:\n                        description: A subject transform pair.\n                        type: object\n                        properties:\n                          source:\n                            description: Source subject.\n                            type: string\n                          dest:\n                            description: Destination subject.\n                            type: string\n              sealed:\n                description: Seal an existing stream so no new messages may be added.\n                type: boolean\n                default: false\n              denyDelete:\n                description: When true, restricts the ability to delete messages from a stream via the API. Cannot be changed once set to true.\n                type: boolean\n                default: false\n              denyPurge:\n                description: When true, restricts the ability to purge a stream via the API. Cannot be changed once set to true.\n                type: boolean\n                default: false\n              allowRollup:\n                description: When true, allows the use of the Nats-Rollup header to replace all contents of a stream, or subject in a stream, with a single new message.\n                type: boolean\n                default: false\n              compression:\n                description: Stream specific compression.\n                type: string\n                enum:\n                - s2\n                - none\n                - ''\n                default: ''\n              firstSequence:\n                description: Sequence number from which the Stream will start.\n                type: number\n                default: 0\n              subjectTransform:\n                description: SubjectTransform is for applying a subject transform (to matching messages) when a new message is received.\n                type: object\n                properties:\n                  source:\n                    type: string\n                    description: Source subject.\n                  dest:\n                    type: string\n                    description: Destination subject to transform into.\n              republish:\n                description: Republish configuration of the stream.\n                type: object\n                properties:\n                  destination:\n                    type: string\n                    description: Messages will be additionally published to this subject.\n                  source:\n                    type: string\n                    description: Messages will be published from this subject to the destination subject.\n              allowDirect:\n                description: When true, allow higher performance, direct access to get individual messages.\n                type: boolean\n                default: false\n              mirrorDirect:\n                description: When true, enables direct access to messages from the origin stream.\n                type: boolean\n                default: false\n              allowMsgTtl:\n                description: When true, allows header initiated per-message TTLs. If disabled, then the `NATS-TTL` header will be ignored.\n                type: boolean\n                default: false\n              subjectDeleteMarkerTtl:\n                description: Enables and sets a duration for adding server markers for delete, purge and max age limits.\n                type: string\n                default: ''\n              allowMsgCounter:\n                description: When true, enables message counters for the stream.\n                type: boolean\n                default: false\n              allowAtomicPublish:\n                description: When true, enables atomic batch publishing.\n                type: boolean\n                default: false\n              allowMsgSchedules:\n                description: When true, enables message scheduling.\n                type: boolean\n                default: false\n              persistMode:\n                description: Configures stream persistence settings (async or default).\n                type: string\n                default: ''\n              consumerLimits:\n                type: object\n                properties:\n                  inactiveThreshold:\n                    description: The duration of inactivity after which a consumer is considered inactive.\n                    type: string\n                  maxAckPending:\n                    description: Maximum number of outstanding unacknowledged messages.\n                    type: integer\n              metadata:\n                description: Additional Stream metadata.\n                type: object\n                additionalProperties:\n                  type: string\n              account:\n                description: Name of the account to which the Stream belongs.\n                type: string\n                pattern: '^[^.*>]*$'\n              creds:\n                description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on this path.\n                type: string\n                default: ''\n              nkey:\n                description: NATS user NKey for connecting to servers.\n                type: string\n                default: ''\n              preventDelete:\n                description: When true, the managed Stream will not be deleted when the resource is deleted.\n                type: boolean\n                default: false\n              preventUpdate:\n                description: When true, the managed Stream will not be updated when the resource is updated.\n                type: boolean\n                default: false\n              servers:\n                description: A list of servers for creating stream.\n                type: array\n                items:\n                  type: string\n                default: []\n              tls:\n                description: A client's TLS certs and keys.\n                type: object\n                properties:\n                  clientCert:\n                    description: A client's cert filepath. Should be mounted.\n                    type: string\n                  clientKey:\n                    description: A client's key filepath. Should be mounted.\n                    type: string\n                  rootCas:\n                    description: A list of filepaths to CAs. Should be mounted.\n                    type: array\n                    items:\n                      type: string\n              tlsFirst:\n                description: When true, the KV Store will initiate TLS before server INFO.\n                type: boolean\n                default: false\n              jsDomain:\n                description: The JetStream domain to use for the stream.\n                type: string\n          status:\n            type: object\n            properties:\n              observedGeneration:\n                type: integer\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    type:\n                      type: string\n                    status:\n                      type: string\n                    lastTransitionTime:\n                      type: string\n                    reason:\n                      type: string\n                    message:\n                      type: string\n    additionalPrinterColumns:\n    - name: State\n      type: string\n      description: The current state of the stream.\n      jsonPath: .status.conditions[?(@.type == 'Ready')].reason\n    - name: Stream Name\n      type: string\n      description: The name of the JetStream Stream.\n      jsonPath: .spec.name\n    - name: Subjects\n      type: string\n      description: The subjects this Stream produces.\n      jsonPath: .spec.subjects\n  - name: v1beta1\n    served: false\n    storage: false\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              name:\n                description: A unique name for the Stream.\n                type: string\n                pattern: '^[^.*>]*$'\n                minLength: 1\n              subjects:\n                description: A list of subjects to consume, supports wildcards.\n                type: array\n                minLength: 1\n                items:\n                  type: string\n                  minLength: 1\n              retention:\n                description: How messages are retained in the Stream, once this is exceeded old messages are removed.\n                type: string\n                enum:\n                - limits\n                - interest\n                - workqueue\n                default: limits\n              maxConsumers:\n                description: How many Consumers can be defined for a given Stream. -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              maxMsgs:\n                description: How many messages may be in a Stream, oldest messages will be removed if the Stream exceeds this size. -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              maxBytes:\n                description: How big the Stream may be, when the combined stream size exceeds this old messages are removed. -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              maxAge:\n                description: Maximum age of any message in the stream, expressed in Go's time.Duration format. Empty for unlimited.\n                type: string\n                default: ''\n              maxMsgSize:\n                description: The largest message that will be accepted by the Stream. -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              storage:\n                description: The storage backend to use for the Stream.\n                type: string\n                enum:\n                - file\n                - memory\n                default: memory\n              replicas:\n                description: How many replicas to keep for each message.\n                type: integer\n                minimum: 1\n                default: 1\n              noAck:\n                description: Disables acknowledging messages that are received by the Stream.\n                type: boolean\n                default: false\n              discard:\n                description: When a Stream reach it's limits either old messages are deleted or new ones are denied.\n                type: string\n                enum:\n                - old\n                - new\n                default: old\n              duplicateWindow:\n                description: The duration window to track duplicate messages for.\n                type: string\n              description:\n                description: The description of the stream.\n                type: string\n              maxMsgsPerSubject:\n                description: The maximum number of messages per subject.\n                type: integer\n                default: 0\n              mirror:\n                description: A stream mirror.\n                type: object\n                properties:\n                  name:\n                    type: string\n                  optStartSeq:\n                    type: integer\n                  optStartTime:\n                    description: Time format must be RFC3339.\n                    type: string\n                  filterSubject:\n                    type: string\n                  externalApiPrefix:\n                    type: string\n                  externalDeliverPrefix:\n                    type: string\n              placement:\n                description: A stream's placement.\n                type: object\n                properties:\n                  cluster:\n                    type: string\n                  tags:\n                    type: array\n                    items:\n                      type: string\n              sources:\n                description: A stream's sources.\n                type: array\n                items:\n                  type: object\n                  properties:\n                    name:\n                      type: string\n                    optStartSeq:\n                      type: integer\n                    optStartTime:\n                      description: Time format must be RFC3339.\n                      type: string\n                    filterSubject:\n                      type: string\n                    externalApiPrefix:\n                      type: string\n                    externalDeliverPrefix:\n                      type: string\n          status:\n            type: object\n            properties:\n              observedGeneration:\n                type: integer\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    type:\n                      type: string\n                    status:\n                      type: string\n                    lastTransitionTime:\n                      type: string\n                    reason:\n                      type: string\n                    message:\n                      type: string\n    additionalPrinterColumns:\n    - name: State\n      type: string\n      description: The current state of the stream.\n      jsonPath: .status.conditions[?(@.type == 'Ready')].reason\n    - name: Stream Name\n      type: string\n      description: The name of the JetStream Stream.\n      jsonPath: .spec.name\n    - name: Subjects\n      type: string\n      description: The subjects this Stream produces.\n      jsonPath: .spec.subjects\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: consumers.jetstream.nats.io\nspec:\n  group: jetstream.nats.io\n  scope: Namespaced\n  names:\n    kind: Consumer\n    singular: consumer\n    plural: consumers\n  versions:\n  - name: v1beta2\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              durableName:\n                description: The name of the Consumer.\n                type: string\n                pattern: '^[^.*>]+$'\n                minLength: 1\n              streamName:\n                description: The name of the Stream to create the Consumer in.\n                type: string\n              deliverPolicy:\n                type: string\n                enum:\n                - all\n                - last\n                - new\n                # Requires optStartSeq\n                - byStartSequence\n                # Requires optStartTime\n                - byStartTime\n                - lastPerSubject\n                default: all\n              optStartSeq:\n                type: integer\n                minimum: 0\n              optStartTime:\n                description: Time format must be RFC3339.\n                type: string\n              deliverSubject:\n                description: The subject to deliver observed messages, when not set, a pull-based Consumer is created.\n                type: string\n              ackPolicy:\n                description: How messages should be acknowledged.\n                type: string\n                enum:\n                - none\n                - all\n                - explicit\n                default: none\n              ackWait:\n                description: How long to allow messages to remain un-acknowledged before attempting redelivery.\n                type: string\n                default: 1ns\n              maxDeliver:\n                type: integer\n                minimum: -1\n              backoff:\n                description: List of durations representing a retry time scale for NaK'd or retried messages.\n                type: array\n                items:\n                  type: string\n              filterSubject:\n                description: Select only a specific incoming subjects, supports wildcards.\n                type: string\n              filterSubjects:\n                description: List of incoming subjects, supports wildcards. Available since 2.10.\n                type: array\n                items:\n                  type: string\n              replayPolicy:\n                description: How messages are sent.\n                type: string\n                enum:\n                - instant\n                - original\n                default: instant\n              sampleFreq:\n                description: What percentage of acknowledgements should be samples for observability.\n                type: string\n              maxWaiting:\n                description: The number of pulls that can be outstanding on a pull consumer, pulls received after this is reached are ignored.\n                type: integer\n              rateLimitBps:\n                description: Rate at which messages will be delivered to clients, expressed in bit per second.\n                type: integer\n              maxAckPending:\n                description: Maximum pending Acks before consumers are paused.\n                type: integer\n              deliverGroup:\n                description: The name of a queue group.\n                type: string\n              description:\n                description: The description of the consumer.\n                type: string\n              flowControl:\n                description: Enables flow control.\n                type: boolean\n                default: false\n              headersOnly:\n                description: When set, only the headers of messages in the stream are delivered, and not the bodies. Additionally, Nats-Msg-Size header is added to indicate the size of the removed payload.\n                type: boolean\n                default: false\n              heartbeatInterval:\n                description: The interval used to deliver idle heartbeats for push-based consumers, in Go's time.Duration format.\n                type: string\n              maxRequestBatch:\n                description: The largest batch property that may be specified when doing a pull on a Pull Consumer.\n                type: integer\n              maxRequestExpires:\n                description: The maximum expires duration that may be set when doing a pull on a Pull Consumer.\n                type: string\n              maxRequestMaxBytes:\n                description: The maximum max_bytes value that maybe set when dong a pull on a Pull Consumer.\n                type: integer\n              inactiveThreshold:\n                description: The idle time an Ephemeral Consumer allows before it is removed.\n                type: string\n              pauseUntil:\n                description: RFC3339 timestamp until which the consumer should be paused.\n                type: string\n                default: ''\n              priorityPolicy:\n                description: Priority policy for consumer (pinned_client, overflow, prioritized, or none).\n                type: string\n                default: ''\n              pinnedTtl:\n                description: TTL for pinned client when using pinned_client priority policy.\n                type: string\n                default: ''\n              priorityGroups:\n                description: List of priority groups for the consumer. For now, only one group is supported.\n                type: array\n                items:\n                  type: string\n              replicas:\n                description: When set do not inherit the replica count from the stream but specifically set it to this amount.\n                type: integer\n              memStorage:\n                description: Force the consumer state to be kept in memory rather than inherit the setting from the stream.\n                type: boolean\n                default: false\n              metadata:\n                description: Additional Consumer metadata.\n                type: object\n                additionalProperties:\n                  type: string\n              account:\n                description: Name of the account to which the Consumer belongs.\n                type: string\n                pattern: '^[^.*>]*$'\n              creds:\n                description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on its path.\n                type: string\n                default: ''\n              nkey:\n                description: NATS user NKey for connecting to servers.\n                type: string\n                default: ''\n              preventDelete:\n                description: When true, the managed Consumer will not be deleted when the resource is deleted.\n                type: boolean\n                default: false\n              preventUpdate:\n                description: When true, the managed Consumer will not be updated when the resource is updated.\n                type: boolean\n                default: false\n              servers:\n                description: A list of servers for creating consumer.\n                type: array\n                items:\n                  type: string\n                default: []\n              tls:\n                description: A client's TLS certs and keys.\n                type: object\n                properties:\n                  clientCert:\n                    description: A client's cert filepath. Should be mounted.\n                    type: string\n                  clientKey:\n                    description: A client's key filepath. Should be mounted.\n                    type: string\n                  rootCas:\n                    description: A list of filepaths to CAs. Should be mounted.\n                    type: array\n                    items:\n                      type: string\n              tlsFirst:\n                description: When true, the KV Store will initiate TLS before server INFO.\n                type: boolean\n                default: false\n              jsDomain:\n                description: The JetStream domain to use for the consumer.\n                type: string\n          status:\n            type: object\n            properties:\n              observedGeneration:\n                type: integer\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    type:\n                      type: string\n                    status:\n                      type: string\n                    lastTransitionTime:\n                      type: string\n                    reason:\n                      type: string\n                    message:\n                      type: string\n    additionalPrinterColumns:\n    - name: State\n      type: string\n      description: The current state of the consumer.\n      jsonPath: .status.conditions[?(@.type == 'Ready')].reason\n    - name: Stream\n      type: string\n      description: The name of the JetStream Stream.\n      jsonPath: .spec.streamName\n    - name: Consumer\n      type: string\n      description: The name of the JetStream Consumer.\n      jsonPath: .spec.durableName\n    - name: Ack Policy\n      type: string\n      description: The ack policy.\n      jsonPath: .spec.ackPolicy\n  - name: v1beta1\n    served: false\n    storage: false\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              streamName:\n                description: The name of the Stream to create the Consumer in.\n                type: string\n              deliverPolicy:\n                type: string\n                enum:\n                - all\n                - last\n                - new\n                # Requires optStartSeq\n                - byStartSequence\n                # Requires optStartTime\n                - byStartTime\n                default: all\n              optStartSeq:\n                type: integer\n                minimum: 0\n              optStartTime:\n                description: Time format must be RFC3339.\n                type: string\n              durableName:\n                description: The name of the Consumer.\n                type: string\n                pattern: '^[^.*>]+$'\n                minLength: 1\n              deliverSubject:\n                description: The subject to deliver observed messages, when not set, a pull-based Consumer is created.\n                type: string\n              ackPolicy:\n                description: How messages should be acknowledged.\n                type: string\n                enum:\n                - none\n                - all\n                - explicit\n                default: none\n              ackWait:\n                description: How long to allow messages to remain un-acknowledged before attempting redelivery.\n                type: string\n                default: 1ns\n              maxDeliver:\n                type: integer\n                minimum: -1\n              filterSubject:\n                description: Select only a specific incoming subjects, supports wildcards.\n                type: string\n              replayPolicy:\n                description: How messages are sent.\n                type: string\n                enum:\n                - instant\n                - original\n                default: instant\n              sampleFreq:\n                description: What percentage of acknowledgements should be samples for observability.\n                type: string\n              rateLimitBps:\n                description: Rate at which messages will be delivered to clients, expressed in bit per second.\n                type: integer\n              maxAckPending:\n                description: Maximum pending Acks before consumers are paused.\n                type: integer\n              deliverGroup:\n                description: The name of a queue group.\n                type: string\n              description:\n                description: The description of the consumer.\n                type: string\n              flowControl:\n                description: Enables flow control.\n                type: boolean\n                default: false\n              heartbeatInterval:\n                description: The interval used to deliver idle heartbeats for push-based consumers, in Go's time.Duration format.\n                type: string\n          status:\n            type: object\n            properties:\n              observedGeneration:\n                type: integer\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    type:\n                      type: string\n                    status:\n                      type: string\n                    lastTransitionTime:\n                      type: string\n                    reason:\n                      type: string\n                    message:\n                      type: string\n    additionalPrinterColumns:\n    - name: State\n      type: string\n      description: The current state of the consumer.\n      jsonPath: .status.conditions[?(@.type == 'Ready')].reason\n    - name: Stream\n      type: string\n      description: The name of the JetStream Stream.\n      jsonPath: .spec.streamName\n    - name: Consumer\n      type: string\n      description: The name of the JetStream Consumer.\n      jsonPath: .spec.durableName\n    - name: Ack Policy\n      type: string\n      description: The ack policy.\n      jsonPath: .spec.ackPolicy\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: streamtemplates.jetstream.nats.io\nspec:\n  group: jetstream.nats.io\n  scope: Namespaced\n  names:\n    kind: StreamTemplate\n    singular: streamtemplate\n    plural: streamtemplates\n  versions:\n  - name: v1beta1\n    served: false\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              name:\n                description: A unique name for the Stream Template.\n                type: string\n                pattern: '^[^.*>]*$'\n                minLength: 1\n              maxStreams:\n                description: The maximum number of Streams this Template can create, -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              subjects:\n                description: A list of subjects to consume, supports wildcards.\n                type: array\n                minLength: 1\n                items:\n                  type: string\n                  minLength: 1\n              retention:\n                description: How messages are retained in the Stream, once this is exceeded old messages are removed.\n                type: string\n                enum:\n                - limits\n                - interest\n                - workqueue\n                default: limits\n              maxConsumers:\n                description: How many Consumers can be defined for a given Stream. -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              maxMsgs:\n                description: How many messages may be in a Stream, oldest messages will be removed if the Stream exceeds this size. -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              maxBytes:\n                description: How big the Stream may be, when the combined stream size exceeds this old messages are removed. -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              maxAge:\n                description: Maximum age of any message in the stream, expressed in Go's time.Duration format. Empty for unlimited.\n                type: string\n                default: ''\n              maxMsgSize:\n                description: The largest message that will be accepted by the Stream. -1 for unlimited.\n                type: integer\n                minimum: -1\n                default: -1\n              storage:\n                description: The storage backend to use for the Stream.\n                type: string\n                enum:\n                - file\n                - memory\n                default: memory\n              replicas:\n                description: How many replicas to keep for each message.\n                type: integer\n                minimum: 1\n                default: 1\n              noAck:\n                description: Disables acknowledging messages that are received by the Stream.\n                type: boolean\n                default: false\n              discard:\n                description: When a Stream reach it's limits either old messages are deleted or new ones are denied.\n                type: string\n                enum:\n                - old\n                - new\n                default: old\n              duplicateWindow:\n                description: The duration window to track duplicate messages for.\n                type: string\n          status:\n            type: object\n            properties:\n              observedGeneration:\n                type: integer\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    type:\n                      type: string\n                    status:\n                      type: string\n                    lastTransitionTime:\n                      type: string\n                    reason:\n                      type: string\n                    message:\n                      type: string\n    additionalPrinterColumns:\n    - name: State\n      type: string\n      description: The current state of the stream.\n      jsonPath: .status.conditions[?(@.type == 'Ready')].reason\n    - name: Stream Template Name\n      type: string\n      description: The name of the JetStream Stream Template.\n      jsonPath: .spec.name\n    - name: Subjects\n      type: string\n      description: The subjects this Stream produces.\n      jsonPath: .spec.subjects\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: accounts.jetstream.nats.io\nspec:\n  group: jetstream.nats.io\n  scope: Namespaced\n  names:\n    kind: Account\n    singular: account\n    plural: accounts\n  versions:\n  - name: v1beta2\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              name:\n                description: A unique name for the Account.\n                type: string\n                pattern: '^[^.*>]*$'\n                minLength: 1\n              creds:\n                description: The creds to be used to connect to the NATS Service.\n                type: object\n                properties:\n                  secret:\n                    type: object\n                    properties:\n                      name:\n                        description: Name of the secret with the creds.\n                        type: string\n                  file:\n                    description: Credentials file, generated with github.com/nats-io/nsc tool.\n                    type: string\n              nkey:\n                description: The NKey seed to be used to connect to the NATS Service.\n                type: object\n                properties:\n                  secret:\n                    type: object\n                    properties:\n                      name:\n                        description: Name of the secret containing the NKey seed.\n                        type: string\n                  seed:\n                    description: Key in the secret that contains the NKey seed.\n                    type: string\n              servers:\n                description: A list of servers to connect.\n                type: array\n                minLength: 1\n                items:\n                  type: string\n                  minLength: 1\n              tls:\n                description: The TLS certs to be used to connect to the NATS Service.\n                type: object\n                properties:\n                  secret:\n                    type: object\n                    properties:\n                      name:\n                        description: Name of the TLS secret with the certs.\n                        type: string\n                  ca:\n                    description: Filename of the Root CA of the TLS cert.\n                    type: string\n                  cert:\n                    description: Filename of the TLS cert.\n                    type: string\n                  key:\n                    description: Filename of the TLS cert key.\n                    type: string\n              token:\n                description: The token to be used to connect to the NATS Service.\n                type: object\n                properties:\n                  secret:\n                    type: object\n                    properties:\n                      name:\n                        description: Name of the secret with the token.\n                        type: string\n                  token:\n                    description: Key in the secret that contains the token.\n                    type: string\n              user:\n                description: The user and password to be used to connect to the NATS Service.\n                type: object\n                properties:\n                  secret:\n                    type: object\n                    properties:\n                      name:\n                        description: Name of the secret with the user and password.\n                        type: string\n                  user:\n                    description: Key in the secret that contains the user.\n                    type: string\n                  password:\n                    description: Key in the secret that contains the password.\n                    type: string\n              tlsFirst:\n                description: When true, the KV Store will initiate TLS before server INFO.\n                type: boolean\n                default: false\n          status:\n            type: object\n            properties:\n              observedGeneration:\n                type: integer\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    type:\n                      type: string\n                    status:\n                      type: string\n                    lastTransitionTime:\n                      type: string\n                    reason:\n                      type: string\n                    message:\n                      type: string\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: keyvalues.jetstream.nats.io\nspec:\n  group: jetstream.nats.io\n  scope: Namespaced\n  names:\n    kind: KeyValue\n    singular: keyvalue\n    plural: keyvalues\n    shortNames:\n    - kv\n  versions:\n  - name: v1beta2\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              bucket:\n                description: A unique name for the KV Store.\n                type: string\n              description:\n                description: The description of the KV Store.\n                type: string\n              maxValueSize:\n                description: The maximum size of a value in bytes.\n                type: integer\n              history:\n                description: The number of historical values to keep per key.\n                type: integer\n              ttl:\n                description: The time expiry for keys.\n                type: string\n              limitMarkerTtl:\n                description: LimitMarkerTTL is how long the bucket keeps markers when\n                  keys are removed by the TTL setting, 0 meaning markers are not supported\n                type: integer\n              maxBytes:\n                description: The maximum size of the KV Store in bytes.\n                type: integer\n              storage:\n                description: The storage backend to use for the KV Store.\n                type: string\n                enum:\n                - file\n                - memory\n              replicas:\n                description: The number of replicas to keep for the KV Store in clustered JetStream.\n                type: integer\n                minimum: 1\n                maximum: 5\n                default: 1\n              placement:\n                description: The KV Store placement via tags or cluster name.\n                type: object\n                properties:\n                  cluster:\n                    type: string\n                  tags:\n                    type: array\n                    items:\n                      type: string\n              republish:\n                description: Republish configuration for the KV Store.\n                type: object\n                properties:\n                  destination:\n                    type: string\n                    description: Messages will be additionally published to this subject after Bucket.\n                  source:\n                    type: string\n                    description: Messages will be published from this subject to the destination subject.\n              mirror:\n                description: A KV Store mirror.\n                type: object\n                properties:\n                  name:\n                    type: string\n                  optStartSeq:\n                    type: integer\n                  optStartTime:\n                    description: Time format must be RFC3339.\n                    type: string\n                  filterSubject:\n                    type: string\n                  externalApiPrefix:\n                    type: string\n                  externalDeliverPrefix:\n                    type: string\n                  subjectTransforms:\n                    description: List of subject transforms for this mirror.\n                    type: array\n                    items:\n                      description: A subject transform pair.\n                      type: object\n                      properties:\n                        source:\n                          description: Source subject.\n                          type: string\n                        dest:\n                          description: Destination subject.\n                          type: string\n              compression:\n                description: KV Store compression.\n                type: boolean\n              sources:\n                description: A KV Store's sources.\n                type: array\n                items:\n                  type: object\n                  properties:\n                    name:\n                      type: string\n                    optStartSeq:\n                      type: integer\n                    optStartTime:\n                      description: Time format must be RFC3339.\n                      type: string\n                    filterSubject:\n                      type: string\n                    externalApiPrefix:\n                      type: string\n                    externalDeliverPrefix:\n                      type: string\n                    subjectTransforms:\n                      description: List of subject transforms for this mirror.\n                      type: array\n                      items:\n                        description: A subject transform pair.\n                        type: object\n                        properties:\n                          source:\n                            description: Source subject.\n                            type: string\n                          dest:\n                            description: Destination subject.\n                            type: string\n              account:\n                description: Name of the account to which the Stream belongs.\n                type: string\n                pattern: '^[^.*>]*$'\n              creds:\n                description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on its path.\n                type: string\n                default: ''\n              nkey:\n                description: NATS user NKey for connecting to servers.\n                type: string\n                default: ''\n              preventDelete:\n                description: When true, the managed KV Store will not be deleted when the resource is deleted.\n                type: boolean\n                default: false\n              preventUpdate:\n                description: When true, the managed KV Store will not be updated when the resource is updated.\n                type: boolean\n                default: false\n              servers:\n                description: A list of servers for creating the KV Store.\n                type: array\n                items:\n                  type: string\n                default: []\n              tls:\n                description: A client's TLS certs and keys.\n                type: object\n                properties:\n                  clientCert:\n                    description: A client's cert filepath. Should be mounted.\n                    type: string\n                  clientKey:\n                    description: A client's key filepath. Should be mounted.\n                    type: string\n                  rootCas:\n                    description: A list of filepaths to CAs. Should be mounted.\n                    type: array\n                    items:\n                      type: string\n              tlsFirst:\n                description: When true, the KV Store will initiate TLS before server INFO.\n                type: boolean\n                default: false\n              jsDomain:\n                description: The JetStream domain to use for the KV store.\n                type: string\n          status:\n            type: object\n            properties:\n              observedGeneration:\n                type: integer\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    type:\n                      type: string\n                    status:\n                      type: string\n                    lastTransitionTime:\n                      type: string\n                    reason:\n                      type: string\n                    message:\n                      type: string\n    additionalPrinterColumns:\n    - name: State\n      type: string\n      description: The current state of the KV Store.\n      jsonPath: .status.conditions[?(@.type == 'Ready')].reason\n    - name: KV Store Name\n      type: string\n      description: The name of the KV Store.\n      jsonPath: .spec.bucket\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: objectstores.jetstream.nats.io\nspec:\n  group: jetstream.nats.io\n  scope: Namespaced\n  names:\n    kind: ObjectStore\n    singular: objectstore\n    plural: objectstores\n  versions:\n  - name: v1beta2\n    served: true\n    storage: true\n    subresources:\n      status: {}\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              bucket:\n                description: A unique name for the Object Store.\n                type: string\n              description:\n                description: The description of the Object Store.\n                type: string\n              ttl:\n                description: The time expiry for keys.\n                type: string\n              maxBytes:\n                description: The maximum size of the Store in bytes.\n                type: integer\n              storage:\n                description: The storage backend to use for the Object Store.\n                type: string\n                enum:\n                - file\n                - memory\n              replicas:\n                description: The number of replicas to keep for the Object Store in clustered JetStream.\n                type: integer\n                minimum: 1\n                maximum: 5\n                default: 1\n              placement:\n                description: The Object Store placement via tags or cluster name.\n                type: object\n                properties:\n                  cluster:\n                    type: string\n                  tags:\n                    type: array\n                    items:\n                      type: string\n              compression:\n                description: Object Store compression.\n                type: boolean\n              metadata:\n                description: Additional Object Store metadata.\n                type: object\n                additionalProperties:\n                  type: string\n              account:\n                description: Name of the account to which the Object Store belongs.\n                type: string\n                pattern: '^[^.*>]*$'\n              creds:\n                description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on its path.\n                type: string\n                default: ''\n              nkey:\n                description: NATS user NKey for connecting to servers.\n                type: string\n                default: ''\n              preventDelete:\n                description: When true, the managed Object Store will not be deleted when the resource is deleted.\n                type: boolean\n                default: false\n              preventUpdate:\n                description: When true, the managed Object Store will not be updated when the resource is updated.\n                type: boolean\n                default: false\n              servers:\n                description: A list of servers for creating the Object Store.\n                type: array\n                items:\n                  type: string\n                default: []\n              tls:\n                description: A client's TLS certs and keys.\n                type: object\n                properties:\n                  clientCert:\n                    description: A client's cert filepath. Should be mounted.\n                    type: string\n                  clientKey:\n                    description: A client's key filepath. Should be mounted.\n                    type: string\n                  rootCas:\n                    description: A list of filepaths to CAs. Should be mounted.\n                    type: array\n                    items:\n                      type: string\n              tlsFirst:\n                description: When true, the Object Store will initiate TLS before server INFO.\n                type: boolean\n                default: false\n              jsDomain:\n                description: The JetStream domain to use for the Object Store.\n                type: string\n          status:\n            type: object\n            properties:\n              observedGeneration:\n                type: integer\n              conditions:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    type:\n                      type: string\n                    status:\n                      type: string\n                    lastTransitionTime:\n                      type: string\n                    reason:\n                      type: string\n                    message:\n                      type: string\n    additionalPrinterColumns:\n    - name: State\n      type: string\n      description: The current state of the Object Store.\n      jsonPath: .status.conditions[?(@.type == 'Ready')].reason\n    - name: Object Store Name\n      type: string\n      description: The name of the Object Store.\n      jsonPath: .spec.bucket\n"
  },
  {
    "path": "deploy/examples/consumer_pull.yml",
    "content": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Consumer\nmetadata:\n  name: my-pull-consumer\nspec:\n  streamName: mystream\n  durableName: my-pull-consumer\n  deliverPolicy: all\n  filterSubject: orders.received\n  maxDeliver: 20\n  ackPolicy: explicit\n"
  },
  {
    "path": "deploy/examples/consumer_push.yml",
    "content": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Consumer\nmetadata:\n  name: my-push-consumer\nspec:\n  streamName: mystream\n  durableName: my-push-consumer\n  deliverSubject: my-push-consumer.orders\n  deliverPolicy: last\n  ackPolicy: none\n  replayPolicy: instant\n  description: my consumer description\n  flowControl: true\n  heartbeatInterval: 1s\n"
  },
  {
    "path": "deploy/examples/stream.yml",
    "content": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: mystream\nspec:\n  name: mystream\n  subjects: [\"orders.*\"]\n  storage: file\n  maxAge: 1h\n  replicas: 1"
  },
  {
    "path": "deploy/examples/stream_mirror.yml",
    "content": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: mystream-mirror\nspec:\n  name: mystream-mirror\n  storage: file\n  mirror:\n    name: my-publish-subj\n    externalApiPrefix: FOO.JS.API\n    externalDeliverPrefix: FOO.DELIVER.SYNC.MIRRORS\n"
  },
  {
    "path": "deploy/examples/stream_placement.yml",
    "content": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: mystream-placement\nspec:\n  name: mystream-placement\n  storage: file\n  placement:\n    tags:\n    - NODE_0\n"
  },
  {
    "path": "deploy/examples/stream_servers.yml",
    "content": "apiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: mystream\nspec:\n  name: mystream\n  servers:\n  - nats://acme.org:4222\n  tls:\n    clientCert: /etc/certs/client/foo/client-cert.pem\n    clientKey: /etc/certs/client/foo/client-key.pem\n    rootCas:\n    - /etc/certs/ca/foo/ca-cert.pem\n  creds: /etc/jsc-creds/my.creds\n"
  },
  {
    "path": "deploy/examples/stream_sources.yml",
    "content": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: mystream-sources\nspec:\n  name: mystream-sources\n  storage: file\n  sources:\n  - name: m1\n  - name: m2\n"
  },
  {
    "path": "deploy/rbac.yml",
    "content": "---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: jetstream-controller\n  namespace: default\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: jetstream-controller-cluster-role\nrules:\n- apiGroups:\n  - ''\n  resources:\n  - events\n  verbs:\n  - create\n  - update\n  - patch\n- apiGroups:\n  - ''\n  resources:\n  - secrets\n  verbs:\n  - get\n  - watch\n  - list\n- apiGroups:\n  - jetstream.nats.io\n  resources:\n  - streams\n  - streams/status\n  - objectstores\n  - objectstores/status\n  - keyvalues\n  - keyvalues/status\n  - consumers\n  - consumers/status\n  - streamtemplates\n  - streamtemplates/status\n  - accounts\n  - accounts/status\n  verbs:\n  - create\n  - get\n  - list\n  - watch\n  - patch\n  - update\n  - delete\n- apiGroups:\n  - jetstream.nats.io\n  resources:\n  - streams/finalizers\n  - keyvalues/finalizers\n  - objectstores/finalizers\n  - consumers/finalizers\n  - accounts/finalizers\n  verbs:\n  - update\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: jetstream-controller-cluster-role-binding\nsubjects:\n- kind: ServiceAccount\n  name: jetstream-controller\n  namespace: default\nroleRef:\n  kind: ClusterRole\n  name: jetstream-controller-cluster-role\n  apiGroup: rbac.authorization.k8s.io\n"
  },
  {
    "path": "docker-bake.hcl",
    "content": "###################\n### Variables\n###################\n\nvariable REGISTRY {\n  default = \"\"\n}\n\n# Comma delimited list of tags\nvariable TAGS {\n  default = \"latest\"\n}\n\nvariable CI {\n  default = false\n}\n\nvariable PUSH {\n  default = false\n}\n\n###################\n### Functions\n###################\n\nfunction \"get_tags\" {\n  params = [image]\n  result = [for tag in split(\",\", TAGS) : join(\"/\", compact([REGISTRY, \"${image}:${tag}\"]))]\n}\n\nfunction \"get_platforms_multiarch\" {\n  params = []\n  result = (CI || PUSH) ? [\"linux/amd64\", \"linux/arm/v6\", \"linux/arm/v7\", \"linux/arm64\"] : []\n}\n\nfunction \"get_output\" {\n  params = []\n  result = (CI || PUSH) ? [\"type=registry\"] : [\"type=docker\"]\n}\n\n###################\n### Groups\n###################\n\ngroup \"default\" {\n  targets = [\n    \"jetstream-controller\",\n    \"nats-boot-config\",\n    \"nats-server-config-reloader\"\n  ]\n}\n\n###################\n### Targets\n###################\n\ntarget \"goreleaser\" {\n  contexts = {\n    src = \".\"\n  }\n  args = {\n    CI = CI\n    PUSH = PUSH\n    GITHUB_TOKEN = \"\"\n  }\n  dockerfile = \"cicd/Dockerfile_goreleaser\"\n}\n\ntarget \"jetstream-controller\" {\n  contexts = {\n    build   = \"target:goreleaser\"\n    assets  = \"cicd/assets\"\n  }\n  args = {\n    GO_APP = \"jetstream-controller\"\n  }\n  dockerfile  = \"cicd/Dockerfile\"\n  platforms   = get_platforms_multiarch()\n  tags        = get_tags(\"jetstream-controller\")\n  output      = get_output()\n}\n\ntarget \"nats-boot-config-base\" {\n  contexts = {\n    build   = \"target:goreleaser\"\n    assets  = \"cicd/assets\"\n  }\n  args = {\n    GO_APP = \"nats-boot-config\"\n  }\n  dockerfile  = \"cicd/Dockerfile\"\n  platforms   = get_platforms_multiarch()\n}\n\ntarget \"nats-boot-config\" {\n  inherits = [\"nats-boot-config-base\"]\n  contexts = {\n    base     = \"target:nats-boot-config-base\"\n  }\n\n  dockerfile-inline = <<EOT\nARG GO_APP\nFROM base\nRUN ln -s /usr/local/bin/$GO_APP /usr/local/bin/nats-pod-bootconfig\nEOT\n\n  platforms   = get_platforms_multiarch()\n  tags        = get_tags(\"nats-boot-config\")\n  output      = get_output()\n}\n\ntarget \"nats-server-config-reloader\" {\n  contexts = {\n    build   = \"target:goreleaser\"\n    assets  = \"cicd/assets\"\n  }\n  args = {\n    GO_APP = \"nats-server-config-reloader\"\n  }\n  dockerfile  = \"cicd/Dockerfile\"\n  platforms   = get_platforms_multiarch()\n  tags        = get_tags(\"nats-server-config-reloader\")\n  output      = get_output()\n}\n"
  },
  {
    "path": "docs/api.md",
    "content": "# API Reference\n\nPackages:\n\n- [jetstream.nats.io/v1beta2](#jetstreamnatsiov1beta2)\n- [jetstream.nats.io/v1beta1](#jetstreamnatsiov1beta1)\n\n# jetstream.nats.io/v1beta2\n\nResource Types:\n\n- [Stream](#stream)\n\n- [Consumer](#consumer)\n\n- [Account](#account)\n\n- [KeyValue](#keyvalue)\n\n- [ObjectStore](#objectstore)\n\n## Stream\n\n<sup><sup>[↩ Parent](#jetstreamnatsiov1beta2)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n      <td><b>apiVersion</b></td>\n      <td>string</td>\n      <td>jetstream.nats.io/v1beta2</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b>kind</b></td>\n      <td>string</td>\n      <td>Stream</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b><a href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta\">metadata</a></b></td>\n      <td>object</td>\n      <td>Refer to the Kubernetes API documentation for the fields of the `metadata` field.</td>\n      <td>true</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspec\">spec</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamstatus\">status</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec\n\n<sup><sup>[↩ Parent](#stream)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>account</b></td>\n        <td>string</td>\n        <td>\n          Name of the account to which the Stream belongs.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>allowDirect</b></td>\n        <td>boolean</td>\n        <td>\n          When true, allow higher performance, direct access to get individual messages.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>allowRollup</b></td>\n        <td>boolean</td>\n        <td>\n          When true, allows the use of the Nats-Rollup header to replace all contents of a stream, or subject in a stream, with a single new message.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>compression</b></td>\n        <td>enum</td>\n        <td>\n          Stream specific compression.<br/>\n          <br/>\n            <i>Enum</i>: s2, none, <br/>\n            <i>Default</i>: <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspecconsumerlimits\">consumerLimits</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>creds</b></td>\n        <td>string</td>\n        <td>\n          NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on this path.<br/>\n          <br/>\n            <i>Default</i>: <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>denyDelete</b></td>\n        <td>boolean</td>\n        <td>\n          When true, restricts the ability to delete messages from a stream via the API. Cannot be changed once set to true.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>denyPurge</b></td>\n        <td>boolean</td>\n        <td>\n          When true, restricts the ability to purge a stream via the API. Cannot be changed once set to true.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>description</b></td>\n        <td>string</td>\n        <td>\n          The description of the stream.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>discard</b></td>\n        <td>enum</td>\n        <td>\n          When a Stream reach it's limits either old messages are deleted or new ones are denied.<br/>\n          <br/>\n            <i>Enum</i>: old, new<br/>\n            <i>Default</i>: old<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>discardPerSubject</b></td>\n        <td>boolean</td>\n        <td>\n          Applies discard policy on a per-subject basis. Requires discard policy 'new' and 'maxMsgs' to be set.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>duplicateWindow</b></td>\n        <td>string</td>\n        <td>\n          The duration window to track duplicate messages for.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>firstSequence</b></td>\n        <td>number</td>\n        <td>\n          Sequence number from which the Stream will start.<br/>\n          <br/>\n            <i>Default</i>: 0<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxAge</b></td>\n        <td>string</td>\n        <td>\n          Maximum age of any message in the stream, expressed in Go's time.Duration format. Empty for unlimited.<br/>\n          <br/>\n            <i>Default</i>: <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxBytes</b></td>\n        <td>integer</td>\n        <td>\n          How big the Stream may be, when the combined stream size exceeds this old messages are removed. -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxConsumers</b></td>\n        <td>integer</td>\n        <td>\n          How many Consumers can be defined for a given Stream. -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxMsgSize</b></td>\n        <td>integer</td>\n        <td>\n          The largest message that will be accepted by the Stream. -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxMsgs</b></td>\n        <td>integer</td>\n        <td>\n          How many messages may be in a Stream, oldest messages will be removed if the Stream exceeds this size. -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxMsgsPerSubject</b></td>\n        <td>integer</td>\n        <td>\n          The maximum number of messages per subject.<br/>\n          <br/>\n            <i>Default</i>: 0<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>allowMsgTtl</b></td>\n        <td>boolean</td>\n        <td>\n          When true, allows header initiated per-message TTLs. If disabled, then the `NATS-TTL` header will be ignored.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>subjectDeleteMarkerTtl</b></td>\n        <td>string</td>\n        <td>\n          Enables and sets a duration for adding server markers for delete, purge and max age limits, expressed in Go's time.Duration format.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>metadata</b></td>\n        <td>map[string]string</td>\n        <td>\n          Additional Stream metadata.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspecmirror\">mirror</a></b></td>\n        <td>object</td>\n        <td>\n          A stream mirror.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>mirrorDirect</b></td>\n        <td>boolean</td>\n        <td>\n          When true, enables direct access to messages from the origin stream.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          A unique name for the Stream.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>nkey</b></td>\n        <td>string</td>\n        <td>\n          NATS user NKey for connecting to servers.<br/>\n          <br/>\n            <i>Default</i>: <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>noAck</b></td>\n        <td>boolean</td>\n        <td>\n          Disables acknowledging messages that are received by the Stream.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspecplacement\">placement</a></b></td>\n        <td>object</td>\n        <td>\n          A stream's placement.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>preventDelete</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the managed Stream will not be deleted when the resource is deleted.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>preventUpdate</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the managed Stream will not be updated when the resource is updated.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>replicas</b></td>\n        <td>integer</td>\n        <td>\n          How many replicas to keep for each message.<br/>\n          <br/>\n            <i>Default</i>: 1<br/>\n            <i>Minimum</i>: 1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspecrepublish\">republish</a></b></td>\n        <td>object</td>\n        <td>\n          Republish configuration of the stream.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>retention</b></td>\n        <td>enum</td>\n        <td>\n          How messages are retained in the Stream, once this is exceeded old messages are removed.<br/>\n          <br/>\n            <i>Enum</i>: limits, interest, workqueue<br/>\n            <i>Default</i>: limits<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>sealed</b></td>\n        <td>boolean</td>\n        <td>\n          Seal an existing stream so no new messages may be added.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>servers</b></td>\n        <td>[]string</td>\n        <td>\n          A list of servers for creating stream.<br/>\n          <br/>\n            <i>Default</i>: []<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspecsourcesindex\">sources</a></b></td>\n        <td>[]object</td>\n        <td>\n          A stream's sources.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>storage</b></td>\n        <td>enum</td>\n        <td>\n          The storage backend to use for the Stream.<br/>\n          <br/>\n            <i>Enum</i>: file, memory<br/>\n            <i>Default</i>: memory<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspecsubjecttransform\">subjectTransform</a></b></td>\n        <td>object</td>\n        <td>\n          SubjectTransform is for applying a subject transform (to matching messages) when a new message is received.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>subjects</b></td>\n        <td>[]string</td>\n        <td>\n          A list of subjects to consume, supports wildcards.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspectls\">tls</a></b></td>\n        <td>object</td>\n        <td>\n          A client's TLS certs and keys.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>tlsFirst</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the KV Store will initiate TLS before server INFO.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec.consumerLimits\n\n<sup><sup>[↩ Parent](#streamspec)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>inactiveThreshold</b></td>\n        <td>string</td>\n        <td>\n          The duration of inactivity after which a consumer is considered inactive.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxAckPending</b></td>\n        <td>integer</td>\n        <td>\n          Maximum number of outstanding unacknowledged messages.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec.mirror\n\n<sup><sup>[↩ Parent](#streamspec)</sup></sup>\n\nA stream mirror.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>externalApiPrefix</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>externalDeliverPrefix</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>filterSubject</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartSeq</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartTime</b></td>\n        <td>string</td>\n        <td>\n          Time format must be RFC3339.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspecmirrorsubjecttransformsindex\">subjectTransforms</a></b></td>\n        <td>[]object</td>\n        <td>\n          List of subject transforms for this mirror.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec.mirror.subjectTransforms[index]\n\n<sup><sup>[↩ Parent](#streamspecmirror)</sup></sup>\n\nA subject transform pair.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>dest</b></td>\n        <td>string</td>\n        <td>\n          Destination subject.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>source</b></td>\n        <td>string</td>\n        <td>\n          Source subject.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec.placement\n\n<sup><sup>[↩ Parent](#streamspec)</sup></sup>\n\nA stream's placement.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>cluster</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>tags</b></td>\n        <td>[]string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec.republish\n\n<sup><sup>[↩ Parent](#streamspec)</sup></sup>\n\nRepublish configuration of the stream.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>destination</b></td>\n        <td>string</td>\n        <td>\n          Messages will be additionally published to this subject.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>source</b></td>\n        <td>string</td>\n        <td>\n          Messages will be published from this subject to the destination subject.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec.sources[index]\n\n<sup><sup>[↩ Parent](#streamspec)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>externalApiPrefix</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>externalDeliverPrefix</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>filterSubject</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartSeq</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartTime</b></td>\n        <td>string</td>\n        <td>\n          Time format must be RFC3339.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspecsourcesindexsubjecttransformsindex\">subjectTransforms</a></b></td>\n        <td>[]object</td>\n        <td>\n          List of subject transforms for this mirror.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec.sources[index].subjectTransforms[index]\n\n<sup><sup>[↩ Parent](#streamspecsourcesindex)</sup></sup>\n\nA subject transform pair.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>dest</b></td>\n        <td>string</td>\n        <td>\n          Destination subject.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>source</b></td>\n        <td>string</td>\n        <td>\n          Source subject.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec.subjectTransform\n\n<sup><sup>[↩ Parent](#streamspec)</sup></sup>\n\nSubjectTransform is for applying a subject transform (to matching messages) when a new message is received.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>dest</b></td>\n        <td>string</td>\n        <td>\n          Destination subject to transform into.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>source</b></td>\n        <td>string</td>\n        <td>\n          Source subject.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec.tls\n\n<sup><sup>[↩ Parent](#streamspec)</sup></sup>\n\nA client's TLS certs and keys.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>clientCert</b></td>\n        <td>string</td>\n        <td>\n          A client's cert filepath. Should be mounted.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>clientKey</b></td>\n        <td>string</td>\n        <td>\n          A client's key filepath. Should be mounted.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>rootCas</b></td>\n        <td>[]string</td>\n        <td>\n          A list of filepaths to CAs. Should be mounted.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.status\n\n<sup><sup>[↩ Parent](#stream)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b><a href=\"#streamstatusconditionsindex\">conditions</a></b></td>\n        <td>[]object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>observedGeneration</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.status.conditions[index]\n\n<sup><sup>[↩ Parent](#streamstatus)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>lastTransitionTime</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>message</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>reason</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>status</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>type</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n## Consumer\n\n<sup><sup>[↩ Parent](#jetstreamnatsiov1beta2)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n      <td><b>apiVersion</b></td>\n      <td>string</td>\n      <td>jetstream.nats.io/v1beta2</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b>kind</b></td>\n      <td>string</td>\n      <td>Consumer</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b><a href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta\">metadata</a></b></td>\n      <td>object</td>\n      <td>Refer to the Kubernetes API documentation for the fields of the `metadata` field.</td>\n      <td>true</td>\n      </tr><tr>\n        <td><b><a href=\"#consumerspec\">spec</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#consumerstatus\">status</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Consumer.spec\n\n<sup><sup>[↩ Parent](#consumer)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>account</b></td>\n        <td>string</td>\n        <td>\n          Name of the account to which the Consumer belongs.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>ackPolicy</b></td>\n        <td>enum</td>\n        <td>\n          How messages should be acknowledged.<br/>\n          <br/>\n            <i>Enum</i>: none, all, explicit<br/>\n            <i>Default</i>: none<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>ackWait</b></td>\n        <td>string</td>\n        <td>\n          How long to allow messages to remain un-acknowledged before attempting redelivery.<br/>\n          <br/>\n            <i>Default</i>: 1ns<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>backoff</b></td>\n        <td>[]string</td>\n        <td>\n          List of durations representing a retry time scale for NaK'd or retried messages.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>creds</b></td>\n        <td>string</td>\n        <td>\n          NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on its path.<br/>\n          <br/>\n            <i>Default</i>: <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>deliverGroup</b></td>\n        <td>string</td>\n        <td>\n          The name of a queue group.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>deliverPolicy</b></td>\n        <td>enum</td>\n        <td>\n            <i>Enum</i>: all, last, new, byStartSequence, byStartTime<br/>\n            <i>Default</i>: all<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>deliverSubject</b></td>\n        <td>string</td>\n        <td>\n          The subject to deliver observed messages, when not set, a pull-based Consumer is created.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>description</b></td>\n        <td>string</td>\n        <td>\n          The description of the consumer.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>durableName</b></td>\n        <td>string</td>\n        <td>\n          The name of the Consumer.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>filterSubject</b></td>\n        <td>string</td>\n        <td>\n          Select only a specific incoming subjects, supports wildcards.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>filterSubjects</b></td>\n        <td>[]string</td>\n        <td>\n          List of incoming subjects, supports wildcards. Available since 2.10.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>flowControl</b></td>\n        <td>boolean</td>\n        <td>\n          Enables flow control.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>headersOnly</b></td>\n        <td>boolean</td>\n        <td>\n          When set, only the headers of messages in the stream are delivered, and not the bodies. Additionally, Nats-Msg-Size header is added to indicate the size of the removed payload.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>heartbeatInterval</b></td>\n        <td>string</td>\n        <td>\n          The interval used to deliver idle heartbeats for push-based consumers, in Go's time.Duration format.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>inactiveThreshold</b></td>\n        <td>string</td>\n        <td>\n          The idle time an Ephemeral Consumer allows before it is removed.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxAckPending</b></td>\n        <td>integer</td>\n        <td>\n          Maximum pending Acks before consumers are paused.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxDeliver</b></td>\n        <td>integer</td>\n        <td>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxRequestBatch</b></td>\n        <td>integer</td>\n        <td>\n          The largest batch property that may be specified when doing a pull on a Pull Consumer.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxRequestExpires</b></td>\n        <td>string</td>\n        <td>\n          The maximum expires duration that may be set when doing a pull on a Pull Consumer.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxRequestMaxBytes</b></td>\n        <td>integer</td>\n        <td>\n          The maximum max_bytes value that maybe set when dong a pull on a Pull Consumer.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxWaiting</b></td>\n        <td>integer</td>\n        <td>\n          The number of pulls that can be outstanding on a pull consumer, pulls received after this is reached are ignored.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>memStorage</b></td>\n        <td>boolean</td>\n        <td>\n          Force the consumer state to be kept in memory rather than inherit the setting from the stream.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>metadata</b></td>\n        <td>map[string]string</td>\n        <td>\n          Additional Consumer metadata.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>nkey</b></td>\n        <td>string</td>\n        <td>\n          NATS user NKey for connecting to servers.<br/>\n          <br/>\n            <i>Default</i>: <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartSeq</b></td>\n        <td>integer</td>\n        <td>\n            <i>Minimum</i>: 0<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartTime</b></td>\n        <td>string</td>\n        <td>\n          Time format must be RFC3339.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>preventDelete</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the managed Consumer will not be deleted when the resource is deleted.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>preventUpdate</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the managed Consumer will not be updated when the resource is updated.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>rateLimitBps</b></td>\n        <td>integer</td>\n        <td>\n          Rate at which messages will be delivered to clients, expressed in bit per second.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>replayPolicy</b></td>\n        <td>enum</td>\n        <td>\n          How messages are sent.<br/>\n          <br/>\n            <i>Enum</i>: instant, original<br/>\n            <i>Default</i>: instant<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>replicas</b></td>\n        <td>integer</td>\n        <td>\n          When set do not inherit the replica count from the stream but specifically set it to this amount.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>sampleFreq</b></td>\n        <td>string</td>\n        <td>\n          What percentage of acknowledgements should be samples for observability.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>servers</b></td>\n        <td>[]string</td>\n        <td>\n          A list of servers for creating consumer.<br/>\n          <br/>\n            <i>Default</i>: []<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>streamName</b></td>\n        <td>string</td>\n        <td>\n          The name of the Stream to create the Consumer in.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#consumerspectls\">tls</a></b></td>\n        <td>object</td>\n        <td>\n          A client's TLS certs and keys.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>tlsFirst</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the KV Store will initiate TLS before server INFO.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Consumer.spec.tls\n\n<sup><sup>[↩ Parent](#consumerspec)</sup></sup>\n\nA client's TLS certs and keys.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>clientCert</b></td>\n        <td>string</td>\n        <td>\n          A client's cert filepath. Should be mounted.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>clientKey</b></td>\n        <td>string</td>\n        <td>\n          A client's key filepath. Should be mounted.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>rootCas</b></td>\n        <td>[]string</td>\n        <td>\n          A list of filepaths to CAs. Should be mounted.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Consumer.status\n\n<sup><sup>[↩ Parent](#consumer)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b><a href=\"#consumerstatusconditionsindex\">conditions</a></b></td>\n        <td>[]object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>observedGeneration</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Consumer.status.conditions[index]\n\n<sup><sup>[↩ Parent](#consumerstatus)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>lastTransitionTime</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>message</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>reason</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>status</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>type</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n## Account\n\n<sup><sup>[↩ Parent](#jetstreamnatsiov1beta2)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n      <td><b>apiVersion</b></td>\n      <td>string</td>\n      <td>jetstream.nats.io/v1beta2</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b>kind</b></td>\n      <td>string</td>\n      <td>Account</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b><a href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta\">metadata</a></b></td>\n      <td>object</td>\n      <td>Refer to the Kubernetes API documentation for the fields of the `metadata` field.</td>\n      <td>true</td>\n      </tr><tr>\n        <td><b><a href=\"#accountspec\">spec</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#accountstatus\">status</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Account.spec\n\n<sup><sup>[↩ Parent](#account)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b><a href=\"#accountspeccreds\">creds</a></b></td>\n        <td>object</td>\n        <td>\n          The creds to be used to connect to the NATS Service.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          A unique name for the Account.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>servers</b></td>\n        <td>[]string</td>\n        <td>\n          A list of servers to connect.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#accountspectls\">tls</a></b></td>\n        <td>object</td>\n        <td>\n          The TLS certs to be used to connect to the NATS Service.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>tlsFirst</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the KV Store will initiate TLS before server INFO.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#accountspectoken\">token</a></b></td>\n        <td>object</td>\n        <td>\n          The token to be used to connect to the NATS Service.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#accountspecuser\">user</a></b></td>\n        <td>object</td>\n        <td>\n          The user and password to be used to connect to the NATS Service.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Account.spec.creds\n\n<sup><sup>[↩ Parent](#accountspec)</sup></sup>\n\nThe creds to be used to connect to the NATS Service.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>file</b></td>\n        <td>string</td>\n        <td>\n          Credentials file, generated with github.com/nats-io/nsc tool.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#accountspeccredssecret\">secret</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Account.spec.creds.secret\n\n<sup><sup>[↩ Parent](#accountspeccreds)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          Name of the secret with the creds.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Account.spec.tls\n\n<sup><sup>[↩ Parent](#accountspec)</sup></sup>\n\nThe TLS certs to be used to connect to the NATS Service.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>ca</b></td>\n        <td>string</td>\n        <td>\n          Filename of the Root CA of the TLS cert.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>cert</b></td>\n        <td>string</td>\n        <td>\n          Filename of the TLS cert.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>key</b></td>\n        <td>string</td>\n        <td>\n          Filename of the TLS cert key.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#accountspectlssecret\">secret</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Account.spec.tls.secret\n\n<sup><sup>[↩ Parent](#accountspectls)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          Name of the TLS secret with the certs.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Account.spec.token\n\n<sup><sup>[↩ Parent](#accountspec)</sup></sup>\n\nThe token to be used to connect to the NATS Service.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b><a href=\"#accountspectokensecret\">secret</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>token</b></td>\n        <td>string</td>\n        <td>\n          Key in the secret that contains the token.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Account.spec.token.secret\n\n<sup><sup>[↩ Parent](#accountspectoken)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          Name of the secret with the token.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Account.spec.user\n\n<sup><sup>[↩ Parent](#accountspec)</sup></sup>\n\nThe user and password to be used to connect to the NATS Service.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>password</b></td>\n        <td>string</td>\n        <td>\n          Key in the secret that contains the password.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#accountspecusersecret\">secret</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>user</b></td>\n        <td>string</td>\n        <td>\n          Key in the secret that contains the user.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Account.spec.user.secret\n\n<sup><sup>[↩ Parent](#accountspecuser)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          Name of the secret with the user and password.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Account.status\n\n<sup><sup>[↩ Parent](#account)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b><a href=\"#accountstatusconditionsindex\">conditions</a></b></td>\n        <td>[]object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>observedGeneration</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Account.status.conditions[index]\n\n<sup><sup>[↩ Parent](#accountstatus)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>lastTransitionTime</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>message</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>reason</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>status</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>type</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n## KeyValue\n\n> **⚠️ Important**: KeyValue resources require the JetStream controller to be running in **control-loop mode** (`--control-loop` flag). They are not supported in the default legacy mode.\n\n<sup><sup>[↩ Parent](#jetstreamnatsiov1beta2)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n      <td><b>apiVersion</b></td>\n      <td>string</td>\n      <td>jetstream.nats.io/v1beta2</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b>kind</b></td>\n      <td>string</td>\n      <td>KeyValue</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b><a href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta\">metadata</a></b></td>\n      <td>object</td>\n      <td>Refer to the Kubernetes API documentation for the fields of the `metadata` field.</td>\n      <td>true</td>\n      </tr><tr>\n        <td><b><a href=\"#keyvaluespec\">spec</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#keyvaluestatus\">status</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### KeyValue.spec\n\n<sup><sup>[↩ Parent](#keyvalue)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>account</b></td>\n        <td>string</td>\n        <td>\n          Name of the account to which the Stream belongs.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>bucket</b></td>\n        <td>string</td>\n        <td>\n          A unique name for the KV Store.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>compression</b></td>\n        <td>boolean</td>\n        <td>\n          KV Store compression.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>creds</b></td>\n        <td>string</td>\n        <td>\n          NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on its path.<br/>\n          <br/>\n            <i>Default</i>: <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>description</b></td>\n        <td>string</td>\n        <td>\n          The description of the KV Store.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>history</b></td>\n        <td>integer</td>\n        <td>\n          The number of historical values to keep per key.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxBytes</b></td>\n        <td>integer</td>\n        <td>\n          The maximum size of the KV Store in bytes.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxValueSize</b></td>\n        <td>integer</td>\n        <td>\n          The maximum size of a value in bytes.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#keyvaluespecmirror\">mirror</a></b></td>\n        <td>object</td>\n        <td>\n          A KV Store mirror.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>nkey</b></td>\n        <td>string</td>\n        <td>\n          NATS user NKey for connecting to servers.<br/>\n          <br/>\n            <i>Default</i>: <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#keyvaluespecplacement\">placement</a></b></td>\n        <td>object</td>\n        <td>\n          The KV Store placement via tags or cluster name.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>preventDelete</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the managed KV Store will not be deleted when the resource is deleted.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>preventUpdate</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the managed KV Store will not be updated when the resource is updated.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>replicas</b></td>\n        <td>integer</td>\n        <td>\n          The number of replicas to keep for the KV Store in clustered JetStream.<br/>\n          <br/>\n            <i>Default</i>: 1<br/>\n            <i>Minimum</i>: 1<br/>\n            <i>Maximum</i>: 5<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#keyvaluespecrepublish\">republish</a></b></td>\n        <td>object</td>\n        <td>\n          Republish configuration for the KV Store.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>servers</b></td>\n        <td>[]string</td>\n        <td>\n          A list of servers for creating the KV Store.<br/>\n          <br/>\n            <i>Default</i>: []<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#keyvaluespecsourcesindex\">sources</a></b></td>\n        <td>[]object</td>\n        <td>\n          A KV Store's sources.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>storage</b></td>\n        <td>enum</td>\n        <td>\n          The storage backend to use for the KV Store.<br/>\n          <br/>\n            <i>Enum</i>: file, memory<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#keyvaluespectls\">tls</a></b></td>\n        <td>object</td>\n        <td>\n          A client's TLS certs and keys.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>tlsFirst</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the KV Store will initiate TLS before server INFO.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>ttl</b></td>\n        <td>string</td>\n        <td>\n          The time expiry for keys.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>limitMarkerTtl</b></td>\n        <td>integer</td>\n        <td>\n           How long the bucket keeps markers when keys are removed by the TTL setting, 0 meaning markers are not supported<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### KeyValue.spec.mirror\n\n<sup><sup>[↩ Parent](#keyvaluespec)</sup></sup>\n\nA KV Store mirror.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>externalApiPrefix</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>externalDeliverPrefix</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>filterSubject</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartSeq</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartTime</b></td>\n        <td>string</td>\n        <td>\n          Time format must be RFC3339.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#keyvaluespecmirrorsubjecttransformsindex\">subjectTransforms</a></b></td>\n        <td>[]object</td>\n        <td>\n          List of subject transforms for this mirror.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### KeyValue.spec.mirror.subjectTransforms[index]\n\n<sup><sup>[↩ Parent](#keyvaluespecmirror)</sup></sup>\n\nA subject transform pair.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>dest</b></td>\n        <td>string</td>\n        <td>\n          Destination subject.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>source</b></td>\n        <td>string</td>\n        <td>\n          Source subject.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### KeyValue.spec.placement\n\n<sup><sup>[↩ Parent](#keyvaluespec)</sup></sup>\n\nThe KV Store placement via tags or cluster name.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>cluster</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>tags</b></td>\n        <td>[]string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### KeyValue.spec.republish\n\n<sup><sup>[↩ Parent](#keyvaluespec)</sup></sup>\n\nRepublish configuration for the KV Store.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>destination</b></td>\n        <td>string</td>\n        <td>\n          Messages will be additionally published to this subject after Bucket.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>source</b></td>\n        <td>string</td>\n        <td>\n          Messages will be published from this subject to the destination subject.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### KeyValue.spec.sources[index]\n\n<sup><sup>[↩ Parent](#keyvaluespec)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>externalApiPrefix</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>externalDeliverPrefix</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>filterSubject</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartSeq</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartTime</b></td>\n        <td>string</td>\n        <td>\n          Time format must be RFC3339.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#keyvaluespecsourcesindexsubjecttransformsindex\">subjectTransforms</a></b></td>\n        <td>[]object</td>\n        <td>\n          List of subject transforms for this mirror.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### KeyValue.spec.sources[index].subjectTransforms[index]\n\n<sup><sup>[↩ Parent](#keyvaluespecsourcesindex)</sup></sup>\n\nA subject transform pair.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>dest</b></td>\n        <td>string</td>\n        <td>\n          Destination subject.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>source</b></td>\n        <td>string</td>\n        <td>\n          Source subject.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### KeyValue.spec.tls\n\n<sup><sup>[↩ Parent](#keyvaluespec)</sup></sup>\n\nA client's TLS certs and keys.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>clientCert</b></td>\n        <td>string</td>\n        <td>\n          A client's cert filepath. Should be mounted.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>clientKey</b></td>\n        <td>string</td>\n        <td>\n          A client's key filepath. Should be mounted.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>rootCas</b></td>\n        <td>[]string</td>\n        <td>\n          A list of filepaths to CAs. Should be mounted.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### KeyValue.status\n\n<sup><sup>[↩ Parent](#keyvalue)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b><a href=\"#keyvaluestatusconditionsindex\">conditions</a></b></td>\n        <td>[]object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>observedGeneration</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### KeyValue.status.conditions[index]\n\n<sup><sup>[↩ Parent](#keyvaluestatus)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>lastTransitionTime</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>message</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>reason</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>status</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>type</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n## ObjectStore\n\n> **⚠️ Important**: ObjectStore resources require the JetStream controller to be running in **control-loop mode** (`--control-loop` flag). They are not supported in the default legacy mode.\n\n<sup><sup>[↩ Parent](#jetstreamnatsiov1beta2)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n      <td><b>apiVersion</b></td>\n      <td>string</td>\n      <td>jetstream.nats.io/v1beta2</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b>kind</b></td>\n      <td>string</td>\n      <td>ObjectStore</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b><a href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta\">metadata</a></b></td>\n      <td>object</td>\n      <td>Refer to the Kubernetes API documentation for the fields of the `metadata` field.</td>\n      <td>true</td>\n      </tr><tr>\n        <td><b><a href=\"#objectstorespec\">spec</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#objectstorestatus\">status</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### ObjectStore.spec\n\n<sup><sup>[↩ Parent](#objectstore)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>account</b></td>\n        <td>string</td>\n        <td>\n          Name of the account to which the Object Store belongs.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>bucket</b></td>\n        <td>string</td>\n        <td>\n          A unique name for the Object Store.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>compression</b></td>\n        <td>boolean</td>\n        <td>\n          Object Store compression.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>creds</b></td>\n        <td>string</td>\n        <td>\n          NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on its path.<br/>\n          <br/>\n            <i>Default</i>: <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>description</b></td>\n        <td>string</td>\n        <td>\n          The description of the Object Store.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxBytes</b></td>\n        <td>integer</td>\n        <td>\n          The maximum size of the Store in bytes.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>metadata</b></td>\n        <td>map[string]string</td>\n        <td>\n          Additional Object Store metadata.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>nkey</b></td>\n        <td>string</td>\n        <td>\n          NATS user NKey for connecting to servers.<br/>\n          <br/>\n            <i>Default</i>: <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#objectstorespecplacement\">placement</a></b></td>\n        <td>object</td>\n        <td>\n          The Object Store placement via tags or cluster name.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>preventDelete</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the managed Object Store will not be deleted when the resource is deleted.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>preventUpdate</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the managed Object Store will not be updated when the resource is updated.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>replicas</b></td>\n        <td>integer</td>\n        <td>\n          The number of replicas to keep for the Object Store in clustered JetStream.<br/>\n          <br/>\n            <i>Default</i>: 1<br/>\n            <i>Minimum</i>: 1<br/>\n            <i>Maximum</i>: 5<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>servers</b></td>\n        <td>[]string</td>\n        <td>\n          A list of servers for creating the Object Store.<br/>\n          <br/>\n            <i>Default</i>: []<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>storage</b></td>\n        <td>enum</td>\n        <td>\n          The storage backend to use for the Object Store.<br/>\n          <br/>\n            <i>Enum</i>: file, memory<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#objectstorespectls\">tls</a></b></td>\n        <td>object</td>\n        <td>\n          A client's TLS certs and keys.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>tlsFirst</b></td>\n        <td>boolean</td>\n        <td>\n          When true, the KV Store will initiate TLS before server INFO.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>ttl</b></td>\n        <td>string</td>\n        <td>\n          The time expiry for keys.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### ObjectStore.spec.placement\n\n<sup><sup>[↩ Parent](#objectstorespec)</sup></sup>\n\nThe Object Store placement via tags or cluster name.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>cluster</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>tags</b></td>\n        <td>[]string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### ObjectStore.spec.tls\n\n<sup><sup>[↩ Parent](#objectstorespec)</sup></sup>\n\nA client's TLS certs and keys.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>clientCert</b></td>\n        <td>string</td>\n        <td>\n          A client's cert filepath. Should be mounted.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>clientKey</b></td>\n        <td>string</td>\n        <td>\n          A client's key filepath. Should be mounted.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>rootCas</b></td>\n        <td>[]string</td>\n        <td>\n          A list of filepaths to CAs. Should be mounted.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### ObjectStore.status\n\n<sup><sup>[↩ Parent](#objectstore)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b><a href=\"#objectstorestatusconditionsindex\">conditions</a></b></td>\n        <td>[]object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>observedGeneration</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### ObjectStore.status.conditions[index]\n\n<sup><sup>[↩ Parent](#objectstorestatus)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>lastTransitionTime</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>message</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>reason</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>status</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>type</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n# jetstream.nats.io/v1beta1\n\nResource Types:\n\n- [Stream](#stream)\n\n- [Consumer](#consumer)\n\n- [StreamTemplate](#streamtemplate)\n\n## Stream\n\n<sup><sup>[↩ Parent](#jetstreamnatsiov1beta1)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n      <td><b>apiVersion</b></td>\n      <td>string</td>\n      <td>jetstream.nats.io/v1beta1</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b>kind</b></td>\n      <td>string</td>\n      <td>Stream</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b><a href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta\">metadata</a></b></td>\n      <td>object</td>\n      <td>Refer to the Kubernetes API documentation for the fields of the `metadata` field.</td>\n      <td>true</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspec-1\">spec</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamstatus-1\">status</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec\n\n<sup><sup>[↩ Parent](#stream-1)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>description</b></td>\n        <td>string</td>\n        <td>\n          The description of the stream.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>discard</b></td>\n        <td>enum</td>\n        <td>\n          When a Stream reach it's limits either old messages are deleted or new ones are denied.<br/>\n          <br/>\n            <i>Enum</i>: old, new<br/>\n            <i>Default</i>: old<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>duplicateWindow</b></td>\n        <td>string</td>\n        <td>\n          The duration window to track duplicate messages for.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxAge</b></td>\n        <td>string</td>\n        <td>\n          Maximum age of any message in the stream, expressed in Go's time.Duration format. Empty for unlimited.<br/>\n          <br/>\n            <i>Default</i>: <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxBytes</b></td>\n        <td>integer</td>\n        <td>\n          How big the Stream may be, when the combined stream size exceeds this old messages are removed. -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxConsumers</b></td>\n        <td>integer</td>\n        <td>\n          How many Consumers can be defined for a given Stream. -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxMsgSize</b></td>\n        <td>integer</td>\n        <td>\n          The largest message that will be accepted by the Stream. -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxMsgs</b></td>\n        <td>integer</td>\n        <td>\n          How many messages may be in a Stream, oldest messages will be removed if the Stream exceeds this size. -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxMsgsPerSubject</b></td>\n        <td>integer</td>\n        <td>\n          The maximum number of messages per subject.<br/>\n          <br/>\n            <i>Default</i>: 0<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspecmirror-1\">mirror</a></b></td>\n        <td>object</td>\n        <td>\n          A stream mirror.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          A unique name for the Stream.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>noAck</b></td>\n        <td>boolean</td>\n        <td>\n          Disables acknowledging messages that are received by the Stream.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspecplacement-1\">placement</a></b></td>\n        <td>object</td>\n        <td>\n          A stream's placement.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>replicas</b></td>\n        <td>integer</td>\n        <td>\n          How many replicas to keep for each message.<br/>\n          <br/>\n            <i>Default</i>: 1<br/>\n            <i>Minimum</i>: 1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>retention</b></td>\n        <td>enum</td>\n        <td>\n          How messages are retained in the Stream, once this is exceeded old messages are removed.<br/>\n          <br/>\n            <i>Enum</i>: limits, interest, workqueue<br/>\n            <i>Default</i>: limits<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamspecsourcesindex-1\">sources</a></b></td>\n        <td>[]object</td>\n        <td>\n          A stream's sources.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>storage</b></td>\n        <td>enum</td>\n        <td>\n          The storage backend to use for the Stream.<br/>\n          <br/>\n            <i>Enum</i>: file, memory<br/>\n            <i>Default</i>: memory<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>subjects</b></td>\n        <td>[]string</td>\n        <td>\n          A list of subjects to consume, supports wildcards.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec.mirror\n\n<sup><sup>[↩ Parent](#streamspec-1)</sup></sup>\n\nA stream mirror.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>externalApiPrefix</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>externalDeliverPrefix</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>filterSubject</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartSeq</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartTime</b></td>\n        <td>string</td>\n        <td>\n          Time format must be RFC3339.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec.placement\n\n<sup><sup>[↩ Parent](#streamspec-1)</sup></sup>\n\nA stream's placement.\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>cluster</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>tags</b></td>\n        <td>[]string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.spec.sources[index]\n\n<sup><sup>[↩ Parent](#streamspec-1)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>externalApiPrefix</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>externalDeliverPrefix</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>filterSubject</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartSeq</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartTime</b></td>\n        <td>string</td>\n        <td>\n          Time format must be RFC3339.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.status\n\n<sup><sup>[↩ Parent](#stream-1)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b><a href=\"#streamstatusconditionsindex-1\">conditions</a></b></td>\n        <td>[]object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>observedGeneration</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Stream.status.conditions[index]\n\n<sup><sup>[↩ Parent](#streamstatus-1)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>lastTransitionTime</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>message</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>reason</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>status</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>type</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n## Consumer\n\n<sup><sup>[↩ Parent](#jetstreamnatsiov1beta1)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n      <td><b>apiVersion</b></td>\n      <td>string</td>\n      <td>jetstream.nats.io/v1beta1</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b>kind</b></td>\n      <td>string</td>\n      <td>Consumer</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b><a href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta\">metadata</a></b></td>\n      <td>object</td>\n      <td>Refer to the Kubernetes API documentation for the fields of the `metadata` field.</td>\n      <td>true</td>\n      </tr><tr>\n        <td><b><a href=\"#consumerspec-1\">spec</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#consumerstatus-1\">status</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Consumer.spec\n\n<sup><sup>[↩ Parent](#consumer-1)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>ackPolicy</b></td>\n        <td>enum</td>\n        <td>\n          How messages should be acknowledged.<br/>\n          <br/>\n            <i>Enum</i>: none, all, explicit<br/>\n            <i>Default</i>: none<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>ackWait</b></td>\n        <td>string</td>\n        <td>\n          How long to allow messages to remain un-acknowledged before attempting redelivery.<br/>\n          <br/>\n            <i>Default</i>: 1ns<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>deliverGroup</b></td>\n        <td>string</td>\n        <td>\n          The name of a queue group.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>deliverPolicy</b></td>\n        <td>enum</td>\n        <td>\n            <i>Enum</i>: all, last, new, byStartSequence, byStartTime<br/>\n            <i>Default</i>: all<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>deliverSubject</b></td>\n        <td>string</td>\n        <td>\n          The subject to deliver observed messages, when not set, a pull-based Consumer is created.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>description</b></td>\n        <td>string</td>\n        <td>\n          The description of the consumer.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>durableName</b></td>\n        <td>string</td>\n        <td>\n          The name of the Consumer.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>filterSubject</b></td>\n        <td>string</td>\n        <td>\n          Select only a specific incoming subjects, supports wildcards.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>flowControl</b></td>\n        <td>boolean</td>\n        <td>\n          Enables flow control.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>heartbeatInterval</b></td>\n        <td>string</td>\n        <td>\n          The interval used to deliver idle heartbeats for push-based consumers, in Go's time.Duration format.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxAckPending</b></td>\n        <td>integer</td>\n        <td>\n          Maximum pending Acks before consumers are paused.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxDeliver</b></td>\n        <td>integer</td>\n        <td>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartSeq</b></td>\n        <td>integer</td>\n        <td>\n            <i>Minimum</i>: 0<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>optStartTime</b></td>\n        <td>string</td>\n        <td>\n          Time format must be RFC3339.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>rateLimitBps</b></td>\n        <td>integer</td>\n        <td>\n          Rate at which messages will be delivered to clients, expressed in bit per second.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>replayPolicy</b></td>\n        <td>enum</td>\n        <td>\n          How messages are sent.<br/>\n          <br/>\n            <i>Enum</i>: instant, original<br/>\n            <i>Default</i>: instant<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>sampleFreq</b></td>\n        <td>string</td>\n        <td>\n          What percentage of acknowledgements should be samples for observability.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>streamName</b></td>\n        <td>string</td>\n        <td>\n          The name of the Stream to create the Consumer in.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Consumer.status\n\n<sup><sup>[↩ Parent](#consumer-1)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b><a href=\"#consumerstatusconditionsindex-1\">conditions</a></b></td>\n        <td>[]object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>observedGeneration</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### Consumer.status.conditions[index]\n\n<sup><sup>[↩ Parent](#consumerstatus-1)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>lastTransitionTime</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>message</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>reason</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>status</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>type</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n## StreamTemplate\n\n<sup><sup>[↩ Parent](#jetstreamnatsiov1beta1)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n      <td><b>apiVersion</b></td>\n      <td>string</td>\n      <td>jetstream.nats.io/v1beta1</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b>kind</b></td>\n      <td>string</td>\n      <td>StreamTemplate</td>\n      <td>true</td>\n      </tr>\n      <tr>\n      <td><b><a href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta\">metadata</a></b></td>\n      <td>object</td>\n      <td>Refer to the Kubernetes API documentation for the fields of the `metadata` field.</td>\n      <td>true</td>\n      </tr><tr>\n        <td><b><a href=\"#streamtemplatespec\">spec</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b><a href=\"#streamtemplatestatus\">status</a></b></td>\n        <td>object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### StreamTemplate.spec\n\n<sup><sup>[↩ Parent](#streamtemplate)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>discard</b></td>\n        <td>enum</td>\n        <td>\n          When a Stream reach it's limits either old messages are deleted or new ones are denied.<br/>\n          <br/>\n            <i>Enum</i>: old, new<br/>\n            <i>Default</i>: old<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>duplicateWindow</b></td>\n        <td>string</td>\n        <td>\n          The duration window to track duplicate messages for.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxAge</b></td>\n        <td>string</td>\n        <td>\n          Maximum age of any message in the stream, expressed in Go's time.Duration format. Empty for unlimited.<br/>\n          <br/>\n            <i>Default</i>: <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxBytes</b></td>\n        <td>integer</td>\n        <td>\n          How big the Stream may be, when the combined stream size exceeds this old messages are removed. -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxConsumers</b></td>\n        <td>integer</td>\n        <td>\n          How many Consumers can be defined for a given Stream. -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxMsgSize</b></td>\n        <td>integer</td>\n        <td>\n          The largest message that will be accepted by the Stream. -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxMsgs</b></td>\n        <td>integer</td>\n        <td>\n          How many messages may be in a Stream, oldest messages will be removed if the Stream exceeds this size. -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>maxStreams</b></td>\n        <td>integer</td>\n        <td>\n          The maximum number of Streams this Template can create, -1 for unlimited.<br/>\n          <br/>\n            <i>Default</i>: -1<br/>\n            <i>Minimum</i>: -1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>name</b></td>\n        <td>string</td>\n        <td>\n          A unique name for the Stream Template.<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>noAck</b></td>\n        <td>boolean</td>\n        <td>\n          Disables acknowledging messages that are received by the Stream.<br/>\n          <br/>\n            <i>Default</i>: false<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>replicas</b></td>\n        <td>integer</td>\n        <td>\n          How many replicas to keep for each message.<br/>\n          <br/>\n            <i>Default</i>: 1<br/>\n            <i>Minimum</i>: 1<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>retention</b></td>\n        <td>enum</td>\n        <td>\n          How messages are retained in the Stream, once this is exceeded old messages are removed.<br/>\n          <br/>\n            <i>Enum</i>: limits, interest, workqueue<br/>\n            <i>Default</i>: limits<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>storage</b></td>\n        <td>enum</td>\n        <td>\n          The storage backend to use for the Stream.<br/>\n          <br/>\n            <i>Enum</i>: file, memory<br/>\n            <i>Default</i>: memory<br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>subjects</b></td>\n        <td>[]string</td>\n        <td>\n          A list of subjects to consume, supports wildcards.<br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### StreamTemplate.status\n\n<sup><sup>[↩ Parent](#streamtemplate)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b><a href=\"#streamtemplatestatusconditionsindex\">conditions</a></b></td>\n        <td>[]object</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>observedGeneration</b></td>\n        <td>integer</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n\n### StreamTemplate.status.conditions[index]\n\n<sup><sup>[↩ Parent](#streamtemplatestatus)</sup></sup>\n\n<table>\n    <thead>\n        <tr>\n            <th>Name</th>\n            <th>Type</th>\n            <th>Description</th>\n            <th>Required</th>\n        </tr>\n    </thead>\n    <tbody><tr>\n        <td><b>lastTransitionTime</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>message</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>reason</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>status</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr><tr>\n        <td><b>type</b></td>\n        <td>string</td>\n        <td>\n          <br/>\n        </td>\n        <td>false</td>\n      </tr></tbody>\n</table>\n"
  },
  {
    "path": "examples/secure/client-tls.yaml",
    "content": "---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: nats-sys-tls\nspec:\n  secretName: nats-sys-tls\n  duration: 2160h # 90 days\n  renewBefore: 240h # 10 days\n  issuerRef:\n    name: nats-ca\n    kind: Issuer\n  usages:\n    - digital signature\n    - key encipherment\n    - client auth\n  commonName: nats-sys-user\n"
  },
  {
    "path": "examples/secure/issuer.yaml",
    "content": "---\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: selfsigning\nspec:\n  selfSigned: {}\n---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: nats-ca\nspec:\n  secretName: nats-ca\n  duration: 8736h # 1 year\n  renewBefore: 240h # 10 days\n  issuerRef:\n    name: selfsigning\n    kind: ClusterIssuer\n  commonName: nats-ca\n  isCA: true\n---\napiVersion: cert-manager.io/v1\nkind: Issuer\nmetadata:\n  name: nats-ca\nspec:\n  ca:\n    secretName: nats-ca\n"
  },
  {
    "path": "examples/secure/nack/account-foo.yaml",
    "content": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Account\nmetadata:\n  name: a\nspec:\n  name: a\n  servers:\n  - nats://nats:4222\n  tls:\n    secret:\n      name: nack-a-tls\n    ca: \"ca.crt\"\n    cert: \"tls.crt\"\n    key: \"tls.key\"\n"
  },
  {
    "path": "examples/secure/nack/nats-account-a.yaml",
    "content": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Account\nmetadata:\n  name: a\nspec:\n  name: a\n  servers:\n  - nats://nats:4222\n  tls:\n    secret:\n      name: nack-a-tls\n    ca: \"ca.crt\"\n    cert: \"tls.crt\"\n    key: \"tls.key\"\n"
  },
  {
    "path": "examples/secure/nack/nats-consumer-bar-a.yaml",
    "content": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Consumer\nmetadata:\n  name: bar\nspec:\n  streamName: foo\n  durableName: bar\n  ackPolicy: explicit\n  account: a\n"
  },
  {
    "path": "examples/secure/nack/nats-stream-foo-a.yaml",
    "content": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: foo\nspec:\n  name: foo\n  subjects: [\"foo\", \"foo.>\"]\n  storage: file\n  replicas: 1\n  account: a\n"
  },
  {
    "path": "examples/secure/nack/stream-foo.yaml",
    "content": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: foo\nspec:\n  name: foo\n  subjects: [\"foo\", \"foo.>\"]\n  storage: file\n  replicas: 1\n  account: a\n"
  },
  {
    "path": "examples/secure/nack-a-client-tls.yaml",
    "content": "---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: nack-a-tls\nspec:\n  secretName: nack-a-tls\n  duration: 2160h # 90 days\n  renewBefore: 240h # 10 days\n  issuerRef:\n    name: nats-ca\n    kind: Issuer\n  usages:\n    - digital signature\n    - key encipherment\n    - client auth\n  commonName: nack-a\n"
  },
  {
    "path": "examples/secure/nack-b-client-tls.yaml",
    "content": "---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: nack-b-tls\nspec:\n  secretName: nack-b-tls\n  duration: 2160h # 90 days\n  renewBefore: 240h # 10 days\n  issuerRef:\n    name: nats-ca\n    kind: Issuer\n  usages:\n    - digital signature\n    - key encipherment\n    - client auth\n  commonName: nack-b\n"
  },
  {
    "path": "examples/secure/nats-helm.yaml",
    "content": "tlsCA:\n  enabled: true\n  secretName: nats-sys-tls\n  key: ca.crt\n\nconfig:\n  cluster:\n    enabled: true\n  jetstream:\n    enabled: true\n\n    memoryStore:\n      enabled: true\n      maxSize: 2Gi\n\n    fileStore:\n      enabled: true\n      pvc:\n        enabled: true\n        size: 1Gi\n\n  nats:\n    tls:\n      enabled: true\n      secretName: nats-server-tls\n      cert: tls.crt\n      key: tls.key\n      merge:\n        verify_and_map: true\n\n  merge:\n    system_account: SYS\n    accounts:\n      SYS:\n        users:\n          - user: CN=nats-sys-user\n      A:\n        jetstream: true\n        users:\n          - user: CN=nack-a\n      B:\n        jetstream: true\n        users:\n          - user: CN=nack-b\n\nnatsBox:\n  contexts:\n    default:\n      tls:\n        secretName: nack-a-tls\n        cert: tls.crt\n        key: tls.key"
  },
  {
    "path": "examples/secure/server-tls.yaml",
    "content": "---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: nats-server-tls\nspec:\n  secretName: nats-server-tls\n  duration: 2160h # 90 days\n  renewBefore: 240h # 10 days\n  issuerRef:\n    name: nats-ca\n    kind: Issuer\n  commonName: nats.default.svc.cluster.local\n  dnsNames:\n  - nats\n  - nats.default\n  - nats.default.svc\n  - nats.default.svc.cluster.local\n  - '*.nats'\n  - '*.nats.default'\n  - '*.nats.default.svc'\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/nats-io/nack\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/fsnotify/fsnotify v1.9.0\n\tgithub.com/go-logr/logr v1.4.3\n\tgithub.com/google/go-cmp v0.7.0\n\tgithub.com/nats-io/jsm.go v0.3.0\n\tgithub.com/nats-io/nats-server/v2 v2.12.6\n\tgithub.com/nats-io/nats.go v1.50.0\n\tgithub.com/onsi/ginkgo/v2 v2.28.1\n\tgithub.com/onsi/gomega v1.39.1\n\tgithub.com/sirupsen/logrus v1.9.4\n\tgithub.com/stretchr/testify v1.11.1\n\tgolang.org/x/sync v0.20.0\n\tk8s.io/api v0.35.3\n\tk8s.io/apimachinery v0.35.3\n\tk8s.io/client-go v0.35.3\n\tk8s.io/code-generator v0.35.3\n\tk8s.io/klog/v2 v2.140.0\n\tsigs.k8s.io/controller-runtime v0.23.3\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482\n)\n\nrequire (\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/antithesishq/antithesis-sdk-go v0.6.0-default-no-op // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/dustin/go-humanize v1.0.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/expr-lang/expr v1.17.7 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // 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/gnostic-models v0.7.0 // indirect\n\tgithub.com/google/go-tpm v0.9.8 // indirect\n\tgithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/compress v1.18.5 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 // 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/nats-io/jwt/v2 v2.8.1 // indirect\n\tgithub.com/nats-io/nkeys v0.4.15 // indirect\n\tgithub.com/nats-io/nuid v1.0.1 // 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.17.0 // indirect\n\tgithub.com/spf13/pflag v1.0.9 // indirect\n\tgithub.com/x448/float16 v0.8.4 // 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/crypto v0.49.0 // indirect\n\tgolang.org/x/mod v0.33.0 // indirect\n\tgolang.org/x/net v0.51.0 // indirect\n\tgolang.org/x/oauth2 v0.30.0 // indirect\n\tgolang.org/x/sys v0.42.0 // indirect\n\tgolang.org/x/term v0.41.0 // indirect\n\tgolang.org/x/text v0.35.0 // indirect\n\tgolang.org/x/time v0.15.0 // indirect\n\tgolang.org/x/tools v0.42.0 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.4.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.9 // 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/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // 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/yaml v1.6.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.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/antithesishq/antithesis-sdk-go v0.5.0 h1:cudCFF83pDDANcXFzkQPUHHedfnnIbUO3JMr9fqwFJs=\ngithub.com/antithesishq/antithesis-sdk-go v0.5.0/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E=\ngithub.com/antithesishq/antithesis-sdk-go v0.6.0-default-no-op h1:kpBdlEPbRvff0mDD1gk7o9BhI16b9p5yYAXRlidpqJE=\ngithub.com/antithesishq/antithesis-sdk-go v0.6.0-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E=\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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\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/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8=\ngithub.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=\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.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\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/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=\ngithub.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\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/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=\ngithub.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=\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-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=\ngithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=\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/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.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=\ngithub.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=\ngithub.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=\ngithub.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=\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/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 h1:KGuD/pM2JpL9FAYvBrnBBeENKZNh6eNtjqytV6TYjnk=\ngithub.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=\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/nats-io/jsm.go v0.3.0 h1:fl0yZtK6U+kkAQ/mm5AXzRGHwJa35j2E+aopCcebfgU=\ngithub.com/nats-io/jsm.go v0.3.0/go.mod h1:PLObK5L+Vcq1GGGJY3DfrFs8QaSDuWF0co81sEHdFcg=\ngithub.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g=\ngithub.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=\ngithub.com/nats-io/jwt/v2 v2.8.1 h1:V0xpGuD/N8Mi+fQNDynXohVvp7ZztevW5io8CUWlPmU=\ngithub.com/nats-io/jwt/v2 v2.8.1/go.mod h1:nWnOEEiVMiKHQpnAy4eXlizVEtSfzacZ1Q43LIRavZg=\ngithub.com/nats-io/nats-server/v2 v2.12.4 h1:ZnT10v2LU2Xcoiy8ek9X6Se4YG8EuMfIfvAEuFVx1Ts=\ngithub.com/nats-io/nats-server/v2 v2.12.4/go.mod h1:5MCp/pqm5SEfsvVZ31ll1088ZTwEUdvRX1Hmh/mTTDg=\ngithub.com/nats-io/nats-server/v2 v2.12.6 h1:Egbx9Vl7Ch8wTtpXPGqbehkZ+IncKqShUxvrt1+Enc8=\ngithub.com/nats-io/nats-server/v2 v2.12.6/go.mod h1:4HPlrvtmSO3yd7KcElDNMx9kv5EBJBnJJzQPptXlheo=\ngithub.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=\ngithub.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=\ngithub.com/nats-io/nats.go v1.50.0 h1:5zAeQrTvyrKrWLJ0fu02W3br8ym57qf7csDzgLOpcds=\ngithub.com/nats-io/nats.go v1.50.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno=\ngithub.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=\ngithub.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=\ngithub.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=\ngithub.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs=\ngithub.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=\ngithub.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=\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 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.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=\ngithub.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=\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/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=\ngithub.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=\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/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.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/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=\ngolang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=\ngolang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=\ngolang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=\ngolang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=\ngolang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=\ngolang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=\ngolang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=\ngolang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=\ngolang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\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.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=\ngolang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\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/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=\ngolang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=\ngolang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=\ngolang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=\ngolang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=\ngolang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=\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/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\ngolang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=\ngolang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=\ngolang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=\ngolang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=\ngolang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=\ngolang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=\ngolang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=\ngolang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=\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/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=\ngoogle.golang.org/protobuf v1.36.9/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/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ=\nk8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4=\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/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8=\nk8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=\nk8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=\nk8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=\nk8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg=\nk8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c=\nk8s.io/code-generator v0.35.0 h1:TvrtfKYZTm9oDF2z+veFKSCcgZE3Igv0svY+ehCmjHQ=\nk8s.io/code-generator v0.35.0/go.mod h1:iS1gvVf3c/T71N5DOGYO+Gt3PdJ6B9LYSvIyQ4FHzgc=\nk8s.io/code-generator v0.35.3 h1:NDGCLkEm6Ho65wTdSe2EgErmmtsrezOPwwOchlNc6FQ=\nk8s.io/code-generator v0.35.3/go.mod h1:LAVriRGXQusHQ0Ns64SE1ublSswm1KrK7cXn0GuQETg=\nk8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ=\nk8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=\nk8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=\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/controller-runtime v0.23.0 h1:Ubi7klJWiwEWqDY+odSVZiFA0aDSevOCXpa38yCSYu8=\nsigs.k8s.io/controller-runtime v0.23.0/go.mod h1:DBOIr9NsprUqCZ1ZhsuJ0wAnQSIxY/C6VjZbmLgw0j0=\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.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.1/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\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": "internal/controller/account_controller.go",
    "content": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS 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\"errors\"\n\t\"fmt\"\n\n\t\"github.com/go-logr/logr\"\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/klog/v2\"\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\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/predicate\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n)\n\n// AccountReconciler reconciles a Account object\ntype AccountReconciler struct {\n\tScheme *runtime.Scheme\n\tJetStreamController\n}\n\ntype JetStreamResource interface {\n\tGetName() string\n\tGetNamespace() string\n}\n\ntype JetStreamResourceList []JetStreamResource\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//\n// It performs two main operations:\n// - Initialize finalizer if not present\n// - Remove the finalizer on deletion once no other resources are referencing the account\n//\n// A call to reconcile may perform only one action, expecting the reconciliation to be triggered again by an update.\n// For example: Setting the finalizer triggers a second reconciliation. Reconcile returns after setting the finalizer,\n// to prevent parallel reconciliations performing the same steps.\nfunc (r *AccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := klog.FromContext(ctx)\n\n\tif ok := r.ValidNamespace(req.Namespace); !ok {\n\t\tlog.Info(\"Controller restricted to namespace, skipping reconciliation.\")\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Fetch Account resource\n\taccount := &api.Account{}\n\tif err := r.Get(ctx, req.NamespacedName, account); err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\tlog.Info(\"Account deleted.\", \"accountName\", req.NamespacedName.String())\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\treturn ctrl.Result{}, fmt.Errorf(\"get account resource '%s': %w\", req.NamespacedName.String(), err)\n\t}\n\n\t// Update ready status to unknown when no status is set\n\tif len(account.Status.Conditions) == 0 {\n\t\tlog.Info(\"Setting initial ready condition to unknown.\")\n\t\taccount.Status.Conditions = updateReadyCondition(account.Status.Conditions, v1.ConditionUnknown, stateReconciling, \"Starting reconciliation\")\n\t\terr := r.Status().Update(ctx, account)\n\t\tif err != nil {\n\t\t\t// If we get a conflict error, another reconciliation has already updated the status.\n\t\t\t// Just requeue and let the next reconciliation handle it.\n\t\t\tif apierrors.IsConflict(err) {\n\t\t\t\treturn ctrl.Result{Requeue: true}, nil\n\t\t\t}\n\t\t\treturn ctrl.Result{}, fmt.Errorf(\"set condition unknown: %w\", err)\n\t\t}\n\t\tr.Get(ctx, req.NamespacedName, account)\n\t\tlog.Info(\"Status\", \"Conditions\", account.Status.Conditions)\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// Check Deletion\n\tmarkedForDeletion := account.GetDeletionTimestamp() != nil\n\tif markedForDeletion {\n\t\tif controllerutil.ContainsFinalizer(account, accountFinalizer) {\n\t\t\t// Get list of resources referencing this account\n\t\t\trequests := r.findDependentResources(ctx, log, account)\n\t\t\tif len(requests) > 0 {\n\t\t\t\tlog.Info(\"Account still has dependent resources, cannot delete\", \"dependentCount\", len(requests))\n\t\t\t\taccount.Status.Conditions = updateReadyCondition(\n\t\t\t\t\taccount.Status.Conditions,\n\t\t\t\t\tv1.ConditionFalse,\n\t\t\t\t\tstateFinalizing,\n\t\t\t\t\t\"Account has dependent resources that must be deleted first\",\n\t\t\t\t)\n\t\t\t\tif err := r.Status().Update(ctx, account); err != nil {\n\t\t\t\t\treturn ctrl.Result{}, fmt.Errorf(\"update status: %w\", err)\n\t\t\t\t}\n\t\t\t\treturn ctrl.Result{Requeue: true}, nil\n\t\t\t}\n\n\t\t\tlog.Info(\"Removing Account finalizer\")\n\t\t\tif ok := controllerutil.RemoveFinalizer(account, accountFinalizer); !ok {\n\t\t\t\treturn ctrl.Result{}, errors.New(\"failed to remove finalizer\")\n\t\t\t}\n\t\t\tif err := r.Update(ctx, account); err != nil {\n\t\t\t\treturn ctrl.Result{}, fmt.Errorf(\"remove finalizer: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Info(\"Account marked for deletion and already finalized. Ignoring.\")\n\t\t}\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Add finalizer\n\tif !controllerutil.ContainsFinalizer(account, accountFinalizer) {\n\t\tlog.Info(\"Adding Account finalizer.\")\n\t\tif ok := controllerutil.AddFinalizer(account, accountFinalizer); !ok {\n\t\t\treturn ctrl.Result{}, errors.New(\"failed to add finalizer to account resource\")\n\t\t}\n\n\t\tif err := r.Update(ctx, account); err != nil {\n\t\t\treturn ctrl.Result{}, fmt.Errorf(\"update account resource to add finalizer: %w\", err)\n\t\t}\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// Update ready status for non-deleted accounts\n\taccount.Status.ObservedGeneration = account.Generation\n\taccount.Status.Conditions = updateReadyCondition(\n\t\taccount.Status.Conditions,\n\t\tv1.ConditionTrue,\n\t\tstateReady,\n\t\t\"Account is ready\",\n\t)\n\tif err := r.Status().Update(ctx, account); err != nil {\n\t\treturn ctrl.Result{}, fmt.Errorf(\"update status: %w\", err)\n\t}\n\n\treturn ctrl.Result{}, nil\n}\n\nfunc (r *AccountReconciler) findDependentResources(ctx context.Context, log logr.Logger, obj client.Object) []reconcile.Request {\n\tvar resourceList JetStreamResourceList\n\n\tvar consumerList api.ConsumerList\n\tif err := r.List(ctx, &consumerList,\n\t\tclient.InNamespace(obj.GetNamespace()),\n\t); err != nil {\n\t\tlog.Error(err, \"Failed to list consumers\")\n\t}\n\tfor _, i := range consumerList.Items {\n\t\tif i.Spec.Account == obj.GetName() {\n\t\t\tresourceList = append(resourceList, &i)\n\t\t}\n\t}\n\n\tvar keyValueList api.KeyValueList\n\tif err := r.List(ctx, &keyValueList,\n\t\tclient.InNamespace(obj.GetNamespace()),\n\t); err != nil {\n\t\tlog.Error(err, \"Failed to list accounts\")\n\t}\n\tfor _, i := range keyValueList.Items {\n\t\tif i.Spec.Account == obj.GetName() {\n\t\t\tresourceList = append(resourceList, &i)\n\t\t}\n\t}\n\n\tvar objectStoreList api.ObjectStoreList\n\tif err := r.List(ctx, &objectStoreList,\n\t\tclient.InNamespace(obj.GetNamespace()),\n\t); err != nil {\n\t\tlog.Error(err, \"Failed to list objectstores\")\n\t}\n\tfor _, i := range objectStoreList.Items {\n\t\tif i.Spec.Account == obj.GetName() {\n\t\t\tresourceList = append(resourceList, &i)\n\t\t}\n\t}\n\n\tvar streamList api.StreamList\n\tif err := r.List(ctx, &streamList,\n\t\tclient.InNamespace(obj.GetNamespace()),\n\t); err != nil {\n\t\tlog.Error(err, \"Failed to list streams\")\n\t}\n\tfor _, i := range streamList.Items {\n\t\tif i.Spec.Account == obj.GetName() {\n\t\t\tresourceList = append(resourceList, &i)\n\t\t}\n\t}\n\n\trequests := make([]reconcile.Request, 0, len(resourceList))\n\tfor _, resource := range resourceList {\n\t\trequests = append(requests, reconcile.Request{\n\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\tNamespace: resource.GetNamespace(),\n\t\t\t\tName:      resource.GetName(),\n\t\t\t},\n\t\t})\n\t}\n\n\treturn requests\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *AccountReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&api.Account{}).\n\t\tWithEventFilter(predicate.GenerationChangedPredicate{}).\n\t\tWithOptions(controller.Options{\n\t\t\tMaxConcurrentReconciles: 1,\n\t\t}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "internal/controller/account_controller_test.go",
    "content": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS 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\"time\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\tv1 \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n)\n\nvar _ = Describe(\"Account Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-account\"\n\n\t\ttypeNamespacedName := types.NamespacedName{\n\t\t\tName:      resourceName,\n\t\t\tNamespace: \"default\",\n\t\t}\n\t\taccount := &api.Account{}\n\n\t\t// Tested controller\n\t\tvar controller *AccountReconciler\n\n\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\tcontroller = &AccountReconciler{\n\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\tJetStreamController: baseController,\n\t\t\t}\n\t\t})\n\n\t\tWhen(\"the resource is marked for deletion\", func() {\n\t\t\tvar stream *api.Stream\n\t\t\tvar streamName types.NamespacedName\n\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"creating the custom resource for the Kind Account\")\n\t\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, account)\n\t\t\t\tif err != nil && k8serrors.IsNotFound(err) {\n\t\t\t\t\tresource := &api.Account{\n\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\tName:      resourceName,\n\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSpec: api.AccountSpec{\n\t\t\t\t\t\t\tServers: []string{\"nats://nats.io\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\n\t\t\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, account)).To(Succeed())\n\n\t\t\t\t\tcontroller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tcontroller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tcontroller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\n\t\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, account)).To(Succeed())\n\t\t\t\t\tExpect(controllerutil.ContainsFinalizer(account, accountFinalizer)).To(BeTrue())\n\t\t\t\t\tExpect(len(account.Status.Conditions)).To(BeNumerically(\"==\", 1))\n\t\t\t\t\tExpect(account.Status.Conditions[0].Type).To(Equal(readyCondType))\n\t\t\t\t\tExpect(account.Status.Conditions[0].Status).To(Equal(v1.ConditionTrue))\n\t\t\t\t\tExpect(account.Status.Conditions[0].Message).To(Equal(\"Account is ready\"))\n\t\t\t\t}\n\n\t\t\t\tBy(\"creating a dependent stream resource\")\n\t\t\t\tstream = &api.Stream{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-stream\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: api.StreamSpec{\n\t\t\t\t\t\tName:      \"test-stream\",\n\t\t\t\t\t\tReplicas:  1,\n\t\t\t\t\t\tDiscard:   \"old\",\n\t\t\t\t\t\tStorage:   \"file\",\n\t\t\t\t\t\tRetention: \"workqueue\",\n\t\t\t\t\t\tBaseStreamConfig: api.BaseStreamConfig{\n\t\t\t\t\t\t\tConnectionOpts: api.ConnectionOpts{\n\t\t\t\t\t\t\t\tAccount: resourceName,\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\tstreamName = types.NamespacedName{\n\t\t\t\t\tName:      stream.Name,\n\t\t\t\t\tNamespace: stream.Namespace,\n\t\t\t\t}\n\t\t\t\tExpect(k8sClient.Create(ctx, stream)).To(Succeed())\n\n\t\t\t\tBy(\"marking the account for deletion\")\n\t\t\t\tExpect(k8sClient.Delete(ctx, account)).To(Succeed())\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, account)).To(Succeed())\n\t\t\t})\n\n\t\t\tAfterEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"cleaning up the stream\")\n\t\t\t\tstream := &api.Stream{}\n\t\t\t\terr := k8sClient.Get(ctx, streamName, stream)\n\t\t\t\tif err == nil {\n\t\t\t\t\tExpect(k8sClient.Delete(ctx, stream)).To(Succeed())\n\t\t\t\t}\n\n\t\t\t\tBy(\"removing the account resource\")\n\t\t\t\tcontroller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, account)).To(Not(Succeed()))\n\t\t\t})\n\n\t\t\tIt(\"should not remove finalizer while dependent resources exist\", func(ctx SpecContext) {\n\t\t\t\tBy(\"reconciling the deletion\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.Requeue).To(BeTrue())\n\n\t\t\t\tBy(\"checking the account still exists\")\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, account)).To(Succeed())\n\t\t\t\tExpect(account.Finalizers).To(ContainElement(accountFinalizer))\n\n\t\t\t\tBy(\"verifying the ready condition is set to false\")\n\t\t\t\tExpect(account.Status.Conditions).To(HaveLen(1))\n\t\t\t\tassertReadyStateMatches(\n\t\t\t\t\taccount.Status.Conditions[0],\n\t\t\t\t\tv1.ConditionFalse,\n\t\t\t\t\tstateFinalizing,\n\t\t\t\t\t\"Account has dependent resources that must be deleted first\",\n\t\t\t\t\ttime.Now(),\n\t\t\t\t)\n\t\t\t})\n\n\t\t\tIt(\"should remove finalizer after dependent resources are removed\", func(ctx SpecContext) {\n\t\t\t\tBy(\"removing the dependent stream\")\n\t\t\t\tExpect(k8sClient.Delete(ctx, stream)).To(Succeed())\n\n\t\t\t\tBy(\"reconciling the deletion\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking the account is deleted\")\n\t\t\t\terr = k8sClient.Get(ctx, typeNamespacedName, account)\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(k8serrors.IsNotFound(err)).To(BeTrue())\n\t\t\t})\n\n\t\t\tIt(\"should remove finalizer after dependent resources are updated\", func(ctx SpecContext) {\n\t\t\t\tBy(\"updating the dependent stream to remove account reference\")\n\t\t\t\tExpect(k8sClient.Get(ctx, streamName, stream)).To(Succeed())\n\t\t\t\tstream.Spec.Account = \"\"\n\t\t\t\tExpect(k8sClient.Update(ctx, stream)).To(Succeed())\n\n\t\t\t\tBy(\"reconciling the deletion\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking the account is deleted\")\n\t\t\t\terr = k8sClient.Get(ctx, typeNamespacedName, account)\n\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\tExpect(k8serrors.IsNotFound(err)).To(BeTrue())\n\t\t\t})\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "internal/controller/client.go",
    "content": "package controller\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/nats-io/jsm.go\"\n\t\"github.com/nats-io/nats.go\"\n\t\"github.com/nats-io/nats.go/jetstream\"\n)\n\ntype NatsConfig struct {\n\tClientName  string   `json:\"name,omitempty\"`\n\tServerURL   string   `json:\"url,omitempty\"`\n\tCertificate string   `json:\"tls_cert,omitempty\"`\n\tKey         string   `json:\"tls_key,omitempty\"`\n\tTLSFirst    bool     `json:\"tls_first,omitempty\"`\n\tCAs         []string `json:\"tls_ca,omitempty\"`\n\tCredentials string   `json:\"credential,omitempty\"`\n\tNKey        string   `json:\"nkey,omitempty\"`\n\tToken       string   `json:\"token,omitempty\"`\n\tUser        string   `json:\"username,omitempty\"`\n\tPassword    string   `json:\"password,omitempty\"`\n\tJsDomain    string   `json:\"js_domain,omitempty\"`\n}\n\nfunc (o *NatsConfig) Copy() *NatsConfig {\n\tif o == nil {\n\t\treturn nil\n\t}\n\n\tcp := *o\n\treturn &cp\n}\n\nfunc (o *NatsConfig) Hash() (string, error) {\n\tb, err := json.Marshal(o)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error marshaling config to json: %v\", err)\n\t}\n\n\tif o.NKey != \"\" {\n\t\tfb, err := os.ReadFile(o.NKey)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"error opening nkey file %s: %v\", o.NKey, err)\n\t\t}\n\t\tb = append(b, fb...)\n\t}\n\n\tif o.Credentials != \"\" {\n\t\tfb, err := os.ReadFile(o.Credentials)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"error opening creds file %s: %v\", o.Credentials, err)\n\t\t}\n\t\tb = append(b, fb...)\n\t}\n\n\tif len(o.CAs) > 0 {\n\t\tfor _, cert := range o.CAs {\n\t\t\tfb, err := os.ReadFile(cert)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"error opening ca file %s: %v\", cert, err)\n\t\t\t}\n\t\t\tb = append(b, fb...)\n\t\t}\n\t}\n\n\tif o.Certificate != \"\" {\n\t\tfb, err := os.ReadFile(o.Certificate)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"error opening cert file %s: %v\", o.Certificate, err)\n\t\t}\n\t\tb = append(b, fb...)\n\t}\n\n\tif o.Key != \"\" {\n\t\tfb, err := os.ReadFile(o.Key)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"error opening key file %s: %v\", o.Key, err)\n\t\t}\n\t\tb = append(b, fb...)\n\t}\n\n\thash := sha256.New()\n\thash.Write(b)\n\treturn fmt.Sprintf(\"%x\", hash.Sum(nil)), nil\n}\n\nfunc (o *NatsConfig) Overlay(overlay *NatsConfig) {\n\tif overlay.ClientName != \"\" {\n\t\to.ClientName = overlay.ClientName\n\t}\n\n\tif overlay.ServerURL != \"\" {\n\t\to.ServerURL = overlay.ServerURL\n\t}\n\n\tif overlay.JsDomain != \"\" {\n\t\to.JsDomain = overlay.JsDomain\n\t}\n\n\tif overlay.Certificate != \"\" && overlay.Key != \"\" {\n\t\to.Certificate = overlay.Certificate\n\t\to.Key = overlay.Key\n\t}\n\n\tif len(overlay.CAs) > 0 {\n\t\to.CAs = overlay.CAs\n\t}\n\n\tif overlay.TLSFirst {\n\t\to.TLSFirst = overlay.TLSFirst\n\t}\n\n\tif !overlay.HasAuth() {\n\t\treturn\n\t}\n\n\to.UnsetAuth()\n\n\tif overlay.Credentials != \"\" {\n\t\to.Credentials = overlay.Credentials\n\t} else if overlay.NKey != \"\" {\n\t\to.NKey = overlay.NKey\n\t} else if overlay.Token != \"\" {\n\t\to.Token = overlay.Token\n\t} else if overlay.User != \"\" && overlay.Password != \"\" {\n\t\to.User = overlay.User\n\t\to.Password = overlay.Password\n\t}\n}\n\nfunc (o *NatsConfig) HasAuth() bool {\n\treturn o.Credentials != \"\" || o.NKey != \"\" || o.Token != \"\" || (o.User != \"\" && o.Password != \"\")\n}\n\nfunc (o *NatsConfig) UnsetAuth() {\n\to.Credentials = \"\"\n\to.NKey = \"\"\n\to.User = \"\"\n\to.Password = \"\"\n\to.Token = \"\"\n}\n\n// buildOptions creates options from the config to be used in nats.Connect.\nfunc (o *NatsConfig) buildOptions() ([]nats.Option, error) {\n\topts := make([]nats.Option, 0)\n\n\tif o.ClientName != \"\" {\n\t\topts = append(opts, nats.Name(o.ClientName))\n\t}\n\n\tif o.ServerURL == \"\" {\n\t\treturn nil, fmt.Errorf(\"server url is required\")\n\t}\n\n\tif o.Certificate != \"\" && o.Key != \"\" {\n\t\topts = append(opts, nats.ClientCert(o.Certificate, o.Key))\n\t}\n\n\tif o.TLSFirst {\n\t\topts = append(opts, nats.TLSHandshakeFirst())\n\t}\n\n\tif len(o.CAs) > 0 {\n\t\topts = append(opts, nats.RootCAs(o.CAs...))\n\t}\n\n\tif o.Credentials != \"\" {\n\t\topts = append(opts, nats.UserCredentials(o.Credentials))\n\t}\n\n\tif o.NKey != \"\" {\n\t\topt, err := nats.NkeyOptionFromSeed(o.NKey)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"nkey option from seed: %w\", err)\n\t\t}\n\t\topts = append(opts, opt)\n\t}\n\n\tif o.Token != \"\" {\n\t\topts = append(opts, nats.Token(o.Token))\n\t}\n\n\tif o.User != \"\" && o.Password != \"\" {\n\t\topts = append(opts, nats.UserInfo(o.User, o.Password))\n\t}\n\n\treturn opts, nil\n}\n\ntype Closable interface {\n\tClose()\n}\n\nfunc CreateJSMClient(conn *pooledConnection, pedantic bool, domain string) (*jsm.Manager, error) {\n\tif !conn.nc.IsConnected() {\n\t\treturn nil, errors.New(\"not connected\")\n\t}\n\n\tmajor, minor, _, err := versionComponents(conn.nc.ConnectedServerVersion())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"parse server version: %w\", err)\n\t}\n\n\t// JetStream pedantic mode unsupported prior to NATS Server 2.11\n\tif pedantic && major < 2 || (major == 2 && minor < 11) {\n\t\tpedantic = false\n\t}\n\n\tjsmOpts := make([]jsm.Option, 0)\n\tif pedantic {\n\t\tjsmOpts = append(jsmOpts, jsm.WithPedanticRequests())\n\t}\n\tif domain != \"\" {\n\t\tjsmOpts = append(jsmOpts, jsm.WithDomain(domain))\n\t}\n\n\tjsmClient, err := jsm.New(conn.nc, jsmOpts...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"new jsm client: %w\", err)\n\t}\n\n\treturn jsmClient, nil\n}\n\n// CreateJetStreamClient creates new Jetstream client with a connection based on the given NatsConfig.\n// Returns a jetstream.Jetstream client and the Closable of the underlying connection.\n// Close should be called when the client is no longer used.\nfunc CreateJetStreamClient(conn *pooledConnection, pedantic bool, domain string) (jetstream.JetStream, error) {\n\tvar (\n\t\terr error\n\t\tjs  jetstream.JetStream\n\t)\n\n\tif domain != \"\" {\n\t\tjs, err = jetstream.NewWithDomain(conn.nc, domain)\n\t} else {\n\t\tjs, err = jetstream.New(conn.nc)\n\t}\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"new jetstream: %w\", err)\n\t}\n\treturn js, nil\n}\n\nfunc createNatsConn(cfg *NatsConfig) (*nats.Conn, error) {\n\topts, err := cfg.buildOptions()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// client should always attempt to reconnect\n\topts = append(opts, nats.MaxReconnects(-1))\n\n\treturn nats.Connect(cfg.ServerURL, opts...)\n}\n"
  },
  {
    "path": "internal/controller/connection_pool.go",
    "content": "package controller\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/nats-io/nats.go\"\n)\n\ntype pooledConnection struct {\n\tnc       *nats.Conn\n\tpool     *connectionPool\n\thash     string\n\trefCount int\n}\n\nfunc (pc *pooledConnection) Close() {\n\tif pc.pool != nil {\n\t\tpc.pool.release(pc.hash)\n\t} else if pc.nc != nil {\n\t\tpc.nc.Close() // Close directly if not pool-managed\n\t}\n}\n\ntype connectionPool struct {\n\tconnections map[string]*pooledConnection\n\tgracePeriod time.Duration\n\tmu          sync.Mutex\n}\n\nfunc newConnPool(gracePeriod time.Duration) *connectionPool {\n\treturn &connectionPool{\n\t\tconnections: make(map[string]*pooledConnection),\n\t\tgracePeriod: gracePeriod,\n\t}\n}\n\nfunc (p *connectionPool) Get(c *NatsConfig, pedantic bool) (*pooledConnection, error) {\n\tp.mu.Lock()\n\tdefer p.mu.Unlock()\n\n\thash, err := c.Hash()\n\tif err != nil {\n\t\t// If hash fails, create a new non-pooled connection\n\t\tnc, err := createNatsConn(c)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &pooledConnection{nc: nc}, nil\n\t}\n\n\tif pc, ok := p.connections[hash]; ok && !pc.nc.IsClosed() {\n\t\tpc.refCount++\n\t\treturn pc, nil\n\t}\n\n\tnc, err := createNatsConn(c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpc := &pooledConnection{\n\t\tnc:       nc,\n\t\tpool:     p,\n\t\thash:     hash,\n\t\trefCount: 1,\n\t}\n\tp.connections[hash] = pc\n\n\treturn pc, nil\n}\n\nfunc (p *connectionPool) release(hash string) {\n\tp.mu.Lock()\n\tdefer p.mu.Unlock()\n\n\tpc, ok := p.connections[hash]\n\tif !ok {\n\t\treturn\n\t}\n\n\tpc.refCount--\n\tif pc.refCount < 1 {\n\t\tgo func() {\n\t\t\tif p.gracePeriod > 0 {\n\t\t\t\ttime.Sleep(p.gracePeriod)\n\t\t\t}\n\n\t\t\tp.mu.Lock()\n\t\t\tdefer p.mu.Unlock()\n\n\t\t\tif pc, ok := p.connections[hash]; ok && pc.refCount < 1 {\n\t\t\t\tpc.nc.Close()\n\t\t\t\tdelete(p.connections, hash)\n\t\t\t}\n\t\t}()\n\t}\n}\n"
  },
  {
    "path": "internal/controller/connection_pool_test.go",
    "content": "package controller\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\tnatsservertest \"github.com/nats-io/nats-server/v2/test\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestConnPool(t *testing.T) {\n\tt.Parallel()\n\n\ts := natsservertest.RunRandClientPortServer()\n\tdefer s.Shutdown()\n\n\tc1 := &NatsConfig{\n\t\tClientName: \"Client 1\",\n\t\tServerURL:  s.ClientURL(),\n\t}\n\n\tc2 := &NatsConfig{\n\t\tClientName: \"Client 1\",\n\t\tServerURL:  s.ClientURL(),\n\t}\n\n\tc3 := &NatsConfig{\n\t\tClientName: \"Client 2\",\n\t\tServerURL:  s.ClientURL(),\n\t}\n\n\tpool := newConnPool(0)\n\n\tvar conn1, conn2, conn3 *pooledConnection\n\tvar err1, err2, err3 error\n\n\twg := &sync.WaitGroup{}\n\twg.Add(3)\n\n\tgo func() {\n\t\tconn1, err1 = pool.Get(c1, true)\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\tconn2, err2 = pool.Get(c2, true)\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\tconn3, err3 = pool.Get(c3, true)\n\t\twg.Done()\n\t}()\n\twg.Wait()\n\n\trequire := require.New(t)\n\n\trequire.NoError(err1)\n\trequire.NoError(err2)\n\trequire.NoError(err3)\n\n\trequire.Same(conn1, conn2)\n\trequire.NotSame(conn1, conn3)\n\trequire.NotSame(conn2, conn3)\n\n\tconn1.Close()\n\tconn3.Close()\n\n\ttime.Sleep(time.Second)\n\n\trequire.False(conn1.nc.IsClosed())\n\trequire.False(conn2.nc.IsClosed())\n\trequire.True(conn3.nc.IsClosed())\n\n\tconn4, err4 := pool.Get(c1, true)\n\trequire.NoError(err4)\n\trequire.Same(conn1, conn4)\n\trequire.Same(conn2, conn4)\n\n\tconn2.Close()\n\tconn4.Close()\n\n\ttime.Sleep(time.Second)\n\n\trequire.True(conn1.nc.IsClosed())\n\trequire.True(conn2.nc.IsClosed())\n\trequire.True(conn3.nc.IsClosed())\n\trequire.True(conn4.nc.IsClosed())\n\n\tconn5, err5 := pool.Get(c1, true)\n\trequire.NoError(err5)\n\trequire.NotSame(conn1, conn5)\n\n\tconn5.Close()\n\n\ttime.Sleep(time.Second)\n\n\trequire.True(conn5.nc.IsClosed())\n}\n"
  },
  {
    "path": "internal/controller/consumer_controller.go",
    "content": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS 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\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/nats-io/jsm.go\"\n\tjsmapi \"github.com/nats-io/jsm.go/api\"\n\n\t\"github.com/go-logr/logr\"\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/klog/v2\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/predicate\"\n\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n)\n\n// ConsumerReconciler reconciles a Consumer object\ntype ConsumerReconciler struct {\n\tScheme *runtime.Scheme\n\n\tJetStreamController\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//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.3/pkg/reconcile\nfunc (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := klog.FromContext(ctx)\n\n\tif ok := r.ValidNamespace(req.Namespace); !ok {\n\t\tlog.Info(\"Controller restricted to namespace, skipping reconciliation.\")\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Fetch consumer resource\n\tconsumer := &api.Consumer{}\n\tif err := r.Get(ctx, req.NamespacedName, consumer); err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\tlog.Info(\"Consumer resource deleted.\", \"consumerName\", req.NamespacedName.String())\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\treturn ctrl.Result{}, fmt.Errorf(\"get consumer resource '%s': %w\", req.NamespacedName.String(), err)\n\t}\n\n\tlog = log.WithValues(\n\t\t\"streamName\", consumer.Spec.StreamName,\n\t\t\"consumerName\", consumer.Spec.DurableName,\n\t)\n\n\t// Update ready status to unknown when no status is set\n\tif len(consumer.Status.Conditions) == 0 {\n\t\tlog.Info(\"Setting initial ready condition to unknown.\")\n\t\tconsumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionUnknown, stateReconciling, \"Starting reconciliation\")\n\t\terr := r.Status().Update(ctx, consumer)\n\t\tif err != nil {\n\t\t\t// If we get a conflict error, another reconciliation has already updated the status.\n\t\t\t// Just requeue and let the next reconciliation handle it.\n\t\t\tif apierrors.IsConflict(err) {\n\t\t\t\treturn ctrl.Result{Requeue: true}, nil\n\t\t\t}\n\t\t\treturn ctrl.Result{}, fmt.Errorf(\"set condition unknown: %w\", err)\n\t\t}\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// Check Deletion\n\tmarkedForDeletion := consumer.GetDeletionTimestamp() != nil\n\tif markedForDeletion {\n\t\tif controllerutil.ContainsFinalizer(consumer, consumerFinalizer) {\n\t\t\terr := r.deleteConsumer(ctx, log, consumer)\n\t\t\tif err != nil {\n\t\t\t\treturn ctrl.Result{}, fmt.Errorf(\"delete consumer: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Info(\"Consumer marked for deletion and already finalized. Ignoring.\")\n\t\t}\n\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Add finalizer\n\tif !controllerutil.ContainsFinalizer(consumer, consumerFinalizer) {\n\t\tlog.Info(\"Adding consumer finalizer.\")\n\t\tif ok := controllerutil.AddFinalizer(consumer, consumerFinalizer); !ok {\n\t\t\treturn ctrl.Result{}, errors.New(\"failed to add finalizer to consumer resource\")\n\t\t}\n\n\t\tif err := r.Update(ctx, consumer); err != nil {\n\t\t\treturn ctrl.Result{}, fmt.Errorf(\"update consumer resource to add finalizer: %w\", err)\n\t\t}\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// Create or update stream\n\tif err := r.createOrUpdate(ctx, log, consumer); err != nil {\n\t\tif err := r.Get(ctx, client.ObjectKeyFromObject(consumer), consumer); err != nil {\n\t\t\treturn ctrl.Result{}, fmt.Errorf(\"get consumer resource: %w\", err)\n\t\t}\n\t\tconsumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, stateErrored, err.Error())\n\t\tif err := r.Status().Update(ctx, consumer); err != nil {\n\t\t\tlog.Error(err, \"Failed to update ready condition to Errored.\")\n\t\t}\n\t\treturn ctrl.Result{}, fmt.Errorf(\"create or update: %s\", err)\n\t}\n\n\treturn ctrl.Result{RequeueAfter: r.RequeueInterval()}, nil\n}\n\nfunc (r *ConsumerReconciler) deleteConsumer(ctx context.Context, log logr.Logger, consumer *api.Consumer) error {\n\t// Set status to false\n\tconsumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, stateFinalizing, \"Performing finalizer operations.\")\n\tif err := r.Status().Update(ctx, consumer); err != nil {\n\t\treturn fmt.Errorf(\"update ready condition: %w\", err)\n\t}\n\n\tstoredState, err := getStoredConsumerState(consumer)\n\tif err != nil {\n\t\tlog.Error(err, \"Failed to fetch stored state.\")\n\t}\n\n\tif !consumer.Spec.PreventDelete && !r.ReadOnly() {\n\t\terr := r.WithJSMClient(consumer.Spec.ConnectionOpts, consumer.Namespace, func(js *jsm.Manager) error {\n\t\t\t_, err := getServerConsumerState(js, consumer)\n\t\t\t// If we have no known state for this consumer it has never been reconciled.\n\t\t\t// If we are also receiving an error fetching state, either the consumer does not exist\n\t\t\t// or this resource config is invalid.\n\t\t\tif err != nil && storedState == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn js.DeleteConsumer(consumer.Spec.StreamName, consumer.Spec.DurableName)\n\t\t})\n\t\tswitch {\n\t\tcase jsm.IsNatsError(err, JSConsumerNotFoundErr):\n\t\t\tlog.Info(\"Consumer does not exist. Unable to delete.\")\n\t\tcase jsm.IsNatsError(err, JSStreamNotFoundErr):\n\t\t\tlog.Info(\"Stream of consumer does not exist. Unable to delete.\")\n\t\tcase err != nil:\n\t\t\tif storedState == nil {\n\t\t\t\tlog.Info(\"Consumer not reconciled and no state received from server. Removing finalizer.\")\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"delete jetstream consumer: %w\", err)\n\t\t\t}\n\t\tdefault:\n\t\t\tlog.Info(\"Consumer deleted.\")\n\t\t}\n\t} else {\n\t\tlog.Info(\"Skipping consumer deletion.\",\n\t\t\t\"consumerName\", consumer.Spec.DurableName,\n\t\t\t\"preventDelete\", consumer.Spec.PreventDelete,\n\t\t\t\"read-only\", r.ReadOnly(),\n\t\t)\n\t}\n\n\tlog.Info(\"Removing consumer finalizer.\")\n\tif ok := controllerutil.RemoveFinalizer(consumer, consumerFinalizer); !ok {\n\t\treturn errors.New(\"failed to remove consumer finalizer\")\n\t}\n\tif err := r.Update(ctx, consumer); err != nil {\n\t\treturn fmt.Errorf(\"remove finalizer: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log klog.Logger, consumer *api.Consumer) error {\n\t// Create or Update the stream based on the spec\n\t// Map spec to consumer target config\n\ttargetConfig, err := consumerSpecToConfig(&consumer.Spec)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"map consumer spec to target config: %w\", err)\n\t}\n\n\terr = r.WithJSMClient(consumer.Spec.ConnectionOpts, consumer.Namespace, func(js *jsm.Manager) error {\n\t\tstoredState, err := getStoredConsumerState(consumer)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to fetch stored consumer state.\")\n\t\t}\n\n\t\tserverState, err := getServerConsumerState(js, consumer)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"fetching consumer current state: %w\", err)\n\t\t}\n\n\t\t// Check against known state. Skip Update if converged.\n\t\t// Storing returned state from the server avoids have to\n\t\t// check default values or call Update on already converged resources\n\t\tif storedState != nil && serverState != nil && consumer.Status.ObservedGeneration == consumer.Generation {\n\t\t\tdiff := compareConfigState(storedState, serverState)\n\n\t\t\tif diff == \"\" {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tlog.Info(\"Consumer config drifted from desired state.\", \"diff\", diff)\n\t\t}\n\n\t\tif r.ReadOnly() {\n\t\t\tlog.Info(\"Skipping consumer creation or update.\",\n\t\t\t\t\"read-only\", r.ReadOnly(),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\n\t\tvar updatedConsumer *jsm.Consumer\n\t\terr = nil\n\n\t\tif serverState == nil {\n\t\t\tlog.Info(\"Creating Consumer.\")\n\t\t\tupdatedConsumer, err = js.NewConsumer(consumer.Spec.StreamName, targetConfig...)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"creating consumer: %w\", err)\n\t\t\t}\n\t\t} else if !consumer.Spec.PreventUpdate {\n\t\t\tlog.Info(\"Updating Consumer.\")\n\t\t\tc, err := js.LoadConsumer(consumer.Spec.StreamName, consumer.Spec.DurableName)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"loading consumer: %w\", err)\n\t\t\t}\n\n\t\t\terr = c.UpdateConfiguration(targetConfig...)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"updating the consumer configuration: %w\", err)\n\t\t\t}\n\n\t\t\tupdatedConsumer, err = js.LoadConsumer(consumer.Spec.StreamName, consumer.Spec.DurableName)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"loading updated consumer: %w\", err)\n\t\t\t}\n\n\t\t\tdiff := compareConfigState(updatedConsumer.Configuration(), *serverState)\n\t\t\tlog.Info(\"Updated Consumer.\", \"diff\", diff)\n\t\t} else {\n\t\t\tlog.Info(\"Skipping Consumer update.\",\n\t\t\t\t\"preventUpdate\", consumer.Spec.PreventUpdate,\n\t\t\t)\n\t\t}\n\n\t\tif updatedConsumer != nil {\n\t\t\t// Store known state in annotation\n\t\t\tupdatedState, err := json.Marshal(updatedConsumer.Configuration())\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"marshaling JSON: %w\", err)\n\t\t\t}\n\n\t\t\tif consumer.Annotations == nil {\n\t\t\t\tconsumer.Annotations = map[string]string{}\n\t\t\t}\n\t\t\tconsumer.Annotations[stateAnnotationConsumer] = string(updatedState)\n\n\t\t\treturn r.Update(ctx, consumer)\n\t\t}\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\terr = fmt.Errorf(\"create or update consumer: %w\", err)\n\t\tconsumer.Status.Conditions = updateReadyCondition(consumer.Status.Conditions, v1.ConditionFalse, stateErrored, err.Error())\n\t\tif err := r.Status().Update(ctx, consumer); err != nil {\n\t\t\tlog.Error(err, \"Failed to update ready condition to Errored.\")\n\t\t}\n\t\treturn err\n\t}\n\n\t// update the observed generation and ready status\n\tconsumer.Status.ObservedGeneration = consumer.Generation\n\tconsumer.Status.Conditions = updateReadyCondition(\n\t\tconsumer.Status.Conditions,\n\t\tv1.ConditionTrue,\n\t\tstateReady,\n\t\t\"Consumer successfully created or updated.\",\n\t)\n\terr = r.Status().Update(ctx, consumer)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"update ready condition: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc getStoredConsumerState(consumer *api.Consumer) (*jsmapi.ConsumerConfig, error) {\n\tvar storedState *jsmapi.ConsumerConfig\n\tif state, ok := consumer.Annotations[stateAnnotationConsumer]; ok {\n\t\terr := json.Unmarshal([]byte(state), &storedState)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn storedState, nil\n}\n\n// Fetch the current state of the consumer from the server.\n// ErrConsumerNotFound is considered a valid response and does not return error\nfunc getServerConsumerState(js *jsm.Manager, consumer *api.Consumer) (*jsmapi.ConsumerConfig, error) {\n\tc, err := js.LoadConsumer(consumer.Spec.StreamName, consumer.Spec.DurableName)\n\tif jsm.IsNatsError(err, JSConsumerNotFoundErr) {\n\t\treturn nil, nil\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconsumerCfg := c.Configuration()\n\treturn &consumerCfg, nil\n}\n\nfunc consumerSpecToConfig(spec *api.ConsumerSpec) ([]jsm.ConsumerOption, error) {\n\topts := []jsm.ConsumerOption{\n\t\tjsm.ConsumerDescription(spec.Description),\n\t\tjsm.DeliverySubject(spec.DeliverSubject),\n\t\tjsm.DeliverGroup(spec.DeliverGroup),\n\t\tjsm.DurableName(spec.DurableName),\n\t\tjsm.MaxAckPending(uint(spec.MaxAckPending)),\n\t\tjsm.MaxWaiting(uint(spec.MaxWaiting)),\n\t\tjsm.RateLimitBitsPerSecond(uint64(spec.RateLimitBps)),\n\t\tjsm.MaxRequestBatch(uint(spec.MaxRequestBatch)),\n\t\tjsm.MaxRequestMaxBytes(spec.MaxRequestMaxBytes),\n\t\tjsm.ConsumerOverrideReplicas(spec.Replicas),\n\t\tjsm.ConsumerMetadata(spec.Metadata),\n\t}\n\n\t// ackPolicy\n\tswitch spec.AckPolicy {\n\tcase \"none\":\n\t\topts = append(opts, jsm.AcknowledgeNone())\n\tcase \"all\":\n\t\topts = append(opts, jsm.AcknowledgeAll())\n\tcase \"explicit\":\n\t\topts = append(opts, jsm.AcknowledgeExplicit())\n\tcase \"\":\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid value for 'ackPolicy': '%s'. Must be one of 'none', 'all', 'explicit'\", spec.AckPolicy)\n\t}\n\n\t//\tackWait\n\tif spec.AckWait != \"\" {\n\t\td, err := time.ParseDuration(spec.AckWait)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid ack wait duration: %w\", err)\n\t\t}\n\t\topts = append(opts, jsm.AckWait(d))\n\t}\n\n\t// deliverPolicy\n\tswitch spec.DeliverPolicy {\n\tcase \"all\":\n\t\topts = append(opts, jsm.DeliverAllAvailable())\n\tcase \"last\":\n\t\topts = append(opts, jsm.StartWithLastReceived())\n\tcase \"new\":\n\t\topts = append(opts, jsm.StartWithNextReceived())\n\tcase \"byStartSequence\":\n\t\topts = append(opts, jsm.StartAtSequence(uint64(spec.OptStartSeq)))\n\tcase \"byStartTime\":\n\t\tif spec.OptStartTime == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"'optStartTime' is required for deliver policy 'byStartTime'\")\n\t\t}\n\t\tt, err := time.Parse(time.RFC3339, spec.OptStartTime)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\topts = append(opts, jsm.StartAtTime(t))\n\tcase \"lastPerSubject\":\n\t\topts = append(opts, jsm.DeliverLastPerSubject())\n\tcase \"\":\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid value for 'deliverPolicy': '%s'. Must be one of 'all', 'last', 'new', 'lastPerSubject', 'byStartSequence', 'byStartTime'\", spec.DeliverPolicy)\n\t}\n\n\t// filterSubject\n\tif spec.FilterSubject != \"\" && len(spec.FilterSubjects) > 0 {\n\t\treturn nil, errors.New(\"cannot set both 'filterSubject' and 'filterSubjects'\")\n\t}\n\n\tif spec.FilterSubject != \"\" {\n\t\topts = append(opts, jsm.FilterStreamBySubject(spec.FilterSubject))\n\t} else if len(spec.FilterSubjects) > 0 {\n\t\topts = append(opts, jsm.FilterStreamBySubject(spec.FilterSubjects...))\n\t}\n\n\t// flowControl\n\tif spec.FlowControl {\n\t\topts = append(opts, jsm.PushFlowControl())\n\t}\n\n\t// heartbeatInterval\n\tif spec.HeartbeatInterval != \"\" {\n\t\td, err := time.ParseDuration(spec.HeartbeatInterval)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid heartbeat interval: %w\", err)\n\t\t}\n\n\t\topts = append(opts, jsm.IdleHeartbeat(d))\n\t}\n\n\t// maxDeliver\n\tif spec.MaxDeliver != 0 {\n\t\topts = append(opts, jsm.MaxDeliveryAttempts(spec.MaxDeliver))\n\t}\n\n\t// backoff\n\tif len(spec.BackOff) > 0 {\n\t\tbackoffs := make([]time.Duration, 0)\n\t\tfor _, bo := range spec.BackOff {\n\t\t\td, err := time.ParseDuration(bo)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid backoff: %w\", err)\n\t\t\t}\n\t\t\tbackoffs = append(backoffs, d)\n\t\t}\n\n\t\topts = append(opts, jsm.BackoffIntervals(backoffs...))\n\t}\n\n\t//\treplayPolicy\n\tswitch spec.ReplayPolicy {\n\tcase \"instant\":\n\t\topts = append(opts, jsm.ReplayInstantly())\n\tcase \"original\":\n\t\topts = append(opts, jsm.ReplayAsReceived())\n\tcase \"\":\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid value for 'replayPolicy': '%s'. Must be one of 'instant', 'original'\", spec.ReplayPolicy)\n\t}\n\n\tif spec.SampleFreq != \"\" {\n\t\tn, err := strconv.Atoi(\n\t\t\tstrings.TrimSuffix(spec.SampleFreq, \"%\"),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\topts = append(opts, jsm.SamplePercent(n))\n\t}\n\n\tif spec.HeadersOnly {\n\t\topts = append(opts, jsm.DeliverHeadersOnly())\n\t}\n\n\t//\tMaxRequestExpires\n\tif spec.MaxRequestExpires != \"\" {\n\t\td, err := time.ParseDuration(spec.MaxRequestExpires)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid opt start time: %w\", err)\n\t\t}\n\t\topts = append(opts, jsm.MaxRequestExpires(d))\n\t}\n\n\t// inactiveThreshold\n\tif spec.InactiveThreshold != \"\" {\n\t\td, err := time.ParseDuration(spec.InactiveThreshold)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid inactive threshold: %w\", err)\n\t\t}\n\t\topts = append(opts, jsm.InactiveThreshold(d))\n\t}\n\n\t// memStorage\n\tif spec.MemStorage {\n\t\topts = append(opts, jsm.ConsumerOverrideMemoryStorage())\n\t}\n\n\tif spec.PauseUntil != \"\" {\n\t\tt, err := time.Parse(time.RFC3339, spec.PauseUntil)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid pauseUntil time: %w\", err)\n\t\t}\n\t\topts = append(opts, jsm.PauseUntil(t))\n\t}\n\n\tswitch spec.PriorityPolicy {\n\tcase \"\", \"none\":\n\t\t// Default is none, no need to set\n\tcase \"pinned_client\":\n\t\tif spec.PinnedTTL != \"\" {\n\t\t\tdur, err := time.ParseDuration(spec.PinnedTTL)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid pinnedTTL duration: %w\", err)\n\t\t\t}\n\t\t\topts = append(opts, jsm.PinnedClientPriorityGroups(dur, spec.PriorityGroups...))\n\t\t}\n\tcase \"overflow\":\n\t\topts = append(opts, jsm.OverflowPriorityGroups(spec.PriorityGroups...))\n\tcase \"prioritized\":\n\t\topts = append(opts, jsm.PrioritizedPriorityGroups(spec.PriorityGroups...))\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid priority policy: %s\", spec.PriorityPolicy)\n\t}\n\n\treturn opts, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *ConsumerReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&api.Consumer{}).\n\t\tWithEventFilter(predicate.GenerationChangedPredicate{}).\n\t\tWithOptions(controller.Options{\n\t\t\tMaxConcurrentReconciles: 1,\n\t\t}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "internal/controller/consumer_controller_test.go",
    "content": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS 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\"testing\"\n\t\"time\"\n\n\tjsmapi \"github.com/nats-io/jsm.go/api\"\n\t\"github.com/nats-io/nats.go/jetstream\"\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/stretchr/testify/assert\"\n\tv1 \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n)\n\nvar _ = Describe(\"Consumer Controller\", func() {\n\tContext(\"When reconciling a resource\", func() {\n\t\tconst resourceName = \"test-consumer\"\n\n\t\tconst streamName = \"orders\"\n\t\tconst consumerName = \"test-consumer\"\n\n\t\tconst alternateResource = \"alternate-consumer\"\n\t\tconst alternateNamespace = \"alternate-namespace\"\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\tconsumer := &api.Consumer{}\n\n\t\temptyStreamConfig := jetstream.StreamConfig{\n\t\t\tName:      streamName,\n\t\t\tReplicas:  1,\n\t\t\tRetention: jetstream.WorkQueuePolicy,\n\t\t\tDiscard:   jetstream.DiscardOld,\n\t\t\tStorage:   jetstream.FileStorage,\n\t\t}\n\n\t\temptyConsumerConfig := jetstream.ConsumerConfig{\n\t\t\tDurable: consumerName,\n\t\t}\n\n\t\t// Tested controller\n\t\tvar controller *ConsumerReconciler\n\n\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\tBy(\"creating the custom resource for the Kind Consumer\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, consumer)\n\t\t\tif err != nil && k8serrors.IsNotFound(err) {\n\t\t\t\tresource := &api.Consumer{\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: api.ConsumerSpec{\n\t\t\t\t\t\tAckPolicy:     \"explicit\",\n\t\t\t\t\t\tDeliverPolicy: \"all\",\n\t\t\t\t\t\tDurableName:   consumerName,\n\t\t\t\t\t\tDescription:   \"test consumer\",\n\t\t\t\t\t\tStreamName:    streamName,\n\t\t\t\t\t\tReplayPolicy:  \"instant\",\n\t\t\t\t\t\tFilterSubject: \"test.wildcard.>\",\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\t// Fetch consumer\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed())\n\t\t\t}\n\n\t\t\tBy(\"creating the underlying stream\")\n\t\t\t_, err = jsClient.CreateStream(ctx, emptyStreamConfig)\n\t\t\tExpect(err).ToNot(HaveOccurred())\n\n\t\t\tBy(\"setting up the tested controller\")\n\t\t\tcontroller = &ConsumerReconciler{\n\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\tJetStreamController: baseController,\n\t\t\t}\n\t\t})\n\n\t\tAfterEach(func(ctx SpecContext) {\n\t\t\tBy(\"removing the consumer resource\")\n\t\t\tresource := &api.Consumer{}\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\t\tif err != nil {\n\t\t\t\tExpect(err).To(MatchError(k8serrors.IsNotFound, \"Is not found\"))\n\t\t\t} else {\n\t\t\t\tif controllerutil.ContainsFinalizer(resource, consumerFinalizer) {\n\t\t\t\t\tBy(\"removing the finalizer\")\n\t\t\t\t\tcontrollerutil.RemoveFinalizer(resource, consumerFinalizer)\n\t\t\t\t\tExpect(k8sClient.Update(ctx, resource)).To(Succeed())\n\t\t\t\t}\n\n\t\t\t\tBy(\"removing the consumer resource\")\n\t\t\t\tExpect(k8sClient.Delete(ctx, resource)).\n\t\t\t\t\tTo(SatisfyAny(\n\t\t\t\t\t\tSucceed(),\n\t\t\t\t\t\tMatchError(k8serrors.IsNotFound, \"is not found\"),\n\t\t\t\t\t))\n\t\t\t}\n\n\t\t\tBy(\"deleting the nats consumer\")\n\t\t\tExpect(jsClient.DeleteConsumer(ctx, streamName, consumerName)).\n\t\t\t\tTo(SatisfyAny(\n\t\t\t\t\tSucceed(),\n\t\t\t\t\tMatchError(jetstream.ErrStreamNotFound),\n\t\t\t\t\tMatchError(jetstream.ErrConsumerNotFound),\n\t\t\t\t))\n\n\t\t\tBy(\"deleting the consumers nats stream\")\n\t\t\tExpect(jsClient.DeleteStream(ctx, streamName)).\n\t\t\t\tTo(SatisfyAny(\n\t\t\t\t\tSucceed(),\n\t\t\t\t\tMatchError(jetstream.ErrStreamNotFound),\n\t\t\t\t))\n\t\t})\n\n\t\tWhen(\"reconciling a not existing resource\", func() {\n\t\t\tIt(\"should stop reconciliation without error\", func(ctx SpecContext) {\n\t\t\t\tBy(\"reconciling the created resource\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\t\tNamespace: \"fake\",\n\t\t\t\t\t\tName:      \"not-existing\",\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result).To(Equal(ctrl.Result{}))\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"reconciling a not initialized resource\", func() {\n\t\t\tIt(\"should initialize a new resource\", func(ctx SpecContext) {\n\t\t\t\tBy(\"re-queueing until it is initialized\")\n\t\t\t\t// Initialization can require multiple reconciliation loops\n\t\t\t\tEventually(func(ctx SpecContext) *api.Consumer {\n\t\t\t\t\t_, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tgot := &api.Consumer{}\n\t\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, got)).To(Succeed())\n\t\t\t\t\treturn got\n\t\t\t\t}).WithContext(ctx).\n\t\t\t\t\tWithin(time.Second).\n\t\t\t\t\tShould(SatisfyAll(\n\t\t\t\t\t\tHaveField(\"Finalizers\", HaveExactElements(consumerFinalizer)),\n\t\t\t\t\t\tHaveField(\"Status.Conditions\", Not(BeEmpty())),\n\t\t\t\t\t))\n\n\t\t\t\tBy(\"validating the ready condition\")\n\t\t\t\t// Fetch consumer\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed())\n\t\t\t\tExpect(consumer.Status.Conditions).To(HaveLen(1))\n\n\t\t\t\tassertReadyStateMatches(consumer.Status.Conditions[0], v1.ConditionUnknown, stateReconciling, \"Starting reconciliation\", time.Now())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"reconciling a resource in a different namespace\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"creating a consumer resource in an alternate namespace while namespaced\")\n\t\t\t\talternateNamespaceResource := &api.Consumer{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      alternateResource,\n\t\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t\t},\n\t\t\t\t\tSpec: api.ConsumerSpec{\n\t\t\t\t\t\tAckPolicy:     \"explicit\",\n\t\t\t\t\t\tDeliverPolicy: \"all\",\n\t\t\t\t\t\tDurableName:   alternateResource,\n\t\t\t\t\t\tDescription:   \"consumer in alternate namespace\",\n\t\t\t\t\t\tStreamName:    streamName,\n\t\t\t\t\t\tReplayPolicy:  \"instant\",\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\tns := &v1.Namespace{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName: alternateNamespace,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\terr := k8sClient.Create(ctx, ns)\n\t\t\t\tif err != nil && !k8serrors.IsAlreadyExists(err) {\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t}\n\n\t\t\t\tExpect(k8sClient.Create(ctx, alternateNamespaceResource)).To(Succeed())\n\t\t\t})\n\n\t\t\tAfterEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"cleaning up the resource in alternate namespace\")\n\t\t\t\talternateConsumer := &api.Consumer{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      alternateResource,\n\t\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\terr := k8sClient.Delete(ctx, alternateConsumer)\n\t\t\t\tif err != nil && !k8serrors.IsNotFound(err) {\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should not watch the resource in alternate namespace\", func(ctx SpecContext) {\n\t\t\t\tBy(\"reconciling with no explicit namespace restriction\")\n\t\t\t\talternateNamespacedName := types.NamespacedName{\n\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t\tName:      alternateResource,\n\t\t\t\t}\n\n\t\t\t\tBy(\"running reconciliation for the resource in alternate namespace\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\t\tNamespacedName: alternateNamespacedName,\n\t\t\t\t})\n\n\t\t\t\tBy(\"verifying reconciliation completes without error\")\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result).To(Equal(ctrl.Result{}))\n\n\t\t\t\tBy(\"checking the consumer doesn't exist in NATS\")\n\t\t\t\t_, err = jsClient.Consumer(ctx, streamName, alternateResource)\n\t\t\t\tExpect(err).To(MatchError(jetstream.ErrConsumerNotFound))\n\n\t\t\t\tBy(\"verifying the resource still exists in the alternate namespace\")\n\t\t\t\talternateConsumer := &api.Consumer{}\n\t\t\t\tExpect(k8sClient.Get(ctx, alternateNamespacedName, alternateConsumer)).To(Succeed())\n\n\t\t\t\tBy(\"checking no conditions were set on the resource\")\n\t\t\t\tExpect(alternateConsumer.Status.Conditions).To(BeEmpty())\n\t\t\t})\n\n\t\t\tIt(\"should watch the resource in alternate namespace when not namespaced\", func(ctx SpecContext) {\n\t\t\t\tBy(\"reconciling with a non-namespaced controller\")\n\t\t\t\ttestNatsConfig := &NatsConfig{ServerURL: clientUrl}\n\t\t\t\talternateBaseController, err := NewJSController(k8sClient, testNatsConfig, &Config{})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\talternateController := &ConsumerReconciler{\n\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\tJetStreamController: alternateBaseController,\n\t\t\t\t}\n\n\t\t\t\tresourceNames := []types.NamespacedName{\n\t\t\t\t\ttypeNamespacedName,\n\t\t\t\t\t{\n\t\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t\t\tName:      alternateResource,\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\tBy(\"running reconciliation for the resources in all namespaces\")\n\t\t\t\tfor _, n := range resourceNames {\n\t\t\t\t\tresult, err := alternateController.Reconcile(ctx, reconcile.Request{\n\t\t\t\t\t\tNamespacedName: n,\n\t\t\t\t\t})\n\n\t\t\t\t\tBy(\"verifying reconciliation completes without error\")\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(result).NotTo(Equal(ctrl.Result{}))\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"reconciling an initialized resource\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"initializing the stream resource\")\n\n\t\t\t\tBy(\"setting the finalizer\")\n\t\t\t\tExpect(controllerutil.AddFinalizer(consumer, consumerFinalizer)).To(BeTrue())\n\t\t\t\tExpect(k8sClient.Update(ctx, consumer)).To(Succeed())\n\n\t\t\t\tBy(\"setting an unknown ready state\")\n\t\t\t\tconsumer.Status.Conditions = []api.Condition{{\n\t\t\t\t\tType:               readyCondType,\n\t\t\t\t\tStatus:             v1.ConditionUnknown,\n\t\t\t\t\tReason:             \"Test\",\n\t\t\t\t\tMessage:            \"start condition\",\n\t\t\t\t\tLastTransitionTime: time.Now().Format(time.RFC3339Nano),\n\t\t\t\t}}\n\t\t\t\tExpect(k8sClient.Status().Update(ctx, consumer)).To(Succeed())\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed())\n\t\t\t})\n\n\t\t\tWhen(\"the underlying stream does not exist\", func() {\n\t\t\t\tIt(\"should set false ready state and error\", func(ctx SpecContext) {\n\t\t\t\t\tBy(\"setting a not existing stream on the resource\")\n\t\t\t\t\tconsumer.Spec.StreamName = \"not-existing\"\n\t\t\t\t\tExpect(k8sClient.Update(ctx, consumer)).To(Succeed())\n\n\t\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).To(HaveOccurred())\n\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\tBy(\"checking for expected ready state\")\n\t\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed())\n\t\t\t\t\tExpect(consumer.Status.Conditions).To(HaveLen(1))\n\t\t\t\t\tassertReadyStateMatches(\n\t\t\t\t\t\tconsumer.Status.Conditions[0],\n\t\t\t\t\t\tv1.ConditionFalse,\n\t\t\t\t\t\tstateErrored,\n\t\t\t\t\t\t\"stream\", // Not existing stream as message\n\t\t\t\t\t\ttime.Now(),\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tIt(\"should create a new consumer\", func(ctx SpecContext) {\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t// Fetch resource\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed())\n\n\t\t\t\tBy(\"checking if the ready state was updated\")\n\t\t\t\tExpect(consumer.Status.Conditions).To(HaveLen(1))\n\t\t\t\tassertReadyStateMatches(consumer.Status.Conditions[0], v1.ConditionTrue, stateReady, \"created or updated\", time.Now())\n\n\t\t\t\tBy(\"checking if the observed generation matches\")\n\t\t\t\tExpect(consumer.Status.ObservedGeneration).To(Equal(consumer.Generation))\n\n\t\t\t\tBy(\"checking if the consumer was created\")\n\t\t\t\tnatsconsumer, err := jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tconsumerInfo, err := natsconsumer.Info(ctx)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(consumerInfo.Config.Name).To(Equal(consumerName))\n\t\t\t\tExpect(consumerInfo.Config.Description).To(Equal(\"test consumer\"))\n\t\t\t\tExpect(consumerInfo.Created).To(BeTemporally(\"~\", time.Now(), time.Second))\n\t\t\t})\n\n\t\t\tIt(\"should update an existing consumer\", func(ctx SpecContext) {\n\t\t\t\tBy(\"reconciling once to create the consumer\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t// Fetch resource\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed())\n\t\t\t\tpreviousTransitionTime := consumer.Status.Conditions[0].LastTransitionTime\n\n\t\t\t\tBy(\"updating the resource\")\n\t\t\t\tconsumer.Spec.Description = \"new description\"\n\t\t\t\tExpect(k8sClient.Update(ctx, consumer)).To(Succeed())\n\n\t\t\t\tBy(\"reconciling the updated resource\")\n\t\t\t\tresult, err = controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t// Fetch resource\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed())\n\n\t\t\t\tBy(\"checking if the state transition time was not updated\")\n\t\t\t\tExpect(consumer.Status.Conditions).To(HaveLen(1))\n\t\t\t\tExpect(consumer.Status.Conditions[0].LastTransitionTime).To(Equal(previousTransitionTime))\n\n\t\t\t\tBy(\"checking if the observed generation matches\")\n\t\t\t\tExpect(consumer.Status.ObservedGeneration).To(Equal(consumer.Generation))\n\n\t\t\t\tBy(\"checking if the consumer was updated\")\n\t\t\t\tnatsStream, err := jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tstreamInfo, err := natsStream.Info(ctx)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(streamInfo.Config.Description).To(Equal(\"new description\"))\n\t\t\t\t// Other fields unchanged\n\t\t\t\tExpect(streamInfo.Config.ReplayPolicy).To(Equal(jetstream.ReplayInstantPolicy))\n\t\t\t})\n\n\t\t\tIt(\"should set InactiveThreshold and priority fields on the server\", func(ctx SpecContext) {\n\t\t\t\tBy(\"updating the consumer spec with new fields\")\n\t\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, consumer)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tconsumer.Spec.InactiveThreshold = \"30s\"\n\t\t\t\tconsumer.Spec.PriorityPolicy = \"pinned_client\"\n\t\t\t\tconsumer.Spec.PinnedTTL = \"5m\"\n\t\t\t\tconsumer.Spec.PriorityGroups = []string{\"high\", \"medium\"}\n\t\t\t\tExpect(k8sClient.Update(ctx, consumer)).To(Succeed())\n\n\t\t\t\tBy(\"reconciling the updated resource\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"fetching the updated consumer from NATS\")\n\t\t\t\tnatsConsumer, err := jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tconsumerInfo, err := natsConsumer.Info(ctx)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"verifying new fields are set on server\")\n\t\t\t\tExpect(consumerInfo.Config.InactiveThreshold).To(Equal(30 * time.Second))\n\t\t\t\tExpect(consumerInfo.Config.PriorityPolicy).To(Equal(jetstream.PriorityPolicyPinned))\n\t\t\t\tExpect(consumerInfo.Config.PinnedTTL).To(Equal(5 * time.Minute))\n\t\t\t\tExpect(consumerInfo.Config.PriorityGroups).To(Equal([]string{\"high\", \"medium\"}))\n\t\t\t})\n\n\t\t\tWhen(\"PreventUpdate is set\", func() {\n\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\tBy(\"setting preventUpdate on the resource\")\n\t\t\t\t\tconsumer.Spec.PreventUpdate = true\n\t\t\t\t\tExpect(k8sClient.Update(ctx, consumer)).To(Succeed())\n\t\t\t\t})\n\t\t\t\tIt(\"should create the consumer\", func(ctx SpecContext) {\n\t\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\tBy(\"checking that consumer was created\")\n\t\t\t\t\t_, err = jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\t\tExpect(err).ToNot(HaveOccurred())\n\t\t\t\t})\n\t\t\t\tIt(\"should not update the consumer\", func(ctx SpecContext) {\n\t\t\t\t\tBy(\"creating the consumer\")\n\t\t\t\t\t_, err := jsClient.CreateConsumer(ctx, streamName, emptyConsumerConfig)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\tBy(\"checking that consumer was not updated\")\n\t\t\t\t\tc, err := jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(c.CachedInfo().Config.Description).To(BeEmpty())\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tWhen(\"read-only mode is enabled\", func() {\n\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\tBy(\"setting read only on the controller\")\n\t\t\t\t\treadOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tcontroller = &ConsumerReconciler{\n\t\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\t\tJetStreamController: readOnly,\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tIt(\"should not create the consumer\", func(ctx SpecContext) {\n\t\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\tBy(\"checking that no consumer was created\")\n\t\t\t\t\t_, err = jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\t\tExpect(err).To(MatchError(jetstream.ErrConsumerNotFound))\n\t\t\t\t})\n\t\t\t\tIt(\"should not update the consumer\", func(ctx SpecContext) {\n\t\t\t\t\tBy(\"creating the consumer\")\n\t\t\t\t\t_, err := jsClient.CreateConsumer(ctx, streamName, emptyConsumerConfig)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\tBy(\"checking that consumer was not updated\")\n\t\t\t\t\ts, err := jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(s.CachedInfo().Config.Description).To(BeEmpty())\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tWhen(\"namespace restriction is enabled\", func() {\n\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\tBy(\"setting a namespace on the resource\")\n\t\t\t\t\tnamespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: alternateNamespace})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tcontroller = &ConsumerReconciler{\n\t\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\t\tJetStreamController: namespaced,\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tIt(\"should not create the consumer\", func(ctx SpecContext) {\n\t\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\tBy(\"checking that no consumer was created\")\n\t\t\t\t\t_, err = jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\t\tExpect(err).To(MatchError(jetstream.ErrConsumerNotFound))\n\t\t\t\t})\n\t\t\t\tIt(\"should not update the consumer\", func(ctx SpecContext) {\n\t\t\t\t\tBy(\"creating the consumer\")\n\t\t\t\t\t_, err := jsClient.CreateConsumer(ctx, streamName, emptyConsumerConfig)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\tBy(\"checking that consumer was not updated\")\n\t\t\t\t\ts, err := jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(s.CachedInfo().Config.Description).To(BeEmpty())\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tWhen(\"the resource is marked for deletion\", func() {\n\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\tBy(\"marking the resource for deletion\")\n\t\t\t\t\tExpect(k8sClient.Delete(ctx, consumer)).To(Succeed())\n\t\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed()) // re-fetch after update\n\t\t\t\t})\n\n\t\t\t\tIt(\"should succeed deleting a not existing consumer\", func(ctx SpecContext) {\n\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, consumer).\n\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t})\n\n\t\t\t\tIt(\"should succeed deleting a consumer of a deleted stream\", func(ctx SpecContext) {\n\t\t\t\t\tBy(\"Setting not existing stream\")\n\t\t\t\t\tconsumer.Spec.StreamName = \"deleted-stream\"\n\t\t\t\t\tExpect(k8sClient.Update(ctx, consumer)).To(Succeed())\n\n\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, consumer).\n\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t})\n\n\t\t\t\tWhen(\"the underlying consumer exists\", func() {\n\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"creating the consumer on the nats server\")\n\t\t\t\t\t\t_, err := jsClient.CreateConsumer(ctx, streamName, emptyConsumerConfig)\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t})\n\n\t\t\t\t\tAfterEach(func(ctx SpecContext) {\n\t\t\t\t\t\terr := jsClient.DeleteConsumer(ctx, streamName, consumerName)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tExpect(err).To(MatchError(jetstream.ErrConsumerNotFound))\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\n\t\t\t\t\tIt(\"should delete the consumer\", func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\tBy(\"checking that the consumer is deleted\")\n\t\t\t\t\t\t_, err = jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\t\t\tExpect(err).To(MatchError(jetstream.ErrConsumerNotFound))\n\n\t\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, consumer).\n\t\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t\t})\n\n\t\t\t\t\tWhen(\"PreventDelete is set\", func() {\n\t\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\t\tBy(\"setting preventDelete on the resource\")\n\t\t\t\t\t\t\tconsumer.Spec.PreventDelete = true\n\t\t\t\t\t\t\tExpect(k8sClient.Update(ctx, consumer)).To(Succeed())\n\t\t\t\t\t\t})\n\t\t\t\t\t\tIt(\"Should delete the resource and not delete the nats consumer\", func(ctx SpecContext) {\n\t\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\t\tBy(\"checking that the consumer is not deleted\")\n\t\t\t\t\t\t\t_, err = jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, consumer).\n\t\t\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\tWhen(\"read only is set\", func() {\n\t\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\t\tBy(\"setting read only on the controller\")\n\t\t\t\t\t\t\treadOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{ReadOnly: true})\n\t\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\t\tcontroller = &ConsumerReconciler{\n\t\t\t\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\t\t\t\tJetStreamController: readOnly,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t\tIt(\"should delete the resource and not delete the consumer\", func(ctx SpecContext) {\n\t\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\t\tBy(\"checking that the consumer is not deleted\")\n\t\t\t\t\t\t\t_, err = jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, consumer).\n\t\t\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\tWhen(\"controller is restricted to different namespace\", func() {\n\t\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\t\tnamespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: testServer.ClientURL()}, &Config{Namespace: alternateNamespace})\n\t\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\t\tcontroller = &ConsumerReconciler{\n\t\t\t\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\t\t\t\tJetStreamController: namespaced,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t\tIt(\"should not delete the resource and consumer\", func(ctx SpecContext) {\n\t\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\t\tBy(\"checking that the consumer is not deleted\")\n\t\t\t\t\t\t\t_, err = jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\t\t\tBy(\"checking that the finalizer is not removed\")\n\t\t\t\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, consumer)).To(Succeed())\n\t\t\t\t\t\t\tExpect(consumer.Finalizers).To(ContainElement(consumerFinalizer))\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\tIt(\"should create consumer on different server as specified in spec\", func(ctx SpecContext) {\n\t\t\t\tBy(\"setting up the alternative server\")\n\t\t\t\taltServer := CreateTestServer()\n\t\t\t\tdefer altServer.Shutdown()\n\n\t\t\t\tconnPool := newConnPool(0)\n\t\t\t\tconn, err := connPool.Get(&NatsConfig{ServerURL: altServer.ClientURL()}, true)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tdomain := \"\"\n\n\t\t\t\t// Setup altClient for alternate server\n\t\t\t\taltClient, err := CreateJetStreamClient(conn, true, domain)\n\t\t\t\tdefer conn.Close()\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"setting up the stream on the alternative server\")\n\t\t\t\t_, err = altClient.CreateStream(ctx, emptyStreamConfig)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"setting the server in the consumer spec\")\n\t\t\t\tconsumer.Spec.Servers = []string{altServer.ClientURL()}\n\t\t\t\tExpect(k8sClient.Update(ctx, consumer)).To(Succeed())\n\n\t\t\t\tBy(\"checking precondition, that the consumer does not yet exist\")\n\t\t\t\t_, err = jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\tExpect(err).To(MatchError(jetstream.ErrConsumerNotFound))\n\n\t\t\t\tBy(\"reconciling the resource\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t\t})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking if the consumer was created on the alternative server\")\n\t\t\t\tgot, err := altClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(got.CachedInfo().Created).To(BeTemporally(\"~\", time.Now(), time.Second))\n\n\t\t\t\tBy(\"checking that the consumer was NOT created on the original server\")\n\t\t\t\t_, err = jsClient.Consumer(ctx, streamName, consumerName)\n\t\t\t\tExpect(err).To(MatchError(jetstream.ErrConsumerNotFound))\n\t\t\t})\n\t\t})\n\t})\n})\n\nfunc Test_consumerSpecToConfig(t *testing.T) {\n\tdate := time.Date(2024, 12, 3, 16, 55, 5, 0, time.UTC)\n\tdateString := date.Format(time.RFC3339)\n\n\ttests := []struct {\n\t\tname    string\n\t\tspec    *api.ConsumerSpec\n\t\twant    *jsmapi.ConsumerConfig\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"empty spec\",\n\t\t\tspec:    &api.ConsumerSpec{},\n\t\t\twant:    &jsmapi.ConsumerConfig{},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"full spec\",\n\t\t\tspec: &api.ConsumerSpec{\n\t\t\t\tAckPolicy:          \"explicit\",\n\t\t\t\tAckWait:            \"10ns\",\n\t\t\t\tBackOff:            []string{\"1s\", \"5m\"},\n\t\t\t\tDeliverGroup:       \"\",\n\t\t\t\tDeliverPolicy:      \"byStartSequence\",\n\t\t\t\tDeliverSubject:     \"\",\n\t\t\t\tDescription:        \"test consumer\",\n\t\t\t\tDurableName:        \"test-consumer\",\n\t\t\t\tFilterSubject:      \"\",\n\t\t\t\tFilterSubjects:     []string{\"time.us.east\", \"time.us.west\"},\n\t\t\t\tFlowControl:        false,\n\t\t\t\tHeadersOnly:        true,\n\t\t\t\tHeartbeatInterval:  \"\",\n\t\t\t\tMaxAckPending:      6,\n\t\t\t\tMaxDeliver:         3,\n\t\t\t\tMaxRequestBatch:    7,\n\t\t\t\tMaxRequestExpires:  \"8s\",\n\t\t\t\tMaxRequestMaxBytes: 1024,\n\t\t\t\tMaxWaiting:         5,\n\t\t\t\tMemStorage:         true,\n\t\t\t\tOptStartSeq:        17,\n\t\t\t\tOptStartTime:       \"\",\n\t\t\t\tRateLimitBps:       512,\n\t\t\t\tReplayPolicy:       \"instant\",\n\t\t\t\tReplicas:           9,\n\t\t\t\tSampleFreq:         \"25%\",\n\t\t\t\tStreamName:         \"\",\n\t\t\t\tInactiveThreshold:  \"30s\",\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"meta\": \"data\",\n\t\t\t\t},\n\t\t\t\tBaseStreamConfig: api.BaseStreamConfig{\n\t\t\t\t\tPreventDelete: false,\n\t\t\t\t\tPreventUpdate: false,\n\t\t\t\t\tConnectionOpts: api.ConnectionOpts{\n\t\t\t\t\t\tAccount: \"\",\n\t\t\t\t\t\tCreds:   \"\",\n\t\t\t\t\t\tNkey:    \"\",\n\t\t\t\t\t\tTLS:     &api.TLS{},\n\t\t\t\t\t\tServers: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: &jsmapi.ConsumerConfig{\n\t\t\t\tDurable:            \"test-consumer\",\n\t\t\t\tDescription:        \"test consumer\",\n\t\t\t\tDeliverPolicy:      jsmapi.DeliverByStartSequence,\n\t\t\t\tOptStartSeq:        17,\n\t\t\t\tAckPolicy:          jsmapi.AckExplicit,\n\t\t\t\tAckWait:            10 * time.Nanosecond,\n\t\t\t\tMaxDeliver:         3,\n\t\t\t\tBackOff:            []time.Duration{time.Second, 5 * time.Minute},\n\t\t\t\tFilterSubject:      \"\",\n\t\t\t\tReplayPolicy:       jsmapi.ReplayInstant,\n\t\t\t\tRateLimit:          512,\n\t\t\t\tSampleFrequency:    \"25%\",\n\t\t\t\tMaxWaiting:         5,\n\t\t\t\tMaxAckPending:      6,\n\t\t\t\tHeadersOnly:        true,\n\t\t\t\tMaxRequestBatch:    7,\n\t\t\t\tMaxRequestExpires:  8 * time.Second,\n\t\t\t\tMaxRequestMaxBytes: 1024,\n\t\t\t\tInactiveThreshold:  30 * time.Second,\n\t\t\t\tReplicas:           9,\n\t\t\t\tMemoryStorage:      true,\n\t\t\t\tFilterSubjects:     []string{\"time.us.east\", \"time.us.west\"},\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"meta\": \"data\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"full spec alt\",\n\t\t\tspec: &api.ConsumerSpec{\n\t\t\t\tAckPolicy:          \"all\",\n\t\t\t\tAckWait:            \"20ns\",\n\t\t\t\tBackOff:            []string{\"1s\", \"5m\"},\n\t\t\t\tDeliverGroup:       \"\",\n\t\t\t\tDeliverPolicy:      \"byStartTime\",\n\t\t\t\tDeliverSubject:     \"\",\n\t\t\t\tDescription:        \"test consumer\",\n\t\t\t\tDurableName:        \"test-consumer\",\n\t\t\t\tFilterSubject:      \"time.us.>\",\n\t\t\t\tFlowControl:        true,\n\t\t\t\tHeadersOnly:        false,\n\t\t\t\tHeartbeatInterval:  \"\",\n\t\t\t\tMaxAckPending:      5,\n\t\t\t\tMaxDeliver:         6,\n\t\t\t\tMaxRequestBatch:    7,\n\t\t\t\tMaxRequestExpires:  \"8s\",\n\t\t\t\tMaxRequestMaxBytes: 1024,\n\t\t\t\tMaxWaiting:         5,\n\t\t\t\tMemStorage:         false,\n\t\t\t\tOptStartSeq:        17,\n\t\t\t\tOptStartTime:       dateString,\n\t\t\t\tRateLimitBps:       1024,\n\t\t\t\tReplayPolicy:       \"original\",\n\t\t\t\tReplicas:           9,\n\t\t\t\tSampleFreq:         \"30%\",\n\t\t\t\tStreamName:         \"\",\n\t\t\t\tInactiveThreshold:  \"1m\",\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"meta\": \"data\",\n\t\t\t\t},\n\t\t\t\tBaseStreamConfig: api.BaseStreamConfig{\n\t\t\t\t\tPreventDelete: false,\n\t\t\t\t\tPreventUpdate: false,\n\t\t\t\t\tConnectionOpts: api.ConnectionOpts{\n\t\t\t\t\t\tAccount: \"\",\n\t\t\t\t\t\tCreds:   \"\",\n\t\t\t\t\t\tNkey:    \"\",\n\t\t\t\t\t\tTLS:     &api.TLS{},\n\t\t\t\t\t\tServers: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: &jsmapi.ConsumerConfig{\n\t\t\t\tDurable:            \"test-consumer\",\n\t\t\t\tDescription:        \"test consumer\",\n\t\t\t\tDeliverPolicy:      jsmapi.DeliverByStartTime,\n\t\t\t\tOptStartSeq:        0,\n\t\t\t\tOptStartTime:       &date,\n\t\t\t\tAckPolicy:          jsmapi.AckAll,\n\t\t\t\tAckWait:            20 * time.Nanosecond,\n\t\t\t\tMaxDeliver:         6,\n\t\t\t\tBackOff:            []time.Duration{time.Second, 5 * time.Minute},\n\t\t\t\tFlowControl:        true,\n\t\t\t\tFilterSubject:      \"time.us.>\",\n\t\t\t\tReplayPolicy:       jsmapi.ReplayOriginal,\n\t\t\t\tRateLimit:          1024,\n\t\t\t\tSampleFrequency:    \"30%\",\n\t\t\t\tMaxWaiting:         5,\n\t\t\t\tMaxAckPending:      5,\n\t\t\t\tHeadersOnly:        false,\n\t\t\t\tMaxRequestBatch:    7,\n\t\t\t\tMaxRequestExpires:  8 * time.Second,\n\t\t\t\tMaxRequestMaxBytes: 1024,\n\t\t\t\tInactiveThreshold:  time.Minute,\n\t\t\t\tReplicas:           9,\n\t\t\t\tMemoryStorage:      false,\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"meta\": \"data\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"deliver policy lastPerSubject\",\n\t\t\tspec: &api.ConsumerSpec{\n\t\t\t\tDurableName:   \"test-consumer\",\n\t\t\t\tDeliverPolicy: \"lastPerSubject\",\n\t\t\t},\n\t\t\twant: &jsmapi.ConsumerConfig{\n\t\t\t\tDurable:       \"test-consumer\",\n\t\t\t\tDeliverPolicy: jsmapi.DeliverLastPerSubject,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"inactive threshold valid duration\",\n\t\t\tspec: &api.ConsumerSpec{\n\t\t\t\tDurableName:       \"test-consumer\",\n\t\t\t\tInactiveThreshold: \"2h30m\",\n\t\t\t},\n\t\t\twant: &jsmapi.ConsumerConfig{\n\t\t\t\tDurable:           \"test-consumer\",\n\t\t\t\tInactiveThreshold: 2*time.Hour + 30*time.Minute,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"inactive threshold empty string\",\n\t\t\tspec: &api.ConsumerSpec{\n\t\t\t\tDurableName:       \"test-consumer\",\n\t\t\t\tInactiveThreshold: \"\",\n\t\t\t},\n\t\t\twant: &jsmapi.ConsumerConfig{\n\t\t\t\tDurable:           \"test-consumer\",\n\t\t\t\tInactiveThreshold: 0,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"inactive threshold invalid duration\",\n\t\t\tspec: &api.ConsumerSpec{\n\t\t\t\tDurableName:       \"test-consumer\",\n\t\t\t\tInactiveThreshold: \"not-a-duration\",\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"priority policy pinned_client with ttl\",\n\t\t\tspec: &api.ConsumerSpec{\n\t\t\t\tDurableName:    \"test-consumer\",\n\t\t\t\tPriorityPolicy: \"pinned_client\",\n\t\t\t\tPinnedTTL:      \"10m\",\n\t\t\t\tPriorityGroups: []string{\"gold\", \"silver\"},\n\t\t\t},\n\t\t\twant: &jsmapi.ConsumerConfig{\n\t\t\t\tDurable:        \"test-consumer\",\n\t\t\t\tPriorityPolicy: jsmapi.PriorityPinnedClient,\n\t\t\t\tPinnedTTL:      10 * time.Minute,\n\t\t\t\tPriorityGroups: []string{\"gold\", \"silver\"},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"priority policy overflow\",\n\t\t\tspec: &api.ConsumerSpec{\n\t\t\t\tDurableName:    \"test-consumer\",\n\t\t\t\tPriorityPolicy: \"overflow\",\n\t\t\t\tPriorityGroups: []string{\"backup1\", \"backup2\"},\n\t\t\t},\n\t\t\twant: &jsmapi.ConsumerConfig{\n\t\t\t\tDurable:        \"test-consumer\",\n\t\t\t\tPriorityPolicy: jsmapi.PriorityOverflow,\n\t\t\t\tPriorityGroups: []string{\"backup1\", \"backup2\"},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"priority policy prioritized\",\n\t\t\tspec: &api.ConsumerSpec{\n\t\t\t\tDurableName:    \"test-consumer\",\n\t\t\t\tPriorityPolicy: \"prioritized\",\n\t\t\t\tPriorityGroups: []string{\"level1\", \"level2\", \"level3\"},\n\t\t\t},\n\t\t\twant: &jsmapi.ConsumerConfig{\n\t\t\t\tDurable:        \"test-consumer\",\n\t\t\t\tPriorityPolicy: jsmapi.PriorityPrioritized,\n\t\t\t\tPriorityGroups: []string{\"level1\", \"level2\", \"level3\"},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"priority policy none\",\n\t\t\tspec: &api.ConsumerSpec{\n\t\t\t\tDurableName:    \"test-consumer\",\n\t\t\t\tPriorityPolicy: \"none\",\n\t\t\t},\n\t\t\twant: &jsmapi.ConsumerConfig{\n\t\t\t\tDurable: \"test-consumer\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"priority policy invalid\",\n\t\t\tspec: &api.ConsumerSpec{\n\t\t\t\tDurableName:    \"test-consumer\",\n\t\t\t\tPriorityPolicy: \"invalid_policy\",\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"priority policy pinned_client invalid ttl\",\n\t\t\tspec: &api.ConsumerSpec{\n\t\t\t\tDurableName:    \"test-consumer\",\n\t\t\t\tPriorityPolicy: \"pinned_client\",\n\t\t\t\tPinnedTTL:      \"not-a-duration\",\n\t\t\t\tPriorityGroups: []string{\"gold\"},\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcOpts, err := consumerSpecToConfig(tt.spec)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"consumerSpecToConfig() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !tt.wantErr {\n\t\t\t\tgot := &jsmapi.ConsumerConfig{}\n\t\t\t\tfor _, o := range cOpts {\n\t\t\t\t\to(got)\n\t\t\t\t}\n\n\t\t\t\tassert.EqualValues(t, tt.want, got, \"consumerSpecToConfig(%v)\", tt.spec)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/controller/helpers_test.go",
    "content": "package controller\n\nimport (\n\t\"os\"\n\t\"time\"\n\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\t\"github.com/nats-io/nats-server/v2/server\"\n\tnatsserver \"github.com/nats-io/nats-server/v2/test\"\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\nfunc assertReadyStateMatches(condition api.Condition, status v1.ConditionStatus, reason string, message string, transitionTime time.Time) {\n\tGinkgoHelper()\n\n\tExpect(condition.Type).To(Equal(readyCondType))\n\tExpect(condition.Status).To(Equal(status))\n\tExpect(condition.Reason).To(Equal(reason))\n\tExpect(condition.Message).To(ContainSubstring(message))\n\n\t// Assert valid transition time\n\tt, err := time.Parse(time.RFC3339Nano, condition.LastTransitionTime)\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(t).To(BeTemporally(\"~\", transitionTime, time.Second))\n}\n\nfunc CreateTestServer() *server.Server {\n\topts := &natsserver.DefaultTestOptions\n\topts.JetStream = true\n\topts.Port = -1\n\topts.Debug = true\n\n\tdir, err := os.MkdirTemp(\"\", \"nats-*\")\n\tExpect(err).NotTo(HaveOccurred())\n\topts.StoreDir = dir\n\n\tns := natsserver.RunServer(opts)\n\tExpect(err).NotTo(HaveOccurred())\n\n\treturn ns\n}\n"
  },
  {
    "path": "internal/controller/jetstream_controller.go",
    "content": "package controller\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand/v2\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/nats-io/jsm.go\"\n\tjs \"github.com/nats-io/nack/controllers/jetstream\"\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\t\"github.com/nats-io/nats.go/jetstream\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n)\n\nconst (\n\tJSConsumerNotFoundErr uint16 = 10014\n\tJSStreamNotFoundErr   uint16 = 10059\n)\n\nvar semVerRe = regexp.MustCompile(`\\Av?([0-9]+)\\.?([0-9]+)?\\.?([0-9]+)?`)\n\ntype JetStreamController interface {\n\tclient.Client\n\n\t// ReadOnly returns true when no changes should be made by the controller.\n\tReadOnly() bool\n\n\t// ValidNamespace ok if the controllers namespace restriction allows the given namespace.\n\tValidNamespace(namespace string) bool\n\n\t// WithJetStreamClient provides a jetStream client to the given operation.\n\t// The client uses the controllers connection configuration merged with opts.\n\t//\n\t// The given opts values take precedence over the controllers base configuration.\n\t//\n\t// Returns the error of the operation or errors during client setup.\n\tWithJetStreamClient(opts api.ConnectionOpts, ns string, op func(js jetstream.JetStream) error) error\n\n\t// WithJSMClient provides a jsm.go client to the given operation.\n\tWithJSMClient(opts api.ConnectionOpts, ns string, op func(jsm *jsm.Manager) error) error\n\n\tRequeueInterval() time.Duration\n}\n\nfunc NewJSController(k8sClient client.Client, natsConfig *NatsConfig, controllerConfig *Config) (JetStreamController, error) {\n\treturn &jsController{\n\t\tClient:           k8sClient,\n\t\tconfig:           natsConfig,\n\t\tcontrollerConfig: controllerConfig,\n\t\tcacheDir:         controllerConfig.CacheDir,\n\t\tconnPool:         newConnPool(time.Second * 15),\n\t}, nil\n}\n\ntype jsController struct {\n\tclient.Client\n\tconfig           *NatsConfig\n\tcontrollerConfig *Config\n\tcacheDir         string\n\tcacheLock        sync.Mutex\n\tconnPool         *connectionPool\n}\n\nfunc (c *jsController) RequeueInterval() time.Duration {\n\t// Stagger the requeue slightly\n\tinterval := c.controllerConfig.RequeueInterval\n\n\t// Allow up to a 10% variance\n\tintervalRange := float64(interval.Nanoseconds()) * 0.1\n\n\trandomFactor := (rand.Float64() * 2) - 1.0\n\n\treturn time.Duration(float64(interval.Nanoseconds()) + (intervalRange * randomFactor))\n}\n\nfunc (c *jsController) ReadOnly() bool {\n\treturn c.controllerConfig.ReadOnly\n}\n\nfunc (c *jsController) ValidNamespace(namespace string) bool {\n\tns := c.controllerConfig.Namespace\n\treturn ns == \"\" || ns == namespace\n}\n\nfunc (c *jsController) WithJSMClient(opts api.ConnectionOpts, ns string, op func(js *jsm.Manager) error) error {\n\tcfg, err := c.natsConfigFromOpts(opts, ns)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tconn, err := c.connPool.Get(cfg, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tjsmClient, err := CreateJSMClient(conn, true, cfg.JsDomain)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create jsm client: %w\", err)\n\t}\n\tdefer conn.Close()\n\n\treturn op(jsmClient)\n}\n\nfunc (c *jsController) WithJetStreamClient(opts api.ConnectionOpts, ns string, op func(js jetstream.JetStream) error) error {\n\tcfg, err := c.natsConfigFromOpts(opts, ns)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tconn, err := c.connPool.Get(cfg, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tjsClient, err := CreateJetStreamClient(conn, true, cfg.JsDomain)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create jetstream client: %w\", err)\n\t}\n\tdefer conn.Close()\n\n\treturn op(jsClient)\n}\n\n// Setup default options, override from account resource and CRD options if configured\nfunc (c *jsController) natsConfigFromOpts(opts api.ConnectionOpts, ns string) (*NatsConfig, error) {\n\tctx, done := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer done()\n\n\tnatsConfig := &NatsConfig{}\n\tnatsConfig.Overlay(c.config)\n\n\tif opts.Account == \"\" {\n\t\tnatsConfig.Overlay(natsConfigFromOpts(opts))\n\t\treturn natsConfig, nil\n\t}\n\n\t// Apply Account options first, over global.\n\t// Apply remaining CRD options last\n\n\taccountOverlay := &NatsConfig{}\n\n\taccount := &api.Account{}\n\terr := c.Get(ctx,\n\t\ttypes.NamespacedName{\n\t\t\tName:      opts.Account,\n\t\t\tNamespace: ns,\n\t\t},\n\t\taccount,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(account.Spec.Servers) > 0 {\n\t\taccountOverlay.ServerURL = strings.Join(account.Spec.Servers, \",\")\n\t}\n\n\tc.cacheLock.Lock()\n\tdefer c.cacheLock.Unlock()\n\n\tif account.Spec.TLS != nil && account.Spec.TLS.Secret != nil {\n\t\ttlsSecret := &v1.Secret{}\n\t\terr := c.Get(ctx,\n\t\t\ttypes.NamespacedName{\n\t\t\t\tName:      account.Spec.TLS.Secret.Name,\n\t\t\t\tNamespace: ns,\n\t\t\t},\n\t\t\ttlsSecret,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\taccDir := filepath.Join(c.cacheDir, ns, opts.Account)\n\t\tif err := os.MkdirAll(accDir, 0o755); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar certData, keyData []byte\n\t\tvar certPath, keyPath string\n\n\t\tfor k, v := range tlsSecret.Data {\n\t\t\tswitch k {\n\t\t\tcase account.Spec.TLS.ClientCert:\n\t\t\t\tcertPath = filepath.Join(accDir, k)\n\t\t\t\tcertData = v\n\t\t\tcase account.Spec.TLS.ClientKey:\n\t\t\t\tkeyPath = filepath.Join(accDir, k)\n\t\t\t\tkeyData = v\n\t\t\tcase account.Spec.TLS.RootCAs:\n\t\t\t\trootCAPath := filepath.Join(accDir, k)\n\t\t\t\taccountOverlay.CAs = append(accountOverlay.CAs, rootCAPath)\n\n\t\t\t\tif _, err := os.Stat(rootCAPath); err == nil {\n\t\t\t\t\tcaBytes, err := os.ReadFile(rootCAPath)\n\t\t\t\t\t// Skip file write if data is unchanged\n\t\t\t\t\tif err == nil && bytes.Equal(caBytes, v) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif err := os.WriteFile(rootCAPath, v, 0o644); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif certData != nil && keyData != nil {\n\t\t\taccountOverlay.Certificate = certPath\n\t\t\taccountOverlay.Key = keyPath\n\n\t\t\twriteCert := true\n\t\t\tif _, err := os.Stat(certPath); err == nil {\n\t\t\t\tfileBytes, err := os.ReadFile(certPath)\n\t\t\t\t// Skip disk write if data is unchanged\n\t\t\t\tif err == nil && bytes.Equal(fileBytes, certData) {\n\t\t\t\t\twriteCert = false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif writeCert {\n\t\t\t\tif err := os.WriteFile(certPath, certData, 0o644); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\twriteKey := true\n\t\t\tif _, err := os.Stat(keyPath); err == nil {\n\t\t\t\tfileBytes, err := os.ReadFile(keyPath)\n\t\t\t\t// Skip disk write if data is unchanged\n\t\t\t\tif err == nil && bytes.Equal(fileBytes, keyData) {\n\t\t\t\t\twriteKey = false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif writeKey {\n\t\t\t\tif err := os.WriteFile(keyPath, keyData, 0o600); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if account.Spec.TLS != nil {\n\t\tif account.Spec.TLS.ClientCert != \"\" && account.Spec.TLS.ClientKey != \"\" {\n\t\t\taccountOverlay.Certificate = account.Spec.TLS.ClientCert\n\t\t\taccountOverlay.Key = account.Spec.TLS.ClientKey\n\t\t}\n\t\taccountOverlay.CAs = []string{account.Spec.TLS.RootCAs}\n\t}\n\n\tif account.Spec.Creds != nil && account.Spec.Creds.Secret != nil {\n\t\tcredsSecret := &v1.Secret{}\n\t\terr := c.Get(ctx,\n\t\t\ttypes.NamespacedName{\n\t\t\t\tName:      account.Spec.Creds.Secret.Name,\n\t\t\t\tNamespace: ns,\n\t\t\t},\n\t\t\tcredsSecret,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\taccDir := filepath.Join(c.cacheDir, ns, opts.Account)\n\t\tif err := os.MkdirAll(accDir, 0o755); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif credsBytes, ok := credsSecret.Data[account.Spec.Creds.File]; ok {\n\t\t\tfilePath := filepath.Join(accDir, account.Spec.Creds.File)\n\t\t\taccountOverlay.Credentials = filePath\n\n\t\t\twriteCreds := true\n\t\t\tif _, err := os.Stat(filePath); err == nil {\n\t\t\t\tfileBytes, err := os.ReadFile(filePath)\n\t\t\t\t// Skip disk write if data is unchanged\n\t\t\t\tif err == nil && bytes.Equal(fileBytes, credsBytes) {\n\t\t\t\t\twriteCreds = false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif writeCreds {\n\t\t\t\tif err := os.WriteFile(filePath, credsBytes, 0o600); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if account.Spec.Creds != nil {\n\t\taccountOverlay.Credentials = account.Spec.Creds.File\n\t}\n\n\tif account.Spec.NKey != nil && account.Spec.NKey.Secret != nil {\n\t\tnkeySecret := &v1.Secret{}\n\t\terr := c.Get(ctx,\n\t\t\ttypes.NamespacedName{\n\t\t\t\tName:      account.Spec.NKey.Secret.Name,\n\t\t\t\tNamespace: ns,\n\t\t\t},\n\t\t\tnkeySecret,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\taccDir := filepath.Join(c.cacheDir, ns, opts.Account)\n\t\tif err := os.MkdirAll(accDir, 0o755); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif nkeyBytes, ok := nkeySecret.Data[account.Spec.NKey.Seed]; ok {\n\t\t\tfilePath := filepath.Join(accDir, account.Spec.NKey.Seed)\n\t\t\taccountOverlay.NKey = filePath\n\n\t\t\twriteNKey := true\n\t\t\tif _, err := os.Stat(filePath); err == nil {\n\t\t\t\tfileBytes, err := os.ReadFile(filePath)\n\t\t\t\tif err == nil && bytes.Equal(fileBytes, nkeyBytes) {\n\t\t\t\t\twriteNKey = false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif writeNKey {\n\t\t\t\tif err := os.WriteFile(filePath, nkeyBytes, 0o600); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif account.Spec.User != nil {\n\t\tuserSecret := &v1.Secret{}\n\t\terr := c.Get(ctx,\n\t\t\ttypes.NamespacedName{\n\t\t\t\tName:      account.Spec.User.Secret.Name,\n\t\t\t\tNamespace: ns,\n\t\t\t},\n\t\t\tuserSecret,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tuserName := userSecret.Data[account.Spec.User.User]\n\t\tuserPassword := userSecret.Data[account.Spec.User.Password]\n\n\t\tif userName != nil && userPassword != nil {\n\t\t\taccountOverlay.User = string(userName)\n\t\t\taccountOverlay.Password = string(userPassword)\n\t\t}\n\t}\n\n\tif account.Spec.Token != nil {\n\t\ttokenSecret := &v1.Secret{}\n\t\terr := c.Get(ctx,\n\t\t\ttypes.NamespacedName{\n\t\t\t\tName:      account.Spec.Token.Secret.Name,\n\t\t\t\tNamespace: ns,\n\t\t\t},\n\t\t\ttokenSecret,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif token := tokenSecret.Data[account.Spec.Token.Token]; token != nil {\n\t\t\taccountOverlay.Token = string(token)\n\t\t}\n\t}\n\n\t// Overlay Account Config\n\tnatsConfig.Overlay(accountOverlay)\n\n\t// Overlay Spec Config\n\tnatsConfig.Overlay(natsConfigFromOpts(opts))\n\n\treturn natsConfig, nil\n}\n\nfunc natsConfigFromOpts(opts api.ConnectionOpts) *NatsConfig {\n\tnatsConfig := &NatsConfig{}\n\n\tif len(opts.Servers) > 0 {\n\t\tnatsConfig.ServerURL = strings.Join(opts.Servers, \",\")\n\t}\n\n\t// Currently, if the global TLSFirst is set, a false value in the CRD will not override\n\t// due to that being the bool zero value. A true value in the CRD can override a global false.\n\tif opts.TLSFirst {\n\t\tnatsConfig.TLSFirst = opts.TLSFirst\n\t}\n\n\tif opts.Creds != \"\" {\n\t\tnatsConfig.Credentials = opts.Creds\n\t}\n\n\tif opts.Nkey != \"\" {\n\t\tnatsConfig.NKey = opts.Nkey\n\t}\n\n\tif opts.TLS != nil {\n\t\tif len(opts.TLS.RootCAs) > 0 {\n\t\t\tnatsConfig.CAs = opts.TLS.RootCAs\n\t\t}\n\n\t\tif opts.TLS.ClientCert != \"\" && opts.TLS.ClientKey != \"\" {\n\t\t\tnatsConfig.Certificate = opts.TLS.ClientCert\n\t\t\tnatsConfig.Key = opts.TLS.ClientKey\n\t\t}\n\t}\n\n\tif opts.JsDomain != \"\" {\n\t\tnatsConfig.JsDomain = opts.JsDomain\n\t}\n\n\treturn natsConfig\n}\n\n// updateReadyCondition returns the given conditions with an added or updated ready condition.\nfunc updateReadyCondition(conditions []api.Condition, status v1.ConditionStatus, reason string, message string) []api.Condition {\n\tvar currentStatus v1.ConditionStatus\n\tvar lastTransitionTime string\n\tfor _, condition := range conditions {\n\t\tif condition.Type == readyCondType {\n\t\t\tcurrentStatus = condition.Status\n\t\t\tlastTransitionTime = condition.LastTransitionTime\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// Set transition time to now, when no previous ready condition or the status changed\n\tif lastTransitionTime == \"\" || currentStatus != status {\n\t\tlastTransitionTime = time.Now().UTC().Format(time.RFC3339Nano)\n\t}\n\n\tnewCondition := api.Condition{\n\t\tType:               readyCondType,\n\t\tStatus:             status,\n\t\tReason:             reason,\n\t\tMessage:            message,\n\t\tLastTransitionTime: lastTransitionTime,\n\t}\n\tif conditions == nil {\n\t\treturn []api.Condition{newCondition}\n\t} else {\n\t\treturn js.UpsertCondition(conditions, newCondition)\n\t}\n}\n\n// jsonString returns the given string wrapped in \" and converted to []byte.\n// Helper for mapping spec config to jetStream config using UnmarshalJSON.\nfunc jsonString(v string) []byte {\n\treturn []byte(\"\\\"\" + v + \"\\\"\")\n}\n\nfunc compareConfigState(actual any, desired any) string {\n\treturn cmp.Diff(desired, actual)\n}\n\nfunc versionComponents(version string) (major, minor, patch int, err error) {\n\tm := semVerRe.FindStringSubmatch(version)\n\tif m == nil {\n\t\treturn 0, 0, 0, errors.New(\"invalid semver\")\n\t}\n\tmajor, err = strconv.Atoi(m[1])\n\tif err != nil {\n\t\treturn -1, -1, -1, err\n\t}\n\tminor, err = strconv.Atoi(m[2])\n\tif err != nil {\n\t\treturn -1, -1, -1, err\n\t}\n\tpatch, err = strconv.Atoi(m[3])\n\tif err != nil {\n\t\treturn -1, -1, -1, err\n\t}\n\treturn major, minor, patch, err\n}\n"
  },
  {
    "path": "internal/controller/jetstream_controller_test.go",
    "content": "package controller\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\t\"github.com/stretchr/testify/assert\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\nfunc Test_updateReadyCondition(t *testing.T) {\n\tpastTransition := time.Now().UTC().Add(-time.Hour).Format(time.RFC3339Nano)\n\tupdatedTransition := \"now\"\n\n\totherCondition := api.Condition{\n\t\tType:               \"other\",\n\t\tStatus:             v1.ConditionFalse,\n\t\tReason:             \"Reason\",\n\t\tMessage:            \"Message\",\n\t\tLastTransitionTime: pastTransition,\n\t}\n\n\ttype args struct {\n\t\tconditions []api.Condition\n\t\tstatus     v1.ConditionStatus\n\t\treason     string\n\t\tmessage    string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant []api.Condition\n\t}{\n\t\t{\n\t\t\tname: \"new ready condition\",\n\t\t\targs: args{\n\t\t\t\tconditions: nil,\n\t\t\t\tstatus:     v1.ConditionTrue,\n\t\t\t\treason:     \"Test\",\n\t\t\t\tmessage:    \"Test Message\",\n\t\t\t},\n\t\t\twant: []api.Condition{\n\t\t\t\t{\n\t\t\t\t\tType:               readyCondType,\n\t\t\t\t\tStatus:             v1.ConditionTrue,\n\t\t\t\t\tReason:             \"Test\",\n\t\t\t\t\tMessage:            \"Test Message\",\n\t\t\t\t\tLastTransitionTime: updatedTransition,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"update ready condition\",\n\t\t\targs: args{\n\t\t\t\tconditions: []api.Condition{\n\t\t\t\t\totherCondition,\n\t\t\t\t\t{\n\t\t\t\t\t\tType:               readyCondType,\n\t\t\t\t\t\tStatus:             v1.ConditionFalse,\n\t\t\t\t\t\tReason:             \"Test\",\n\t\t\t\t\t\tMessage:            \"Test Message\",\n\t\t\t\t\t\tLastTransitionTime: pastTransition,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tstatus:  v1.ConditionTrue,\n\t\t\t\treason:  \"New Reason\",\n\t\t\t\tmessage: \"New Message\",\n\t\t\t},\n\t\t\twant: []api.Condition{\n\t\t\t\totherCondition,\n\t\t\t\t{\n\t\t\t\t\tType:               readyCondType,\n\t\t\t\t\tStatus:             v1.ConditionTrue,\n\t\t\t\t\tReason:             \"New Reason\",\n\t\t\t\t\tMessage:            \"New Message\",\n\t\t\t\t\tLastTransitionTime: updatedTransition,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"should not update transition time when status is not changed\",\n\t\t\targs: args{\n\t\t\t\tconditions: []api.Condition{\n\t\t\t\t\totherCondition,\n\t\t\t\t\t{\n\t\t\t\t\t\tType:               readyCondType,\n\t\t\t\t\t\tStatus:             v1.ConditionTrue,\n\t\t\t\t\t\tReason:             \"Test\",\n\t\t\t\t\t\tMessage:            \"Test Message\",\n\t\t\t\t\t\tLastTransitionTime: pastTransition,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tstatus:  v1.ConditionTrue,\n\t\t\t\treason:  \"New Reason\",\n\t\t\t\tmessage: \"New Message\",\n\t\t\t},\n\t\t\twant: []api.Condition{\n\t\t\t\totherCondition,\n\t\t\t\t{\n\t\t\t\t\tType:               readyCondType,\n\t\t\t\t\tStatus:             v1.ConditionTrue,\n\t\t\t\t\tReason:             \"New Reason\",\n\t\t\t\t\tMessage:            \"New Message\",\n\t\t\t\t\tLastTransitionTime: pastTransition,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert := assert.New(t)\n\n\t\t\tgot := updateReadyCondition(tt.args.conditions, tt.args.status, tt.args.reason, tt.args.message)\n\n\t\t\tassert.Len(got, len(tt.want))\n\t\t\tfor i, want := range tt.want {\n\t\t\t\tactual := got[i]\n\n\t\t\t\tassert.Equal(actual.Type, want.Type)\n\t\t\t\tassert.Equal(actual.Status, want.Status)\n\t\t\t\tassert.Equal(actual.Reason, want.Reason)\n\t\t\t\tassert.Equal(actual.Message, want.Message)\n\n\t\t\t\t// Assert transition time was updated\n\t\t\t\tif want.LastTransitionTime == updatedTransition {\n\t\t\t\t\tactualTransitionTime, err := time.Parse(time.RFC3339Nano, actual.LastTransitionTime)\n\t\t\t\t\tassert.NoError(err)\n\t\t\t\t\tassert.WithinDuration(actualTransitionTime, time.Now(), 5*time.Second)\n\t\t\t\t}\n\t\t\t\t// Assert transition time was not updated\n\t\t\t\tif want.LastTransitionTime == pastTransition {\n\t\t\t\t\tassert.Equal(pastTransition, actual.LastTransitionTime)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/controller/keyvalue_controller.go",
    "content": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS 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\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\t\"github.com/nats-io/nats.go/jetstream\"\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/klog/v2\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/predicate\"\n)\n\nconst (\n\tkvStreamPrefix = \"KV_\"\n)\n\n// KeyValueReconciler reconciles a KeyValue object\ntype KeyValueReconciler struct {\n\tScheme *runtime.Scheme\n\tJetStreamController\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//\n// It performs three main operations:\n// - Initialize finalizer and ready condition if not present\n// - Delete KeyValue if it is marked for deletion.\n// - Create or Update the KeyValue\n//\n// A call to reconcile may perform only one action, expecting the reconciliation to be triggered again by an update.\n// For example: Setting the finalizer triggers a second reconciliation. Reconcile returns after setting the finalizer,\n// to prevent parallel reconciliations performing the same steps.\nfunc (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := klog.FromContext(ctx)\n\n\tif ok := r.ValidNamespace(req.Namespace); !ok {\n\t\tlog.Info(\"Controller restricted to namespace, skipping reconciliation.\")\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Fetch KeyValue resource\n\tkeyValue := &api.KeyValue{}\n\tif err := r.Get(ctx, req.NamespacedName, keyValue); err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\tlog.Info(\"KeyValue resource deleted.\", \"keyValueName\", req.NamespacedName.String())\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\treturn ctrl.Result{}, fmt.Errorf(\"get keyvalue resource '%s': %w\", req.NamespacedName.String(), err)\n\t}\n\n\tlog = log.WithValues(\"keyValueName\", keyValue.Spec.Bucket)\n\n\t// Update ready status to unknown when no status is set\n\tif len(keyValue.Status.Conditions) == 0 {\n\t\tlog.Info(\"Setting initial ready condition to unknown.\")\n\t\tkeyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionUnknown, stateReconciling, \"Starting reconciliation\")\n\t\terr := r.Status().Update(ctx, keyValue)\n\t\tif err != nil {\n\t\t\t// If we get a conflict error, another reconciliation has already updated the status.\n\t\t\t// Just requeue and let the next reconciliation handle it.\n\t\t\tif apierrors.IsConflict(err) {\n\t\t\t\treturn ctrl.Result{Requeue: true}, nil\n\t\t\t}\n\t\t\treturn ctrl.Result{}, fmt.Errorf(\"set condition unknown: %w\", err)\n\t\t}\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// Check Deletion\n\tmarkedForDeletion := keyValue.GetDeletionTimestamp() != nil\n\tif markedForDeletion {\n\t\tif controllerutil.ContainsFinalizer(keyValue, keyValueFinalizer) {\n\t\t\terr := r.deleteKeyValue(ctx, log, keyValue)\n\t\t\tif err != nil {\n\t\t\t\treturn ctrl.Result{}, fmt.Errorf(\"delete keyvalue: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Info(\"KeyValue marked for deletion and already finalized. Ignoring.\")\n\t\t}\n\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Add finalizer\n\tif !controllerutil.ContainsFinalizer(keyValue, keyValueFinalizer) {\n\t\tlog.Info(\"Adding KeyValue finalizer.\")\n\t\tif ok := controllerutil.AddFinalizer(keyValue, keyValueFinalizer); !ok {\n\t\t\treturn ctrl.Result{}, errors.New(\"failed to add finalizer to keyvalue resource\")\n\t\t}\n\n\t\tif err := r.Update(ctx, keyValue); err != nil {\n\t\t\treturn ctrl.Result{}, fmt.Errorf(\"update keyvalue resource to add finalizer: %w\", err)\n\t\t}\n\t\t// After we have added the finalizer, we need to requeue to make sure we reconcile the\n\t\t// rest of the object. Just updating metadata won't make the API server update generation\n\t\t// so the update above shouldn't trigger a new reconciliation.\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// Create or update KeyValue\n\tif err := r.createOrUpdate(ctx, log, keyValue); err != nil {\n\t\treturn ctrl.Result{}, fmt.Errorf(\"create or update: %s\", err)\n\t}\n\n\treturn ctrl.Result{RequeueAfter: r.RequeueInterval()}, nil\n}\n\nfunc (r *KeyValueReconciler) deleteKeyValue(ctx context.Context, log logr.Logger, keyValue *api.KeyValue) error {\n\t// Set status to false\n\tkeyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionFalse, stateFinalizing, \"Performing finalizer operations.\")\n\tif err := r.Status().Update(ctx, keyValue); err != nil {\n\t\treturn fmt.Errorf(\"update ready condition: %w\", err)\n\t}\n\n\tstoredState, err := getStoredKeyValueState(keyValue)\n\tif err != nil {\n\t\tlog.Error(err, \"Failed to fetch stored state.\")\n\t}\n\n\tif !keyValue.Spec.PreventDelete && !r.ReadOnly() {\n\t\tlog.Info(\"Deleting KeyValue.\")\n\t\terr := r.WithJetStreamClient(keyValue.Spec.ConnectionOpts, keyValue.Namespace, func(js jetstream.JetStream) error {\n\t\t\t_, err := getServerKeyValueState(ctx, js, keyValue)\n\t\t\t// If we have no known state for this KeyValue it has never been reconciled.\n\t\t\t// If we are also receiving an error fetching state, either the KeyValue does not exist\n\t\t\t// or this resource config is invalid.\n\t\t\tif err != nil && storedState == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn js.DeleteKeyValue(ctx, keyValue.Spec.Bucket)\n\t\t})\n\t\tif errors.Is(err, jetstream.ErrBucketNotFound) {\n\t\t\tlog.Info(\"KeyValue does not exist, unable to delete.\", \"keyValueName\", keyValue.Spec.Bucket)\n\t\t} else if err != nil && storedState == nil {\n\t\t\tlog.Info(\"KeyValue not reconciled and no state received from server. Removing finalizer.\")\n\t\t} else if err != nil {\n\t\t\treturn fmt.Errorf(\"delete keyvalue during finalization: %w\", err)\n\t\t}\n\t} else {\n\t\tlog.Info(\"Skipping KeyValue deletion.\",\n\t\t\t\"preventDelete\", keyValue.Spec.PreventDelete,\n\t\t\t\"read-only\", r.ReadOnly(),\n\t\t)\n\t}\n\n\tlog.Info(\"Removing KeyValue finalizer.\")\n\tif ok := controllerutil.RemoveFinalizer(keyValue, keyValueFinalizer); !ok {\n\t\treturn errors.New(\"failed to remove keyvalue finalizer\")\n\t}\n\tif err := r.Update(ctx, keyValue); err != nil {\n\t\treturn fmt.Errorf(\"remove finalizer: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (r *KeyValueReconciler) createOrUpdate(ctx context.Context, log logr.Logger, keyValue *api.KeyValue) error {\n\t// Create or Update the KeyValue based on the spec\n\t// Map spec to KeyValue targetConfig\n\ttargetConfig, err := keyValueSpecToConfig(&keyValue.Spec)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"map spec to keyvalue targetConfig: %w\", err)\n\t}\n\n\t// UpdateKeyValue is called on every reconciliation when the stream is not to be deleted.\n\terr = r.WithJetStreamClient(keyValue.Spec.ConnectionOpts, keyValue.Namespace, func(js jetstream.JetStream) error {\n\t\tstoredState, err := getStoredKeyValueState(keyValue)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to fetch stored KeyValue state\")\n\t\t}\n\n\t\tserverState, err := getServerKeyValueState(ctx, js, keyValue)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Check against known state. Skip Update if converged.\n\t\t// Storing returned state from the server avoids have to\n\t\t// check default values or call Update on already converged resources\n\t\tif storedState != nil && serverState != nil && keyValue.Status.ObservedGeneration == keyValue.Generation {\n\t\t\tdiff := compareConfigState(storedState, serverState)\n\n\t\t\tif diff == \"\" {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tlog.Info(\"KeyValue config drifted from desired state.\", \"diff\", diff)\n\t\t}\n\n\t\tif r.ReadOnly() {\n\t\t\tlog.Info(\"Skipping KeyValue creation or update.\",\n\t\t\t\t\"read-only\", r.ReadOnly(),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\n\t\tvar updatedKeyValue jetstream.KeyValue\n\t\terr = nil\n\n\t\tif serverState == nil {\n\t\t\tlog.Info(\"Creating KeyValue.\")\n\t\t\tupdatedKeyValue, err = js.CreateKeyValue(ctx, targetConfig)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else if !keyValue.Spec.PreventUpdate {\n\t\t\tlog.Info(\"Updating KeyValue.\")\n\t\t\tupdatedKeyValue, err = js.UpdateKeyValue(ctx, targetConfig)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tupdatedKeyValue, err := getServerKeyValueState(ctx, js, keyValue)\n\t\t\tif err != nil {\n\t\t\t\tlog.Error(err, \"Failed to fetch updated KeyValue state\")\n\t\t\t} else {\n\t\t\t\tdiff := compareConfigState(updatedKeyValue, serverState)\n\t\t\t\tlog.Info(\"Updated KeyValue.\", \"diff\", diff)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Info(\"Skipping KeyValue update.\",\n\t\t\t\t\"preventUpdate\", keyValue.Spec.PreventUpdate,\n\t\t\t)\n\t\t}\n\n\t\tif updatedKeyValue != nil {\n\t\t\t// Store known state in annotation\n\t\t\tserverState, err = getServerKeyValueState(ctx, js, keyValue)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tupdatedState, err := json.Marshal(serverState)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif keyValue.Annotations == nil {\n\t\t\t\tkeyValue.Annotations = map[string]string{}\n\t\t\t}\n\t\t\tkeyValue.Annotations[stateAnnotationKV] = string(updatedState)\n\n\t\t\treturn r.Update(ctx, keyValue)\n\t\t}\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\terr = fmt.Errorf(\"create or update keyvalue: %w\", err)\n\t\tkeyValue.Status.Conditions = updateReadyCondition(keyValue.Status.Conditions, v1.ConditionFalse, stateErrored, err.Error())\n\t\tif err := r.Status().Update(ctx, keyValue); err != nil {\n\t\t\tlog.Error(err, \"Failed to update ready condition to Errored.\")\n\t\t}\n\t\treturn err\n\t}\n\n\t// update the observed generation and ready status\n\tkeyValue.Status.ObservedGeneration = keyValue.Generation\n\tkeyValue.Status.Conditions = updateReadyCondition(\n\t\tkeyValue.Status.Conditions,\n\t\tv1.ConditionTrue,\n\t\tstateReady,\n\t\t\"KeyValue successfully created or updated.\",\n\t)\n\terr = r.Status().Update(ctx, keyValue)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"update ready condition: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc getStoredKeyValueState(keyValue *api.KeyValue) (*jetstream.StreamConfig, error) {\n\tvar storedState *jetstream.StreamConfig\n\tif state, ok := keyValue.Annotations[stateAnnotationKV]; ok {\n\t\terr := json.Unmarshal([]byte(state), &storedState)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn storedState, nil\n}\n\n// Fetch the current state of the KeyValue stream from the server.\n// ErrStreamNotFound is considered a valid response and does not return error\nfunc getServerKeyValueState(ctx context.Context, js jetstream.JetStream, keyValue *api.KeyValue) (*jetstream.StreamConfig, error) {\n\ts, err := js.Stream(ctx, fmt.Sprintf(\"%s%s\", kvStreamPrefix, keyValue.Spec.Bucket))\n\tif errors.Is(err, jetstream.ErrStreamNotFound) {\n\t\treturn nil, nil\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &s.CachedInfo().Config, nil\n}\n\n// keyValueSpecToConfig creates a jetstream.KeyValueConfig matching the given KeyValue resource spec\nfunc keyValueSpecToConfig(spec *api.KeyValueSpec) (jetstream.KeyValueConfig, error) {\n\t// Set directly mapped fields\n\tconfig := jetstream.KeyValueConfig{\n\t\tBucket:         spec.Bucket,\n\t\tCompression:    spec.Compression,\n\t\tDescription:    spec.Description,\n\t\tHistory:        uint8(spec.History),\n\t\tMaxBytes:       int64(spec.MaxBytes),\n\t\tMaxValueSize:   int32(spec.MaxValueSize),\n\t\tReplicas:       spec.Replicas,\n\t\tLimitMarkerTTL: spec.LimitMarkerTTL,\n\t}\n\n\t// TTL\n\tif spec.TTL != \"\" {\n\t\tt, err := time.ParseDuration(spec.TTL)\n\t\tif err != nil {\n\t\t\treturn jetstream.KeyValueConfig{}, fmt.Errorf(\"invalid ttl: %w\", err)\n\t\t}\n\t\tconfig.TTL = t\n\t}\n\n\t// storage\n\tif spec.Storage != \"\" {\n\t\terr := config.Storage.UnmarshalJSON(jsonString(spec.Storage))\n\t\tif err != nil {\n\t\t\treturn jetstream.KeyValueConfig{}, fmt.Errorf(\"invalid storage: %w\", err)\n\t\t}\n\t}\n\n\t// placement\n\tif spec.Placement != nil {\n\t\tconfig.Placement = &jetstream.Placement{\n\t\t\tCluster: spec.Placement.Cluster,\n\t\t\tTags:    spec.Placement.Tags,\n\t\t}\n\t}\n\n\t// mirror\n\tif spec.Mirror != nil {\n\t\tss, err := mapStreamSource(spec.Mirror)\n\t\tif err != nil {\n\t\t\treturn jetstream.KeyValueConfig{}, fmt.Errorf(\"map mirror keyvalue source: %w\", err)\n\t\t}\n\t\tconfig.Mirror = ss\n\t}\n\n\t// sources\n\tif spec.Sources != nil {\n\t\tconfig.Sources = []*jetstream.StreamSource{}\n\t\tfor _, source := range spec.Sources {\n\t\t\ts, err := mapStreamSource(source)\n\t\t\tif err != nil {\n\t\t\t\treturn jetstream.KeyValueConfig{}, fmt.Errorf(\"map keyvalue source: %w\", err)\n\t\t\t}\n\t\t\tconfig.Sources = append(config.Sources, s)\n\t\t}\n\t}\n\n\t// RePublish\n\tif spec.RePublish != nil {\n\t\tconfig.RePublish = &jetstream.RePublish{\n\t\t\tSource:      spec.RePublish.Source,\n\t\t\tDestination: spec.RePublish.Destination,\n\t\t\tHeadersOnly: spec.RePublish.HeadersOnly,\n\t\t}\n\t}\n\n\treturn config, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *KeyValueReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&api.KeyValue{}).\n\t\tWithEventFilter(predicate.GenerationChangedPredicate{}).\n\t\tWithOptions(controller.Options{\n\t\t\tMaxConcurrentReconciles: 1,\n\t\t}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "internal/controller/keyvalue_controller_test.go",
    "content": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS 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\"testing\"\n\t\"time\"\n\n\t\"github.com/nats-io/nats.go/jetstream\"\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/stretchr/testify/assert\"\n\tv1 \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n)\n\nvar _ = Describe(\"KeyValue Controller\", func() {\n\t// The test keyValue resource\n\tconst resourceName = \"test-kv\"\n\tconst keyValueName = \"orders\"\n\n\tconst alternateResource = \"alternate-kv\"\n\tconst alternateNamespace = \"alternate-namespace\"\n\n\ttypeNamespacedName := types.NamespacedName{\n\t\tName:      resourceName,\n\t\tNamespace: \"default\",\n\t}\n\tkeyValue := &api.KeyValue{}\n\n\t// The tested controller\n\tvar controller *KeyValueReconciler\n\n\t// Config to create minimal nats KeyValue store\n\temptyKeyValueConfig := jetstream.KeyValueConfig{\n\t\tBucket:   keyValueName,\n\t\tReplicas: 1,\n\t\tStorage:  jetstream.FileStorage,\n\t}\n\n\tBeforeEach(func(ctx SpecContext) {\n\t\tBy(\"creating a test keyvalue resource\")\n\t\terr := k8sClient.Get(ctx, typeNamespacedName, keyValue)\n\t\tif err != nil && k8serrors.IsNotFound(err) {\n\t\t\tresource := &api.KeyValue{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      resourceName,\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t},\n\t\t\t\tSpec: api.KeyValueSpec{\n\t\t\t\t\tBucket:      keyValueName,\n\t\t\t\t\tReplicas:    1,\n\t\t\t\t\tHistory:     10,\n\t\t\t\t\tTTL:         \"5m\",\n\t\t\t\t\tCompression: true,\n\t\t\t\t\tDescription: \"test keyvalue\",\n\t\t\t\t\tStorage:     \"file\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t// Re-fetch KeyValue\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed())\n\t\t}\n\n\t\tBy(\"checking precondition: nats keyvalue does not exist\")\n\t\t_, err = jsClient.KeyValue(ctx, keyValueName)\n\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\n\t\tBy(\"setting up the tested controller\")\n\t\tcontroller = &KeyValueReconciler{\n\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\tJetStreamController: baseController,\n\t\t}\n\t})\n\n\tAfterEach(func(ctx SpecContext) {\n\t\tBy(\"removing the test keyvalue resource\")\n\t\tresource := &api.KeyValue{}\n\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\tif err != nil {\n\t\t\tExpect(err).To(MatchError(k8serrors.IsNotFound, \"Is not found\"))\n\t\t} else {\n\t\t\tif controllerutil.ContainsFinalizer(resource, keyValueFinalizer) {\n\t\t\t\tBy(\"removing the finalizer\")\n\t\t\t\tcontrollerutil.RemoveFinalizer(resource, keyValueFinalizer)\n\t\t\t\tExpect(k8sClient.Update(ctx, resource)).To(Succeed())\n\t\t\t}\n\n\t\t\tBy(\"removing the keyvalue resource\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).\n\t\t\t\tTo(SatisfyAny(\n\t\t\t\t\tSucceed(),\n\t\t\t\t\tMatchError(k8serrors.IsNotFound, \"is not found\"),\n\t\t\t\t))\n\t\t}\n\n\t\tBy(\"deleting the nats keyvalue store\")\n\t\tExpect(jsClient.DeleteKeyValue(ctx, keyValueName)).\n\t\t\tTo(SatisfyAny(\n\t\t\t\tSucceed(),\n\t\t\t\tMatchError(jetstream.ErrBucketNotFound),\n\t\t\t))\n\t})\n\n\tWhen(\"reconciling a not existing resource\", func() {\n\t\tIt(\"should stop reconciliation without error\", func(ctx SpecContext) {\n\t\t\tBy(\"reconciling the created resource\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\tNamespace: \"fake\",\n\t\t\t\t\tName:      \"not-existing\",\n\t\t\t\t},\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result).To(Equal(ctrl.Result{}))\n\t\t})\n\t})\n\n\tWhen(\"reconciling a not initialized resource\", func() {\n\t\tIt(\"should initialize a new resource\", func(ctx SpecContext) {\n\t\t\tBy(\"re-queueing until it is initialized\")\n\t\t\t// Initialization can require multiple reconciliation loops\n\t\t\tEventually(func(ctx SpecContext) *api.KeyValue {\n\t\t\t\t_, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tgot := &api.KeyValue{}\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, got)).To(Succeed())\n\t\t\t\treturn got\n\t\t\t}).WithContext(ctx).\n\t\t\t\tShould(SatisfyAll(\n\t\t\t\t\tHaveField(\"Finalizers\", HaveExactElements(keyValueFinalizer)),\n\t\t\t\t\tHaveField(\"Status.Conditions\", Not(BeEmpty())),\n\t\t\t\t))\n\n\t\t\tBy(\"validating the ready condition\")\n\t\t\t// Fetch KeyValue\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed())\n\t\t\tExpect(keyValue.Status.Conditions).To(HaveLen(1))\n\n\t\t\tassertReadyStateMatches(keyValue.Status.Conditions[0], v1.ConditionUnknown, stateReconciling, \"Starting reconciliation\", time.Now())\n\t\t})\n\t})\n\n\tWhen(\"reconciling a resource in a different namespace\", func() {\n\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\tBy(\"creating a keyvalue resource in an alternate namespace while namespaced\")\n\t\t\talternateNamespaceResource := &api.KeyValue{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      alternateResource,\n\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t},\n\t\t\t\tSpec: api.KeyValueSpec{\n\t\t\t\t\tBucket:      alternateResource,\n\t\t\t\t\tReplicas:    1,\n\t\t\t\t\tHistory:     10,\n\t\t\t\t\tTTL:         \"5m\",\n\t\t\t\t\tCompression: true,\n\t\t\t\t\tDescription: \"keyvalue in alternate namespace\",\n\t\t\t\t\tStorage:     \"file\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tns := &v1.Namespace{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName: alternateNamespace,\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := k8sClient.Create(ctx, ns)\n\t\t\tif err != nil && !k8serrors.IsAlreadyExists(err) {\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\n\t\t\tExpect(k8sClient.Create(ctx, alternateNamespaceResource)).To(Succeed())\n\t\t})\n\n\t\tAfterEach(func(ctx SpecContext) {\n\t\t\tBy(\"cleaning up the resource in alternate namespace\")\n\t\t\talternateKeyValue := &api.KeyValue{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      alternateResource,\n\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := k8sClient.Delete(ctx, alternateKeyValue)\n\t\t\tif err != nil && !k8serrors.IsNotFound(err) {\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should not watch the resource in alternate namespace\", func(ctx SpecContext) {\n\t\t\tBy(\"reconciling with no explicit namespace restriction\")\n\t\t\talternateNamespacedName := types.NamespacedName{\n\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\tName:      alternateResource,\n\t\t\t}\n\n\t\t\tBy(\"running reconciliation for the resource in alternate namespace\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: alternateNamespacedName,\n\t\t\t})\n\n\t\t\tBy(\"verifying reconciliation completes without error\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result).To(Equal(ctrl.Result{}))\n\n\t\t\tBy(\"checking the keyvalue doesn't exist in NATS\")\n\t\t\t_, err = jsClient.KeyValue(ctx, alternateResource)\n\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\n\t\t\tBy(\"verifying the resource still exists in the alternate namespace\")\n\t\t\talternateKeyValue := &api.KeyValue{}\n\t\t\tExpect(k8sClient.Get(ctx, alternateNamespacedName, alternateKeyValue)).To(Succeed())\n\n\t\t\tBy(\"checking no conditions were set on the resource\")\n\t\t\tExpect(alternateKeyValue.Status.Conditions).To(BeEmpty())\n\t\t})\n\n\t\tIt(\"should watch the resource in alternate namespace when not namespaced\", func(ctx SpecContext) {\n\t\t\tBy(\"reconciling with a non-namespaced controller\")\n\t\t\ttestNatsConfig := &NatsConfig{ServerURL: clientUrl}\n\t\t\talternateBaseController, err := NewJSController(k8sClient, testNatsConfig, &Config{})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\talternateController := &KeyValueReconciler{\n\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\tJetStreamController: alternateBaseController,\n\t\t\t}\n\n\t\t\tresourceNames := []types.NamespacedName{\n\t\t\t\ttypeNamespacedName,\n\t\t\t\t{\n\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t\tName:      alternateResource,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tBy(\"running reconciliation for the resources in all namespaces\")\n\t\t\tfor _, n := range resourceNames {\n\t\t\t\tresult, err := alternateController.Reconcile(ctx, reconcile.Request{\n\t\t\t\t\tNamespacedName: n,\n\t\t\t\t})\n\n\t\t\t\tBy(\"verifying reconciliation completes without error\")\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result).NotTo(Equal(ctrl.Result{}))\n\t\t\t}\n\t\t})\n\t})\n\n\tWhen(\"reconciling an initialized resource\", func() {\n\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\tBy(\"initializing the keyvalue resource\")\n\n\t\t\tBy(\"setting the finalizer\")\n\t\t\tExpect(controllerutil.AddFinalizer(keyValue, keyValueFinalizer)).To(BeTrue())\n\t\t\tExpect(k8sClient.Update(ctx, keyValue)).To(Succeed())\n\n\t\t\tBy(\"setting an unknown ready state\")\n\t\t\tkeyValue.Status.Conditions = []api.Condition{{\n\t\t\t\tType:               readyCondType,\n\t\t\t\tStatus:             v1.ConditionUnknown,\n\t\t\t\tReason:             \"Test\",\n\t\t\t\tMessage:            \"start condition\",\n\t\t\t\tLastTransitionTime: time.Now().Format(time.RFC3339Nano),\n\t\t\t}}\n\t\t\tExpect(k8sClient.Status().Update(ctx, keyValue)).To(Succeed())\n\t\t})\n\n\t\tIt(\"should create a new keyvalue store\", func(ctx SpecContext) {\n\t\t\tBy(\"running Reconcile\")\n\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t// Fetch resource\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed())\n\n\t\t\tBy(\"checking if the ready state was updated\")\n\t\t\tExpect(keyValue.Status.Conditions).To(HaveLen(1))\n\t\t\tassertReadyStateMatches(keyValue.Status.Conditions[0], v1.ConditionTrue, stateReady, \"created or updated\", time.Now())\n\n\t\t\tBy(\"checking if the observed generation matches\")\n\t\t\tExpect(keyValue.Status.ObservedGeneration).To(Equal(keyValue.Generation))\n\n\t\t\tBy(\"checking if the keyvalue store was created\")\n\t\t\tnatsKeyValue, err := jsClient.KeyValue(ctx, keyValueName)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tkvStatus, err := natsKeyValue.Status(ctx)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(kvStatus.Bucket()).To(Equal(keyValueName))\n\t\t\tExpect(kvStatus.History()).To(Equal(int64(10)))\n\t\t\tExpect(kvStatus.TTL()).To(Equal(5 * time.Minute))\n\t\t\tExpect(kvStatus.IsCompressed()).To(BeTrue())\n\t\t})\n\n\t\tWhen(\"PreventUpdate is set\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"setting preventDelete on the resource\")\n\t\t\t\tkeyValue.Spec.PreventUpdate = true\n\t\t\t\tExpect(k8sClient.Update(ctx, keyValue)).To(Succeed())\n\t\t\t})\n\t\t\tIt(\"should create the keyvalue\", func(ctx SpecContext) {\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that keyvalue was created\")\n\t\t\t\t_, err = jsClient.KeyValue(ctx, keyValueName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\t\t\tIt(\"should not update the keyvalue\", func(ctx SpecContext) {\n\t\t\t\tBy(\"creating the keyvalue\")\n\t\t\t\t_, err := jsClient.CreateKeyValue(ctx, emptyKeyValueConfig)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that keyvalue was not updated\")\n\t\t\t\tnatsKeyValue, err := jsClient.KeyValue(ctx, keyValueName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\ts, err := natsKeyValue.Status(ctx)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(s.IsCompressed()).To(BeFalse())\n\t\t\t\tExpect(s.History()).To(BeEquivalentTo(int64(1)))\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"read-only mode is enabled\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"setting read only on the controller\")\n\t\t\t\treadOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontroller = &KeyValueReconciler{\n\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\tJetStreamController: readOnly,\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should not create the keyvalue\", func(ctx SpecContext) {\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that no keyvalue was created\")\n\t\t\t\t_, err = jsClient.KeyValue(ctx, keyValueName)\n\t\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\t\t\t})\n\t\t\tIt(\"should not update the keyvalue\", func(ctx SpecContext) {\n\t\t\t\tBy(\"creating the keyvalue\")\n\t\t\t\t_, err := jsClient.CreateKeyValue(ctx, emptyKeyValueConfig)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that keyvalue was not updated\")\n\t\t\t\tnatsKeyValue, err := jsClient.KeyValue(ctx, keyValueName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\ts, err := natsKeyValue.Status(ctx)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(s.IsCompressed()).To(BeFalse())\n\t\t\t\tExpect(s.History()).To(BeEquivalentTo(int64(1)))\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"namespace restriction is enabled\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"setting a namespace on the resource\")\n\t\t\t\tnamespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: alternateNamespace})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontroller = &KeyValueReconciler{\n\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\tJetStreamController: namespaced,\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should not create the keyvalue\", func(ctx SpecContext) {\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that no keyvalue was created\")\n\t\t\t\t_, err = jsClient.KeyValue(ctx, keyValueName)\n\t\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\t\t\t})\n\t\t\tIt(\"should not update the keyvalue\", func(ctx SpecContext) {\n\t\t\t\tBy(\"creating the keyvalue\")\n\t\t\t\t_, err := jsClient.CreateKeyValue(ctx, emptyKeyValueConfig)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that keyvalue was not updated\")\n\t\t\t\tnatsKeyValue, err := jsClient.KeyValue(ctx, keyValueName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\ts, err := natsKeyValue.Status(ctx)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(s.IsCompressed()).To(BeFalse())\n\t\t\t\tExpect(s.History()).To(BeEquivalentTo(int64(1)))\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should update an existing keyvalue\", func(ctx SpecContext) {\n\t\t\tBy(\"reconciling once to create the keyvalue\")\n\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t// Fetch resource\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed())\n\t\t\tpreviousTransitionTime := keyValue.Status.Conditions[0].LastTransitionTime\n\n\t\t\tBy(\"updating the resource\")\n\t\t\tkeyValue.Spec.Description = \"new description\"\n\t\t\tkeyValue.Spec.History = 50\n\t\t\tkeyValue.Spec.TTL = \"1h\"\n\t\t\tExpect(k8sClient.Update(ctx, keyValue)).To(Succeed())\n\n\t\t\tBy(\"reconciling the updated resource\")\n\t\t\tresult, err = controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t// Fetch resource\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed())\n\n\t\t\tBy(\"checking if the state transition time was not updated\")\n\t\t\tExpect(keyValue.Status.Conditions).To(HaveLen(1))\n\t\t\tExpect(keyValue.Status.Conditions[0].LastTransitionTime).To(Equal(previousTransitionTime))\n\n\t\t\tBy(\"checking if the observed generation matches\")\n\t\t\tExpect(keyValue.Status.ObservedGeneration).To(Equal(keyValue.Generation))\n\n\t\t\tBy(\"checking if the keyvalue was updated\")\n\t\t\tnatsKeyValue, err := jsClient.KeyValue(ctx, keyValueName)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tkeyValueStatus, err := natsKeyValue.Status(ctx)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(keyValueStatus.Bucket()).To(Equal(keyValueName))\n\t\t\tExpect(keyValueStatus.History()).To(Equal(int64(50)))\n\t\t\tExpect(keyValueStatus.TTL()).To(Equal(1 * time.Hour))\n\t\t\tExpect(keyValueStatus.IsCompressed()).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should set an error state when the nats server is not available\", func(ctx SpecContext) {\n\t\t\tBy(\"setting up controller with unavailable nats server\")\n\t\t\t// Setup client for not running server\n\t\t\t// Use actual test server to ensure port not used by other service on test instance\n\t\t\tsv := CreateTestServer()\n\t\t\tdisconnectedController, err := NewJSController(k8sClient, &NatsConfig{ServerURL: sv.ClientURL()}, &Config{})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tsv.Shutdown()\n\n\t\t\tcontroller := &KeyValueReconciler{\n\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\tJetStreamController: disconnectedController,\n\t\t\t}\n\n\t\t\tBy(\"reconciling resource\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(result).To(Equal(ctrl.Result{}))\n\t\t\tExpect(err).To(HaveOccurred()) // Will be re-queued with back-off\n\n\t\t\t// Fetch resource\n\t\t\terr = k8sClient.Get(ctx, typeNamespacedName, keyValue)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"checking if the status was updated\")\n\t\t\tExpect(keyValue.Status.Conditions).To(HaveLen(1))\n\t\t\tassertReadyStateMatches(\n\t\t\t\tkeyValue.Status.Conditions[0],\n\t\t\t\tv1.ConditionFalse,\n\t\t\t\tstateErrored,\n\t\t\t\t\"create or update keyvalue:\",\n\t\t\t\ttime.Now(),\n\t\t\t)\n\n\t\t\tBy(\"checking if the observed generation does not match\")\n\t\t\tExpect(keyValue.Status.ObservedGeneration).ToNot(Equal(keyValue.Generation))\n\t\t})\n\n\t\tWhen(\"the resource is marked for deletion\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"marking the resource for deletion\")\n\t\t\t\tExpect(k8sClient.Delete(ctx, keyValue)).To(Succeed())\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed()) // re-fetch after update\n\t\t\t})\n\n\t\t\tIt(\"should succeed deleting a not existing keyvalue\", func(ctx SpecContext) {\n\t\t\t\tBy(\"reconciling\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\tWithArguments(ctx, typeNamespacedName, keyValue).\n\t\t\t\t\tShouldNot(Succeed())\n\t\t\t})\n\n\t\t\tWhen(\"the underlying keyvalue exists\", func() {\n\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\tBy(\"creating the keyvalue on the nats server\")\n\t\t\t\t\t_, err := jsClient.CreateKeyValue(ctx, emptyKeyValueConfig)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t})\n\n\t\t\t\tAfterEach(func(ctx SpecContext) {\n\t\t\t\t\terr := jsClient.DeleteKeyValue(ctx, keyValueName)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tIt(\"should delete the keyvalue\", func(ctx SpecContext) {\n\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\tBy(\"checking that the keyvalue is deleted\")\n\t\t\t\t\t_, err = jsClient.KeyValue(ctx, keyValueName)\n\t\t\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\n\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, keyValue).\n\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t})\n\n\t\t\t\tWhen(\"PreventDelete is set\", func() {\n\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"setting preventDelete on the resource\")\n\t\t\t\t\t\tkeyValue.Spec.PreventDelete = true\n\t\t\t\t\t\tExpect(k8sClient.Update(ctx, keyValue)).To(Succeed())\n\t\t\t\t\t})\n\t\t\t\t\tIt(\"Should delete the resource and not delete the nats keyvalue\", func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\tBy(\"checking that the keyvalue is not deleted\")\n\t\t\t\t\t\t_, err = jsClient.KeyValue(ctx, keyValueName)\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, keyValue).\n\t\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tWhen(\"read only is set\", func() {\n\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"setting read only on the controller\")\n\t\t\t\t\t\treadOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tcontroller = &KeyValueReconciler{\n\t\t\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\t\t\tJetStreamController: readOnly,\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\tIt(\"should delete the resource and not delete the keyvalue\", func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\tBy(\"checking that the keyvalue is not deleted\")\n\t\t\t\t\t\t_, err = jsClient.KeyValue(ctx, keyValueName)\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, keyValue).\n\t\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tWhen(\"controller is restricted to different namespace\", func() {\n\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\tnamespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: alternateNamespace})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tcontroller = &KeyValueReconciler{\n\t\t\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\t\t\tJetStreamController: namespaced,\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\tIt(\"should not delete the resource and keyvalue\", func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\tBy(\"checking that the keyvalue is not deleted\")\n\t\t\t\t\t\t_, err = jsClient.KeyValue(ctx, keyValueName)\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\t\tBy(\"checking that the finalizer is not removed\")\n\t\t\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, keyValue)).To(Succeed())\n\t\t\t\t\t\tExpect(keyValue.Finalizers).To(ContainElement(keyValueFinalizer))\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should update keyvalue on different server as specified in spec\", func(ctx SpecContext) {\n\t\t\tBy(\"setting up the alternative server\")\n\t\t\t// Setup altClient for alternate server\n\t\t\taltServer := CreateTestServer()\n\t\t\tdefer altServer.Shutdown()\n\n\t\t\tBy(\"setting the server in the keyvalue spec\")\n\t\t\tkeyValue.Spec.Servers = []string{altServer.ClientURL()}\n\t\t\tExpect(k8sClient.Update(ctx, keyValue)).To(Succeed())\n\n\t\t\tBy(\"checking precondition, that the keyvalue does not yet exist\")\n\t\t\t_, err := jsClient.KeyValue(ctx, keyValueName)\n\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\n\t\t\tBy(\"reconciling the resource\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\tconnPool := newConnPool(0)\n\t\t\tconn, err := connPool.Get(&NatsConfig{ServerURL: altServer.ClientURL()}, true)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdomain := \"\"\n\n\t\t\tBy(\"checking if the keyvalue was created on the alternative server\")\n\t\t\taltClient, err := CreateJetStreamClient(conn, true, domain)\n\t\t\tdefer conn.Close()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t_, err = altClient.KeyValue(ctx, keyValueName)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"checking that the keyvalue was NOT created on the original server\")\n\t\t\t_, err = jsClient.KeyValue(ctx, keyValueName)\n\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\t\t})\n\t})\n})\n\nfunc Test_mapKVSpecToConfig(t *testing.T) {\n\tdate := time.Date(2024, 12, 3, 16, 55, 5, 0, time.UTC)\n\tdateString := date.Format(time.RFC3339)\n\n\ttests := []struct {\n\t\tname    string\n\t\tspec    *api.KeyValueSpec\n\t\twant    jetstream.KeyValueConfig\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"empty spec\",\n\t\t\tspec:    &api.KeyValueSpec{},\n\t\t\twant:    jetstream.KeyValueConfig{},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"full spec\",\n\t\t\tspec: &api.KeyValueSpec{\n\t\t\t\tDescription:    \"kv description\",\n\t\t\t\tHistory:        20,\n\t\t\t\tMaxValueSize:   1024,\n\t\t\t\tMaxBytes:       1048576,\n\t\t\t\tTTL:            \"1h\",\n\t\t\t\tLimitMarkerTTL: 2 * time.Hour,\n\t\t\t\tMirror: &api.StreamSource{\n\t\t\t\t\tName:                  \"mirror\",\n\t\t\t\t\tOptStartSeq:           5,\n\t\t\t\t\tOptStartTime:          dateString,\n\t\t\t\t\tFilterSubject:         \"orders\",\n\t\t\t\t\tExternalAPIPrefix:     \"api\",\n\t\t\t\t\tExternalDeliverPrefix: \"deliver\",\n\t\t\t\t\tSubjectTransforms: []*api.SubjectTransform{{\n\t\t\t\t\t\tSource: \"transform-source\",\n\t\t\t\t\t\tDest:   \"transform-dest\",\n\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t\tBucket: \"kv-name\",\n\t\t\t\tPlacement: &api.StreamPlacement{\n\t\t\t\t\tCluster: \"test-cluster\",\n\t\t\t\t\tTags:    []string{\"tag\"},\n\t\t\t\t},\n\t\t\t\tReplicas: 3,\n\t\t\t\tRePublish: &api.RePublish{\n\t\t\t\t\tSource:      \"re-publish-source\",\n\t\t\t\t\tDestination: \"re-publish-dest\",\n\t\t\t\t\tHeadersOnly: true,\n\t\t\t\t},\n\t\t\t\tCompression: true,\n\t\t\t\tSources: []*api.StreamSource{{\n\t\t\t\t\tName:                  \"source\",\n\t\t\t\t\tOptStartSeq:           5,\n\t\t\t\t\tOptStartTime:          dateString,\n\t\t\t\t\tFilterSubject:         \"orders\",\n\t\t\t\t\tExternalAPIPrefix:     \"api\",\n\t\t\t\t\tExternalDeliverPrefix: \"deliver\",\n\t\t\t\t\tSubjectTransforms: []*api.SubjectTransform{{\n\t\t\t\t\t\tSource: \"transform-source\",\n\t\t\t\t\t\tDest:   \"transform-dest\",\n\t\t\t\t\t}},\n\t\t\t\t}},\n\t\t\t\tStorage: \"memory\",\n\t\t\t\tBaseStreamConfig: api.BaseStreamConfig{\n\t\t\t\t\tPreventDelete: false,\n\t\t\t\t\tPreventUpdate: false,\n\t\t\t\t\tConnectionOpts: api.ConnectionOpts{\n\t\t\t\t\t\tAccount: \"\",\n\t\t\t\t\t\tCreds:   \"\",\n\t\t\t\t\t\tNkey:    \"\",\n\t\t\t\t\t\tServers: nil,\n\t\t\t\t\t\tTLS:     &api.TLS{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: jetstream.KeyValueConfig{\n\t\t\t\tBucket:         \"kv-name\",\n\t\t\t\tDescription:    \"kv description\",\n\t\t\t\tMaxBytes:       1048576,\n\t\t\t\tTTL:            time.Hour,\n\t\t\t\tLimitMarkerTTL: 2 * time.Hour,\n\t\t\t\tMaxValueSize:   1024,\n\t\t\t\tHistory:        20,\n\t\t\t\tStorage:        jetstream.MemoryStorage,\n\t\t\t\tReplicas:       3,\n\t\t\t\tPlacement: &jetstream.Placement{\n\t\t\t\t\tCluster: \"test-cluster\",\n\t\t\t\t\tTags:    []string{\"tag\"},\n\t\t\t\t},\n\t\t\t\tMirror: &jetstream.StreamSource{\n\t\t\t\t\tName:          \"mirror\",\n\t\t\t\t\tOptStartSeq:   5,\n\t\t\t\t\tOptStartTime:  &date,\n\t\t\t\t\tFilterSubject: \"orders\",\n\t\t\t\t\tSubjectTransforms: []jetstream.SubjectTransformConfig{{\n\t\t\t\t\t\tSource:      \"transform-source\",\n\t\t\t\t\t\tDestination: \"transform-dest\",\n\t\t\t\t\t}},\n\t\t\t\t\tExternal: &jetstream.ExternalStream{\n\t\t\t\t\t\tAPIPrefix:     \"api\",\n\t\t\t\t\t\tDeliverPrefix: \"deliver\",\n\t\t\t\t\t},\n\t\t\t\t\tDomain: \"\",\n\t\t\t\t},\n\t\t\t\tSources: []*jetstream.StreamSource{{\n\t\t\t\t\tName:          \"source\",\n\t\t\t\t\tOptStartSeq:   5,\n\t\t\t\t\tOptStartTime:  &date,\n\t\t\t\t\tFilterSubject: \"orders\",\n\t\t\t\t\tSubjectTransforms: []jetstream.SubjectTransformConfig{{\n\t\t\t\t\t\tSource:      \"transform-source\",\n\t\t\t\t\t\tDestination: \"transform-dest\",\n\t\t\t\t\t}},\n\t\t\t\t\tExternal: &jetstream.ExternalStream{\n\t\t\t\t\t\tAPIPrefix:     \"api\",\n\t\t\t\t\t\tDeliverPrefix: \"deliver\",\n\t\t\t\t\t},\n\t\t\t\t\tDomain: \"\",\n\t\t\t\t}},\n\t\t\t\tCompression: true,\n\t\t\t\tRePublish: &jetstream.RePublish{\n\t\t\t\t\tSource:      \"re-publish-source\",\n\t\t\t\t\tDestination: \"re-publish-dest\",\n\t\t\t\t\tHeadersOnly: true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert := assert.New(t)\n\t\t\tgot, err := keyValueSpecToConfig(tt.spec)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"keyValueSpecToConfig() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Compare nested structs\n\t\t\tassert.EqualValues(tt.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/controller/objectstore_controller.go",
    "content": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS 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\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\t\"github.com/nats-io/nats.go/jetstream\"\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/klog/v2\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/predicate\"\n)\n\nconst (\n\tobjStreamPrefix = \"OBJ_\"\n)\n\n// ObjectStoreReconciler reconciles a ObjectStore object\ntype ObjectStoreReconciler struct {\n\tScheme *runtime.Scheme\n\n\tJetStreamController\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//\n// It performs three main operations:\n// - Initialize finalizer and ready condition if not present\n// - Delete ObjectStore if it is marked for deletion.\n// - Create or Update the ObjectStore\n//\n// A call to reconcile may perform only one action, expecting the reconciliation to be triggered again by an update.\n// For example: Setting the finalizer triggers a second reconciliation. Reconcile returns after setting the finalizer,\n// to prevent parallel reconciliations performing the same steps.\nfunc (r *ObjectStoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := klog.FromContext(ctx)\n\n\tif ok := r.ValidNamespace(req.Namespace); !ok {\n\t\tlog.Info(\"Controller restricted to namespace, skipping reconciliation.\")\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Fetch ObjectStore resource\n\tobjectStore := &api.ObjectStore{}\n\tif err := r.Get(ctx, req.NamespacedName, objectStore); err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\tlog.Info(\"ObjectStore resource deleted.\", \"objectStoreName\", req.NamespacedName.String())\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\treturn ctrl.Result{}, fmt.Errorf(\"get objectstore resource '%s': %w\", req.NamespacedName.String(), err)\n\t}\n\n\tlog = log.WithValues(\"objectStoreName\", objectStore.Spec.Bucket)\n\n\t// Update ready status to unknown when no status is set\n\tif len(objectStore.Status.Conditions) == 0 {\n\t\tlog.Info(\"Setting initial ready condition to unknown.\")\n\t\tobjectStore.Status.Conditions = updateReadyCondition(objectStore.Status.Conditions, v1.ConditionUnknown, stateReconciling, \"Starting reconciliation\")\n\t\terr := r.Status().Update(ctx, objectStore)\n\t\tif err != nil {\n\t\t\t// If we get a conflict error, another reconciliation has already updated the status.\n\t\t\t// Just requeue and let the next reconciliation handle it.\n\t\t\tif apierrors.IsConflict(err) {\n\t\t\t\treturn ctrl.Result{Requeue: true}, nil\n\t\t\t}\n\t\t\treturn ctrl.Result{}, fmt.Errorf(\"set condition unknown: %w\", err)\n\t\t}\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// Check Deletion\n\tmarkedForDeletion := objectStore.GetDeletionTimestamp() != nil\n\tif markedForDeletion {\n\t\tif controllerutil.ContainsFinalizer(objectStore, objectStoreFinalizer) {\n\t\t\terr := r.deleteObjectStore(ctx, log, objectStore)\n\t\t\tif err != nil {\n\t\t\t\treturn ctrl.Result{}, fmt.Errorf(\"delete objectstore: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Info(\"ObjectStore marked for deletion and already finalized. Ignoring.\")\n\t\t}\n\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Add finalizer\n\tif !controllerutil.ContainsFinalizer(objectStore, objectStoreFinalizer) {\n\t\tlog.Info(\"Adding ObjectStore finalizer.\")\n\t\tif ok := controllerutil.AddFinalizer(objectStore, objectStoreFinalizer); !ok {\n\t\t\treturn ctrl.Result{}, errors.New(\"failed to add finalizer to objectstore resource\")\n\t\t}\n\n\t\tif err := r.Update(ctx, objectStore); err != nil {\n\t\t\treturn ctrl.Result{}, fmt.Errorf(\"update objectstore resource to add finalizer: %w\", err)\n\t\t}\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// Create or update ObjectStore\n\tif err := r.createOrUpdate(ctx, log, objectStore); err != nil {\n\t\treturn ctrl.Result{}, fmt.Errorf(\"create or update: %s\", err)\n\t}\n\n\treturn ctrl.Result{RequeueAfter: r.RequeueInterval()}, nil\n}\n\nfunc (r *ObjectStoreReconciler) deleteObjectStore(ctx context.Context, log logr.Logger, objectStore *api.ObjectStore) error {\n\t// Set status to false\n\tobjectStore.Status.Conditions = updateReadyCondition(objectStore.Status.Conditions, v1.ConditionFalse, stateFinalizing, \"Performing finalizer operations.\")\n\tif err := r.Status().Update(ctx, objectStore); err != nil {\n\t\treturn fmt.Errorf(\"update ready condition: %w\", err)\n\t}\n\n\tstoredState, err := getStoredObjectStoreState(objectStore)\n\tif err != nil {\n\t\tlog.Error(err, \"Failed to fetch stored state.\")\n\t}\n\n\tif !objectStore.Spec.PreventDelete && !r.ReadOnly() {\n\t\tlog.Info(\"Deleting ObjectStore.\")\n\t\terr := r.WithJetStreamClient(objectStore.Spec.ConnectionOpts, objectStore.Namespace, func(js jetstream.JetStream) error {\n\t\t\t_, err := getServerObjectStoreState(ctx, js, objectStore)\n\t\t\t// If we have no known state for this object store it has never been reconciled.\n\t\t\t// If we are also receiving an error fetching state, either the object store does not exist\n\t\t\t// or this resource config is invalid.\n\t\t\tif err != nil && storedState == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn js.DeleteObjectStore(ctx, objectStore.Spec.Bucket)\n\t\t})\n\t\tif errors.Is(err, jetstream.ErrStreamNotFound) || errors.Is(err, jetstream.ErrBucketNotFound) {\n\t\t\tlog.Info(\"ObjectStore does not exist, unable to delete.\", \"objectStoreName\", objectStore.Spec.Bucket)\n\t\t} else if err != nil && storedState == nil {\n\t\t\tlog.Info(\"ObjectStore not reconciled and no state received from server. Removing finalizer.\")\n\t\t} else if err != nil {\n\t\t\treturn fmt.Errorf(\"delete objectstore during finalization: %w\", err)\n\t\t}\n\t} else {\n\t\tlog.Info(\"Skipping ObjectStore deletion.\",\n\t\t\t\"preventDelete\", objectStore.Spec.PreventDelete,\n\t\t\t\"read-only\", r.ReadOnly(),\n\t\t)\n\t}\n\n\tlog.Info(\"Removing ObjectStore finalizer.\")\n\tif ok := controllerutil.RemoveFinalizer(objectStore, objectStoreFinalizer); !ok {\n\t\treturn errors.New(\"failed to remove objectstore finalizer\")\n\t}\n\tif err := r.Update(ctx, objectStore); err != nil {\n\t\treturn fmt.Errorf(\"remove finalizer: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (r *ObjectStoreReconciler) createOrUpdate(ctx context.Context, log logr.Logger, objectStore *api.ObjectStore) error {\n\t// Create or Update the ObjectStore based on the spec\n\t// Map spec to ObjectStore targetConfig\n\ttargetConfig, err := objectStoreSpecToConfig(&objectStore.Spec)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"map spec to objectstore targetConfig: %w\", err)\n\t}\n\n\t// UpdateObjectStore is called on every reconciliation when the stream is not to be deleted.\n\terr = r.WithJetStreamClient(objectStore.Spec.ConnectionOpts, objectStore.Namespace, func(js jetstream.JetStream) error {\n\t\tstoredState, err := getStoredObjectStoreState(objectStore)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to fetch stored objectstore state\")\n\t\t}\n\n\t\tserverState, err := getServerObjectStoreState(ctx, js, objectStore)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Check against known state. Skip Update if converged.\n\t\t// Storing returned state from the server avoids have to\n\t\t// check default values or call Update on already converged resources\n\t\tif storedState != nil && serverState != nil && objectStore.Status.ObservedGeneration == objectStore.Generation {\n\t\t\tdiff := compareConfigState(storedState, serverState)\n\n\t\t\tif diff == \"\" {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tlog.Info(\"Object Store config drifted from desired state.\", \"diff\", diff)\n\t\t}\n\n\t\tif r.ReadOnly() {\n\t\t\tlog.Info(\"Skipping ObjectStore creation or update.\",\n\t\t\t\t\"read-only\", r.ReadOnly(),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\n\t\tvar updatedObjectStore jetstream.ObjectStore\n\t\terr = nil\n\n\t\tif serverState == nil {\n\t\t\tlog.Info(\"Creating ObjectStore.\")\n\t\t\tupdatedObjectStore, err = js.CreateObjectStore(ctx, targetConfig)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else if !objectStore.Spec.PreventUpdate {\n\t\t\tlog.Info(\"Updating ObjectStore.\")\n\t\t\tupdatedObjectStore, err = js.UpdateObjectStore(ctx, targetConfig)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tupdatedObjectStore, err := getServerObjectStoreState(ctx, js, objectStore)\n\t\t\tif err != nil {\n\t\t\t\tlog.Error(err, \"Failed to fetch updated objectstore state\")\n\t\t\t} else {\n\t\t\t\tdiff := compareConfigState(updatedObjectStore, serverState)\n\t\t\t\tlog.Info(\"Updated ObjectStore.\", \"diff\", diff)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Info(\"Skipping ObjectStore update.\",\n\t\t\t\t\"preventUpdate\", objectStore.Spec.PreventUpdate,\n\t\t\t)\n\t\t}\n\n\t\tif updatedObjectStore != nil {\n\t\t\t// Store known state in annotation\n\t\t\tserverState, err = getServerObjectStoreState(ctx, js, objectStore)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tupdatedState, err := json.Marshal(serverState)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif objectStore.Annotations == nil {\n\t\t\t\tobjectStore.Annotations = map[string]string{}\n\t\t\t}\n\t\t\tobjectStore.Annotations[stateAnnotationObj] = string(updatedState)\n\n\t\t\treturn r.Update(ctx, objectStore)\n\t\t}\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\terr = fmt.Errorf(\"create or update objectstore: %w\", err)\n\t\tobjectStore.Status.Conditions = updateReadyCondition(objectStore.Status.Conditions, v1.ConditionFalse, stateErrored, err.Error())\n\t\tif err := r.Status().Update(ctx, objectStore); err != nil {\n\t\t\tlog.Error(err, \"Failed to update ready condition to Errored.\")\n\t\t}\n\t\treturn err\n\t}\n\n\t// update the observed generation and ready status\n\tobjectStore.Status.ObservedGeneration = objectStore.Generation\n\tobjectStore.Status.Conditions = updateReadyCondition(\n\t\tobjectStore.Status.Conditions,\n\t\tv1.ConditionTrue,\n\t\tstateReady,\n\t\t\"ObjectStore successfully created or updated.\",\n\t)\n\terr = r.Status().Update(ctx, objectStore)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"update ready condition: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc getStoredObjectStoreState(objectStore *api.ObjectStore) (*jetstream.StreamConfig, error) {\n\tvar storedState *jetstream.StreamConfig\n\tif state, ok := objectStore.Annotations[stateAnnotationObj]; ok {\n\t\terr := json.Unmarshal([]byte(state), &storedState)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn storedState, nil\n}\n\n// Fetch the current state of the ObjectStore stream from the server.\n// ErrStreamNotFound is considered a valid response and does not return error\nfunc getServerObjectStoreState(ctx context.Context, js jetstream.JetStream, objectStore *api.ObjectStore) (*jetstream.StreamConfig, error) {\n\ts, err := js.Stream(ctx, fmt.Sprintf(\"%s%s\", objStreamPrefix, objectStore.Spec.Bucket))\n\tif errors.Is(err, jetstream.ErrStreamNotFound) {\n\t\treturn nil, nil\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &s.CachedInfo().Config, nil\n}\n\n// objectStoreSpecToConfig creates a jetstream.ObjectStoreConfig matching the given ObjectStore resource spec\nfunc objectStoreSpecToConfig(spec *api.ObjectStoreSpec) (jetstream.ObjectStoreConfig, error) {\n\t// Set directly mapped fields\n\tconfig := jetstream.ObjectStoreConfig{\n\t\tBucket:      spec.Bucket,\n\t\tDescription: spec.Description,\n\t\tMaxBytes:    int64(spec.MaxBytes),\n\t\tReplicas:    spec.Replicas,\n\t\tCompression: spec.Compression,\n\t\tMetadata:    spec.Metadata,\n\t}\n\n\t// TTL\n\tif spec.TTL != \"\" {\n\t\tt, err := time.ParseDuration(spec.TTL)\n\t\tif err != nil {\n\t\t\treturn jetstream.ObjectStoreConfig{}, fmt.Errorf(\"invalid ttl: %w\", err)\n\t\t}\n\t\tconfig.TTL = t\n\t}\n\n\t// storage\n\tif spec.Storage != \"\" {\n\t\terr := config.Storage.UnmarshalJSON(jsonString(spec.Storage))\n\t\tif err != nil {\n\t\t\treturn jetstream.ObjectStoreConfig{}, fmt.Errorf(\"invalid storage: %w\", err)\n\t\t}\n\t}\n\n\t// placement\n\tif spec.Placement != nil {\n\t\tconfig.Placement = &jetstream.Placement{\n\t\t\tCluster: spec.Placement.Cluster,\n\t\t\tTags:    spec.Placement.Tags,\n\t\t}\n\t}\n\n\treturn config, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *ObjectStoreReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&api.ObjectStore{}).\n\t\tWithEventFilter(predicate.GenerationChangedPredicate{}).\n\t\tWithOptions(controller.Options{\n\t\t\tMaxConcurrentReconciles: 1,\n\t\t}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "internal/controller/objectstore_controller_test.go",
    "content": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS 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\"testing\"\n\t\"time\"\n\n\t\"github.com/nats-io/nats.go/jetstream\"\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/stretchr/testify/assert\"\n\tv1 \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n)\n\nvar _ = Describe(\"ObjectStore Controller\", func() {\n\t// The test objectStore resource\n\tconst resourceName = \"test-objectstore\"\n\tconst objectStoreName = \"orders\"\n\n\tconst alternateResource = \"alternate-objectstore\"\n\tconst alternateNamespace = \"alternate-namespace\"\n\n\ttypeNamespacedName := types.NamespacedName{\n\t\tName:      resourceName,\n\t\tNamespace: \"default\",\n\t}\n\tobjectStore := &api.ObjectStore{}\n\n\t// The tested controller\n\tvar controller *ObjectStoreReconciler\n\n\t// Config to create minimal nats ObjectStore store\n\temptyObjectStoreConfig := jetstream.ObjectStoreConfig{\n\t\tBucket:   objectStoreName,\n\t\tReplicas: 1,\n\t\tStorage:  jetstream.FileStorage,\n\t}\n\n\tBeforeEach(func(ctx SpecContext) {\n\t\tBy(\"creating a test objectstore resource\")\n\t\terr := k8sClient.Get(ctx, typeNamespacedName, objectStore)\n\t\tif err != nil && k8serrors.IsNotFound(err) {\n\t\t\tresource := &api.ObjectStore{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      resourceName,\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t},\n\t\t\t\tSpec: api.ObjectStoreSpec{\n\t\t\t\t\tBucket:      objectStoreName,\n\t\t\t\t\tReplicas:    1,\n\t\t\t\t\tTTL:         \"5m\",\n\t\t\t\t\tCompression: true,\n\t\t\t\t\tDescription: \"test objectstore\",\n\t\t\t\t\tStorage:     \"file\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t// Re-fetch ObjectStore\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed())\n\t\t}\n\n\t\tBy(\"checking precondition: nats objectstore does not exist\")\n\t\t_, err = jsClient.ObjectStore(ctx, objectStoreName)\n\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\n\t\tBy(\"setting up the tested controller\")\n\t\tcontroller = &ObjectStoreReconciler{\n\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\tJetStreamController: baseController,\n\t\t}\n\t})\n\n\tAfterEach(func(ctx SpecContext) {\n\t\tBy(\"removing the test objectstore resource\")\n\t\tresource := &api.ObjectStore{}\n\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\tif err != nil {\n\t\t\tExpect(err).To(MatchError(k8serrors.IsNotFound, \"Is not found\"))\n\t\t} else {\n\t\t\tif controllerutil.ContainsFinalizer(resource, objectStoreFinalizer) {\n\t\t\t\tBy(\"removing the finalizer\")\n\t\t\t\tcontrollerutil.RemoveFinalizer(resource, objectStoreFinalizer)\n\t\t\t\tExpect(k8sClient.Update(ctx, resource)).To(Succeed())\n\t\t\t}\n\n\t\t\tBy(\"removing the objectstore resource\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).\n\t\t\t\tTo(SatisfyAny(\n\t\t\t\t\tSucceed(),\n\t\t\t\t\tMatchError(k8serrors.IsNotFound, \"is not found\"),\n\t\t\t\t))\n\t\t}\n\n\t\tBy(\"deleting the nats objectstore store\")\n\t\tExpect(jsClient.DeleteObjectStore(ctx, objectStoreName)).\n\t\t\tTo(SatisfyAny(\n\t\t\t\tSucceed(),\n\t\t\t\tMatchError(jetstream.ErrStreamNotFound),\n\t\t\t))\n\t})\n\n\tWhen(\"reconciling a not existing resource\", func() {\n\t\tIt(\"should stop reconciliation without error\", func(ctx SpecContext) {\n\t\t\tBy(\"reconciling the created resource\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\tNamespace: \"fake\",\n\t\t\t\t\tName:      \"not-existing\",\n\t\t\t\t},\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result).To(Equal(ctrl.Result{}))\n\t\t})\n\t})\n\n\tWhen(\"reconciling a not initialized resource\", func() {\n\t\tIt(\"should initialize a new resource\", func(ctx SpecContext) {\n\t\t\tBy(\"re-queueing until it is initialized\")\n\t\t\t// Initialization can require multiple reconciliation loops\n\t\t\tEventually(func(ctx SpecContext) *api.ObjectStore {\n\t\t\t\t_, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tgot := &api.ObjectStore{}\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, got)).To(Succeed())\n\t\t\t\treturn got\n\t\t\t}).WithContext(ctx).\n\t\t\t\tShould(SatisfyAll(\n\t\t\t\t\tHaveField(\"Status.Conditions\", Not(BeEmpty())),\n\t\t\t\t))\n\n\t\t\tBy(\"validating the ready condition\")\n\t\t\t// Fetch ObjectStore\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed())\n\t\t\tExpect(objectStore.Status.Conditions).To(HaveLen(1))\n\n\t\t\tassertReadyStateMatches(objectStore.Status.Conditions[0], v1.ConditionUnknown, stateReconciling, \"Starting reconciliation\", time.Now())\n\t\t})\n\t})\n\n\tWhen(\"reconciling a resource in a different namespace\", func() {\n\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\tBy(\"creating an objectstore resource in an alternate namespace while namespaced\")\n\t\t\talternateNamespaceResource := &api.ObjectStore{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      alternateResource,\n\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t},\n\t\t\t\tSpec: api.ObjectStoreSpec{\n\t\t\t\t\tBucket:      alternateResource,\n\t\t\t\t\tReplicas:    1,\n\t\t\t\t\tTTL:         \"5m\",\n\t\t\t\t\tDescription: \"objectstore in alternate namespace\",\n\t\t\t\t\tStorage:     \"file\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tns := &v1.Namespace{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName: alternateNamespace,\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := k8sClient.Create(ctx, ns)\n\t\t\tif err != nil && !k8serrors.IsAlreadyExists(err) {\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\n\t\t\tExpect(k8sClient.Create(ctx, alternateNamespaceResource)).To(Succeed())\n\t\t})\n\n\t\tAfterEach(func(ctx SpecContext) {\n\t\t\tBy(\"cleaning up the resource in alternate namespace\")\n\t\t\talternateObjectStore := &api.ObjectStore{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      alternateResource,\n\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := k8sClient.Delete(ctx, alternateObjectStore)\n\t\t\tif err != nil && !k8serrors.IsNotFound(err) {\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should not watch the resource in alternate namespace\", func(ctx SpecContext) {\n\t\t\tBy(\"reconciling with no explicit namespace restriction\")\n\t\t\talternateNamespacedName := types.NamespacedName{\n\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\tName:      alternateResource,\n\t\t\t}\n\n\t\t\tBy(\"running reconciliation for the resource in alternate namespace\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: alternateNamespacedName,\n\t\t\t})\n\n\t\t\tBy(\"verifying reconciliation completes without error\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result).To(Equal(ctrl.Result{}))\n\n\t\t\tBy(\"checking the objectstore doesn't exist in NATS\")\n\t\t\t_, err = jsClient.ObjectStore(ctx, alternateResource)\n\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\n\t\t\tBy(\"verifying the resource still exists in the alternate namespace\")\n\t\t\talternateObjectStore := &api.ObjectStore{}\n\t\t\tExpect(k8sClient.Get(ctx, alternateNamespacedName, alternateObjectStore)).To(Succeed())\n\n\t\t\tBy(\"checking no conditions were set on the resource\")\n\t\t\tExpect(alternateObjectStore.Status.Conditions).To(BeEmpty())\n\t\t})\n\n\t\tIt(\"should watch the resource in alternate namespace when not namespaced\", func(ctx SpecContext) {\n\t\t\tBy(\"reconciling with a non-namespaced controller\")\n\t\t\ttestNatsConfig := &NatsConfig{ServerURL: clientUrl}\n\t\t\talternateBaseController, err := NewJSController(k8sClient, testNatsConfig, &Config{})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\talternateController := &ObjectStoreReconciler{\n\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\tJetStreamController: alternateBaseController,\n\t\t\t}\n\n\t\t\tresourceNames := []types.NamespacedName{\n\t\t\t\ttypeNamespacedName,\n\t\t\t\t{\n\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t\tName:      alternateResource,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tBy(\"running reconciliation for the resources in all namespaces\")\n\t\t\tfor _, n := range resourceNames {\n\t\t\t\tresult, err := alternateController.Reconcile(ctx, reconcile.Request{\n\t\t\t\t\tNamespacedName: n,\n\t\t\t\t})\n\n\t\t\t\tBy(\"verifying reconciliation completes without error\")\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result).NotTo(Equal(ctrl.Result{}))\n\t\t\t}\n\t\t})\n\t})\n\n\tWhen(\"reconciling an initialized resource\", func() {\n\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\tBy(\"initializing the objectstore resource\")\n\n\t\t\tBy(\"setting the finalizer\")\n\t\t\tExpect(controllerutil.AddFinalizer(objectStore, objectStoreFinalizer)).To(BeTrue())\n\t\t\tExpect(k8sClient.Update(ctx, objectStore)).To(Succeed())\n\n\t\t\tBy(\"setting an unknown ready state\")\n\t\t\tobjectStore.Status.Conditions = []api.Condition{{\n\t\t\t\tType:               readyCondType,\n\t\t\t\tStatus:             v1.ConditionUnknown,\n\t\t\t\tReason:             \"Test\",\n\t\t\t\tMessage:            \"start condition\",\n\t\t\t\tLastTransitionTime: time.Now().Format(time.RFC3339Nano),\n\t\t\t}}\n\t\t\tExpect(k8sClient.Status().Update(ctx, objectStore)).To(Succeed())\n\t\t})\n\n\t\tIt(\"should create a new objectstore store\", func(ctx SpecContext) {\n\t\t\tBy(\"running Reconcile\")\n\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t// Fetch resource\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed())\n\n\t\t\tBy(\"checking if the ready state was updated\")\n\t\t\tExpect(objectStore.Status.Conditions).To(HaveLen(1))\n\t\t\tassertReadyStateMatches(objectStore.Status.Conditions[0], v1.ConditionTrue, stateReady, \"created or updated\", time.Now())\n\n\t\t\tBy(\"checking if the observed generation matches\")\n\t\t\tExpect(objectStore.Status.ObservedGeneration).To(Equal(objectStore.Generation))\n\n\t\t\tBy(\"checking if the objectstore store was created\")\n\t\t\tnatsObjectStore, err := jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tobjectstoreStatus, err := natsObjectStore.Status(ctx)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(objectstoreStatus.Bucket()).To(Equal(objectStoreName))\n\t\t\tExpect(objectstoreStatus.TTL()).To(Equal(5 * time.Minute))\n\t\t\tExpect(objectstoreStatus.IsCompressed()).To(BeTrue())\n\t\t})\n\n\t\tWhen(\"PreventUpdate is set\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"setting preventDelete on the resource\")\n\t\t\t\tobjectStore.Spec.PreventUpdate = true\n\t\t\t\tExpect(k8sClient.Update(ctx, objectStore)).To(Succeed())\n\t\t\t})\n\t\t\tIt(\"should create the objectstore\", func(ctx SpecContext) {\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that objectstore was created\")\n\t\t\t\t_, err = jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\t\t\tIt(\"should not update the objectstore\", func(ctx SpecContext) {\n\t\t\t\tBy(\"creating the objectstore\")\n\t\t\t\t_, err := jsClient.CreateObjectStore(ctx, emptyObjectStoreConfig)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that objectstore was not updated\")\n\t\t\t\tnatsObjectStore, err := jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\ts, err := natsObjectStore.Status(ctx)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(s.IsCompressed()).To(BeFalse())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"read-only mode is enabled\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"setting read only on the controller\")\n\t\t\t\treadOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontroller = &ObjectStoreReconciler{\n\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\tJetStreamController: readOnly,\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should not create the objectstore\", func(ctx SpecContext) {\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that no objectstore was created\")\n\t\t\t\t_, err = jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\t\t\t})\n\t\t\tIt(\"should not update the objectstore\", func(ctx SpecContext) {\n\t\t\t\tBy(\"creating the objectstore\")\n\t\t\t\t_, err := jsClient.CreateObjectStore(ctx, emptyObjectStoreConfig)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that objectstore was not updated\")\n\t\t\t\tnatsObjectStore, err := jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\ts, err := natsObjectStore.Status(ctx)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(s.IsCompressed()).To(BeFalse())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"namespace restriction is enabled\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"setting a namespace on the resource\")\n\t\t\t\tnamespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: alternateNamespace})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontroller = &ObjectStoreReconciler{\n\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\tJetStreamController: namespaced,\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should not create the objectstore\", func(ctx SpecContext) {\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that no objectstore was created\")\n\t\t\t\t_, err = jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\t\t\t})\n\t\t\tIt(\"should not update the objectstore\", func(ctx SpecContext) {\n\t\t\t\tBy(\"creating the objectstore\")\n\t\t\t\t_, err := jsClient.CreateObjectStore(ctx, emptyObjectStoreConfig)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that objectstore was not updated\")\n\t\t\t\tnatsObjectStore, err := jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\ts, err := natsObjectStore.Status(ctx)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(s.IsCompressed()).To(BeFalse())\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should update an existing objectstore\", func(ctx SpecContext) {\n\t\t\tBy(\"reconciling once to create the objectstore\")\n\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t// Fetch resource\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed())\n\t\t\tpreviousTransitionTime := objectStore.Status.Conditions[0].LastTransitionTime\n\n\t\t\tBy(\"updating the resource\")\n\t\t\tobjectStore.Spec.Description = \"new description\"\n\t\t\tobjectStore.Spec.TTL = \"1h\"\n\t\t\tExpect(k8sClient.Update(ctx, objectStore)).To(Succeed())\n\n\t\t\tBy(\"reconciling the updated resource\")\n\t\t\tresult, err = controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t// Fetch resource\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed())\n\n\t\t\tBy(\"checking if the state transition time was not updated\")\n\t\t\tExpect(objectStore.Status.Conditions).To(HaveLen(1))\n\t\t\tExpect(objectStore.Status.Conditions[0].LastTransitionTime).To(Equal(previousTransitionTime))\n\n\t\t\tBy(\"checking if the observed generation matches\")\n\t\t\tExpect(objectStore.Status.ObservedGeneration).To(Equal(objectStore.Generation))\n\n\t\t\tBy(\"checking if the objectstore was updated\")\n\t\t\tnatsObjectStore, err := jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tobjectStoreStatus, err := natsObjectStore.Status(ctx)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(objectStoreStatus.Bucket()).To(Equal(objectStoreName))\n\t\t\tExpect(objectStoreStatus.TTL()).To(Equal(1 * time.Hour))\n\t\t\tExpect(objectStoreStatus.IsCompressed()).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should set an error state when the nats server is not available\", func(ctx SpecContext) {\n\t\t\tBy(\"setting up controller with unavailable nats server\")\n\t\t\t// Setup client for not running server\n\t\t\t// Use actual test server to ensure port not used by other service on test instance\n\t\t\tsv := CreateTestServer()\n\t\t\tdisconnectedController, err := NewJSController(k8sClient, &NatsConfig{ServerURL: sv.ClientURL()}, &Config{})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tsv.Shutdown()\n\n\t\t\tcontroller := &ObjectStoreReconciler{\n\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\tJetStreamController: disconnectedController,\n\t\t\t}\n\n\t\t\tBy(\"reconciling resource\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(result).To(Equal(ctrl.Result{}))\n\t\t\tExpect(err).To(HaveOccurred()) // Will be re-queued with back-off\n\n\t\t\t// Fetch resource\n\t\t\terr = k8sClient.Get(ctx, typeNamespacedName, objectStore)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"checking if the status was updated\")\n\t\t\tExpect(objectStore.Status.Conditions).To(HaveLen(1))\n\t\t\tassertReadyStateMatches(\n\t\t\t\tobjectStore.Status.Conditions[0],\n\t\t\t\tv1.ConditionFalse,\n\t\t\t\tstateErrored,\n\t\t\t\t\"create or update objectstore:\",\n\t\t\t\ttime.Now(),\n\t\t\t)\n\n\t\t\tBy(\"checking if the observed generation does not match\")\n\t\t\tExpect(objectStore.Status.ObservedGeneration).ToNot(Equal(objectStore.Generation))\n\t\t})\n\n\t\tWhen(\"the resource is marked for deletion\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"marking the resource for deletion\")\n\t\t\t\tExpect(k8sClient.Delete(ctx, objectStore)).To(Succeed())\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed()) // re-fetch after update\n\t\t\t})\n\n\t\t\tIt(\"should succeed deleting a not existing objectstore\", func(ctx SpecContext) {\n\t\t\t\tBy(\"reconciling\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\tWithArguments(ctx, typeNamespacedName, objectStore).\n\t\t\t\t\tShouldNot(Succeed())\n\t\t\t})\n\n\t\t\tWhen(\"the underlying objectstore exists\", func() {\n\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\tBy(\"creating the objectstore on the nats server\")\n\t\t\t\t\t_, err := jsClient.CreateObjectStore(ctx, emptyObjectStoreConfig)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t})\n\n\t\t\t\tAfterEach(func(ctx SpecContext) {\n\t\t\t\t\terr := jsClient.DeleteObjectStore(ctx, objectStoreName)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tExpect(err).To(MatchError(jetstream.ErrStreamNotFound))\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tIt(\"should delete the objectstore\", func(ctx SpecContext) {\n\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\tBy(\"checking that the objectstore is deleted\")\n\t\t\t\t\t_, err = jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\n\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, objectStore).\n\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t})\n\n\t\t\t\tWhen(\"PreventDelete is set\", func() {\n\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"setting preventDelete on the resource\")\n\t\t\t\t\t\tobjectStore.Spec.PreventDelete = true\n\t\t\t\t\t\tExpect(k8sClient.Update(ctx, objectStore)).To(Succeed())\n\t\t\t\t\t})\n\t\t\t\t\tIt(\"Should delete the resource and not delete the nats objectstore\", func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\tBy(\"checking that the objectstore is not deleted\")\n\t\t\t\t\t\t_, err = jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, objectStore).\n\t\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tWhen(\"read only is set\", func() {\n\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"setting read only on the controller\")\n\t\t\t\t\t\treadOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tcontroller = &ObjectStoreReconciler{\n\t\t\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\t\t\tJetStreamController: readOnly,\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\tIt(\"should delete the resource and not delete the objectstore\", func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\tBy(\"checking that the objectstore is not deleted\")\n\t\t\t\t\t\t_, err = jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, objectStore).\n\t\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tWhen(\"controller is restricted to different namespace\", func() {\n\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\tnamespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: alternateNamespace})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tcontroller = &ObjectStoreReconciler{\n\t\t\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\t\t\tJetStreamController: namespaced,\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\tIt(\"should not delete the resource and objectstore\", func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\tBy(\"checking that the objectstore is not deleted\")\n\t\t\t\t\t\t_, err = jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\t\tBy(\"checking that the finalizer is not removed\")\n\t\t\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, objectStore)).To(Succeed())\n\t\t\t\t\t\tExpect(objectStore.Finalizers).To(ContainElement(objectStoreFinalizer))\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should update objectstore on different server as specified in spec\", func(ctx SpecContext) {\n\t\t\tBy(\"setting up the alternative server\")\n\t\t\t// Setup altClient for alternate server\n\t\t\taltServer := CreateTestServer()\n\t\t\tdefer altServer.Shutdown()\n\n\t\t\tBy(\"setting the server in the objectstore spec\")\n\t\t\tobjectStore.Spec.Servers = []string{altServer.ClientURL()}\n\t\t\tExpect(k8sClient.Update(ctx, objectStore)).To(Succeed())\n\n\t\t\tBy(\"checking precondition, that the objectstore does not yet exist\")\n\t\t\t_, err := jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\n\t\t\tBy(\"reconciling the resource\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\tconnPool := newConnPool(0)\n\t\t\tconn, err := connPool.Get(&NatsConfig{ServerURL: altServer.ClientURL()}, true)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdomain := \"\"\n\n\t\t\tBy(\"checking if the objectstore was created on the alternative server\")\n\t\t\taltClient, err := CreateJetStreamClient(conn, true, domain)\n\t\t\tdefer conn.Close()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t_, err = altClient.ObjectStore(ctx, objectStoreName)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"checking that the objectstore was NOT created on the original server\")\n\t\t\t_, err = jsClient.ObjectStore(ctx, objectStoreName)\n\t\t\tExpect(err).To(MatchError(jetstream.ErrBucketNotFound))\n\t\t})\n\t})\n})\n\nfunc Test_mapobjectstoreSpecToConfig(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tspec    *api.ObjectStoreSpec\n\t\twant    jetstream.ObjectStoreConfig\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"empty spec\",\n\t\t\tspec:    &api.ObjectStoreSpec{},\n\t\t\twant:    jetstream.ObjectStoreConfig{},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"full spec\",\n\t\t\tspec: &api.ObjectStoreSpec{\n\t\t\t\tDescription: \"objectstore description\",\n\t\t\t\tMaxBytes:    1048576,\n\t\t\t\tTTL:         \"1h\",\n\t\t\t\tBucket:      \"objectstore-name\",\n\t\t\t\tPlacement: &api.StreamPlacement{\n\t\t\t\t\tCluster: \"test-cluster\",\n\t\t\t\t\tTags:    []string{\"tag\"},\n\t\t\t\t},\n\t\t\t\tReplicas:    3,\n\t\t\t\tCompression: true,\n\t\t\t\tStorage:     \"memory\",\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"foo\": \"bar\",\n\t\t\t\t},\n\t\t\t\tBaseStreamConfig: api.BaseStreamConfig{\n\t\t\t\t\tPreventDelete: false,\n\t\t\t\t\tPreventUpdate: false,\n\t\t\t\t\tConnectionOpts: api.ConnectionOpts{\n\t\t\t\t\t\tAccount: \"\",\n\t\t\t\t\t\tCreds:   \"\",\n\t\t\t\t\t\tNkey:    \"\",\n\t\t\t\t\t\tServers: nil,\n\t\t\t\t\t\tTLS:     &api.TLS{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: jetstream.ObjectStoreConfig{\n\t\t\t\tBucket:      \"objectstore-name\",\n\t\t\t\tDescription: \"objectstore description\",\n\t\t\t\tMaxBytes:    1048576,\n\t\t\t\tTTL:         time.Hour,\n\t\t\t\tStorage:     jetstream.MemoryStorage,\n\t\t\t\tReplicas:    3,\n\t\t\t\tPlacement: &jetstream.Placement{\n\t\t\t\t\tCluster: \"test-cluster\",\n\t\t\t\t\tTags:    []string{\"tag\"},\n\t\t\t\t},\n\t\t\t\tCompression: true,\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"foo\": \"bar\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert := assert.New(t)\n\t\t\tgot, err := objectStoreSpecToConfig(tt.spec)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"objectStoreSpecToConfig() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Compare nested structs\n\t\t\tassert.EqualValues(tt.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/controller/register.go",
    "content": "package controller\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\tctrl \"sigs.k8s.io/controller-runtime\"\n)\n\n// The Config contains parameters to be considered by the registered controllers.\n//\n// ReadOnly prevents controllers from actually applying changes NATS resources.\n//\n// Namespace restricts the controller to resources of the given namespace.\ntype Config struct {\n\tReadOnly               bool\n\tNamespace              string\n\tRequeueInterval        time.Duration\n\tCacheDir               string\n\tHealthProbeBindAddress string\n}\n\n// RegisterAll registers all available jetStream controllers to the manager.\n// natsCfg is specific to the nats server connection.\n// controllerCfg defines behaviour of the registered controllers.\nfunc RegisterAll(mgr ctrl.Manager, clientConfig *NatsConfig, config *Config) error {\n\tscheme := mgr.GetScheme()\n\n\t// Register controllers\n\tbaseController, err := NewJSController(mgr.GetClient(), clientConfig, config)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create base jetstream controller: %w\", err)\n\t}\n\n\tif err := (&AccountReconciler{\n\t\tScheme:              scheme,\n\t\tJetStreamController: baseController,\n\t}).SetupWithManager(mgr); err != nil {\n\t\treturn fmt.Errorf(\"unable to create account controller: %w\", err)\n\t}\n\n\tif err := (&ConsumerReconciler{\n\t\tScheme:              scheme,\n\t\tJetStreamController: baseController,\n\t}).SetupWithManager(mgr); err != nil {\n\t\treturn fmt.Errorf(\"unable to create consumer controller: %w\", err)\n\t}\n\n\tif err := (&KeyValueReconciler{\n\t\tScheme:              scheme,\n\t\tJetStreamController: baseController,\n\t}).SetupWithManager(mgr); err != nil {\n\t\treturn fmt.Errorf(\"unable to create key-value controller: %w\", err)\n\t}\n\n\tif err := (&ObjectStoreReconciler{\n\t\tScheme:              scheme,\n\t\tJetStreamController: baseController,\n\t}).SetupWithManager(mgr); err != nil {\n\t\treturn fmt.Errorf(\"unable to create object store controller: %w\", err)\n\t}\n\n\tif err := (&StreamReconciler{\n\t\tScheme:              scheme,\n\t\tJetStreamController: baseController,\n\t}).SetupWithManager(mgr); err != nil {\n\t\treturn fmt.Errorf(\"unable to create stream controller: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/controller/stream_controller.go",
    "content": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS 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\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/nats-io/jsm.go\"\n\tjsmapi \"github.com/nats-io/jsm.go/api\"\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\t\"github.com/nats-io/nats.go/jetstream\"\n\tv1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/klog/v2\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/predicate\"\n)\n\n// StreamReconciler reconciles a Stream object\ntype StreamReconciler struct {\n\tScheme *runtime.Scheme\n\n\tJetStreamController\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//\n// It performs three main operations:\n// - Initialize finalizer and ready condition if not present\n// - Delete stream if it is marked for deletion.\n// - Create or Update the stream\n//\n// A call to reconcile may perform only one action, expecting the reconciliation to be triggered again by an update.\n// For example: Setting the finalizer triggers a second reconciliation. Reconcile returns after setting the finalizer,\n// to prevent parallel reconciliations performing the same steps.\nfunc (r *StreamReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := klog.FromContext(ctx)\n\n\tif ok := r.ValidNamespace(req.Namespace); !ok {\n\t\tlog.Info(\"Controller restricted to namespace, skipping reconciliation.\")\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Fetch stream resource\n\tstream := &api.Stream{}\n\tif err := r.Get(ctx, req.NamespacedName, stream); err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\tlog.Info(\"Stream resource deleted.\", \"streamName\", req.NamespacedName.String())\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\treturn ctrl.Result{}, fmt.Errorf(\"get stream resource '%s': %w\", req.NamespacedName.String(), err)\n\t}\n\n\tlog = log.WithValues(\"streamName\", stream.Spec.Name)\n\n\t// Update ready status to unknown when no status is set\n\tif len(stream.Status.Conditions) == 0 {\n\t\tlog.Info(\"Setting initial ready condition to unknown.\")\n\t\tstream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionUnknown, stateReconciling, \"Starting reconciliation\")\n\t\terr := r.Status().Update(ctx, stream)\n\t\tif err != nil {\n\t\t\t// If we get a conflict error, another reconciliation has already updated the status.\n\t\t\t// Just requeue and let the next reconciliation handle it.\n\t\t\tif apierrors.IsConflict(err) {\n\t\t\t\treturn ctrl.Result{Requeue: true}, nil\n\t\t\t}\n\t\t\treturn ctrl.Result{}, fmt.Errorf(\"set condition unknown: %w\", err)\n\t\t}\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// Check Deletion\n\tmarkedForDeletion := stream.GetDeletionTimestamp() != nil\n\tif markedForDeletion {\n\t\tif controllerutil.ContainsFinalizer(stream, streamFinalizer) {\n\t\t\terr := r.deleteStream(ctx, log, stream)\n\t\t\tif err != nil {\n\t\t\t\treturn ctrl.Result{}, fmt.Errorf(\"delete stream: %w\", err)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Info(\"Stream marked for deletion and already finalized. Ignoring.\")\n\t\t}\n\n\t\treturn ctrl.Result{}, nil\n\t}\n\n\t// Add finalizer\n\tif !controllerutil.ContainsFinalizer(stream, streamFinalizer) {\n\t\tlog.Info(\"Adding stream finalizer.\")\n\t\tif ok := controllerutil.AddFinalizer(stream, streamFinalizer); !ok {\n\t\t\treturn ctrl.Result{}, errors.New(\"failed to add finalizer to stream resource\")\n\t\t}\n\n\t\tif err := r.Update(ctx, stream); err != nil {\n\t\t\treturn ctrl.Result{}, fmt.Errorf(\"update stream resource to add finalizer: %w\", err)\n\t\t}\n\t\t// After we have added the finalizer, we need to requeue to make sure we reconcile the\n\t\t// rest of the object. Just updating metadata won't make the API server update generation\n\t\t// so the update above shouldn't trigger a new reconciliation.\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t}\n\n\t// Create or update stream\n\tif err := r.createOrUpdate(ctx, log, stream); err != nil {\n\t\treturn ctrl.Result{}, fmt.Errorf(\"create or update: %s\", err)\n\t}\n\n\treturn ctrl.Result{RequeueAfter: r.RequeueInterval()}, nil\n}\n\nfunc (r *StreamReconciler) deleteStream(ctx context.Context, log logr.Logger, stream *api.Stream) error {\n\t// Set status to false\n\tstream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionFalse, stateFinalizing, \"Performing finalizer operations.\")\n\tif err := r.Status().Update(ctx, stream); err != nil {\n\t\treturn fmt.Errorf(\"update ready condition: %w\", err)\n\t}\n\n\tstoredState, err := getStoredStreamState(stream)\n\tif err != nil {\n\t\tlog.Error(err, \"Failed to fetch stored state.\")\n\t}\n\n\tif !stream.Spec.PreventDelete && !r.ReadOnly() {\n\t\tlog.Info(\"Deleting stream.\")\n\t\terr := r.WithJSMClient(stream.Spec.ConnectionOpts, stream.Namespace, func(js *jsm.Manager) error {\n\t\t\t_, err := getServerStreamState(js, stream)\n\t\t\t// If we have no known state for this stream it has never been reconciled.\n\t\t\t// If we are also receiving an error fetching state, either the stream does not exist\n\t\t\t// or this resource config is invalid.\n\t\t\tif err != nil && storedState == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn js.DeleteStream(stream.Spec.Name)\n\t\t})\n\t\tif jsmapi.IsNatsErr(err, JSStreamNotFoundErr) {\n\t\t\tlog.Info(\"Stream does not exist, unable to delete.\", \"streamName\", stream.Spec.Name)\n\t\t} else if err != nil && storedState == nil {\n\t\t\tlog.Info(\"Stream not reconciled and no state received from server. Removing finalizer.\")\n\t\t} else if err != nil {\n\t\t\treturn fmt.Errorf(\"delete stream during finalization: %w\", err)\n\t\t}\n\t} else {\n\t\tlog.Info(\"Skipping stream deletion.\",\n\t\t\t\"preventDelete\", stream.Spec.PreventDelete,\n\t\t\t\"read-only\", r.ReadOnly(),\n\t\t)\n\t}\n\n\tlog.Info(\"Removing stream finalizer.\")\n\tif ok := controllerutil.RemoveFinalizer(stream, streamFinalizer); !ok {\n\t\treturn errors.New(\"failed to remove stream finalizer\")\n\t}\n\tif err := r.Update(ctx, stream); err != nil {\n\t\treturn fmt.Errorf(\"remove finalizer: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (r *StreamReconciler) createOrUpdate(ctx context.Context, log logr.Logger, stream *api.Stream) error {\n\t// CreateOrUpdateStream is called on every reconciliation when the stream is not to be deleted.\n\terr := r.WithJSMClient(stream.Spec.ConnectionOpts, stream.Namespace, func(js *jsm.Manager) error {\n\t\tstoredState, err := getStoredStreamState(stream)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to fetch stored stream state\")\n\t\t}\n\n\t\tserverState, err := getServerStreamState(js, stream)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Map spec to stream targetConfig, passing current server state for context\n\t\ttargetConfig, err := streamSpecToConfig(&stream.Spec, serverState)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"map spec to stream targetConfig: %w\", err)\n\t\t}\n\n\t\t// Check against known state. Skip Update if converged.\n\t\t// Storing returned state from the server avoids have to\n\t\t// check default values or call Update on already converged resources\n\t\tif storedState != nil && serverState != nil && stream.Status.ObservedGeneration == stream.Generation {\n\t\t\tdiff := compareConfigState(storedState, serverState)\n\n\t\t\tif diff == \"\" {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tlog.Info(\"Stream config drifted from desired state.\", \"diff\", diff)\n\t\t}\n\n\t\tif r.ReadOnly() {\n\t\t\tlog.Info(\"Skipping stream creation or update.\",\n\t\t\t\t\"read-only\", r.ReadOnly(),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\n\t\tvar updatedStream *jsm.Stream\n\t\terr = nil\n\n\t\tif serverState == nil {\n\t\t\tlog.Info(\"Creating Stream.\")\n\t\t\tupdatedStream, err = js.NewStream(stream.Spec.Name, targetConfig...)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else if !stream.Spec.PreventUpdate {\n\t\t\tlog.Info(\"Updating Stream.\")\n\t\t\ts, err := js.LoadStream(stream.Spec.Name)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\terr = s.UpdateConfiguration(*serverState, targetConfig...)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tupdatedStream, err = js.LoadStream(stream.Spec.Name)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tdiff := compareConfigState(updatedStream.Configuration(), *serverState)\n\t\t\tlog.Info(\"Updated Stream.\", \"diff\", diff)\n\t\t} else {\n\t\t\tlog.Info(\"Skipping Stream update.\",\n\t\t\t\t\"preventUpdate\", stream.Spec.PreventUpdate,\n\t\t\t)\n\t\t}\n\n\t\tif updatedStream != nil {\n\t\t\t// Store known state in annotation\n\t\t\tupdatedState, err := json.Marshal(updatedStream.Configuration())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif stream.Annotations == nil {\n\t\t\t\tstream.Annotations = map[string]string{}\n\t\t\t}\n\t\t\tstream.Annotations[stateAnnotationStream] = string(updatedState)\n\n\t\t\treturn r.Update(ctx, stream)\n\t\t}\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\terr = fmt.Errorf(\"create or update stream: %w\", err)\n\t\tstream.Status.Conditions = updateReadyCondition(stream.Status.Conditions, v1.ConditionFalse, stateErrored, err.Error())\n\t\tif err := r.Status().Update(ctx, stream); err != nil {\n\t\t\tlog.Error(err, \"Failed to update ready condition to Errored.\")\n\t\t}\n\t\treturn err\n\t}\n\n\t// update the observed generation and ready status\n\tstream.Status.ObservedGeneration = stream.Generation\n\tstream.Status.Conditions = updateReadyCondition(\n\t\tstream.Status.Conditions,\n\t\tv1.ConditionTrue,\n\t\tstateReady,\n\t\t\"Stream successfully created or updated.\",\n\t)\n\terr = r.Status().Update(ctx, stream)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"update ready condition: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc getStoredStreamState(stream *api.Stream) (*jsmapi.StreamConfig, error) {\n\tvar storedState *jsmapi.StreamConfig\n\tif state, ok := stream.Annotations[stateAnnotationStream]; ok {\n\t\terr := json.Unmarshal([]byte(state), &storedState)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn storedState, nil\n}\n\n// Fetch the current state of the stream from the server.\n// JSStreamNotFoundErr is considered a valid response and does not return error\nfunc getServerStreamState(jsm *jsm.Manager, stream *api.Stream) (*jsmapi.StreamConfig, error) {\n\ts, err := jsm.LoadStream(stream.Spec.Name)\n\tif jsmapi.IsNatsErr(err, JSStreamNotFoundErr) {\n\t\treturn nil, nil\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstreamCfg := s.Configuration()\n\treturn &streamCfg, nil\n}\n\nfunc streamSpecToConfig(spec *api.StreamSpec, currentConfig *jsmapi.StreamConfig) ([]jsm.StreamOption, error) {\n\topts := []jsm.StreamOption{\n\t\tjsm.StreamDescription(spec.Description),\n\t\tjsm.Subjects(spec.Subjects...),\n\t\tjsm.MaxConsumers(spec.MaxConsumers),\n\t\tjsm.MaxMessages(int64(spec.MaxMsgs)),\n\t\tjsm.MaxBytes(int64(spec.MaxBytes)),\n\t\tjsm.MaxMessageSize(int32(spec.MaxMsgSize)),\n\t\tjsm.Replicas(spec.Replicas),\n\t\tjsm.StreamMetadata(spec.Metadata),\n\t}\n\n\t// Set not directly mapped fields\n\n\t// retention\n\tswitch spec.Retention {\n\tcase \"limits\":\n\t\topts = append(opts, jsm.LimitsRetention())\n\tcase \"interest\":\n\t\topts = append(opts, jsm.InterestRetention())\n\tcase \"workqueue\":\n\t\topts = append(opts, jsm.WorkQueueRetention())\n\t}\n\n\t// maxMsgsPerSubject\n\tif spec.MaxMsgsPerSubject > 0 {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.MaxMsgsPer = int64(spec.MaxMsgsPerSubject)\n\t\t\treturn nil\n\t\t})\n\t}\n\n\t// maxAge\n\tif spec.MaxAge != \"\" {\n\t\td, err := time.ParseDuration(spec.MaxAge)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"parse max age: %w\", err)\n\t\t}\n\t\topts = append(opts, jsm.MaxAge(d))\n\t}\n\n\t// storage\n\tswitch spec.Storage {\n\tcase \"file\":\n\t\topts = append(opts, jsm.FileStorage())\n\tcase \"memory\":\n\t\topts = append(opts, jsm.MemoryStorage())\n\t}\n\n\t// discard\n\tswitch spec.Discard {\n\tcase \"old\":\n\t\topts = append(opts, jsm.DiscardOld())\n\tcase \"new\":\n\t\topts = append(opts, jsm.DiscardNew())\n\t}\n\n\t// noAck\n\tif spec.NoAck {\n\t\topts = append(opts, jsm.NoAck())\n\t}\n\n\t// duplicateWindow\n\tif spec.DuplicateWindow != \"\" {\n\t\td, err := time.ParseDuration(spec.DuplicateWindow)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"parse duplicate window: %w\", err)\n\t\t}\n\t\topts = append(opts, jsm.DuplicateWindow(d))\n\t}\n\n\t// placement\n\tif spec.Placement != nil {\n\t\tif spec.Placement.Cluster != \"\" {\n\t\t\topts = append(opts, jsm.PlacementCluster(spec.Placement.Cluster))\n\t\t}\n\t\tif spec.Placement.Tags != nil {\n\t\t\topts = append(opts, jsm.PlacementTags(spec.Placement.Tags...))\n\t\t}\n\t} else if currentConfig != nil && currentConfig.Placement != nil {\n\t\t// Only clear placement if the current config has placement set.\n\t\t// This avoids triggering NATS error 10123: \"can not move and scale a stream in a single update\"\n\t\t// when we're only trying to change replicas.\n\t\topts = append(opts, jsm.PlacementCluster(\"\"))\n\t}\n\t// If spec.Placement is nil and currentConfig.Placement is also nil/empty,\n\t// we don't set any placement option, avoiding unnecessary placement changes.\n\n\t// mirror\n\tif spec.Mirror != nil {\n\t\tss, err := mapJSMStreamSource(spec.Mirror)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"map mirror stream source: %w\", err)\n\t\t}\n\t\topts = append(opts, jsm.Mirror(ss))\n\t}\n\n\t// sources\n\tif spec.Sources != nil {\n\t\tstreamSources := make([]*jsmapi.StreamSource, 0)\n\t\tfor _, source := range spec.Sources {\n\t\t\tss, err := mapJSMStreamSource(source)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"map stream source: %w\", err)\n\t\t\t}\n\t\t\tstreamSources = append(streamSources, ss)\n\t\t}\n\n\t\topts = append(opts, jsm.Sources(streamSources...))\n\t}\n\n\t// compression\n\tswitch spec.Compression {\n\tcase \"s2\":\n\t\topts = append(opts, jsm.Compression(jsmapi.S2Compression))\n\tcase \"none\":\n\t\topts = append(opts, jsm.Compression(jsmapi.NoCompression))\n\t}\n\n\t// subjectTransform\n\tif spec.SubjectTransform != nil {\n\t\tst := &jsmapi.SubjectTransformConfig{\n\t\t\tSource:      spec.SubjectTransform.Source,\n\t\t\tDestination: spec.SubjectTransform.Dest,\n\t\t}\n\n\t\topts = append(opts, jsm.SubjectTransform(st))\n\t}\n\n\t// rePublish\n\tif spec.RePublish != nil {\n\t\tr := &jsmapi.RePublish{\n\t\t\tSource:      spec.RePublish.Source,\n\t\t\tDestination: spec.RePublish.Destination,\n\t\t\tHeadersOnly: spec.RePublish.HeadersOnly,\n\t\t}\n\n\t\topts = append(opts, jsm.Republish(r))\n\t}\n\n\tif spec.Sealed {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.Sealed = spec.Sealed\n\t\t\treturn nil\n\t\t})\n\t}\n\n\t// denyDelete\n\tif spec.DenyDelete {\n\t\topts = append(opts, jsm.DenyDelete())\n\t}\n\n\t// denyPurge\n\tif spec.DenyPurge {\n\t\topts = append(opts, jsm.DenyPurge())\n\t}\n\n\t// allowDirect\n\tif spec.AllowDirect {\n\t\topts = append(opts, jsm.AllowDirect())\n\t}\n\n\t// allowRollup\n\tif spec.AllowRollup {\n\t\topts = append(opts, jsm.AllowRollup())\n\t}\n\n\t// mirrorDirect\n\tif spec.MirrorDirect {\n\t\topts = append(opts, jsm.MirrorDirect())\n\t}\n\n\t// discardPerSubject\n\tif spec.DiscardPerSubject {\n\t\topts = append(opts, jsm.DiscardNewPerSubject())\n\t}\n\n\t// firstSequence\n\tif spec.FirstSequence > 0 {\n\t\topts = append(opts, jsm.FirstSequence(spec.FirstSequence))\n\t}\n\n\t// consumerLimits\n\tif spec.ConsumerLimits != nil {\n\t\tcl := jsmapi.StreamConsumerLimits{\n\t\t\tMaxAckPending: spec.ConsumerLimits.MaxAckPending,\n\t\t}\n\t\tif spec.ConsumerLimits.InactiveThreshold != \"\" {\n\t\t\tinactiveThreshold, err := time.ParseDuration(spec.ConsumerLimits.InactiveThreshold)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"parse inactive threshold: %w\", err)\n\t\t\t}\n\t\t\tcl.InactiveThreshold = inactiveThreshold\n\t\t}\n\n\t\topts = append(opts, jsm.ConsumerLimits(cl))\n\t}\n\n\t// allowMsgTtl\n\tif spec.AllowMsgTTL {\n\t\topts = append(opts, jsm.AllowMsgTTL())\n\t}\n\n\t// subjectDeleteMarkerTtl\n\tif spec.SubjectDeleteMarkerTTL != \"\" {\n\t\td, err := time.ParseDuration(spec.SubjectDeleteMarkerTTL)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"parse subject delete marker TTL: %w\", err)\n\t\t}\n\t\topts = append(opts, jsm.SubjectDeleteMarkerTTL(d))\n\t}\n\n\t// allowMsgCounter\n\tif spec.AllowMsgCounter {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.AllowMsgCounter = true\n\t\t\treturn nil\n\t\t})\n\t}\n\n\t// allowAtomicPublish\n\tif spec.AllowAtomicPublish {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.AllowAtomicPublish = true\n\t\t\treturn nil\n\t\t})\n\t}\n\n\t// allowMsgSchedules\n\tif spec.AllowMsgSchedules {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.AllowMsgSchedules = true\n\t\t\treturn nil\n\t\t})\n\t}\n\n\t// persistMode\n\tif spec.PersistMode == \"async\" {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.PersistMode = jsmapi.AsyncPersistMode\n\t\t\treturn nil\n\t\t})\n\t} else if spec.PersistMode == \"default\" {\n\t\topts = append(opts, func(o *jsmapi.StreamConfig) error {\n\t\t\to.PersistMode = jsmapi.DefaultPersistMode\n\t\t\treturn nil\n\t\t})\n\t}\n\n\treturn opts, nil\n}\n\nfunc mapStreamSource(ss *api.StreamSource) (*jetstream.StreamSource, error) {\n\tjss := &jetstream.StreamSource{\n\t\tName:          ss.Name,\n\t\tFilterSubject: ss.FilterSubject,\n\t}\n\n\tif ss.OptStartSeq > 0 {\n\t\tjss.OptStartSeq = uint64(ss.OptStartSeq)\n\t}\n\n\tif ss.OptStartTime != \"\" {\n\t\tt, err := time.Parse(time.RFC3339, ss.OptStartTime)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"parse opt start time: %w\", err)\n\t\t}\n\t\tjss.OptStartTime = &t\n\t}\n\n\tif ss.ExternalAPIPrefix != \"\" || ss.ExternalDeliverPrefix != \"\" {\n\t\tjss.External = &jetstream.ExternalStream{\n\t\t\tAPIPrefix:     ss.ExternalAPIPrefix,\n\t\t\tDeliverPrefix: ss.ExternalDeliverPrefix,\n\t\t}\n\t}\n\n\tfor _, transform := range ss.SubjectTransforms {\n\t\tjss.SubjectTransforms = append(jss.SubjectTransforms, jetstream.SubjectTransformConfig{\n\t\t\tSource:      transform.Source,\n\t\t\tDestination: transform.Dest,\n\t\t})\n\t}\n\n\treturn jss, nil\n}\n\nfunc mapJSMStreamSource(ss *api.StreamSource) (*jsmapi.StreamSource, error) {\n\tjss := &jsmapi.StreamSource{\n\t\tName:          ss.Name,\n\t\tFilterSubject: ss.FilterSubject,\n\t}\n\n\tif ss.OptStartSeq > 0 {\n\t\tjss.OptStartSeq = uint64(ss.OptStartSeq)\n\t}\n\n\tif ss.OptStartTime != \"\" {\n\t\tt, err := time.Parse(time.RFC3339, ss.OptStartTime)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"parse opt start time: %w\", err)\n\t\t}\n\t\tjss.OptStartTime = &t\n\t}\n\n\tif ss.ExternalAPIPrefix != \"\" || ss.ExternalDeliverPrefix != \"\" {\n\t\tjss.External = &jsmapi.ExternalStream{\n\t\t\tApiPrefix:     ss.ExternalAPIPrefix,\n\t\t\tDeliverPrefix: ss.ExternalDeliverPrefix,\n\t\t}\n\t}\n\n\tfor _, transform := range ss.SubjectTransforms {\n\t\tjss.SubjectTransforms = append(jss.SubjectTransforms, jsmapi.SubjectTransformConfig{\n\t\t\tSource:      transform.Source,\n\t\t\tDestination: transform.Dest,\n\t\t})\n\t}\n\n\treturn jss, nil\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *StreamReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&api.Stream{}).\n\t\tWithEventFilter(predicate.GenerationChangedPredicate{}).\n\t\tWithOptions(controller.Options{\n\t\t\tMaxConcurrentReconciles: 1,\n\t\t}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "internal/controller/stream_controller_test.go",
    "content": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS 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\"os\"\n\t\"testing\"\n\t\"time\"\n\n\tjsmapi \"github.com/nats-io/jsm.go/api\"\n\tnatsserver \"github.com/nats-io/nats-server/v2/test\"\n\t\"github.com/nats-io/nats.go\"\n\t\"github.com/nats-io/nats.go/jetstream\"\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\t\"github.com/stretchr/testify/assert\"\n\tv1 \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n)\n\nvar _ = Describe(\"Stream Controller\", func() {\n\t// The test stream resource\n\tconst resourceName = \"test-stream\"\n\tconst streamName = \"orders\"\n\n\tconst alternateResource = \"alternate-stream\"\n\tconst alternateNamespace = \"alternate-namespace\"\n\n\ttypeNamespacedName := types.NamespacedName{\n\t\tName:      resourceName,\n\t\tNamespace: \"default\",\n\t}\n\tstream := &api.Stream{}\n\n\t// The tested controller\n\tvar controller *StreamReconciler\n\n\t// Config to create minimal nats stream\n\temptyStreamConfig := jetstream.StreamConfig{\n\t\tName:      streamName,\n\t\tReplicas:  1,\n\t\tRetention: jetstream.WorkQueuePolicy,\n\t\tDiscard:   jetstream.DiscardOld,\n\t\tStorage:   jetstream.FileStorage,\n\t}\n\n\tBeforeEach(func(ctx SpecContext) {\n\t\tBy(\"creating a test stream resource\")\n\t\terr := k8sClient.Get(ctx, typeNamespacedName, stream)\n\t\tif err != nil && k8serrors.IsNotFound(err) {\n\t\t\tresource := &api.Stream{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      resourceName,\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t},\n\t\t\t\tSpec: api.StreamSpec{\n\t\t\t\t\tName:        streamName,\n\t\t\t\t\tReplicas:    1,\n\t\t\t\t\tSubjects:    []string{\"tests.*\"},\n\t\t\t\t\tDescription: \"test stream\",\n\t\t\t\t\tRetention:   \"workqueue\",\n\t\t\t\t\tDiscard:     \"old\",\n\t\t\t\t\tStorage:     \"file\",\n\t\t\t\t},\n\t\t\t}\n\t\t\tExpect(k8sClient.Create(ctx, resource)).To(Succeed())\n\t\t\t// Re-fetch stream\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed())\n\t\t}\n\n\t\tBy(\"checking precondition: nats stream does not exist\")\n\t\t_, err = jsClient.Stream(ctx, streamName)\n\t\tExpect(err).To(MatchError(jetstream.ErrStreamNotFound))\n\n\t\tBy(\"setting up the tested controller\")\n\t\tcontroller = &StreamReconciler{\n\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\tJetStreamController: baseController,\n\t\t}\n\t})\n\n\tAfterEach(func(ctx SpecContext) {\n\t\tBy(\"removing the test stream resource\")\n\t\tresource := &api.Stream{}\n\t\terr := k8sClient.Get(ctx, typeNamespacedName, resource)\n\t\tif err != nil {\n\t\t\tExpect(err).To(MatchError(k8serrors.IsNotFound, \"Is not found\"))\n\t\t} else {\n\t\t\tif controllerutil.ContainsFinalizer(resource, streamFinalizer) {\n\t\t\t\tBy(\"removing the finalizer\")\n\t\t\t\tcontrollerutil.RemoveFinalizer(resource, streamFinalizer)\n\t\t\t\tExpect(k8sClient.Update(ctx, resource)).To(Succeed())\n\t\t\t}\n\n\t\t\tBy(\"removing the stream resource\")\n\t\t\tExpect(k8sClient.Delete(ctx, resource)).\n\t\t\t\tTo(SatisfyAny(\n\t\t\t\t\tSucceed(),\n\t\t\t\t\tMatchError(k8serrors.IsNotFound, \"is not found\"),\n\t\t\t\t))\n\t\t}\n\n\t\tBy(\"deleting the nats stream\")\n\t\tExpect(jsClient.DeleteStream(ctx, streamName)).\n\t\t\tTo(SatisfyAny(\n\t\t\t\tSucceed(),\n\t\t\t\tMatchError(jetstream.ErrStreamNotFound),\n\t\t\t))\n\t})\n\n\tWhen(\"reconciling a not existing resource\", func() {\n\t\tIt(\"should stop reconciliation without error\", func(ctx SpecContext) {\n\t\t\tBy(\"reconciling the created resource\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\tNamespace: \"fake\",\n\t\t\t\t\tName:      \"not-existing\",\n\t\t\t\t},\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result).To(Equal(ctrl.Result{}))\n\t\t})\n\t})\n\n\tWhen(\"reconciling a not initialized resource\", func() {\n\t\tIt(\"should initialize a new resource\", func(ctx SpecContext) {\n\t\t\tBy(\"re-queueing until it is initialized\")\n\t\t\t// Initialization can require multiple reconciliation loops\n\t\t\tEventually(func(ctx SpecContext) *api.Stream {\n\t\t\t\t_, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tgot := &api.Stream{}\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, got)).To(Succeed())\n\t\t\t\treturn got\n\t\t\t}).WithContext(ctx).\n\t\t\t\tShould(SatisfyAll(\n\t\t\t\t\tHaveField(\"Finalizers\", HaveExactElements(streamFinalizer)),\n\t\t\t\t\tHaveField(\"Status.Conditions\", Not(BeEmpty())),\n\t\t\t\t))\n\n\t\t\tBy(\"validating the ready condition\")\n\t\t\t// Fetch stream\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed())\n\t\t\tExpect(stream.Status.Conditions).To(HaveLen(1))\n\n\t\t\tassertReadyStateMatches(stream.Status.Conditions[0], v1.ConditionUnknown, stateReconciling, \"Starting reconciliation\", time.Now())\n\t\t})\n\t})\n\n\tWhen(\"reconciling a resource in a different namespace\", func() {\n\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\tBy(\"creating a stream resource in an alternate namespace while namespaced\")\n\t\t\talternateNamespaceResource := &api.Stream{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      alternateResource,\n\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t},\n\t\t\t\tSpec: api.StreamSpec{\n\t\t\t\t\tName:        alternateResource,\n\t\t\t\t\tReplicas:    1,\n\t\t\t\t\tSubjects:    []string{\"alternate.*\"},\n\t\t\t\t\tDescription: \"stream in alternate namespace\",\n\t\t\t\t\tRetention:   \"workqueue\",\n\t\t\t\t\tDiscard:     \"old\",\n\t\t\t\t\tStorage:     \"file\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\t// Create the namespace if it doesn't exist\n\t\t\tns := &v1.Namespace{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName: alternateNamespace,\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := k8sClient.Create(ctx, ns)\n\t\t\tif err != nil && !k8serrors.IsAlreadyExists(err) {\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\n\t\t\t// Create the stream in the other namespace\n\t\t\tExpect(k8sClient.Create(ctx, alternateNamespaceResource)).To(Succeed())\n\t\t})\n\n\t\tAfterEach(func(ctx SpecContext) {\n\t\t\tBy(\"cleaning up the resource in alternate namespace\")\n\t\t\talternateStream := &api.Stream{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      alternateResource,\n\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t},\n\t\t\t}\n\t\t\terr := k8sClient.Delete(ctx, alternateStream)\n\t\t\tif err != nil && !k8serrors.IsNotFound(err) {\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should not watch the resource in alternate namespace\", func(ctx SpecContext) {\n\t\t\tBy(\"reconciling with no explicit namespace restriction\")\n\t\t\talternateNamespacedName := types.NamespacedName{\n\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\tName:      alternateResource,\n\t\t\t}\n\n\t\t\tBy(\"running reconciliation for the resource in alternate namespace\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: alternateNamespacedName,\n\t\t\t})\n\n\t\t\tBy(\"verifying reconciliation completes without error\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result).To(Equal(ctrl.Result{}))\n\n\t\t\tBy(\"checking the stream doesn't exist in NATS\")\n\t\t\t_, err = jsClient.Stream(ctx, alternateResource)\n\t\t\tExpect(err).To(MatchError(jetstream.ErrStreamNotFound))\n\n\t\t\tBy(\"verifying the resource still exists in the alternate namespace\")\n\t\t\talternateStream := &api.Stream{}\n\t\t\tExpect(k8sClient.Get(ctx, alternateNamespacedName, alternateStream)).To(Succeed())\n\n\t\t\tBy(\"checking no conditions were set on the resource\")\n\t\t\tExpect(alternateStream.Status.Conditions).To(BeEmpty())\n\t\t})\n\n\t\tIt(\"should watch the resource in alternate namespace when not namespaced\", func(ctx SpecContext) {\n\t\t\tBy(\"reconciling with a non-namespaced controller\")\n\t\t\ttestNatsConfig := &NatsConfig{ServerURL: clientUrl}\n\t\t\talternateBaseController, err := NewJSController(k8sClient, testNatsConfig, &Config{})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\talternateController := &StreamReconciler{\n\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\tJetStreamController: alternateBaseController,\n\t\t\t}\n\n\t\t\tresourceNames := []types.NamespacedName{\n\t\t\t\ttypeNamespacedName,\n\t\t\t\t{\n\t\t\t\t\tNamespace: alternateNamespace,\n\t\t\t\t\tName:      alternateResource,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tBy(\"running reconciliation for the resources in all namespaces\")\n\t\t\tfor _, n := range resourceNames {\n\t\t\t\tresult, err := alternateController.Reconcile(ctx, reconcile.Request{\n\t\t\t\t\tNamespacedName: n,\n\t\t\t\t})\n\n\t\t\t\tBy(\"verifying reconciliation completes without error\")\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result).NotTo(Equal(ctrl.Result{}))\n\t\t\t}\n\t\t})\n\t})\n\n\tWhen(\"reconciling an initialized resource\", func() {\n\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\tBy(\"initializing the stream resource\")\n\n\t\t\tBy(\"setting the finalizer\")\n\t\t\tExpect(controllerutil.AddFinalizer(stream, streamFinalizer)).To(BeTrue())\n\t\t\tExpect(k8sClient.Update(ctx, stream)).To(Succeed())\n\n\t\t\tBy(\"setting an unknown ready state\")\n\t\t\tstream.Status.Conditions = []api.Condition{{\n\t\t\t\tType:               readyCondType,\n\t\t\t\tStatus:             v1.ConditionUnknown,\n\t\t\t\tReason:             \"Test\",\n\t\t\t\tMessage:            \"start condition\",\n\t\t\t\tLastTransitionTime: time.Now().Format(time.RFC3339Nano),\n\t\t\t}}\n\t\t\tExpect(k8sClient.Status().Update(ctx, stream)).To(Succeed())\n\t\t})\n\n\t\tIt(\"should create a new stream\", func(ctx SpecContext) {\n\t\t\tBy(\"running Reconcile\")\n\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t// Fetch resource\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed())\n\n\t\t\tBy(\"checking if the ready state was updated\")\n\t\t\tExpect(stream.Status.Conditions).To(HaveLen(1))\n\t\t\tassertReadyStateMatches(stream.Status.Conditions[0], v1.ConditionTrue, stateReady, \"created or updated\", time.Now())\n\n\t\t\tBy(\"checking if the observed generation matches\")\n\t\t\tExpect(stream.Status.ObservedGeneration).To(Equal(stream.Generation))\n\n\t\t\tBy(\"checking if the stream was created\")\n\t\t\tnatsStream, err := jsClient.Stream(ctx, streamName)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tstreamInfo, err := natsStream.Info(ctx)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(streamInfo.Config.Name).To(Equal(streamName))\n\t\t\tExpect(streamInfo.Config.Description).To(Equal(\"test stream\"))\n\t\t\tExpect(streamInfo.Created).To(BeTemporally(\"~\", time.Now(), time.Second))\n\t\t})\n\n\t\tWhen(\"sealed is true\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"setting sealed to true\")\n\t\t\t\tstream.Spec.Name = \"sealed\"\n\t\t\t\tstream.Spec.Sealed = true\n\t\t\t\tExpect(k8sClient.Update(ctx, stream)).To(Succeed())\n\t\t\t})\n\t\t\tIt(\"should not create the stream\", func(ctx SpecContext) {\n\t\t\t\t_, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err.Error()).To(HaveSuffix(\"can not be sealed (10052)\"))\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"PreventUpdate is set\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"setting preventDelete on the resource\")\n\t\t\t\tstream.Spec.PreventUpdate = true\n\t\t\t\tExpect(k8sClient.Update(ctx, stream)).To(Succeed())\n\t\t\t})\n\t\t\tIt(\"should create the stream\", func(ctx SpecContext) {\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that stream was created\")\n\t\t\t\t_, err = jsClient.Stream(ctx, streamName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\t\t\tIt(\"should not update the stream\", func(ctx SpecContext) {\n\t\t\t\tBy(\"creating the stream\")\n\t\t\t\t_, err := jsClient.CreateStream(ctx, emptyStreamConfig)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that stream was not updated\")\n\t\t\t\ts, err := jsClient.Stream(ctx, streamName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(s.CachedInfo().Config.Description).To(BeEmpty())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"read-only mode is enabled\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"setting read only on the controller\")\n\t\t\t\treadOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontroller = &StreamReconciler{\n\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\tJetStreamController: readOnly,\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should not create the stream\", func(ctx SpecContext) {\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that no stream was created\")\n\t\t\t\t_, err = jsClient.Stream(ctx, streamName)\n\t\t\t\tExpect(err).To(MatchError(jetstream.ErrStreamNotFound))\n\t\t\t})\n\t\t\tIt(\"should not update the stream\", func(ctx SpecContext) {\n\t\t\t\tBy(\"creating the stream\")\n\t\t\t\t_, err := jsClient.CreateStream(ctx, emptyStreamConfig)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that stream was not updated\")\n\t\t\t\ts, err := jsClient.Stream(ctx, streamName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(s.CachedInfo().Config.Description).To(BeEmpty())\n\t\t\t})\n\t\t})\n\n\t\tWhen(\"namespace restriction is enabled\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"setting a namespace on the resource\")\n\t\t\t\tnamespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: alternateNamespace})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tcontroller = &StreamReconciler{\n\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\tJetStreamController: namespaced,\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tIt(\"should not create the stream\", func(ctx SpecContext) {\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that no stream was created\")\n\t\t\t\t_, err = jsClient.Stream(ctx, streamName)\n\t\t\t\tExpect(err).To(MatchError(jetstream.ErrStreamNotFound))\n\t\t\t})\n\t\t\tIt(\"should not update the stream\", func(ctx SpecContext) {\n\t\t\t\tBy(\"creating the stream\")\n\t\t\t\t_, err := jsClient.CreateStream(ctx, emptyStreamConfig)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tBy(\"running Reconcile\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that stream was not updated\")\n\t\t\t\ts, err := jsClient.Stream(ctx, streamName)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(s.CachedInfo().Config.Description).To(BeEmpty())\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should update an existing stream\", func(ctx SpecContext) {\n\t\t\tBy(\"reconciling once to create the stream\")\n\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t// Fetch resource\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed())\n\t\t\tpreviousTransitionTime := stream.Status.Conditions[0].LastTransitionTime\n\n\t\t\tBy(\"updating the resource\")\n\t\t\tstream.Spec.Description = \"new description\"\n\t\t\tstream.Spec.Sealed = true\n\t\t\tExpect(k8sClient.Update(ctx, stream)).To(Succeed())\n\n\t\t\tBy(\"reconciling the updated resource\")\n\t\t\tresult, err = controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t// Fetch resource\n\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed())\n\n\t\t\tBy(\"checking if the state transition time was not updated\")\n\t\t\tExpect(stream.Status.Conditions).To(HaveLen(1))\n\t\t\tExpect(stream.Status.Conditions[0].LastTransitionTime).To(Equal(previousTransitionTime))\n\n\t\t\tBy(\"checking if the observed generation matches\")\n\t\t\tExpect(stream.Status.ObservedGeneration).To(Equal(stream.Generation))\n\n\t\t\tBy(\"checking if the stream was updated\")\n\t\t\tnatsStream, err := jsClient.Stream(ctx, streamName)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tstreamInfo, err := natsStream.Info(ctx)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(streamInfo.Config.Description).To(Equal(\"new description\"))\n\t\t\tExpect(streamInfo.Config.Sealed).To(BeTrue())\n\t\t\t// Other fields unchanged\n\t\t\tExpect(streamInfo.Config.Subjects).To(Equal([]string{\"tests.*\"}))\n\t\t})\n\n\t\tIt(\"should create stream with new feature flags on the server\", func(ctx SpecContext) {\n\t\t\tBy(\"updating the stream spec with new feature flags\")\n\t\t\terr := k8sClient.Get(ctx, typeNamespacedName, stream)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tstream.Spec.AllowMsgCounter = true\n\t\t\tstream.Spec.AllowAtomicPublish = true\n\t\t\tstream.Spec.AllowMsgSchedules = true\n\t\t\tstream.Spec.AllowRollup = true\n\t\t\tstream.Spec.Retention = \"limits\"\n\t\t\t// Note: PersistMode \"async\" is not compatible with AllowAtomicPublish\n\t\t\tExpect(k8sClient.Update(ctx, stream)).To(Succeed())\n\n\t\t\tBy(\"reconciling the updated resource\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\tBy(\"fetching the updated stream from NATS\")\n\t\t\tnatsStream, err := jsClient.Stream(ctx, streamName)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tstreamInfo, err := natsStream.Info(ctx)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"verifying new feature flags are set on server\")\n\t\t\tExpect(streamInfo.Config.AllowMsgCounter).To(BeTrue())\n\t\t\tExpect(streamInfo.Config.AllowAtomicPublish).To(BeTrue())\n\t\t\tExpect(streamInfo.Config.AllowMsgSchedules).To(BeTrue())\n\t\t})\n\n\t\tIt(\"should set an error state when the nats server is not available\", func(ctx SpecContext) {\n\t\t\tBy(\"setting up controller with unavailable nats server\")\n\t\t\t// Setup client for not running server\n\t\t\t// Use actual test server to ensure port not used by other service on test instance\n\t\t\tsv := CreateTestServer()\n\t\t\tdisconnectedController, err := NewJSController(k8sClient, &NatsConfig{ServerURL: sv.ClientURL()}, &Config{})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tsv.Shutdown()\n\n\t\t\tcontroller := &StreamReconciler{\n\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\tJetStreamController: disconnectedController,\n\t\t\t}\n\n\t\t\tBy(\"reconciling resource\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(result).To(Equal(ctrl.Result{}))\n\t\t\tExpect(err).To(HaveOccurred()) // Will be re-queued with back-off\n\n\t\t\t// Fetch resource\n\t\t\terr = k8sClient.Get(ctx, typeNamespacedName, stream)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tBy(\"checking if the status was updated\")\n\t\t\tExpect(stream.Status.Conditions).To(HaveLen(1))\n\t\t\tassertReadyStateMatches(\n\t\t\t\tstream.Status.Conditions[0],\n\t\t\t\tv1.ConditionFalse,\n\t\t\t\tstateErrored,\n\t\t\t\t\"create or update stream:\",\n\t\t\t\ttime.Now(),\n\t\t\t)\n\n\t\t\tBy(\"checking if the observed generation does not match\")\n\t\t\tExpect(stream.Status.ObservedGeneration).ToNot(Equal(stream.Generation))\n\t\t})\n\n\t\tWhen(\"the resource is marked for deletion\", func() {\n\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\tBy(\"marking the resource for deletion\")\n\t\t\t\tExpect(k8sClient.Delete(ctx, stream)).To(Succeed())\n\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed()) // re-fetch after update\n\t\t\t})\n\n\t\t\tIt(\"should succeed deleting a not existing stream\", func(ctx SpecContext) {\n\t\t\t\tBy(\"reconciling\")\n\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\tWithArguments(ctx, typeNamespacedName, stream).\n\t\t\t\t\tShouldNot(Succeed())\n\t\t\t})\n\n\t\t\tWhen(\"the underlying stream exists\", func() {\n\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\tBy(\"creating the stream on the nats server\")\n\t\t\t\t\t_, err := jsClient.CreateStream(ctx, emptyStreamConfig)\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t})\n\n\t\t\t\tAfterEach(func(ctx SpecContext) {\n\t\t\t\t\terr := jsClient.DeleteStream(ctx, streamName)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tExpect(err).To(MatchError(jetstream.ErrStreamNotFound))\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tIt(\"should delete the stream\", func(ctx SpecContext) {\n\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\tBy(\"checking that the stream is deleted\")\n\t\t\t\t\t_, err = jsClient.Stream(ctx, streamName)\n\t\t\t\t\tExpect(err).To(MatchError(jetstream.ErrStreamNotFound))\n\n\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, stream).\n\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t})\n\n\t\t\t\tWhen(\"PreventDelete is set\", func() {\n\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"setting preventDelete on the resource\")\n\t\t\t\t\t\tstream.Spec.PreventDelete = true\n\t\t\t\t\t\tExpect(k8sClient.Update(ctx, stream)).To(Succeed())\n\t\t\t\t\t})\n\t\t\t\t\tIt(\"Should delete the resource and not delete the nats stream\", func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\tBy(\"checking that the stream is not deleted\")\n\t\t\t\t\t\t_, err = jsClient.Stream(ctx, streamName)\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, stream).\n\t\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tWhen(\"read only is set\", func() {\n\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"setting read only on the controller\")\n\t\t\t\t\t\treadOnly, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{ReadOnly: true})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tcontroller = &StreamReconciler{\n\t\t\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\t\t\tJetStreamController: readOnly,\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\tIt(\"should delete the resource and not delete the stream\", func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\tBy(\"checking that the stream is not deleted\")\n\t\t\t\t\t\t_, err = jsClient.Stream(ctx, streamName)\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\t\tBy(\"checking that the resource is deleted\")\n\t\t\t\t\t\tEventually(k8sClient.Get).\n\t\t\t\t\t\t\tWithArguments(ctx, typeNamespacedName, stream).\n\t\t\t\t\t\t\tShouldNot(Succeed())\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tWhen(\"controller is restricted to different namespace\", func() {\n\t\t\t\t\tBeforeEach(func(ctx SpecContext) {\n\t\t\t\t\t\tnamespaced, err := NewJSController(k8sClient, &NatsConfig{ServerURL: clientUrl}, &Config{Namespace: alternateNamespace})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tcontroller = &StreamReconciler{\n\t\t\t\t\t\t\tScheme:              k8sClient.Scheme(),\n\t\t\t\t\t\t\tJetStreamController: namespaced,\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\tIt(\"should not delete the resource and stream\", func(ctx SpecContext) {\n\t\t\t\t\t\tBy(\"reconciling\")\n\t\t\t\t\t\tresult, err := controller.Reconcile(ctx, ctrl.Request{NamespacedName: typeNamespacedName})\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\t\t\t\tBy(\"checking that the stream is not deleted\")\n\t\t\t\t\t\t_, err = jsClient.Stream(ctx, streamName)\n\t\t\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\t\t\tBy(\"checking that the finalizer is not removed\")\n\t\t\t\t\t\tExpect(k8sClient.Get(ctx, typeNamespacedName, stream)).To(Succeed())\n\t\t\t\t\t\tExpect(stream.Finalizers).To(ContainElement(streamFinalizer))\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tIt(\"should update stream on different server as specified in spec\", func(ctx SpecContext) {\n\t\t\tBy(\"setting up the alternative server\")\n\t\t\t// Setup altClient for alternate server\n\t\t\taltServer := CreateTestServer()\n\t\t\tdefer altServer.Shutdown()\n\n\t\t\tBy(\"setting the server in the stream spec\")\n\t\t\tstream.Spec.Servers = []string{altServer.ClientURL()}\n\t\t\tExpect(k8sClient.Update(ctx, stream)).To(Succeed())\n\n\t\t\tBy(\"checking precondition, that the stream does not yet exist\")\n\t\t\t_, err := jsClient.Stream(ctx, streamName)\n\t\t\tExpect(err).To(MatchError(jetstream.ErrStreamNotFound))\n\n\t\t\tBy(\"reconciling the resource\")\n\t\t\tresult, err := controller.Reconcile(ctx, reconcile.Request{\n\t\t\t\tNamespacedName: typeNamespacedName,\n\t\t\t})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(result.IsZero()).To(BeTrue())\n\n\t\t\tconnPool := newConnPool(0)\n\t\t\tconn, err := connPool.Get(&NatsConfig{ServerURL: altServer.ClientURL()}, true)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tdomain := \"\"\n\n\t\t\tBy(\"checking if the stream was created on the alternative server\")\n\t\t\taltClient, err := CreateJetStreamClient(conn, true, domain)\n\t\t\tdefer conn.Close()\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tgot, err := altClient.Stream(ctx, streamName)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(got.CachedInfo().Created).To(BeTemporally(\"~\", time.Now(), time.Second))\n\n\t\t\tBy(\"checking that the stream was NOT created on the original server\")\n\t\t\t_, err = jsClient.Stream(ctx, streamName)\n\t\t\tExpect(err).To(MatchError(jetstream.ErrStreamNotFound))\n\t\t})\n\t})\n})\n\nfunc Test_mapSpecToConfig(t *testing.T) {\n\tdate := time.Date(2024, 12, 3, 16, 55, 5, 0, time.UTC)\n\tdateString := date.Format(time.RFC3339)\n\n\ttests := []struct {\n\t\tname    string\n\t\tspec    *api.StreamSpec\n\t\twant    jsmapi.StreamConfig\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"empty spec\",\n\t\t\tspec: &api.StreamSpec{},\n\t\t\twant: jsmapi.StreamConfig{\n\t\t\t\t// Placement will be nil when no current config is provided\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"full spec\",\n\t\t\tspec: &api.StreamSpec{\n\t\t\t\tAllowDirect:       true,\n\t\t\t\tAllowRollup:       true,\n\t\t\t\tDenyDelete:        true,\n\t\t\t\tDenyPurge:         true,\n\t\t\t\tDescription:       \"stream description\",\n\t\t\t\tDiscardPerSubject: true,\n\t\t\t\tDiscard:           \"new\",\n\t\t\t\tDuplicateWindow:   \"5s\",\n\t\t\t\tMaxAge:            \"30s\",\n\t\t\t\tMaxBytes:          -1,\n\t\t\t\tMaxConsumers:      -1,\n\t\t\t\tMaxMsgs:           -1,\n\t\t\t\tMaxMsgSize:        -1,\n\t\t\t\tMaxMsgsPerSubject: 10,\n\t\t\t\tMirror: &api.StreamSource{\n\t\t\t\t\tName:                  \"mirror\",\n\t\t\t\t\tOptStartSeq:           5,\n\t\t\t\t\tOptStartTime:          dateString,\n\t\t\t\t\tFilterSubject:         \"orders\",\n\t\t\t\t\tExternalAPIPrefix:     \"api\",\n\t\t\t\t\tExternalDeliverPrefix: \"deliver\",\n\t\t\t\t\tSubjectTransforms: []*api.SubjectTransform{{\n\t\t\t\t\t\tSource: \"transform-source\",\n\t\t\t\t\t\tDest:   \"transform-dest\",\n\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t\tNoAck: true,\n\t\t\t\tPlacement: &api.StreamPlacement{\n\t\t\t\t\tCluster: \"test-cluster\",\n\t\t\t\t\tTags:    []string{\"tag\"},\n\t\t\t\t},\n\t\t\t\tReplicas: 3,\n\t\t\t\tRePublish: &api.RePublish{\n\t\t\t\t\tSource:      \"re-publish-source\",\n\t\t\t\t\tDestination: \"re-publish-dest\",\n\t\t\t\t\tHeadersOnly: true,\n\t\t\t\t},\n\t\t\t\tSubjectTransform: &api.SubjectTransform{\n\t\t\t\t\tSource: \"transform-source\",\n\t\t\t\t\tDest:   \"transform-dest\",\n\t\t\t\t},\n\t\t\t\tFirstSequence: 42,\n\t\t\t\tCompression:   \"s2\",\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"meta\": \"data\",\n\t\t\t\t},\n\t\t\t\tRetention: \"interest\",\n\t\t\t\tSources: []*api.StreamSource{{\n\t\t\t\t\tName:                  \"source\",\n\t\t\t\t\tOptStartSeq:           5,\n\t\t\t\t\tOptStartTime:          dateString,\n\t\t\t\t\tFilterSubject:         \"orders\",\n\t\t\t\t\tExternalAPIPrefix:     \"api\",\n\t\t\t\t\tExternalDeliverPrefix: \"deliver\",\n\t\t\t\t\tSubjectTransforms: []*api.SubjectTransform{{\n\t\t\t\t\t\tSource: \"transform-source\",\n\t\t\t\t\t\tDest:   \"transform-dest\",\n\t\t\t\t\t}},\n\t\t\t\t}},\n\t\t\t\tStorage:                \"file\",\n\t\t\t\tSubjects:               []string{\"orders.*\"},\n\t\t\t\tAllowMsgTTL:            true,\n\t\t\t\tSubjectDeleteMarkerTTL: \"10s\",\n\t\t\t\tAllowMsgCounter:        true,\n\t\t\t\tAllowAtomicPublish:     true,\n\t\t\t\tAllowMsgSchedules:      true,\n\t\t\t\tPersistMode:            \"async\",\n\t\t\t\tBaseStreamConfig: api.BaseStreamConfig{\n\t\t\t\t\tPreventDelete: false,\n\t\t\t\t\tPreventUpdate: false,\n\t\t\t\t\tConnectionOpts: api.ConnectionOpts{\n\t\t\t\t\t\tAccount: \"\",\n\t\t\t\t\t\tCreds:   \"\",\n\t\t\t\t\t\tNkey:    \"\",\n\t\t\t\t\t\tServers: nil,\n\t\t\t\t\t\tTLS:     &api.TLS{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: jsmapi.StreamConfig{\n\t\t\t\tDescription:   \"stream description\",\n\t\t\t\tSubjects:      []string{\"orders.*\"},\n\t\t\t\tRetention:     jsmapi.InterestPolicy,\n\t\t\t\tMaxConsumers:  -1,\n\t\t\t\tMaxMsgs:       -1,\n\t\t\t\tMaxBytes:      -1,\n\t\t\t\tDiscard:       jsmapi.DiscardNew,\n\t\t\t\tDiscardNewPer: true,\n\t\t\t\tMaxAge:        time.Second * 30,\n\t\t\t\tMaxMsgsPer:    10,\n\t\t\t\tMaxMsgSize:    -1,\n\t\t\t\tStorage:       jsmapi.FileStorage,\n\t\t\t\tReplicas:      3,\n\t\t\t\tNoAck:         true,\n\t\t\t\tDuplicates:    time.Second * 5,\n\t\t\t\tPlacement: &jsmapi.Placement{\n\t\t\t\t\tCluster: \"test-cluster\",\n\t\t\t\t\tTags:    []string{\"tag\"},\n\t\t\t\t},\n\t\t\t\tMirror: &jsmapi.StreamSource{\n\t\t\t\t\tName:          \"mirror\",\n\t\t\t\t\tOptStartSeq:   5,\n\t\t\t\t\tOptStartTime:  &date,\n\t\t\t\t\tFilterSubject: \"orders\",\n\t\t\t\t\tSubjectTransforms: []jsmapi.SubjectTransformConfig{{\n\t\t\t\t\t\tSource:      \"transform-source\",\n\t\t\t\t\t\tDestination: \"transform-dest\",\n\t\t\t\t\t}},\n\t\t\t\t\tExternal: &jsmapi.ExternalStream{\n\t\t\t\t\t\tApiPrefix:     \"api\",\n\t\t\t\t\t\tDeliverPrefix: \"deliver\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSources: []*jsmapi.StreamSource{{\n\t\t\t\t\tName:          \"source\",\n\t\t\t\t\tOptStartSeq:   5,\n\t\t\t\t\tOptStartTime:  &date,\n\t\t\t\t\tFilterSubject: \"orders\",\n\t\t\t\t\tSubjectTransforms: []jsmapi.SubjectTransformConfig{{\n\t\t\t\t\t\tSource:      \"transform-source\",\n\t\t\t\t\t\tDestination: \"transform-dest\",\n\t\t\t\t\t}},\n\t\t\t\t\tExternal: &jsmapi.ExternalStream{\n\t\t\t\t\t\tApiPrefix:     \"api\",\n\t\t\t\t\t\tDeliverPrefix: \"deliver\",\n\t\t\t\t\t},\n\t\t\t\t}},\n\t\t\t\tSealed:        false,\n\t\t\t\tDenyDelete:    true,\n\t\t\t\tDenyPurge:     true,\n\t\t\t\tRollupAllowed: true,\n\t\t\t\tCompression:   jsmapi.S2Compression,\n\t\t\t\tFirstSeq:      42,\n\t\t\t\tSubjectTransform: &jsmapi.SubjectTransformConfig{\n\t\t\t\t\tSource:      \"transform-source\",\n\t\t\t\t\tDestination: \"transform-dest\",\n\t\t\t\t},\n\t\t\t\tRePublish: &jsmapi.RePublish{\n\t\t\t\t\tSource:      \"re-publish-source\",\n\t\t\t\t\tDestination: \"re-publish-dest\",\n\t\t\t\t\tHeadersOnly: true,\n\t\t\t\t},\n\t\t\t\tAllowDirect:            true,\n\t\t\t\tMirrorDirect:           false,\n\t\t\t\tConsumerLimits:         jsmapi.StreamConsumerLimits{},\n\t\t\t\tAllowMsgTTL:            true,\n\t\t\t\tSubjectDeleteMarkerTTL: time.Second * 10,\n\t\t\t\tAllowMsgCounter:        true,\n\t\t\t\tAllowAtomicPublish:     true,\n\t\t\t\tAllowMsgSchedules:      true,\n\t\t\t\tPersistMode:            jsmapi.AsyncPersistMode,\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"meta\": \"data\",\n\t\t\t\t},\n\t\t\t\tTemplate: \"\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"persist mode default\",\n\t\t\tspec: &api.StreamSpec{\n\t\t\t\tPersistMode:        \"default\",\n\t\t\t\tAllowMsgCounter:    false,\n\t\t\t\tAllowAtomicPublish: false,\n\t\t\t\tAllowMsgSchedules:  false,\n\t\t\t},\n\t\t\twant: jsmapi.StreamConfig{\n\t\t\t\tPersistMode: jsmapi.DefaultPersistMode,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert := assert.New(t)\n\t\t\tsOpts, err := streamSpecToConfig(tt.spec, nil)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"streamSpecToConfig() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tgot := &jsmapi.StreamConfig{}\n\t\t\tfor _, o := range sOpts {\n\t\t\t\to(got)\n\t\t\t}\n\n\t\t\t// Compare nested structs\n\t\t\tassert.EqualValues(tt.want, *got)\n\t\t})\n\t}\n}\n\n// TestStreamUpdateWithoutPlacement verifies that updating replicas doesn't\n// inadvertently set an empty Placement field, which would cause error 10123:\n// \"can not move and scale a stream in a single update\"\n// This test reproduces issue #273 where enabling control loop resulted in failures\n// when updating streams without placement configuration.\nfunc TestStreamUpdateWithoutPlacement(t *testing.T) {\n\t// First test the config conversion logic\n\tt.Run(\"config conversion\", func(t *testing.T) {\n\t\ttests := []struct {\n\t\t\tname            string\n\t\t\tspec            *api.StreamSpec\n\t\t\tcurrentConfig   *jsmapi.StreamConfig\n\t\t\texpectPlacement bool\n\t\t\texpectedCluster string\n\t\t}{\n\t\t\t{\n\t\t\t\tname: \"spec without placement, no current placement - should not set placement\",\n\t\t\t\tspec: &api.StreamSpec{\n\t\t\t\t\tName:     \"test-stream\",\n\t\t\t\t\tSubjects: []string{\"test.>\"},\n\t\t\t\t\tStorage:  \"file\",\n\t\t\t\t\tReplicas: 3,\n\t\t\t\t},\n\t\t\t\tcurrentConfig:   nil,\n\t\t\t\texpectPlacement: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"spec without placement, current has placement - should clear placement\",\n\t\t\t\tspec: &api.StreamSpec{\n\t\t\t\t\tName:     \"test-stream\",\n\t\t\t\t\tSubjects: []string{\"test.>\"},\n\t\t\t\t\tStorage:  \"file\",\n\t\t\t\t\tReplicas: 3,\n\t\t\t\t},\n\t\t\t\tcurrentConfig: &jsmapi.StreamConfig{\n\t\t\t\t\tPlacement: &jsmapi.Placement{\n\t\t\t\t\t\tCluster: \"old-cluster\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\texpectPlacement: true,\n\t\t\t\texpectedCluster: \"\", // Should be cleared\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"spec with placement - should set placement\",\n\t\t\t\tspec: &api.StreamSpec{\n\t\t\t\t\tName:     \"test-stream\",\n\t\t\t\t\tSubjects: []string{\"test.>\"},\n\t\t\t\t\tStorage:  \"file\",\n\t\t\t\t\tReplicas: 3,\n\t\t\t\t\tPlacement: &api.StreamPlacement{\n\t\t\t\t\t\tCluster: \"test-cluster\",\n\t\t\t\t\t\tTags:    []string{\"tag1\", \"tag2\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tcurrentConfig:   nil,\n\t\t\t\texpectPlacement: true,\n\t\t\t\texpectedCluster: \"test-cluster\",\n\t\t\t},\n\t\t}\n\n\t\tfor _, tt := range tests {\n\t\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t\tassert := assert.New(t)\n\n\t\t\t\t// Convert spec to config options with context\n\t\t\t\topts, err := streamSpecToConfig(tt.spec, tt.currentConfig)\n\t\t\t\tassert.NoError(err)\n\n\t\t\t\t// Apply options to get the resulting config\n\t\t\t\tcfg := &jsmapi.StreamConfig{}\n\t\t\t\tfor _, opt := range opts {\n\t\t\t\t\topt(cfg)\n\t\t\t\t}\n\n\t\t\t\t// Verify placement is only set when expected\n\t\t\t\tif tt.expectPlacement {\n\t\t\t\t\tassert.NotNil(cfg.Placement, \"Placement should be set\")\n\t\t\t\t\tassert.Equal(tt.expectedCluster, cfg.Placement.Cluster)\n\t\t\t\t\tif tt.spec.Placement != nil && tt.spec.Placement.Tags != nil {\n\t\t\t\t\t\tassert.Equal(tt.spec.Placement.Tags, cfg.Placement.Tags)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tassert.Nil(cfg.Placement, \"Placement should be nil\")\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n\n\t// Integration test that reproduces the actual NATS error scenario from issue #273\n\tt.Run(\"scale replicas without placement error\", func(t *testing.T) {\n\t\t// Create test NATS server without using Gomega\n\t\topts := &natsserver.DefaultTestOptions\n\t\topts.JetStream = true\n\t\topts.Port = -1\n\t\topts.Debug = true\n\n\t\tdir, err := os.MkdirTemp(\"\", \"nats-*\")\n\t\tassert.NoError(t, err)\n\t\tdefer os.RemoveAll(dir)\n\t\topts.StoreDir = dir\n\n\t\tsrv := natsserver.RunServer(opts)\n\t\tassert.NotNil(t, srv)\n\t\tdefer srv.Shutdown()\n\n\t\t// Create NATS connection and JetStream client\n\t\tnc, err := nats.Connect(srv.ClientURL())\n\t\tassert.NoError(t, err)\n\t\tdefer nc.Close()\n\n\t\tjs, err := nc.JetStream()\n\t\tassert.NoError(t, err)\n\n\t\t// Create initial stream with 1 replica and no placement\n\t\tstreamName := \"test-scale-stream\"\n\t\tinitialConfig := &nats.StreamConfig{\n\t\t\tName:     streamName,\n\t\t\tSubjects: []string{\"test.>\"},\n\t\t\tStorage:  nats.FileStorage,\n\t\t\tReplicas: 1,\n\t\t\t// Explicitly no Placement field set\n\t\t}\n\n\t\t_, err = js.AddStream(initialConfig)\n\t\tassert.NoError(t, err, \"Failed to create initial stream\")\n\n\t\t// Now attempt to scale replicas from 1 to 3 without setting placement\n\t\t// This should NOT fail with error 10123\n\t\tupdateConfig := &nats.StreamConfig{\n\t\t\tName:     streamName,\n\t\t\tSubjects: []string{\"test.>\"},\n\t\t\tStorage:  nats.FileStorage,\n\t\t\tReplicas: 3,\n\t\t\t// Still no Placement field - this is the critical test\n\t\t}\n\n\t\t_, err = js.UpdateStream(updateConfig)\n\t\tassert.NoError(t, err, \"Scaling replicas should not fail with error 10123\")\n\n\t\t// Verify the stream was updated successfully\n\t\tinfo, err := js.StreamInfo(streamName)\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, 3, info.Config.Replicas, \"Replicas should be scaled to 3\")\n\t})\n}\n"
  },
  {
    "path": "internal/controller/suite_test.go",
    "content": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS 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\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/nats-io/nats-server/v2/server\"\n\t\"github.com/nats-io/nats.go/jetstream\"\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\tapi \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\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\tcfg            *rest.Config\n\tk8sClient      client.Client\n\ttestEnv        *envtest.Environment\n\ttestServer     *server.Server\n\tclientUrl      string\n\tjsClient       jetstream.JetStream\n\tbaseController JetStreamController\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\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths:     []string{filepath.Join(\"..\", \"..\", \"deploy\")},\n\t\tErrorIfCRDPathMissing: true,\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(\"..\", \"..\", \"bin\", \"k8s\",\n\t\t\tfmt.Sprintf(\"1.32.0-%s-%s\", runtime.GOOS, runtime.GOARCH)),\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\terr = api.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).NotTo(HaveOccurred())\n\tExpect(k8sClient).NotTo(BeNil())\n\n\tBy(\"bootstrapping the test server\")\n\ttestServer = CreateTestServer()\n\tExpect(err).NotTo(HaveOccurred())\n\n\tclientUrl = testServer.ClientURL()\n\ttestNatsConfig := &NatsConfig{ServerURL: clientUrl}\n\tbaseController, err = NewJSController(k8sClient, testNatsConfig, &Config{\n\t\tNamespace: \"default\",\n\t})\n\tExpect(err).NotTo(HaveOccurred())\n\n\tconnPool := newConnPool(0)\n\tconn, err := connPool.Get(testNatsConfig, true)\n\tExpect(err).NotTo(HaveOccurred())\n\tdomain := \"\"\n\n\tjsClient, err = CreateJetStreamClient(conn, true, domain)\n\tExpect(err).NotTo(HaveOccurred())\n})\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\terr := testEnv.Stop()\n\tExpect(err).NotTo(HaveOccurred())\n\n\tBy(\"tearing down the test server\")\n\tstoreDir := testServer.StoreDir()\n\ttestServer.Shutdown()\n\terr = os.RemoveAll(storeDir)\n\tExpect(err).NotTo(HaveOccurred())\n})\n"
  },
  {
    "path": "internal/controller/types.go",
    "content": "package controller\n\nconst (\n\treadyCondType        = \"Ready\"\n\taccountFinalizer     = \"account.nats.io/finalizer\"\n\tstreamFinalizer      = \"stream.nats.io/finalizer\"\n\tkeyValueFinalizer    = \"kv.nats.io/finalizer\"\n\tobjectStoreFinalizer = \"objectstore.nats.io/finalizer\"\n\tconsumerFinalizer    = \"consumer.nats.io/finalizer\"\n\n\tstateAnnotationConsumer = \"consumer.nats.io/state\"\n\tstateAnnotationKV       = \"kv.nats.io/state\"\n\tstateAnnotationObj      = \"objectstore.nats.io/state\"\n\tstateAnnotationStream   = \"stream.nats.io/state\"\n\n\tstateReady       = \"Ready\"\n\tstateReconciling = \"Reconciling\"\n\tstateErrored     = \"Errored\"\n\tstateFinalizing  = \"Finalizing\"\n)\n"
  },
  {
    "path": "kuttl-test.yaml",
    "content": "apiVersion: kuttl.dev/v1beta1\nkind: TestSuite\ntestDirs:\n  - tests/\ncommands:\n  - command: kubectl apply -f ./deploy/crds.yml\ntimeout: 120\n"
  },
  {
    "path": "pkg/bootconfig/bootconfig.go",
    "content": "// Copyright 2018 The NATS 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//\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\npackage bootconfig\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\tlog \"github.com/sirupsen/logrus\"\n\tk8smetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tk8sclient \"k8s.io/client-go/kubernetes\"\n\tk8srestapi \"k8s.io/client-go/rest\"\n\tk8sclientcmd \"k8s.io/client-go/tools/clientcmd\"\n)\n\ntype Options struct {\n\t// TargetTag is the tag that will be looked up to find\n\t// the public ip from the node.\n\tTargetTag string\n\n\t// ClientAdvertiseFileName is the name of the file where\n\t// the advertise configuration will be written into.\n\tClientAdvertiseFileName string\n\n\t// GatewayAdvertiseFileName is the name of the file where\n\t// the advertise configuration will be written into for gateways.\n\tGatewayAdvertiseFileName string\n\n\t// NoSignals marks whether to enable the signal handler.\n\tNoSignals bool\n}\n\n// Controller for the boot config.\ntype Controller struct {\n\t// Start/Stop cancellation.\n\tquit func()\n\n\t// Client to interact with Kubernetes resources.\n\tkc k8sclient.Interface\n\n\t// opts is the set of options.\n\topts *Options\n}\n\n// NewController creates a new controller with the configuration.\nfunc NewController(opts *Options) *Controller {\n\treturn &Controller{\n\t\topts: opts,\n\t}\n}\n\n// SetupClients takes the configuration and prepares the rest\n// clients that will be used to interact with the cluster objects.\nfunc (c *Controller) SetupClients(cfg *k8srestapi.Config) error {\n\tkc, err := k8sclient.NewForConfig(cfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.kc = kc\n\treturn nil\n}\n\n// Run starts the controller.\nfunc (c *Controller) Run(ctx context.Context) error {\n\tvar err error\n\tvar cfg *k8srestapi.Config\n\tif kubeconfig := os.Getenv(\"KUBERNETES_CONFIG_FILE\"); kubeconfig != \"\" {\n\t\tcfg, err = k8sclientcmd.BuildConfigFromFlags(\"\", kubeconfig)\n\t} else {\n\t\tcfg, err = k8srestapi.InClusterConfig()\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := c.SetupClients(cfg); err != nil {\n\t\treturn err\n\t}\n\n\tnodeName := os.Getenv(\"KUBERNETES_NODE_NAME\")\n\tif nodeName == \"\" {\n\t\treturn errors.New(\"Target node name is missing\")\n\t}\n\tlog.Infof(\"Pod running on node %q\", nodeName)\n\n\tnode, err := c.kc.CoreV1().Nodes().Get(ctx, nodeName, k8smetav1.GetOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar ok bool\n\tvar externalAddress string\n\tfor _, addr := range node.Status.Addresses {\n\t\tif addr.Type == \"ExternalIP\" {\n\t\t\texternalAddress = addr.Address\n\t\t\tok = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// Fallback to use a label to find the external address.\n\tif !ok {\n\t\texternalAddress, ok = node.Labels[c.opts.TargetTag]\n\t\tif !ok || len(externalAddress) == 0 {\n\t\t\treturn errors.New(\"Could not find external IP address.\")\n\t\t}\n\t}\n\tlog.Infof(\"Pod is running on node with external IP: %s\", externalAddress)\n\n\tclientAdvertiseConfig := fmt.Sprintf(\"\\nclient_advertise = \\\"%s\\\"\\n\\n\", externalAddress)\n\n\terr = os.WriteFile(c.opts.ClientAdvertiseFileName, []byte(clientAdvertiseConfig), 0o644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not write client advertise config: %s\", err)\n\t}\n\tlog.Infof(\"Successfully wrote client advertise config to %q\", c.opts.ClientAdvertiseFileName)\n\n\tgatewayAdvertiseConfig := fmt.Sprintf(\"\\nadvertise = \\\"%s\\\"\\n\\n\", externalAddress)\n\n\terr = os.WriteFile(c.opts.GatewayAdvertiseFileName, []byte(gatewayAdvertiseConfig), 0o644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Could not write gateway advertise config: %s\", err)\n\t}\n\tlog.Infof(\"Successfully wrote gateway advertise config to %q\", c.opts.GatewayAdvertiseFileName)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/register.go",
    "content": "package jetstream\n\n// GroupName is the group name used in this package\nconst (\n\tGroupName = \"jetstream.nats.io\"\n)\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta1/consumertypes.go",
    "content": "package v1beta1\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// Consumer is a specification for a Consumer resource\ntype Consumer struct {\n\tk8smeta.TypeMeta   `json:\",inline\"`\n\tk8smeta.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   ConsumerSpec `json:\"spec\"`\n\tStatus Status       `json:\"status\"`\n}\n\nfunc (c *Consumer) GetSpec() interface{} {\n\treturn c.Spec\n}\n\n// ConsumerSpec is the spec for a Consumer resource\ntype ConsumerSpec struct {\n\tAckPolicy         string `json:\"ackPolicy\"`\n\tAckWait           string `json:\"ackWait\"`\n\tDeliverGroup      string `json:\"deliverGroup\"`\n\tDeliverPolicy     string `json:\"deliverPolicy\"`\n\tDeliverSubject    string `json:\"deliverSubject\"`\n\tDescription       string `json:\"description\"`\n\tDurableName       string `json:\"durableName\"`\n\tFilterSubject     string `json:\"filterSubject\"`\n\tFlowControl       bool   `json:\"flowControl\"`\n\tHeartbeatInterval string `json:\"heartbeatInterval\"`\n\tMaxAckPending     int    `json:\"maxAckPending\"`\n\tMaxDeliver        int    `json:\"maxDeliver\"`\n\tOptStartSeq       int    `json:\"optStartSeq\"`\n\tOptStartTime      string `json:\"optStartTime\"`\n\tRateLimitBps      int    `json:\"rateLimitBps\"`\n\tReplayPolicy      string `json:\"replayPolicy\"`\n\tSampleFreq        string `json:\"sampleFreq\"`\n\n\tStreamName string `json:\"streamName\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// ConsumerList is a list of Consumer resources\ntype ConsumerList struct {\n\tk8smeta.TypeMeta `json:\",inline\"`\n\tk8smeta.ListMeta `json:\"metadata\"`\n\n\tItems []Consumer `json:\"items\"`\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta1/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n// +groupName=jetstream.nats.io\n\n// Package v1 is the v1 version of the API.\npackage v1beta1\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta1/register.go",
    "content": "package v1beta1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\n\t\"github.com/nats-io/nack/pkg/jetstream/apis/jetstream\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects\n\tSchemeGroupVersion = schema.GroupVersion{Group: jetstream.GroupName, Version: \"v1beta1\"}\n\n\t// SchemeBuilder initializes a scheme builder\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\t// AddToScheme is a global function that registers this API group & version to a scheme\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&Stream{},\n\t\t&StreamList{},\n\t\t&Consumer{},\n\t\t&ConsumerList{},\n\t\t&StreamTemplate{},\n\t\t&StreamTemplateList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta1/streamtemplatetypes.go",
    "content": "package v1beta1\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// StreamTemplate is a specification for a StreamTemplate resource\ntype StreamTemplate struct {\n\tk8smeta.TypeMeta   `json:\",inline\"`\n\tk8smeta.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   StreamTemplateSpec `json:\"spec\"`\n\tStatus Status             `json:\"status\"`\n}\n\nfunc (s *StreamTemplate) GetSpec() interface{} {\n\treturn s.Spec\n}\n\n// StreamTemplateSpec is the spec for a StreamTemplate resource\ntype StreamTemplateSpec struct {\n\tStreamSpec `json:\",inline\"`\n\n\tMaxStreams int `json:\"maxStreams\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// StreamTemplateList is a list of StreamTemplate resources\ntype StreamTemplateList struct {\n\tk8smeta.TypeMeta `json:\",inline\"`\n\tk8smeta.ListMeta `json:\"metadata\"`\n\n\tItems []StreamTemplate `json:\"items\"`\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta1/streamtypes.go",
    "content": "package v1beta1\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// Stream is a specification for a Stream resource\ntype Stream struct {\n\tk8smeta.TypeMeta   `json:\",inline\"`\n\tk8smeta.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   StreamSpec `json:\"spec\"`\n\tStatus Status     `json:\"status\"`\n}\n\nfunc (s *Stream) GetSpec() interface{} {\n\treturn s.Spec\n}\n\n// StreamSpec is the spec for a Stream resource\ntype StreamSpec struct {\n\tDescription            string           `json:\"description\"`\n\tDiscard                string           `json:\"discard\"`\n\tDuplicateWindow        string           `json:\"duplicateWindow\"`\n\tMaxAge                 string           `json:\"maxAge\"`\n\tMaxBytes               int              `json:\"maxBytes\"`\n\tMaxConsumers           int              `json:\"maxConsumers\"`\n\tMaxMsgs                int              `json:\"maxMsgs\"`\n\tMaxMsgSize             int              `json:\"maxMsgSize\"`\n\tMaxMsgsPerSubject      int              `json:\"maxMsgsPerSubject\"`\n\tMirror                 *StreamSource    `json:\"mirror\"`\n\tName                   string           `json:\"name\"`\n\tNoAck                  bool             `json:\"noAck\"`\n\tPlacement              *StreamPlacement `json:\"placement\"`\n\tReplicas               int              `json:\"replicas\"`\n\tRetention              string           `json:\"retention\"`\n\tSources                []*StreamSource  `json:\"sources\"`\n\tStorage                string           `json:\"storage\"`\n\tSubjects               []string         `json:\"subjects\"`\n\tAllowMsgTTL            bool             `json:\"allowMsgTtl\"`\n\tSubjectDeleteMarkerTTL string           `json:\"subjectDeleteMarkerTtl\"`\n}\n\ntype StreamPlacement struct {\n\tCluster string   `json:\"cluster\"`\n\tTags    []string `json:\"tags\"`\n}\n\ntype StreamSource struct {\n\tName          string `json:\"name\"`\n\tOptStartSeq   int    `json:\"optStartSeq\"`\n\tOptStartTime  string `json:\"optStartTime\"`\n\tFilterSubject string `json:\"filterSubject\"`\n\n\tExternalAPIPrefix     string `json:\"externalApiPrefix\"`\n\tExternalDeliverPrefix string `json:\"externalDeliverPrefix\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// StreamList is a list of Stream resources\ntype StreamList struct {\n\tk8smeta.TypeMeta `json:\",inline\"`\n\tk8smeta.ListMeta `json:\"metadata\"`\n\n\tItems []Stream `json:\"items\"`\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta1/types.go",
    "content": "package v1beta1\n\nimport (\n\tk8sapi \"k8s.io/api/core/v1\"\n)\n\ntype CredentialsSecret struct {\n\tName string `json:\"name\"`\n\tKey  string `json:\"key\"`\n}\n\ntype Status struct {\n\tObservedGeneration int64       `json:\"observedGeneration\"`\n\tConditions         []Condition `json:\"conditions\"`\n}\n\ntype Condition struct {\n\tType               string                 `json:\"type\"`\n\tStatus             k8sapi.ConditionStatus `json:\"status\"`\n\tReason             string                 `json:\"reason\"`\n\tMessage            string                 `json:\"message\"`\n\tLastTransitionTime string                 `json:\"lastTransitionTime\"`\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n// Copyright 2025 The NATS 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//\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// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1beta1\n\nimport (\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 *Condition) DeepCopyInto(out *Condition) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.\nfunc (in *Condition) DeepCopy() *Condition {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Condition)\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 *Consumer) DeepCopyInto(out *Consumer) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tout.Spec = in.Spec\n\tin.Status.DeepCopyInto(&out.Status)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Consumer.\nfunc (in *Consumer) DeepCopy() *Consumer {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Consumer)\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 *Consumer) 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 *ConsumerList) DeepCopyInto(out *ConsumerList) {\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([]Consumer, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsumerList.\nfunc (in *ConsumerList) DeepCopy() *ConsumerList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ConsumerList)\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 *ConsumerList) 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 *ConsumerSpec) DeepCopyInto(out *ConsumerSpec) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsumerSpec.\nfunc (in *ConsumerSpec) DeepCopy() *ConsumerSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ConsumerSpec)\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 *CredentialsSecret) DeepCopyInto(out *CredentialsSecret) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialsSecret.\nfunc (in *CredentialsSecret) DeepCopy() *CredentialsSecret {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CredentialsSecret)\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 *Status) DeepCopyInto(out *Status) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]Condition, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status.\nfunc (in *Status) DeepCopy() *Status {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Status)\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 *Stream) DeepCopyInto(out *Stream) {\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\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Stream.\nfunc (in *Stream) DeepCopy() *Stream {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Stream)\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 *Stream) 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 *StreamList) DeepCopyInto(out *StreamList) {\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([]Stream, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamList.\nfunc (in *StreamList) DeepCopy() *StreamList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(StreamList)\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 *StreamList) 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 *StreamPlacement) DeepCopyInto(out *StreamPlacement) {\n\t*out = *in\n\tif in.Tags != nil {\n\t\tin, out := &in.Tags, &out.Tags\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamPlacement.\nfunc (in *StreamPlacement) DeepCopy() *StreamPlacement {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(StreamPlacement)\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 *StreamSource) DeepCopyInto(out *StreamSource) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamSource.\nfunc (in *StreamSource) DeepCopy() *StreamSource {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(StreamSource)\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 *StreamSpec) DeepCopyInto(out *StreamSpec) {\n\t*out = *in\n\tif in.Mirror != nil {\n\t\tin, out := &in.Mirror, &out.Mirror\n\t\t*out = new(StreamSource)\n\t\t**out = **in\n\t}\n\tif in.Placement != nil {\n\t\tin, out := &in.Placement, &out.Placement\n\t\t*out = new(StreamPlacement)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Sources != nil {\n\t\tin, out := &in.Sources, &out.Sources\n\t\t*out = make([]*StreamSource, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(StreamSource)\n\t\t\t\t**out = **in\n\t\t\t}\n\t\t}\n\t}\n\tif in.Subjects != nil {\n\t\tin, out := &in.Subjects, &out.Subjects\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamSpec.\nfunc (in *StreamSpec) DeepCopy() *StreamSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(StreamSpec)\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 *StreamTemplate) DeepCopyInto(out *StreamTemplate) {\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\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamTemplate.\nfunc (in *StreamTemplate) DeepCopy() *StreamTemplate {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(StreamTemplate)\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 *StreamTemplate) 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 *StreamTemplateList) DeepCopyInto(out *StreamTemplateList) {\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([]StreamTemplate, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamTemplateList.\nfunc (in *StreamTemplateList) DeepCopy() *StreamTemplateList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(StreamTemplateList)\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 *StreamTemplateList) 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 *StreamTemplateSpec) DeepCopyInto(out *StreamTemplateSpec) {\n\t*out = *in\n\tin.StreamSpec.DeepCopyInto(&out.StreamSpec)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamTemplateSpec.\nfunc (in *StreamTemplateSpec) DeepCopy() *StreamTemplateSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(StreamTemplateSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta2/accounttypes.go",
    "content": "package v1beta2\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// Account is a specification for a Account resource.\ntype Account struct {\n\tk8smeta.TypeMeta   `json:\",inline\"`\n\tk8smeta.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   AccountSpec `json:\"spec\"`\n\tStatus Status      `json:\"status\"`\n}\n\nfunc (c *Account) GetSpec() interface{} {\n\treturn c.Spec\n}\n\n// AccountSpec is the spec for a Account resource\ntype AccountSpec struct {\n\tServers []string     `json:\"servers,omitempty\"`\n\tTLS     *TLSSecret   `json:\"tls,omitempty\"`\n\tCreds   *CredsSecret `json:\"creds,omitempty\"`\n\tNKey    *NKeySecret  `json:\"nkey,omitempty\"`\n\tToken   *TokenSecret `json:\"token,omitempty\"`\n\tUser    *User        `json:\"user,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// AccountList is a list of Account resources\ntype AccountList struct {\n\tk8smeta.TypeMeta `json:\",inline\"`\n\tk8smeta.ListMeta `json:\"metadata\"`\n\n\tItems []Account `json:\"items\"`\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta2/consumertypes.go",
    "content": "package v1beta2\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// Consumer is a specification for a Consumer resource\ntype Consumer struct {\n\tk8smeta.TypeMeta   `json:\",inline\"`\n\tk8smeta.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   ConsumerSpec `json:\"spec\"`\n\tStatus Status       `json:\"status\"`\n}\n\nfunc (c *Consumer) GetSpec() interface{} {\n\treturn c.Spec\n}\n\n// ConsumerSpec is the spec for a Consumer resource\ntype ConsumerSpec struct {\n\tDescription        string            `json:\"description,omitempty\"`\n\tAckPolicy          string            `json:\"ackPolicy,omitempty\"`\n\tAckWait            string            `json:\"ackWait,omitempty\"`\n\tDeliverPolicy      string            `json:\"deliverPolicy,omitempty\"`\n\tDeliverSubject     string            `json:\"deliverSubject,omitempty\"`\n\tDeliverGroup       string            `json:\"deliverGroup,omitempty\"`\n\tDurableName        string            `json:\"durableName,omitempty\"` // Maps to Durable\n\tFilterSubject      string            `json:\"filterSubject,omitempty\"`\n\tFilterSubjects     []string          `json:\"filterSubjects,omitempty\"`\n\tFlowControl        bool              `json:\"flowControl,omitempty\"`\n\tHeartbeatInterval  string            `json:\"heartbeatInterval,omitempty\"` // Maps to IdleHeartbeat\n\tMaxAckPending      int               `json:\"maxAckPending,omitempty\"`\n\tMaxDeliver         int               `json:\"maxDeliver,omitempty\"`\n\tBackOff            []string          `json:\"backoff,omitempty\"`\n\tMaxWaiting         int               `json:\"maxWaiting,omitempty\"`\n\tOptStartSeq        int               `json:\"optStartSeq,omitempty\"`\n\tOptStartTime       string            `json:\"optStartTime,omitempty\"`\n\tRateLimitBps       int               `json:\"rateLimitBps,omitempty\"` // Maps to RateLimit\n\tReplayPolicy       string            `json:\"replayPolicy,omitempty\"`\n\tSampleFreq         string            `json:\"sampleFreq,omitempty\"` // Maps to SampleFrequency\n\tHeadersOnly        bool              `json:\"headersOnly,omitempty\"`\n\tMaxRequestBatch    int               `json:\"maxRequestBatch,omitempty\"`\n\tMaxRequestExpires  string            `json:\"maxRequestExpires,omitempty\"`\n\tMaxRequestMaxBytes int               `json:\"maxRequestMaxBytes,omitempty\"`\n\tInactiveThreshold  string            `json:\"inactiveThreshold,omitempty\"`\n\tReplicas           int               `json:\"replicas,omitempty\"`\n\tMemStorage         bool              `json:\"memStorage,omitempty\"` // Maps to MemoryStorage\n\tMetadata           map[string]string `json:\"metadata,omitempty\"`\n\tPauseUntil         string            `json:\"pauseUntil,omitempty\"`     // RFC3339 timestamp for pausing consumer\n\tPriorityPolicy     string            `json:\"priorityPolicy,omitempty\"` // Priority policy: none, pinned_client, overflow, prioritized\n\tPinnedTTL          string            `json:\"pinnedTtl,omitempty\"`      // Duration for pinned client timeout\n\tPriorityGroups     []string          `json:\"priorityGroups,omitempty\"` // List of priority groups\n\n\tStreamName string `json:\"streamName\"`\n\tBaseStreamConfig\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// ConsumerList is a list of Consumer resources\ntype ConsumerList struct {\n\tk8smeta.TypeMeta `json:\",inline\"`\n\tk8smeta.ListMeta `json:\"metadata\"`\n\n\tItems []Consumer `json:\"items\"`\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta2/doc.go",
    "content": "// +k8s:deepcopy-gen=package\n// +groupName=jetstream.nats.io\n\n// Package v1 is the v1 version of the API.\npackage v1beta2\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go",
    "content": "package v1beta2\n\nimport (\n\t\"time\"\n\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// Stream is a specification for a Stream resource\ntype KeyValue struct {\n\tk8smeta.TypeMeta   `json:\",inline\"`\n\tk8smeta.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   KeyValueSpec `json:\"spec\"`\n\tStatus Status       `json:\"status\"`\n}\n\nfunc (s *KeyValue) GetSpec() interface{} {\n\treturn s.Spec\n}\n\n// StreamSpec is the spec for a Stream resource\ntype KeyValueSpec struct {\n\tBucket       string           `json:\"bucket\"`\n\tDescription  string           `json:\"description,omitempty\"`\n\tMaxValueSize int              `json:\"maxValueSize,omitempty\"`\n\tHistory      int              `json:\"history,omitempty\"`\n\tTTL          string           `json:\"ttl,omitempty\"`\n\tMaxBytes     int              `json:\"maxBytes,omitempty\"`\n\tStorage      string           `json:\"storage,omitempty\"`\n\tReplicas     int              `json:\"replicas,omitempty\"`\n\tPlacement    *StreamPlacement `json:\"placement,omitempty\"`\n\tRePublish    *RePublish       `json:\"republish,omitempty\"`\n\tMirror       *StreamSource    `json:\"mirror,omitempty\"`\n\tSources      []*StreamSource  `json:\"sources,omitempty\"`\n\tCompression  bool             `json:\"compression,omitempty\"`\n\t// LimitMarkerTTL is how long the bucket keeps markers when keys are removed by the TTL setting, 0 meaning markers are not supported\n\t// +optional\n\tLimitMarkerTTL time.Duration `json:\"limitMarkerTtl,omitempty\"`\n\n\tBaseStreamConfig\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// KeyValueList is a list of Stream resources\ntype KeyValueList struct {\n\tk8smeta.TypeMeta `json:\",inline\"`\n\tk8smeta.ListMeta `json:\"metadata\"`\n\n\tItems []KeyValue `json:\"items\"`\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta2/objectstoretypes.go",
    "content": "package v1beta2\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// Stream is a specification for a Stream resource\ntype ObjectStore struct {\n\tk8smeta.TypeMeta   `json:\",inline\"`\n\tk8smeta.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   ObjectStoreSpec `json:\"spec\"`\n\tStatus Status          `json:\"status\"`\n}\n\nfunc (s *ObjectStore) GetSpec() interface{} {\n\treturn s.Spec\n}\n\n// StreamSpec is the spec for a Stream resource\ntype ObjectStoreSpec struct {\n\tBucket      string            `json:\"bucket\"`\n\tDescription string            `json:\"description,omitempty\"`\n\tTTL         string            `json:\"ttl,omitempty\"`\n\tMaxBytes    int               `json:\"maxBytes,omitempty\"`\n\tStorage     string            `json:\"storage,omitempty\"`\n\tReplicas    int               `json:\"replicas,omitempty\"`\n\tPlacement   *StreamPlacement  `json:\"placement,omitempty\"`\n\tCompression bool              `json:\"compression,omitempty\"`\n\tMetadata    map[string]string `json:\"metadata,omitempty\"`\n\tBaseStreamConfig\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// ObjectStoreList is a list of Stream resources\ntype ObjectStoreList struct {\n\tk8smeta.TypeMeta `json:\",inline\"`\n\tk8smeta.ListMeta `json:\"metadata\"`\n\n\tItems []ObjectStore `json:\"items\"`\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta2/register.go",
    "content": "package v1beta2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\n\t\"github.com/nats-io/nack/pkg/jetstream/apis/jetstream\"\n)\n\nvar (\n\t// SchemeGroupVersion is group version used to register these objects\n\tSchemeGroupVersion = schema.GroupVersion{Group: jetstream.GroupName, Version: \"v1beta2\"}\n\n\t// SchemeBuilder initializes a scheme builder\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\t// AddToScheme is a global function that registers this API group & version to a scheme\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\n// Kind takes an unqualified kind and returns back a Group qualified GroupKind\nfunc Kind(kind string) schema.GroupKind {\n\treturn SchemeGroupVersion.WithKind(kind).GroupKind()\n}\n\n// Resource takes an unqualified resource and returns a Group qualified GroupResource\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\n// Adds the list of known types to Scheme.\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&Stream{},\n\t\t&StreamList{},\n\t\t&KeyValue{},\n\t\t&KeyValueList{},\n\t\t&ObjectStore{},\n\t\t&ObjectStoreList{},\n\t\t&Consumer{},\n\t\t&ConsumerList{},\n\t\t&Account{},\n\t\t&AccountList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta2/streamtypes.go",
    "content": "package v1beta2\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// Stream is a specification for a Stream resource\ntype Stream struct {\n\tk8smeta.TypeMeta   `json:\",inline\"`\n\tk8smeta.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   StreamSpec `json:\"spec\"`\n\tStatus Status     `json:\"status\"`\n}\n\nfunc (s *Stream) GetSpec() interface{} {\n\treturn s.Spec\n}\n\n// StreamSpec is the spec for a Stream resource\ntype StreamSpec struct {\n\tName                   string            `json:\"name\"`\n\tDescription            string            `json:\"description,omitempty\"`\n\tSubjects               []string          `json:\"subjects,omitempty\"`\n\tRetention              string            `json:\"retention,omitempty\"`\n\tMaxConsumers           int               `json:\"maxConsumers,omitempty\"`\n\tMaxMsgsPerSubject      int               `json:\"maxMsgsPerSubject,omitempty\"`\n\tMaxMsgs                int               `json:\"maxMsgs,omitempty\"`\n\tMaxBytes               int               `json:\"maxBytes,omitempty\"`\n\tMaxAge                 string            `json:\"maxAge,omitempty\"`\n\tMaxMsgSize             int               `json:\"maxMsgSize,omitempty\"`\n\tStorage                string            `json:\"storage,omitempty\"`\n\tDiscard                string            `json:\"discard,omitempty\"`\n\tReplicas               int               `json:\"replicas,omitempty\"`\n\tNoAck                  bool              `json:\"noAck,omitempty\"`\n\tDuplicateWindow        string            `json:\"duplicateWindow,omitempty\"` // Maps to Duplicates\n\tPlacement              *StreamPlacement  `json:\"placement,omitempty\"`\n\tMirror                 *StreamSource     `json:\"mirror,omitempty\"`\n\tSources                []*StreamSource   `json:\"sources,omitempty\"`\n\tCompression            string            `json:\"compression,omitempty\"`\n\tSubjectTransform       *SubjectTransform `json:\"subjectTransform,omitempty\"`\n\tRePublish              *RePublish        `json:\"republish,omitempty\"`\n\tSealed                 bool              `json:\"sealed,omitempty\"`\n\tDenyDelete             bool              `json:\"denyDelete,omitempty\"`\n\tDenyPurge              bool              `json:\"denyPurge,omitempty\"`\n\tAllowDirect            bool              `json:\"allowDirect,omitempty\"`\n\tAllowRollup            bool              `json:\"allowRollup,omitempty\"` // Maps to RollupAllowed\n\tMirrorDirect           bool              `json:\"mirrorDirect,omitempty\"`\n\tDiscardPerSubject      bool              `json:\"discardPerSubject,omitempty\"` // Maps to DiscardNewPer\n\tFirstSequence          uint64            `json:\"firstSequence,omitempty\"`     // Maps to FirstSeq\n\tMetadata               map[string]string `json:\"metadata,omitempty\"`\n\tConsumerLimits         *ConsumerLimits   `json:\"consumerLimits,omitempty\"`\n\tAllowMsgTTL            bool              `json:\"allowMsgTtl,omitempty\"`\n\tSubjectDeleteMarkerTTL string            `json:\"subjectDeleteMarkerTtl,omitempty\"`\n\tAllowMsgCounter        bool              `json:\"allowMsgCounter,omitempty\"`\n\tAllowAtomicPublish     bool              `json:\"allowAtomicPublish,omitempty\"`\n\tAllowMsgSchedules      bool              `json:\"allowMsgSchedules,omitempty\"`\n\tPersistMode            string            `json:\"persistMode,omitempty\"`\n\tBaseStreamConfig\n}\n\ntype SubjectTransform struct {\n\tSource string `json:\"source\"`\n\tDest   string `json:\"dest\"`\n}\n\ntype StreamPlacement struct {\n\tCluster string   `json:\"cluster\"`\n\tTags    []string `json:\"tags\"`\n}\n\ntype StreamSource struct {\n\tName          string `json:\"name\"`\n\tOptStartSeq   int    `json:\"optStartSeq,omitempty\"`\n\tOptStartTime  string `json:\"optStartTime,omitempty\"`\n\tFilterSubject string `json:\"filterSubject,omitempty\"`\n\n\tExternalAPIPrefix     string `json:\"externalApiPrefix,omitempty\"`\n\tExternalDeliverPrefix string `json:\"externalDeliverPrefix,omitempty\"`\n\n\tSubjectTransforms []*SubjectTransform `json:\"subjectTransforms,omitempty\"`\n}\n\ntype RePublish struct {\n\tSource      string `json:\"source\"`\n\tDestination string `json:\"destination\"`\n\tHeadersOnly bool   `json:\"headers_only,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// StreamList is a list of Stream resources\ntype StreamList struct {\n\tk8smeta.TypeMeta `json:\",inline\"`\n\tk8smeta.ListMeta `json:\"metadata\"`\n\n\tItems []Stream `json:\"items\"`\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta2/types.go",
    "content": "package v1beta2\n\nimport (\n\tk8sapi \"k8s.io/api/core/v1\"\n)\n\ntype CredentialsSecret struct {\n\tName string `json:\"name\"`\n\tKey  string `json:\"key\"`\n}\n\ntype Status struct {\n\tObservedGeneration int64       `json:\"observedGeneration\"`\n\tConditions         []Condition `json:\"conditions\"`\n}\n\ntype Condition struct {\n\tType               string                 `json:\"type\"`\n\tStatus             k8sapi.ConditionStatus `json:\"status\"`\n\tReason             string                 `json:\"reason\"`\n\tMessage            string                 `json:\"message\"`\n\tLastTransitionTime string                 `json:\"lastTransitionTime\"`\n}\n\ntype BaseStreamConfig struct {\n\tPreventDelete bool `json:\"preventDelete\"`\n\tPreventUpdate bool `json:\"preventUpdate\"`\n\tConnectionOpts\n}\n\ntype ConnectionOpts struct {\n\tAccount  string   `json:\"account,omitempty\"`\n\tCreds    string   `json:\"creds,omitempty\"`\n\tNkey     string   `json:\"nkey,omitempty\"`\n\tServers  []string `json:\"servers,omitempty\"`\n\tTLS      *TLS     `json:\"tls,omitempty\"`\n\tTLSFirst bool     `json:\"tlsFirst,omitempty\"`\n\tJsDomain string   `json:\"jsDomain,omitempty\"`\n}\n\ntype ConsumerLimits struct {\n\tInactiveThreshold string `json:\"inactiveThreshold,omitempty\"`\n\tMaxAckPending     int    `json:\"maxAckPending,omitempty\"`\n}\n\ntype TLS struct {\n\tClientCert string   `json:\"clientCert\"`\n\tClientKey  string   `json:\"clientKey\"`\n\tRootCAs    []string `json:\"rootCas,omitempty\"`\n}\n\ntype TLSSecret struct {\n\tClientCert string     `json:\"cert,omitempty\"`\n\tClientKey  string     `json:\"key,omitempty\"`\n\tRootCAs    string     `json:\"ca,omitempty\"`\n\tSecret     *SecretRef `json:\"secret\"`\n}\n\ntype CredsSecret struct {\n\tFile   string     `json:\"file,omitempty\"`\n\tSecret *SecretRef `json:\"secret\"`\n}\n\ntype NKeySecret struct {\n\tSeed   string     `json:\"seed,omitempty\"`\n\tSecret *SecretRef `json:\"secret\"`\n}\n\ntype TokenSecret struct {\n\tToken  string    `json:\"token,omitempty\"`\n\tSecret SecretRef `json:\"secret\"`\n}\n\ntype User struct {\n\tUser     string    `json:\"user,omitempty\"`\n\tPassword string    `json:\"password,omitempty\"`\n\tSecret   SecretRef `json:\"secret\"`\n}\n\ntype SecretRef struct {\n\tName string `json:\"name\"`\n}\n"
  },
  {
    "path": "pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n// Copyright 2025 The NATS 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//\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// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\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 *Account) DeepCopyInto(out *Account) {\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\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Account.\nfunc (in *Account) DeepCopy() *Account {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Account)\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 *Account) 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 *AccountList) DeepCopyInto(out *AccountList) {\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([]Account, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccountList.\nfunc (in *AccountList) DeepCopy() *AccountList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AccountList)\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 *AccountList) 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 *AccountSpec) DeepCopyInto(out *AccountSpec) {\n\t*out = *in\n\tif in.Servers != nil {\n\t\tin, out := &in.Servers, &out.Servers\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.TLS != nil {\n\t\tin, out := &in.TLS, &out.TLS\n\t\t*out = new(TLSSecret)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Creds != nil {\n\t\tin, out := &in.Creds, &out.Creds\n\t\t*out = new(CredsSecret)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.NKey != nil {\n\t\tin, out := &in.NKey, &out.NKey\n\t\t*out = new(NKeySecret)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Token != nil {\n\t\tin, out := &in.Token, &out.Token\n\t\t*out = new(TokenSecret)\n\t\t**out = **in\n\t}\n\tif in.User != nil {\n\t\tin, out := &in.User, &out.User\n\t\t*out = new(User)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccountSpec.\nfunc (in *AccountSpec) DeepCopy() *AccountSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(AccountSpec)\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 *BaseStreamConfig) DeepCopyInto(out *BaseStreamConfig) {\n\t*out = *in\n\tin.ConnectionOpts.DeepCopyInto(&out.ConnectionOpts)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BaseStreamConfig.\nfunc (in *BaseStreamConfig) DeepCopy() *BaseStreamConfig {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(BaseStreamConfig)\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 *Condition) DeepCopyInto(out *Condition) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.\nfunc (in *Condition) DeepCopy() *Condition {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Condition)\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 *ConnectionOpts) DeepCopyInto(out *ConnectionOpts) {\n\t*out = *in\n\tif in.Servers != nil {\n\t\tin, out := &in.Servers, &out.Servers\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.TLS != nil {\n\t\tin, out := &in.TLS, &out.TLS\n\t\t*out = new(TLS)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionOpts.\nfunc (in *ConnectionOpts) DeepCopy() *ConnectionOpts {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ConnectionOpts)\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 *Consumer) DeepCopyInto(out *Consumer) {\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\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Consumer.\nfunc (in *Consumer) DeepCopy() *Consumer {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Consumer)\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 *Consumer) 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 *ConsumerLimits) DeepCopyInto(out *ConsumerLimits) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsumerLimits.\nfunc (in *ConsumerLimits) DeepCopy() *ConsumerLimits {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ConsumerLimits)\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 *ConsumerList) DeepCopyInto(out *ConsumerList) {\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([]Consumer, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsumerList.\nfunc (in *ConsumerList) DeepCopy() *ConsumerList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ConsumerList)\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 *ConsumerList) 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 *ConsumerSpec) DeepCopyInto(out *ConsumerSpec) {\n\t*out = *in\n\tif in.FilterSubjects != nil {\n\t\tin, out := &in.FilterSubjects, &out.FilterSubjects\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.BackOff != nil {\n\t\tin, out := &in.BackOff, &out.BackOff\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.Metadata != nil {\n\t\tin, out := &in.Metadata, &out.Metadata\n\t\t*out = make(map[string]string, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\tif in.PriorityGroups != nil {\n\t\tin, out := &in.PriorityGroups, &out.PriorityGroups\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tin.BaseStreamConfig.DeepCopyInto(&out.BaseStreamConfig)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsumerSpec.\nfunc (in *ConsumerSpec) DeepCopy() *ConsumerSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ConsumerSpec)\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 *CredentialsSecret) DeepCopyInto(out *CredentialsSecret) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredentialsSecret.\nfunc (in *CredentialsSecret) DeepCopy() *CredentialsSecret {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CredentialsSecret)\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 *CredsSecret) DeepCopyInto(out *CredsSecret) {\n\t*out = *in\n\tif in.Secret != nil {\n\t\tin, out := &in.Secret, &out.Secret\n\t\t*out = new(SecretRef)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CredsSecret.\nfunc (in *CredsSecret) DeepCopy() *CredsSecret {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(CredsSecret)\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 *KeyValue) DeepCopyInto(out *KeyValue) {\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\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeyValue.\nfunc (in *KeyValue) DeepCopy() *KeyValue {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(KeyValue)\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 *KeyValue) 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 *KeyValueList) DeepCopyInto(out *KeyValueList) {\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([]KeyValue, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeyValueList.\nfunc (in *KeyValueList) DeepCopy() *KeyValueList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(KeyValueList)\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 *KeyValueList) 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 *KeyValueSpec) DeepCopyInto(out *KeyValueSpec) {\n\t*out = *in\n\tif in.Placement != nil {\n\t\tin, out := &in.Placement, &out.Placement\n\t\t*out = new(StreamPlacement)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.RePublish != nil {\n\t\tin, out := &in.RePublish, &out.RePublish\n\t\t*out = new(RePublish)\n\t\t**out = **in\n\t}\n\tif in.Mirror != nil {\n\t\tin, out := &in.Mirror, &out.Mirror\n\t\t*out = new(StreamSource)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Sources != nil {\n\t\tin, out := &in.Sources, &out.Sources\n\t\t*out = make([]*StreamSource, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(StreamSource)\n\t\t\t\t(*in).DeepCopyInto(*out)\n\t\t\t}\n\t\t}\n\t}\n\tin.BaseStreamConfig.DeepCopyInto(&out.BaseStreamConfig)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeyValueSpec.\nfunc (in *KeyValueSpec) DeepCopy() *KeyValueSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(KeyValueSpec)\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 *NKeySecret) DeepCopyInto(out *NKeySecret) {\n\t*out = *in\n\tif in.Secret != nil {\n\t\tin, out := &in.Secret, &out.Secret\n\t\t*out = new(SecretRef)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NKeySecret.\nfunc (in *NKeySecret) DeepCopy() *NKeySecret {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(NKeySecret)\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 *ObjectStore) DeepCopyInto(out *ObjectStore) {\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\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStore.\nfunc (in *ObjectStore) DeepCopy() *ObjectStore {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ObjectStore)\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 *ObjectStore) 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 *ObjectStoreList) DeepCopyInto(out *ObjectStoreList) {\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([]ObjectStore, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStoreList.\nfunc (in *ObjectStoreList) DeepCopy() *ObjectStoreList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ObjectStoreList)\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 *ObjectStoreList) 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 *ObjectStoreSpec) DeepCopyInto(out *ObjectStoreSpec) {\n\t*out = *in\n\tif in.Placement != nil {\n\t\tin, out := &in.Placement, &out.Placement\n\t\t*out = new(StreamPlacement)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Metadata != nil {\n\t\tin, out := &in.Metadata, &out.Metadata\n\t\t*out = make(map[string]string, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\tin.BaseStreamConfig.DeepCopyInto(&out.BaseStreamConfig)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStoreSpec.\nfunc (in *ObjectStoreSpec) DeepCopy() *ObjectStoreSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ObjectStoreSpec)\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 *RePublish) DeepCopyInto(out *RePublish) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RePublish.\nfunc (in *RePublish) DeepCopy() *RePublish {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(RePublish)\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 *SecretRef) DeepCopyInto(out *SecretRef) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretRef.\nfunc (in *SecretRef) DeepCopy() *SecretRef {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SecretRef)\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 *Status) DeepCopyInto(out *Status) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]Condition, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status.\nfunc (in *Status) DeepCopy() *Status {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Status)\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 *Stream) DeepCopyInto(out *Stream) {\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\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Stream.\nfunc (in *Stream) DeepCopy() *Stream {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Stream)\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 *Stream) 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 *StreamList) DeepCopyInto(out *StreamList) {\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([]Stream, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamList.\nfunc (in *StreamList) DeepCopy() *StreamList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(StreamList)\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 *StreamList) 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 *StreamPlacement) DeepCopyInto(out *StreamPlacement) {\n\t*out = *in\n\tif in.Tags != nil {\n\t\tin, out := &in.Tags, &out.Tags\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamPlacement.\nfunc (in *StreamPlacement) DeepCopy() *StreamPlacement {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(StreamPlacement)\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 *StreamSource) DeepCopyInto(out *StreamSource) {\n\t*out = *in\n\tif in.SubjectTransforms != nil {\n\t\tin, out := &in.SubjectTransforms, &out.SubjectTransforms\n\t\t*out = make([]*SubjectTransform, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(SubjectTransform)\n\t\t\t\t**out = **in\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamSource.\nfunc (in *StreamSource) DeepCopy() *StreamSource {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(StreamSource)\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 *StreamSpec) DeepCopyInto(out *StreamSpec) {\n\t*out = *in\n\tif in.Subjects != nil {\n\t\tin, out := &in.Subjects, &out.Subjects\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.Placement != nil {\n\t\tin, out := &in.Placement, &out.Placement\n\t\t*out = new(StreamPlacement)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Mirror != nil {\n\t\tin, out := &in.Mirror, &out.Mirror\n\t\t*out = new(StreamSource)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.Sources != nil {\n\t\tin, out := &in.Sources, &out.Sources\n\t\t*out = make([]*StreamSource, len(*in))\n\t\tfor i := range *in {\n\t\t\tif (*in)[i] != nil {\n\t\t\t\tin, out := &(*in)[i], &(*out)[i]\n\t\t\t\t*out = new(StreamSource)\n\t\t\t\t(*in).DeepCopyInto(*out)\n\t\t\t}\n\t\t}\n\t}\n\tif in.SubjectTransform != nil {\n\t\tin, out := &in.SubjectTransform, &out.SubjectTransform\n\t\t*out = new(SubjectTransform)\n\t\t**out = **in\n\t}\n\tif in.RePublish != nil {\n\t\tin, out := &in.RePublish, &out.RePublish\n\t\t*out = new(RePublish)\n\t\t**out = **in\n\t}\n\tif in.Metadata != nil {\n\t\tin, out := &in.Metadata, &out.Metadata\n\t\t*out = make(map[string]string, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\tif in.ConsumerLimits != nil {\n\t\tin, out := &in.ConsumerLimits, &out.ConsumerLimits\n\t\t*out = new(ConsumerLimits)\n\t\t**out = **in\n\t}\n\tin.BaseStreamConfig.DeepCopyInto(&out.BaseStreamConfig)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StreamSpec.\nfunc (in *StreamSpec) DeepCopy() *StreamSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(StreamSpec)\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 *SubjectTransform) DeepCopyInto(out *SubjectTransform) {\n\t*out = *in\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubjectTransform.\nfunc (in *SubjectTransform) DeepCopy() *SubjectTransform {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SubjectTransform)\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 *TLS) DeepCopyInto(out *TLS) {\n\t*out = *in\n\tif in.RootCAs != nil {\n\t\tin, out := &in.RootCAs, &out.RootCAs\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLS.\nfunc (in *TLS) DeepCopy() *TLS {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(TLS)\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 *TLSSecret) DeepCopyInto(out *TLSSecret) {\n\t*out = *in\n\tif in.Secret != nil {\n\t\tin, out := &in.Secret, &out.Secret\n\t\t*out = new(SecretRef)\n\t\t**out = **in\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSSecret.\nfunc (in *TLSSecret) DeepCopy() *TLSSecret {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(TLSSecret)\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 *TokenSecret) DeepCopyInto(out *TokenSecret) {\n\t*out = *in\n\tout.Secret = in.Secret\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TokenSecret.\nfunc (in *TokenSecret) DeepCopy() *TokenSecret {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(TokenSecret)\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 *User) DeepCopyInto(out *User) {\n\t*out = *in\n\tout.Secret = in.Secret\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new User.\nfunc (in *User) DeepCopy() *User {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(User)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/internal/internal.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage internal\n\nimport (\n\tfmt \"fmt\"\n\tsync \"sync\"\n\n\ttyped \"sigs.k8s.io/structured-merge-diff/v6/typed\"\n)\n\nfunc Parser() *typed.Parser {\n\tparserOnce.Do(func() {\n\t\tvar err error\n\t\tparser, err = typed.NewParser(schemaYAML)\n\t\tif err != nil {\n\t\t\tpanic(fmt.Sprintf(\"Failed to parse schema: %v\", err))\n\t\t}\n\t})\n\treturn parser\n}\n\nvar parserOnce sync.Once\nvar parser *typed.Parser\nvar schemaYAML = typed.YAMLObject(`types:\n- name: __untyped_atomic_\n  scalar: untyped\n  list:\n    elementType:\n      namedType: __untyped_atomic_\n    elementRelationship: atomic\n  map:\n    elementType:\n      namedType: __untyped_atomic_\n    elementRelationship: atomic\n- name: __untyped_deduced_\n  scalar: untyped\n  list:\n    elementType:\n      namedType: __untyped_atomic_\n    elementRelationship: atomic\n  map:\n    elementType:\n      namedType: __untyped_deduced_\n    elementRelationship: separable\n`)\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/account.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\tv1 \"k8s.io/client-go/applyconfigurations/meta/v1\"\n)\n\n// AccountApplyConfiguration represents a declarative configuration of the Account type for use\n// with apply.\ntype AccountApplyConfiguration struct {\n\tv1.TypeMetaApplyConfiguration    `json:\",inline\"`\n\t*v1.ObjectMetaApplyConfiguration `json:\"metadata,omitempty\"`\n\tSpec                             *AccountSpecApplyConfiguration `json:\"spec,omitempty\"`\n\tStatus                           *StatusApplyConfiguration      `json:\"status,omitempty\"`\n}\n\n// Account constructs a declarative configuration of the Account type for use with\n// apply.\nfunc Account(name, namespace string) *AccountApplyConfiguration {\n\tb := &AccountApplyConfiguration{}\n\tb.WithName(name)\n\tb.WithNamespace(namespace)\n\tb.WithKind(\"Account\")\n\tb.WithAPIVersion(\"jetstream.nats.io/v1beta2\")\n\treturn b\n}\nfunc (b AccountApplyConfiguration) IsApplyConfiguration() {}\n\n// WithKind sets the Kind field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Kind field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithKind(value string) *AccountApplyConfiguration {\n\tb.TypeMetaApplyConfiguration.Kind = &value\n\treturn b\n}\n\n// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the APIVersion field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithAPIVersion(value string) *AccountApplyConfiguration {\n\tb.TypeMetaApplyConfiguration.APIVersion = &value\n\treturn b\n}\n\n// WithName sets the Name field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Name field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithName(value string) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Name = &value\n\treturn b\n}\n\n// WithGenerateName sets the GenerateName field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the GenerateName field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithGenerateName(value string) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.GenerateName = &value\n\treturn b\n}\n\n// WithNamespace sets the Namespace field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Namespace field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithNamespace(value string) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Namespace = &value\n\treturn b\n}\n\n// WithUID sets the UID field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the UID field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithUID(value types.UID) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.UID = &value\n\treturn b\n}\n\n// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ResourceVersion field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithResourceVersion(value string) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.ResourceVersion = &value\n\treturn b\n}\n\n// WithGeneration sets the Generation field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Generation field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithGeneration(value int64) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Generation = &value\n\treturn b\n}\n\n// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the CreationTimestamp field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithCreationTimestamp(value metav1.Time) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.CreationTimestamp = &value\n\treturn b\n}\n\n// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeletionTimestamp field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.DeletionTimestamp = &value\n\treturn b\n}\n\n// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value\n\treturn b\n}\n\n// WithLabels puts the entries into the Labels field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Labels field,\n// overwriting an existing map entries in Labels field with the same key.\nfunc (b *AccountApplyConfiguration) WithLabels(entries map[string]string) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tif b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 {\n\t\tb.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.ObjectMetaApplyConfiguration.Labels[k] = v\n\t}\n\treturn b\n}\n\n// WithAnnotations puts the entries into the Annotations field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Annotations field,\n// overwriting an existing map entries in Annotations field with the same key.\nfunc (b *AccountApplyConfiguration) WithAnnotations(entries map[string]string) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tif b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 {\n\t\tb.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.ObjectMetaApplyConfiguration.Annotations[k] = v\n\t}\n\treturn b\n}\n\n// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the OwnerReferences field.\nfunc (b *AccountApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tfor i := range values {\n\t\tif values[i] == nil {\n\t\t\tpanic(\"nil value passed to WithOwnerReferences\")\n\t\t}\n\t\tb.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i])\n\t}\n\treturn b\n}\n\n// WithFinalizers adds the given value to the Finalizers field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the Finalizers field.\nfunc (b *AccountApplyConfiguration) WithFinalizers(values ...string) *AccountApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tfor i := range values {\n\t\tb.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i])\n\t}\n\treturn b\n}\n\nfunc (b *AccountApplyConfiguration) ensureObjectMetaApplyConfigurationExists() {\n\tif b.ObjectMetaApplyConfiguration == nil {\n\t\tb.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{}\n\t}\n}\n\n// WithSpec sets the Spec field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Spec field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithSpec(value *AccountSpecApplyConfiguration) *AccountApplyConfiguration {\n\tb.Spec = value\n\treturn b\n}\n\n// WithStatus sets the Status field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Status field is set to the value of the last call.\nfunc (b *AccountApplyConfiguration) WithStatus(value *StatusApplyConfiguration) *AccountApplyConfiguration {\n\tb.Status = value\n\treturn b\n}\n\n// GetKind retrieves the value of the Kind field in the declarative configuration.\nfunc (b *AccountApplyConfiguration) GetKind() *string {\n\treturn b.TypeMetaApplyConfiguration.Kind\n}\n\n// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration.\nfunc (b *AccountApplyConfiguration) GetAPIVersion() *string {\n\treturn b.TypeMetaApplyConfiguration.APIVersion\n}\n\n// GetName retrieves the value of the Name field in the declarative configuration.\nfunc (b *AccountApplyConfiguration) GetName() *string {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\treturn b.ObjectMetaApplyConfiguration.Name\n}\n\n// GetNamespace retrieves the value of the Namespace field in the declarative configuration.\nfunc (b *AccountApplyConfiguration) GetNamespace() *string {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\treturn b.ObjectMetaApplyConfiguration.Namespace\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/accountspec.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// AccountSpecApplyConfiguration represents a declarative configuration of the AccountSpec type for use\n// with apply.\ntype AccountSpecApplyConfiguration struct {\n\tServers []string                       `json:\"servers,omitempty\"`\n\tTLS     *TLSSecretApplyConfiguration   `json:\"tls,omitempty\"`\n\tCreds   *CredsSecretApplyConfiguration `json:\"creds,omitempty\"`\n\tNKey    *NKeySecretApplyConfiguration  `json:\"nkey,omitempty\"`\n\tToken   *TokenSecretApplyConfiguration `json:\"token,omitempty\"`\n\tUser    *UserApplyConfiguration        `json:\"user,omitempty\"`\n}\n\n// AccountSpecApplyConfiguration constructs a declarative configuration of the AccountSpec type for use with\n// apply.\nfunc AccountSpec() *AccountSpecApplyConfiguration {\n\treturn &AccountSpecApplyConfiguration{}\n}\n\n// WithServers adds the given value to the Servers field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the Servers field.\nfunc (b *AccountSpecApplyConfiguration) WithServers(values ...string) *AccountSpecApplyConfiguration {\n\tfor i := range values {\n\t\tb.Servers = append(b.Servers, values[i])\n\t}\n\treturn b\n}\n\n// WithTLS sets the TLS field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the TLS field is set to the value of the last call.\nfunc (b *AccountSpecApplyConfiguration) WithTLS(value *TLSSecretApplyConfiguration) *AccountSpecApplyConfiguration {\n\tb.TLS = value\n\treturn b\n}\n\n// WithCreds sets the Creds field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Creds field is set to the value of the last call.\nfunc (b *AccountSpecApplyConfiguration) WithCreds(value *CredsSecretApplyConfiguration) *AccountSpecApplyConfiguration {\n\tb.Creds = value\n\treturn b\n}\n\n// WithNKey sets the NKey field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the NKey field is set to the value of the last call.\nfunc (b *AccountSpecApplyConfiguration) WithNKey(value *NKeySecretApplyConfiguration) *AccountSpecApplyConfiguration {\n\tb.NKey = value\n\treturn b\n}\n\n// WithToken sets the Token field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Token field is set to the value of the last call.\nfunc (b *AccountSpecApplyConfiguration) WithToken(value *TokenSecretApplyConfiguration) *AccountSpecApplyConfiguration {\n\tb.Token = value\n\treturn b\n}\n\n// WithUser sets the User field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the User field is set to the value of the last call.\nfunc (b *AccountSpecApplyConfiguration) WithUser(value *UserApplyConfiguration) *AccountSpecApplyConfiguration {\n\tb.User = value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/basestreamconfig.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// BaseStreamConfigApplyConfiguration represents a declarative configuration of the BaseStreamConfig type for use\n// with apply.\ntype BaseStreamConfigApplyConfiguration struct {\n\tPreventDelete *bool `json:\"preventDelete,omitempty\"`\n\tPreventUpdate *bool `json:\"preventUpdate,omitempty\"`\n}\n\n// BaseStreamConfigApplyConfiguration constructs a declarative configuration of the BaseStreamConfig type for use with\n// apply.\nfunc BaseStreamConfig() *BaseStreamConfigApplyConfiguration {\n\treturn &BaseStreamConfigApplyConfiguration{}\n}\n\n// WithPreventDelete sets the PreventDelete field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the PreventDelete field is set to the value of the last call.\nfunc (b *BaseStreamConfigApplyConfiguration) WithPreventDelete(value bool) *BaseStreamConfigApplyConfiguration {\n\tb.PreventDelete = &value\n\treturn b\n}\n\n// WithPreventUpdate sets the PreventUpdate field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the PreventUpdate field is set to the value of the last call.\nfunc (b *BaseStreamConfigApplyConfiguration) WithPreventUpdate(value bool) *BaseStreamConfigApplyConfiguration {\n\tb.PreventUpdate = &value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/condition.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tv1 \"k8s.io/api/core/v1\"\n)\n\n// ConditionApplyConfiguration represents a declarative configuration of the Condition type for use\n// with apply.\ntype ConditionApplyConfiguration struct {\n\tType               *string             `json:\"type,omitempty\"`\n\tStatus             *v1.ConditionStatus `json:\"status,omitempty\"`\n\tReason             *string             `json:\"reason,omitempty\"`\n\tMessage            *string             `json:\"message,omitempty\"`\n\tLastTransitionTime *string             `json:\"lastTransitionTime,omitempty\"`\n}\n\n// ConditionApplyConfiguration constructs a declarative configuration of the Condition type for use with\n// apply.\nfunc Condition() *ConditionApplyConfiguration {\n\treturn &ConditionApplyConfiguration{}\n}\n\n// WithType sets the Type field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Type field is set to the value of the last call.\nfunc (b *ConditionApplyConfiguration) WithType(value string) *ConditionApplyConfiguration {\n\tb.Type = &value\n\treturn b\n}\n\n// WithStatus sets the Status field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Status field is set to the value of the last call.\nfunc (b *ConditionApplyConfiguration) WithStatus(value v1.ConditionStatus) *ConditionApplyConfiguration {\n\tb.Status = &value\n\treturn b\n}\n\n// WithReason sets the Reason field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Reason field is set to the value of the last call.\nfunc (b *ConditionApplyConfiguration) WithReason(value string) *ConditionApplyConfiguration {\n\tb.Reason = &value\n\treturn b\n}\n\n// WithMessage sets the Message field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Message field is set to the value of the last call.\nfunc (b *ConditionApplyConfiguration) WithMessage(value string) *ConditionApplyConfiguration {\n\tb.Message = &value\n\treturn b\n}\n\n// WithLastTransitionTime sets the LastTransitionTime field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the LastTransitionTime field is set to the value of the last call.\nfunc (b *ConditionApplyConfiguration) WithLastTransitionTime(value string) *ConditionApplyConfiguration {\n\tb.LastTransitionTime = &value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/connectionopts.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// ConnectionOptsApplyConfiguration represents a declarative configuration of the ConnectionOpts type for use\n// with apply.\ntype ConnectionOptsApplyConfiguration struct {\n\tAccount  *string                `json:\"account,omitempty\"`\n\tCreds    *string                `json:\"creds,omitempty\"`\n\tNkey     *string                `json:\"nkey,omitempty\"`\n\tServers  []string               `json:\"servers,omitempty\"`\n\tTLS      *TLSApplyConfiguration `json:\"tls,omitempty\"`\n\tTLSFirst *bool                  `json:\"tlsFirst,omitempty\"`\n\tJsDomain *string                `json:\"jsDomain,omitempty\"`\n}\n\n// ConnectionOptsApplyConfiguration constructs a declarative configuration of the ConnectionOpts type for use with\n// apply.\nfunc ConnectionOpts() *ConnectionOptsApplyConfiguration {\n\treturn &ConnectionOptsApplyConfiguration{}\n}\n\n// WithAccount sets the Account field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Account field is set to the value of the last call.\nfunc (b *ConnectionOptsApplyConfiguration) WithAccount(value string) *ConnectionOptsApplyConfiguration {\n\tb.Account = &value\n\treturn b\n}\n\n// WithCreds sets the Creds field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Creds field is set to the value of the last call.\nfunc (b *ConnectionOptsApplyConfiguration) WithCreds(value string) *ConnectionOptsApplyConfiguration {\n\tb.Creds = &value\n\treturn b\n}\n\n// WithNkey sets the Nkey field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Nkey field is set to the value of the last call.\nfunc (b *ConnectionOptsApplyConfiguration) WithNkey(value string) *ConnectionOptsApplyConfiguration {\n\tb.Nkey = &value\n\treturn b\n}\n\n// WithServers adds the given value to the Servers field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the Servers field.\nfunc (b *ConnectionOptsApplyConfiguration) WithServers(values ...string) *ConnectionOptsApplyConfiguration {\n\tfor i := range values {\n\t\tb.Servers = append(b.Servers, values[i])\n\t}\n\treturn b\n}\n\n// WithTLS sets the TLS field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the TLS field is set to the value of the last call.\nfunc (b *ConnectionOptsApplyConfiguration) WithTLS(value *TLSApplyConfiguration) *ConnectionOptsApplyConfiguration {\n\tb.TLS = value\n\treturn b\n}\n\n// WithTLSFirst sets the TLSFirst field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the TLSFirst field is set to the value of the last call.\nfunc (b *ConnectionOptsApplyConfiguration) WithTLSFirst(value bool) *ConnectionOptsApplyConfiguration {\n\tb.TLSFirst = &value\n\treturn b\n}\n\n// WithJsDomain sets the JsDomain field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the JsDomain field is set to the value of the last call.\nfunc (b *ConnectionOptsApplyConfiguration) WithJsDomain(value string) *ConnectionOptsApplyConfiguration {\n\tb.JsDomain = &value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumer.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\tv1 \"k8s.io/client-go/applyconfigurations/meta/v1\"\n)\n\n// ConsumerApplyConfiguration represents a declarative configuration of the Consumer type for use\n// with apply.\ntype ConsumerApplyConfiguration struct {\n\tv1.TypeMetaApplyConfiguration    `json:\",inline\"`\n\t*v1.ObjectMetaApplyConfiguration `json:\"metadata,omitempty\"`\n\tSpec                             *ConsumerSpecApplyConfiguration `json:\"spec,omitempty\"`\n\tStatus                           *StatusApplyConfiguration       `json:\"status,omitempty\"`\n}\n\n// Consumer constructs a declarative configuration of the Consumer type for use with\n// apply.\nfunc Consumer(name, namespace string) *ConsumerApplyConfiguration {\n\tb := &ConsumerApplyConfiguration{}\n\tb.WithName(name)\n\tb.WithNamespace(namespace)\n\tb.WithKind(\"Consumer\")\n\tb.WithAPIVersion(\"jetstream.nats.io/v1beta2\")\n\treturn b\n}\nfunc (b ConsumerApplyConfiguration) IsApplyConfiguration() {}\n\n// WithKind sets the Kind field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Kind field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithKind(value string) *ConsumerApplyConfiguration {\n\tb.TypeMetaApplyConfiguration.Kind = &value\n\treturn b\n}\n\n// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the APIVersion field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithAPIVersion(value string) *ConsumerApplyConfiguration {\n\tb.TypeMetaApplyConfiguration.APIVersion = &value\n\treturn b\n}\n\n// WithName sets the Name field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Name field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithName(value string) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Name = &value\n\treturn b\n}\n\n// WithGenerateName sets the GenerateName field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the GenerateName field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithGenerateName(value string) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.GenerateName = &value\n\treturn b\n}\n\n// WithNamespace sets the Namespace field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Namespace field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithNamespace(value string) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Namespace = &value\n\treturn b\n}\n\n// WithUID sets the UID field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the UID field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithUID(value types.UID) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.UID = &value\n\treturn b\n}\n\n// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ResourceVersion field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithResourceVersion(value string) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.ResourceVersion = &value\n\treturn b\n}\n\n// WithGeneration sets the Generation field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Generation field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithGeneration(value int64) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Generation = &value\n\treturn b\n}\n\n// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the CreationTimestamp field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithCreationTimestamp(value metav1.Time) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.CreationTimestamp = &value\n\treturn b\n}\n\n// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeletionTimestamp field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.DeletionTimestamp = &value\n\treturn b\n}\n\n// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value\n\treturn b\n}\n\n// WithLabels puts the entries into the Labels field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Labels field,\n// overwriting an existing map entries in Labels field with the same key.\nfunc (b *ConsumerApplyConfiguration) WithLabels(entries map[string]string) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tif b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 {\n\t\tb.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.ObjectMetaApplyConfiguration.Labels[k] = v\n\t}\n\treturn b\n}\n\n// WithAnnotations puts the entries into the Annotations field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Annotations field,\n// overwriting an existing map entries in Annotations field with the same key.\nfunc (b *ConsumerApplyConfiguration) WithAnnotations(entries map[string]string) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tif b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 {\n\t\tb.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.ObjectMetaApplyConfiguration.Annotations[k] = v\n\t}\n\treturn b\n}\n\n// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the OwnerReferences field.\nfunc (b *ConsumerApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tfor i := range values {\n\t\tif values[i] == nil {\n\t\t\tpanic(\"nil value passed to WithOwnerReferences\")\n\t\t}\n\t\tb.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i])\n\t}\n\treturn b\n}\n\n// WithFinalizers adds the given value to the Finalizers field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the Finalizers field.\nfunc (b *ConsumerApplyConfiguration) WithFinalizers(values ...string) *ConsumerApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tfor i := range values {\n\t\tb.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i])\n\t}\n\treturn b\n}\n\nfunc (b *ConsumerApplyConfiguration) ensureObjectMetaApplyConfigurationExists() {\n\tif b.ObjectMetaApplyConfiguration == nil {\n\t\tb.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{}\n\t}\n}\n\n// WithSpec sets the Spec field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Spec field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithSpec(value *ConsumerSpecApplyConfiguration) *ConsumerApplyConfiguration {\n\tb.Spec = value\n\treturn b\n}\n\n// WithStatus sets the Status field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Status field is set to the value of the last call.\nfunc (b *ConsumerApplyConfiguration) WithStatus(value *StatusApplyConfiguration) *ConsumerApplyConfiguration {\n\tb.Status = value\n\treturn b\n}\n\n// GetKind retrieves the value of the Kind field in the declarative configuration.\nfunc (b *ConsumerApplyConfiguration) GetKind() *string {\n\treturn b.TypeMetaApplyConfiguration.Kind\n}\n\n// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration.\nfunc (b *ConsumerApplyConfiguration) GetAPIVersion() *string {\n\treturn b.TypeMetaApplyConfiguration.APIVersion\n}\n\n// GetName retrieves the value of the Name field in the declarative configuration.\nfunc (b *ConsumerApplyConfiguration) GetName() *string {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\treturn b.ObjectMetaApplyConfiguration.Name\n}\n\n// GetNamespace retrieves the value of the Namespace field in the declarative configuration.\nfunc (b *ConsumerApplyConfiguration) GetNamespace() *string {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\treturn b.ObjectMetaApplyConfiguration.Namespace\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerlimits.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// ConsumerLimitsApplyConfiguration represents a declarative configuration of the ConsumerLimits type for use\n// with apply.\ntype ConsumerLimitsApplyConfiguration struct {\n\tInactiveThreshold *string `json:\"inactiveThreshold,omitempty\"`\n\tMaxAckPending     *int    `json:\"maxAckPending,omitempty\"`\n}\n\n// ConsumerLimitsApplyConfiguration constructs a declarative configuration of the ConsumerLimits type for use with\n// apply.\nfunc ConsumerLimits() *ConsumerLimitsApplyConfiguration {\n\treturn &ConsumerLimitsApplyConfiguration{}\n}\n\n// WithInactiveThreshold sets the InactiveThreshold field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the InactiveThreshold field is set to the value of the last call.\nfunc (b *ConsumerLimitsApplyConfiguration) WithInactiveThreshold(value string) *ConsumerLimitsApplyConfiguration {\n\tb.InactiveThreshold = &value\n\treturn b\n}\n\n// WithMaxAckPending sets the MaxAckPending field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxAckPending field is set to the value of the last call.\nfunc (b *ConsumerLimitsApplyConfiguration) WithMaxAckPending(value int) *ConsumerLimitsApplyConfiguration {\n\tb.MaxAckPending = &value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// ConsumerSpecApplyConfiguration represents a declarative configuration of the ConsumerSpec type for use\n// with apply.\ntype ConsumerSpecApplyConfiguration struct {\n\tDescription        *string           `json:\"description,omitempty\"`\n\tAckPolicy          *string           `json:\"ackPolicy,omitempty\"`\n\tAckWait            *string           `json:\"ackWait,omitempty\"`\n\tDeliverPolicy      *string           `json:\"deliverPolicy,omitempty\"`\n\tDeliverSubject     *string           `json:\"deliverSubject,omitempty\"`\n\tDeliverGroup       *string           `json:\"deliverGroup,omitempty\"`\n\tDurableName        *string           `json:\"durableName,omitempty\"`\n\tFilterSubject      *string           `json:\"filterSubject,omitempty\"`\n\tFilterSubjects     []string          `json:\"filterSubjects,omitempty\"`\n\tFlowControl        *bool             `json:\"flowControl,omitempty\"`\n\tHeartbeatInterval  *string           `json:\"heartbeatInterval,omitempty\"`\n\tMaxAckPending      *int              `json:\"maxAckPending,omitempty\"`\n\tMaxDeliver         *int              `json:\"maxDeliver,omitempty\"`\n\tBackOff            []string          `json:\"backoff,omitempty\"`\n\tMaxWaiting         *int              `json:\"maxWaiting,omitempty\"`\n\tOptStartSeq        *int              `json:\"optStartSeq,omitempty\"`\n\tOptStartTime       *string           `json:\"optStartTime,omitempty\"`\n\tRateLimitBps       *int              `json:\"rateLimitBps,omitempty\"`\n\tReplayPolicy       *string           `json:\"replayPolicy,omitempty\"`\n\tSampleFreq         *string           `json:\"sampleFreq,omitempty\"`\n\tHeadersOnly        *bool             `json:\"headersOnly,omitempty\"`\n\tMaxRequestBatch    *int              `json:\"maxRequestBatch,omitempty\"`\n\tMaxRequestExpires  *string           `json:\"maxRequestExpires,omitempty\"`\n\tMaxRequestMaxBytes *int              `json:\"maxRequestMaxBytes,omitempty\"`\n\tInactiveThreshold  *string           `json:\"inactiveThreshold,omitempty\"`\n\tReplicas           *int              `json:\"replicas,omitempty\"`\n\tMemStorage         *bool             `json:\"memStorage,omitempty\"`\n\tMetadata           map[string]string `json:\"metadata,omitempty\"`\n\tPauseUntil         *string           `json:\"pauseUntil,omitempty\"`\n\tPriorityPolicy     *string           `json:\"priorityPolicy,omitempty\"`\n\tPinnedTTL          *string           `json:\"pinnedTtl,omitempty\"`\n\tPriorityGroups     []string          `json:\"priorityGroups,omitempty\"`\n\tStreamName         *string           `json:\"streamName,omitempty\"`\n}\n\n// ConsumerSpecApplyConfiguration constructs a declarative configuration of the ConsumerSpec type for use with\n// apply.\nfunc ConsumerSpec() *ConsumerSpecApplyConfiguration {\n\treturn &ConsumerSpecApplyConfiguration{}\n}\n\n// WithDescription sets the Description field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Description field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithDescription(value string) *ConsumerSpecApplyConfiguration {\n\tb.Description = &value\n\treturn b\n}\n\n// WithAckPolicy sets the AckPolicy field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the AckPolicy field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithAckPolicy(value string) *ConsumerSpecApplyConfiguration {\n\tb.AckPolicy = &value\n\treturn b\n}\n\n// WithAckWait sets the AckWait field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the AckWait field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithAckWait(value string) *ConsumerSpecApplyConfiguration {\n\tb.AckWait = &value\n\treturn b\n}\n\n// WithDeliverPolicy sets the DeliverPolicy field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeliverPolicy field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithDeliverPolicy(value string) *ConsumerSpecApplyConfiguration {\n\tb.DeliverPolicy = &value\n\treturn b\n}\n\n// WithDeliverSubject sets the DeliverSubject field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeliverSubject field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithDeliverSubject(value string) *ConsumerSpecApplyConfiguration {\n\tb.DeliverSubject = &value\n\treturn b\n}\n\n// WithDeliverGroup sets the DeliverGroup field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeliverGroup field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithDeliverGroup(value string) *ConsumerSpecApplyConfiguration {\n\tb.DeliverGroup = &value\n\treturn b\n}\n\n// WithDurableName sets the DurableName field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DurableName field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithDurableName(value string) *ConsumerSpecApplyConfiguration {\n\tb.DurableName = &value\n\treturn b\n}\n\n// WithFilterSubject sets the FilterSubject field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the FilterSubject field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithFilterSubject(value string) *ConsumerSpecApplyConfiguration {\n\tb.FilterSubject = &value\n\treturn b\n}\n\n// WithFilterSubjects adds the given value to the FilterSubjects field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the FilterSubjects field.\nfunc (b *ConsumerSpecApplyConfiguration) WithFilterSubjects(values ...string) *ConsumerSpecApplyConfiguration {\n\tfor i := range values {\n\t\tb.FilterSubjects = append(b.FilterSubjects, values[i])\n\t}\n\treturn b\n}\n\n// WithFlowControl sets the FlowControl field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the FlowControl field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithFlowControl(value bool) *ConsumerSpecApplyConfiguration {\n\tb.FlowControl = &value\n\treturn b\n}\n\n// WithHeartbeatInterval sets the HeartbeatInterval field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the HeartbeatInterval field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithHeartbeatInterval(value string) *ConsumerSpecApplyConfiguration {\n\tb.HeartbeatInterval = &value\n\treturn b\n}\n\n// WithMaxAckPending sets the MaxAckPending field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxAckPending field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithMaxAckPending(value int) *ConsumerSpecApplyConfiguration {\n\tb.MaxAckPending = &value\n\treturn b\n}\n\n// WithMaxDeliver sets the MaxDeliver field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxDeliver field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithMaxDeliver(value int) *ConsumerSpecApplyConfiguration {\n\tb.MaxDeliver = &value\n\treturn b\n}\n\n// WithBackOff adds the given value to the BackOff field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the BackOff field.\nfunc (b *ConsumerSpecApplyConfiguration) WithBackOff(values ...string) *ConsumerSpecApplyConfiguration {\n\tfor i := range values {\n\t\tb.BackOff = append(b.BackOff, values[i])\n\t}\n\treturn b\n}\n\n// WithMaxWaiting sets the MaxWaiting field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxWaiting field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithMaxWaiting(value int) *ConsumerSpecApplyConfiguration {\n\tb.MaxWaiting = &value\n\treturn b\n}\n\n// WithOptStartSeq sets the OptStartSeq field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the OptStartSeq field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithOptStartSeq(value int) *ConsumerSpecApplyConfiguration {\n\tb.OptStartSeq = &value\n\treturn b\n}\n\n// WithOptStartTime sets the OptStartTime field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the OptStartTime field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithOptStartTime(value string) *ConsumerSpecApplyConfiguration {\n\tb.OptStartTime = &value\n\treturn b\n}\n\n// WithRateLimitBps sets the RateLimitBps field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the RateLimitBps field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithRateLimitBps(value int) *ConsumerSpecApplyConfiguration {\n\tb.RateLimitBps = &value\n\treturn b\n}\n\n// WithReplayPolicy sets the ReplayPolicy field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ReplayPolicy field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithReplayPolicy(value string) *ConsumerSpecApplyConfiguration {\n\tb.ReplayPolicy = &value\n\treturn b\n}\n\n// WithSampleFreq sets the SampleFreq field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the SampleFreq field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithSampleFreq(value string) *ConsumerSpecApplyConfiguration {\n\tb.SampleFreq = &value\n\treturn b\n}\n\n// WithHeadersOnly sets the HeadersOnly field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the HeadersOnly field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithHeadersOnly(value bool) *ConsumerSpecApplyConfiguration {\n\tb.HeadersOnly = &value\n\treturn b\n}\n\n// WithMaxRequestBatch sets the MaxRequestBatch field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxRequestBatch field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithMaxRequestBatch(value int) *ConsumerSpecApplyConfiguration {\n\tb.MaxRequestBatch = &value\n\treturn b\n}\n\n// WithMaxRequestExpires sets the MaxRequestExpires field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxRequestExpires field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithMaxRequestExpires(value string) *ConsumerSpecApplyConfiguration {\n\tb.MaxRequestExpires = &value\n\treturn b\n}\n\n// WithMaxRequestMaxBytes sets the MaxRequestMaxBytes field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxRequestMaxBytes field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithMaxRequestMaxBytes(value int) *ConsumerSpecApplyConfiguration {\n\tb.MaxRequestMaxBytes = &value\n\treturn b\n}\n\n// WithInactiveThreshold sets the InactiveThreshold field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the InactiveThreshold field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithInactiveThreshold(value string) *ConsumerSpecApplyConfiguration {\n\tb.InactiveThreshold = &value\n\treturn b\n}\n\n// WithReplicas sets the Replicas field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Replicas field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithReplicas(value int) *ConsumerSpecApplyConfiguration {\n\tb.Replicas = &value\n\treturn b\n}\n\n// WithMemStorage sets the MemStorage field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MemStorage field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithMemStorage(value bool) *ConsumerSpecApplyConfiguration {\n\tb.MemStorage = &value\n\treturn b\n}\n\n// WithMetadata puts the entries into the Metadata field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Metadata field,\n// overwriting an existing map entries in Metadata field with the same key.\nfunc (b *ConsumerSpecApplyConfiguration) WithMetadata(entries map[string]string) *ConsumerSpecApplyConfiguration {\n\tif b.Metadata == nil && len(entries) > 0 {\n\t\tb.Metadata = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.Metadata[k] = v\n\t}\n\treturn b\n}\n\n// WithPauseUntil sets the PauseUntil field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the PauseUntil field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithPauseUntil(value string) *ConsumerSpecApplyConfiguration {\n\tb.PauseUntil = &value\n\treturn b\n}\n\n// WithPriorityPolicy sets the PriorityPolicy field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the PriorityPolicy field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithPriorityPolicy(value string) *ConsumerSpecApplyConfiguration {\n\tb.PriorityPolicy = &value\n\treturn b\n}\n\n// WithPinnedTTL sets the PinnedTTL field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the PinnedTTL field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithPinnedTTL(value string) *ConsumerSpecApplyConfiguration {\n\tb.PinnedTTL = &value\n\treturn b\n}\n\n// WithPriorityGroups adds the given value to the PriorityGroups field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the PriorityGroups field.\nfunc (b *ConsumerSpecApplyConfiguration) WithPriorityGroups(values ...string) *ConsumerSpecApplyConfiguration {\n\tfor i := range values {\n\t\tb.PriorityGroups = append(b.PriorityGroups, values[i])\n\t}\n\treturn b\n}\n\n// WithStreamName sets the StreamName field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the StreamName field is set to the value of the last call.\nfunc (b *ConsumerSpecApplyConfiguration) WithStreamName(value string) *ConsumerSpecApplyConfiguration {\n\tb.StreamName = &value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/credssecret.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// CredsSecretApplyConfiguration represents a declarative configuration of the CredsSecret type for use\n// with apply.\ntype CredsSecretApplyConfiguration struct {\n\tFile   *string                      `json:\"file,omitempty\"`\n\tSecret *SecretRefApplyConfiguration `json:\"secret,omitempty\"`\n}\n\n// CredsSecretApplyConfiguration constructs a declarative configuration of the CredsSecret type for use with\n// apply.\nfunc CredsSecret() *CredsSecretApplyConfiguration {\n\treturn &CredsSecretApplyConfiguration{}\n}\n\n// WithFile sets the File field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the File field is set to the value of the last call.\nfunc (b *CredsSecretApplyConfiguration) WithFile(value string) *CredsSecretApplyConfiguration {\n\tb.File = &value\n\treturn b\n}\n\n// WithSecret sets the Secret field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Secret field is set to the value of the last call.\nfunc (b *CredsSecretApplyConfiguration) WithSecret(value *SecretRefApplyConfiguration) *CredsSecretApplyConfiguration {\n\tb.Secret = value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvalue.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\tv1 \"k8s.io/client-go/applyconfigurations/meta/v1\"\n)\n\n// KeyValueApplyConfiguration represents a declarative configuration of the KeyValue type for use\n// with apply.\ntype KeyValueApplyConfiguration struct {\n\tv1.TypeMetaApplyConfiguration    `json:\",inline\"`\n\t*v1.ObjectMetaApplyConfiguration `json:\"metadata,omitempty\"`\n\tSpec                             *KeyValueSpecApplyConfiguration `json:\"spec,omitempty\"`\n\tStatus                           *StatusApplyConfiguration       `json:\"status,omitempty\"`\n}\n\n// KeyValue constructs a declarative configuration of the KeyValue type for use with\n// apply.\nfunc KeyValue(name, namespace string) *KeyValueApplyConfiguration {\n\tb := &KeyValueApplyConfiguration{}\n\tb.WithName(name)\n\tb.WithNamespace(namespace)\n\tb.WithKind(\"KeyValue\")\n\tb.WithAPIVersion(\"jetstream.nats.io/v1beta2\")\n\treturn b\n}\nfunc (b KeyValueApplyConfiguration) IsApplyConfiguration() {}\n\n// WithKind sets the Kind field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Kind field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithKind(value string) *KeyValueApplyConfiguration {\n\tb.TypeMetaApplyConfiguration.Kind = &value\n\treturn b\n}\n\n// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the APIVersion field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithAPIVersion(value string) *KeyValueApplyConfiguration {\n\tb.TypeMetaApplyConfiguration.APIVersion = &value\n\treturn b\n}\n\n// WithName sets the Name field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Name field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithName(value string) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Name = &value\n\treturn b\n}\n\n// WithGenerateName sets the GenerateName field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the GenerateName field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithGenerateName(value string) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.GenerateName = &value\n\treturn b\n}\n\n// WithNamespace sets the Namespace field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Namespace field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithNamespace(value string) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Namespace = &value\n\treturn b\n}\n\n// WithUID sets the UID field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the UID field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithUID(value types.UID) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.UID = &value\n\treturn b\n}\n\n// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ResourceVersion field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithResourceVersion(value string) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.ResourceVersion = &value\n\treturn b\n}\n\n// WithGeneration sets the Generation field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Generation field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithGeneration(value int64) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Generation = &value\n\treturn b\n}\n\n// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the CreationTimestamp field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithCreationTimestamp(value metav1.Time) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.CreationTimestamp = &value\n\treturn b\n}\n\n// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeletionTimestamp field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.DeletionTimestamp = &value\n\treturn b\n}\n\n// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value\n\treturn b\n}\n\n// WithLabels puts the entries into the Labels field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Labels field,\n// overwriting an existing map entries in Labels field with the same key.\nfunc (b *KeyValueApplyConfiguration) WithLabels(entries map[string]string) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tif b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 {\n\t\tb.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.ObjectMetaApplyConfiguration.Labels[k] = v\n\t}\n\treturn b\n}\n\n// WithAnnotations puts the entries into the Annotations field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Annotations field,\n// overwriting an existing map entries in Annotations field with the same key.\nfunc (b *KeyValueApplyConfiguration) WithAnnotations(entries map[string]string) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tif b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 {\n\t\tb.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.ObjectMetaApplyConfiguration.Annotations[k] = v\n\t}\n\treturn b\n}\n\n// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the OwnerReferences field.\nfunc (b *KeyValueApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tfor i := range values {\n\t\tif values[i] == nil {\n\t\t\tpanic(\"nil value passed to WithOwnerReferences\")\n\t\t}\n\t\tb.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i])\n\t}\n\treturn b\n}\n\n// WithFinalizers adds the given value to the Finalizers field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the Finalizers field.\nfunc (b *KeyValueApplyConfiguration) WithFinalizers(values ...string) *KeyValueApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tfor i := range values {\n\t\tb.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i])\n\t}\n\treturn b\n}\n\nfunc (b *KeyValueApplyConfiguration) ensureObjectMetaApplyConfigurationExists() {\n\tif b.ObjectMetaApplyConfiguration == nil {\n\t\tb.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{}\n\t}\n}\n\n// WithSpec sets the Spec field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Spec field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithSpec(value *KeyValueSpecApplyConfiguration) *KeyValueApplyConfiguration {\n\tb.Spec = value\n\treturn b\n}\n\n// WithStatus sets the Status field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Status field is set to the value of the last call.\nfunc (b *KeyValueApplyConfiguration) WithStatus(value *StatusApplyConfiguration) *KeyValueApplyConfiguration {\n\tb.Status = value\n\treturn b\n}\n\n// GetKind retrieves the value of the Kind field in the declarative configuration.\nfunc (b *KeyValueApplyConfiguration) GetKind() *string {\n\treturn b.TypeMetaApplyConfiguration.Kind\n}\n\n// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration.\nfunc (b *KeyValueApplyConfiguration) GetAPIVersion() *string {\n\treturn b.TypeMetaApplyConfiguration.APIVersion\n}\n\n// GetName retrieves the value of the Name field in the declarative configuration.\nfunc (b *KeyValueApplyConfiguration) GetName() *string {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\treturn b.ObjectMetaApplyConfiguration.Name\n}\n\n// GetNamespace retrieves the value of the Namespace field in the declarative configuration.\nfunc (b *KeyValueApplyConfiguration) GetNamespace() *string {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\treturn b.ObjectMetaApplyConfiguration.Namespace\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvaluespec.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\ttime \"time\"\n\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n)\n\n// KeyValueSpecApplyConfiguration represents a declarative configuration of the KeyValueSpec type for use\n// with apply.\ntype KeyValueSpecApplyConfiguration struct {\n\tBucket         *string                            `json:\"bucket,omitempty\"`\n\tDescription    *string                            `json:\"description,omitempty\"`\n\tMaxValueSize   *int                               `json:\"maxValueSize,omitempty\"`\n\tHistory        *int                               `json:\"history,omitempty\"`\n\tTTL            *string                            `json:\"ttl,omitempty\"`\n\tMaxBytes       *int                               `json:\"maxBytes,omitempty\"`\n\tStorage        *string                            `json:\"storage,omitempty\"`\n\tReplicas       *int                               `json:\"replicas,omitempty\"`\n\tPlacement      *StreamPlacementApplyConfiguration `json:\"placement,omitempty\"`\n\tRePublish      *RePublishApplyConfiguration       `json:\"republish,omitempty\"`\n\tMirror         *StreamSourceApplyConfiguration    `json:\"mirror,omitempty\"`\n\tSources        []*jetstreamv1beta2.StreamSource   `json:\"sources,omitempty\"`\n\tCompression    *bool                              `json:\"compression,omitempty\"`\n\tLimitMarkerTTL *time.Duration                     `json:\"limitMarkerTtl,omitempty\"`\n}\n\n// KeyValueSpecApplyConfiguration constructs a declarative configuration of the KeyValueSpec type for use with\n// apply.\nfunc KeyValueSpec() *KeyValueSpecApplyConfiguration {\n\treturn &KeyValueSpecApplyConfiguration{}\n}\n\n// WithBucket sets the Bucket field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Bucket field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithBucket(value string) *KeyValueSpecApplyConfiguration {\n\tb.Bucket = &value\n\treturn b\n}\n\n// WithDescription sets the Description field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Description field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithDescription(value string) *KeyValueSpecApplyConfiguration {\n\tb.Description = &value\n\treturn b\n}\n\n// WithMaxValueSize sets the MaxValueSize field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxValueSize field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithMaxValueSize(value int) *KeyValueSpecApplyConfiguration {\n\tb.MaxValueSize = &value\n\treturn b\n}\n\n// WithHistory sets the History field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the History field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithHistory(value int) *KeyValueSpecApplyConfiguration {\n\tb.History = &value\n\treturn b\n}\n\n// WithTTL sets the TTL field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the TTL field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithTTL(value string) *KeyValueSpecApplyConfiguration {\n\tb.TTL = &value\n\treturn b\n}\n\n// WithMaxBytes sets the MaxBytes field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxBytes field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithMaxBytes(value int) *KeyValueSpecApplyConfiguration {\n\tb.MaxBytes = &value\n\treturn b\n}\n\n// WithStorage sets the Storage field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Storage field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithStorage(value string) *KeyValueSpecApplyConfiguration {\n\tb.Storage = &value\n\treturn b\n}\n\n// WithReplicas sets the Replicas field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Replicas field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithReplicas(value int) *KeyValueSpecApplyConfiguration {\n\tb.Replicas = &value\n\treturn b\n}\n\n// WithPlacement sets the Placement field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Placement field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithPlacement(value *StreamPlacementApplyConfiguration) *KeyValueSpecApplyConfiguration {\n\tb.Placement = value\n\treturn b\n}\n\n// WithRePublish sets the RePublish field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the RePublish field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithRePublish(value *RePublishApplyConfiguration) *KeyValueSpecApplyConfiguration {\n\tb.RePublish = value\n\treturn b\n}\n\n// WithMirror sets the Mirror field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Mirror field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithMirror(value *StreamSourceApplyConfiguration) *KeyValueSpecApplyConfiguration {\n\tb.Mirror = value\n\treturn b\n}\n\n// WithSources adds the given value to the Sources field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the Sources field.\nfunc (b *KeyValueSpecApplyConfiguration) WithSources(values ...**jetstreamv1beta2.StreamSource) *KeyValueSpecApplyConfiguration {\n\tfor i := range values {\n\t\tif values[i] == nil {\n\t\t\tpanic(\"nil value passed to WithSources\")\n\t\t}\n\t\tb.Sources = append(b.Sources, *values[i])\n\t}\n\treturn b\n}\n\n// WithCompression sets the Compression field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Compression field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithCompression(value bool) *KeyValueSpecApplyConfiguration {\n\tb.Compression = &value\n\treturn b\n}\n\n// WithLimitMarkerTTL sets the LimitMarkerTTL field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the LimitMarkerTTL field is set to the value of the last call.\nfunc (b *KeyValueSpecApplyConfiguration) WithLimitMarkerTTL(value time.Duration) *KeyValueSpecApplyConfiguration {\n\tb.LimitMarkerTTL = &value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/nkeysecret.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// NKeySecretApplyConfiguration represents a declarative configuration of the NKeySecret type for use\n// with apply.\ntype NKeySecretApplyConfiguration struct {\n\tSeed   *string                      `json:\"seed,omitempty\"`\n\tSecret *SecretRefApplyConfiguration `json:\"secret,omitempty\"`\n}\n\n// NKeySecretApplyConfiguration constructs a declarative configuration of the NKeySecret type for use with\n// apply.\nfunc NKeySecret() *NKeySecretApplyConfiguration {\n\treturn &NKeySecretApplyConfiguration{}\n}\n\n// WithSeed sets the Seed field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Seed field is set to the value of the last call.\nfunc (b *NKeySecretApplyConfiguration) WithSeed(value string) *NKeySecretApplyConfiguration {\n\tb.Seed = &value\n\treturn b\n}\n\n// WithSecret sets the Secret field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Secret field is set to the value of the last call.\nfunc (b *NKeySecretApplyConfiguration) WithSecret(value *SecretRefApplyConfiguration) *NKeySecretApplyConfiguration {\n\tb.Secret = value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstore.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\tv1 \"k8s.io/client-go/applyconfigurations/meta/v1\"\n)\n\n// ObjectStoreApplyConfiguration represents a declarative configuration of the ObjectStore type for use\n// with apply.\ntype ObjectStoreApplyConfiguration struct {\n\tv1.TypeMetaApplyConfiguration    `json:\",inline\"`\n\t*v1.ObjectMetaApplyConfiguration `json:\"metadata,omitempty\"`\n\tSpec                             *ObjectStoreSpecApplyConfiguration `json:\"spec,omitempty\"`\n\tStatus                           *StatusApplyConfiguration          `json:\"status,omitempty\"`\n}\n\n// ObjectStore constructs a declarative configuration of the ObjectStore type for use with\n// apply.\nfunc ObjectStore(name, namespace string) *ObjectStoreApplyConfiguration {\n\tb := &ObjectStoreApplyConfiguration{}\n\tb.WithName(name)\n\tb.WithNamespace(namespace)\n\tb.WithKind(\"ObjectStore\")\n\tb.WithAPIVersion(\"jetstream.nats.io/v1beta2\")\n\treturn b\n}\nfunc (b ObjectStoreApplyConfiguration) IsApplyConfiguration() {}\n\n// WithKind sets the Kind field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Kind field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithKind(value string) *ObjectStoreApplyConfiguration {\n\tb.TypeMetaApplyConfiguration.Kind = &value\n\treturn b\n}\n\n// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the APIVersion field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithAPIVersion(value string) *ObjectStoreApplyConfiguration {\n\tb.TypeMetaApplyConfiguration.APIVersion = &value\n\treturn b\n}\n\n// WithName sets the Name field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Name field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithName(value string) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Name = &value\n\treturn b\n}\n\n// WithGenerateName sets the GenerateName field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the GenerateName field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithGenerateName(value string) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.GenerateName = &value\n\treturn b\n}\n\n// WithNamespace sets the Namespace field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Namespace field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithNamespace(value string) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Namespace = &value\n\treturn b\n}\n\n// WithUID sets the UID field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the UID field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithUID(value types.UID) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.UID = &value\n\treturn b\n}\n\n// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ResourceVersion field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithResourceVersion(value string) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.ResourceVersion = &value\n\treturn b\n}\n\n// WithGeneration sets the Generation field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Generation field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithGeneration(value int64) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Generation = &value\n\treturn b\n}\n\n// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the CreationTimestamp field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithCreationTimestamp(value metav1.Time) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.CreationTimestamp = &value\n\treturn b\n}\n\n// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeletionTimestamp field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.DeletionTimestamp = &value\n\treturn b\n}\n\n// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value\n\treturn b\n}\n\n// WithLabels puts the entries into the Labels field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Labels field,\n// overwriting an existing map entries in Labels field with the same key.\nfunc (b *ObjectStoreApplyConfiguration) WithLabels(entries map[string]string) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tif b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 {\n\t\tb.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.ObjectMetaApplyConfiguration.Labels[k] = v\n\t}\n\treturn b\n}\n\n// WithAnnotations puts the entries into the Annotations field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Annotations field,\n// overwriting an existing map entries in Annotations field with the same key.\nfunc (b *ObjectStoreApplyConfiguration) WithAnnotations(entries map[string]string) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tif b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 {\n\t\tb.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.ObjectMetaApplyConfiguration.Annotations[k] = v\n\t}\n\treturn b\n}\n\n// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the OwnerReferences field.\nfunc (b *ObjectStoreApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tfor i := range values {\n\t\tif values[i] == nil {\n\t\t\tpanic(\"nil value passed to WithOwnerReferences\")\n\t\t}\n\t\tb.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i])\n\t}\n\treturn b\n}\n\n// WithFinalizers adds the given value to the Finalizers field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the Finalizers field.\nfunc (b *ObjectStoreApplyConfiguration) WithFinalizers(values ...string) *ObjectStoreApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tfor i := range values {\n\t\tb.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i])\n\t}\n\treturn b\n}\n\nfunc (b *ObjectStoreApplyConfiguration) ensureObjectMetaApplyConfigurationExists() {\n\tif b.ObjectMetaApplyConfiguration == nil {\n\t\tb.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{}\n\t}\n}\n\n// WithSpec sets the Spec field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Spec field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithSpec(value *ObjectStoreSpecApplyConfiguration) *ObjectStoreApplyConfiguration {\n\tb.Spec = value\n\treturn b\n}\n\n// WithStatus sets the Status field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Status field is set to the value of the last call.\nfunc (b *ObjectStoreApplyConfiguration) WithStatus(value *StatusApplyConfiguration) *ObjectStoreApplyConfiguration {\n\tb.Status = value\n\treturn b\n}\n\n// GetKind retrieves the value of the Kind field in the declarative configuration.\nfunc (b *ObjectStoreApplyConfiguration) GetKind() *string {\n\treturn b.TypeMetaApplyConfiguration.Kind\n}\n\n// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration.\nfunc (b *ObjectStoreApplyConfiguration) GetAPIVersion() *string {\n\treturn b.TypeMetaApplyConfiguration.APIVersion\n}\n\n// GetName retrieves the value of the Name field in the declarative configuration.\nfunc (b *ObjectStoreApplyConfiguration) GetName() *string {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\treturn b.ObjectMetaApplyConfiguration.Name\n}\n\n// GetNamespace retrieves the value of the Namespace field in the declarative configuration.\nfunc (b *ObjectStoreApplyConfiguration) GetNamespace() *string {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\treturn b.ObjectMetaApplyConfiguration.Namespace\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstorespec.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// ObjectStoreSpecApplyConfiguration represents a declarative configuration of the ObjectStoreSpec type for use\n// with apply.\ntype ObjectStoreSpecApplyConfiguration struct {\n\tBucket      *string                            `json:\"bucket,omitempty\"`\n\tDescription *string                            `json:\"description,omitempty\"`\n\tTTL         *string                            `json:\"ttl,omitempty\"`\n\tMaxBytes    *int                               `json:\"maxBytes,omitempty\"`\n\tStorage     *string                            `json:\"storage,omitempty\"`\n\tReplicas    *int                               `json:\"replicas,omitempty\"`\n\tPlacement   *StreamPlacementApplyConfiguration `json:\"placement,omitempty\"`\n\tCompression *bool                              `json:\"compression,omitempty\"`\n\tMetadata    map[string]string                  `json:\"metadata,omitempty\"`\n}\n\n// ObjectStoreSpecApplyConfiguration constructs a declarative configuration of the ObjectStoreSpec type for use with\n// apply.\nfunc ObjectStoreSpec() *ObjectStoreSpecApplyConfiguration {\n\treturn &ObjectStoreSpecApplyConfiguration{}\n}\n\n// WithBucket sets the Bucket field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Bucket field is set to the value of the last call.\nfunc (b *ObjectStoreSpecApplyConfiguration) WithBucket(value string) *ObjectStoreSpecApplyConfiguration {\n\tb.Bucket = &value\n\treturn b\n}\n\n// WithDescription sets the Description field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Description field is set to the value of the last call.\nfunc (b *ObjectStoreSpecApplyConfiguration) WithDescription(value string) *ObjectStoreSpecApplyConfiguration {\n\tb.Description = &value\n\treturn b\n}\n\n// WithTTL sets the TTL field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the TTL field is set to the value of the last call.\nfunc (b *ObjectStoreSpecApplyConfiguration) WithTTL(value string) *ObjectStoreSpecApplyConfiguration {\n\tb.TTL = &value\n\treturn b\n}\n\n// WithMaxBytes sets the MaxBytes field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxBytes field is set to the value of the last call.\nfunc (b *ObjectStoreSpecApplyConfiguration) WithMaxBytes(value int) *ObjectStoreSpecApplyConfiguration {\n\tb.MaxBytes = &value\n\treturn b\n}\n\n// WithStorage sets the Storage field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Storage field is set to the value of the last call.\nfunc (b *ObjectStoreSpecApplyConfiguration) WithStorage(value string) *ObjectStoreSpecApplyConfiguration {\n\tb.Storage = &value\n\treturn b\n}\n\n// WithReplicas sets the Replicas field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Replicas field is set to the value of the last call.\nfunc (b *ObjectStoreSpecApplyConfiguration) WithReplicas(value int) *ObjectStoreSpecApplyConfiguration {\n\tb.Replicas = &value\n\treturn b\n}\n\n// WithPlacement sets the Placement field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Placement field is set to the value of the last call.\nfunc (b *ObjectStoreSpecApplyConfiguration) WithPlacement(value *StreamPlacementApplyConfiguration) *ObjectStoreSpecApplyConfiguration {\n\tb.Placement = value\n\treturn b\n}\n\n// WithCompression sets the Compression field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Compression field is set to the value of the last call.\nfunc (b *ObjectStoreSpecApplyConfiguration) WithCompression(value bool) *ObjectStoreSpecApplyConfiguration {\n\tb.Compression = &value\n\treturn b\n}\n\n// WithMetadata puts the entries into the Metadata field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Metadata field,\n// overwriting an existing map entries in Metadata field with the same key.\nfunc (b *ObjectStoreSpecApplyConfiguration) WithMetadata(entries map[string]string) *ObjectStoreSpecApplyConfiguration {\n\tif b.Metadata == nil && len(entries) > 0 {\n\t\tb.Metadata = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.Metadata[k] = v\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/republish.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// RePublishApplyConfiguration represents a declarative configuration of the RePublish type for use\n// with apply.\ntype RePublishApplyConfiguration struct {\n\tSource      *string `json:\"source,omitempty\"`\n\tDestination *string `json:\"destination,omitempty\"`\n\tHeadersOnly *bool   `json:\"headers_only,omitempty\"`\n}\n\n// RePublishApplyConfiguration constructs a declarative configuration of the RePublish type for use with\n// apply.\nfunc RePublish() *RePublishApplyConfiguration {\n\treturn &RePublishApplyConfiguration{}\n}\n\n// WithSource sets the Source field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Source field is set to the value of the last call.\nfunc (b *RePublishApplyConfiguration) WithSource(value string) *RePublishApplyConfiguration {\n\tb.Source = &value\n\treturn b\n}\n\n// WithDestination sets the Destination field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Destination field is set to the value of the last call.\nfunc (b *RePublishApplyConfiguration) WithDestination(value string) *RePublishApplyConfiguration {\n\tb.Destination = &value\n\treturn b\n}\n\n// WithHeadersOnly sets the HeadersOnly field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the HeadersOnly field is set to the value of the last call.\nfunc (b *RePublishApplyConfiguration) WithHeadersOnly(value bool) *RePublishApplyConfiguration {\n\tb.HeadersOnly = &value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/secretref.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// SecretRefApplyConfiguration represents a declarative configuration of the SecretRef type for use\n// with apply.\ntype SecretRefApplyConfiguration struct {\n\tName *string `json:\"name,omitempty\"`\n}\n\n// SecretRefApplyConfiguration constructs a declarative configuration of the SecretRef type for use with\n// apply.\nfunc SecretRef() *SecretRefApplyConfiguration {\n\treturn &SecretRefApplyConfiguration{}\n}\n\n// WithName sets the Name field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Name field is set to the value of the last call.\nfunc (b *SecretRefApplyConfiguration) WithName(value string) *SecretRefApplyConfiguration {\n\tb.Name = &value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/status.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// StatusApplyConfiguration represents a declarative configuration of the Status type for use\n// with apply.\ntype StatusApplyConfiguration struct {\n\tObservedGeneration *int64                        `json:\"observedGeneration,omitempty\"`\n\tConditions         []ConditionApplyConfiguration `json:\"conditions,omitempty\"`\n}\n\n// StatusApplyConfiguration constructs a declarative configuration of the Status type for use with\n// apply.\nfunc Status() *StatusApplyConfiguration {\n\treturn &StatusApplyConfiguration{}\n}\n\n// WithObservedGeneration sets the ObservedGeneration field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ObservedGeneration field is set to the value of the last call.\nfunc (b *StatusApplyConfiguration) WithObservedGeneration(value int64) *StatusApplyConfiguration {\n\tb.ObservedGeneration = &value\n\treturn b\n}\n\n// WithConditions adds the given value to the Conditions field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the Conditions field.\nfunc (b *StatusApplyConfiguration) WithConditions(values ...*ConditionApplyConfiguration) *StatusApplyConfiguration {\n\tfor i := range values {\n\t\tif values[i] == nil {\n\t\t\tpanic(\"nil value passed to WithConditions\")\n\t\t}\n\t\tb.Conditions = append(b.Conditions, *values[i])\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/stream.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\tv1 \"k8s.io/client-go/applyconfigurations/meta/v1\"\n)\n\n// StreamApplyConfiguration represents a declarative configuration of the Stream type for use\n// with apply.\ntype StreamApplyConfiguration struct {\n\tv1.TypeMetaApplyConfiguration    `json:\",inline\"`\n\t*v1.ObjectMetaApplyConfiguration `json:\"metadata,omitempty\"`\n\tSpec                             *StreamSpecApplyConfiguration `json:\"spec,omitempty\"`\n\tStatus                           *StatusApplyConfiguration     `json:\"status,omitempty\"`\n}\n\n// Stream constructs a declarative configuration of the Stream type for use with\n// apply.\nfunc Stream(name, namespace string) *StreamApplyConfiguration {\n\tb := &StreamApplyConfiguration{}\n\tb.WithName(name)\n\tb.WithNamespace(namespace)\n\tb.WithKind(\"Stream\")\n\tb.WithAPIVersion(\"jetstream.nats.io/v1beta2\")\n\treturn b\n}\nfunc (b StreamApplyConfiguration) IsApplyConfiguration() {}\n\n// WithKind sets the Kind field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Kind field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithKind(value string) *StreamApplyConfiguration {\n\tb.TypeMetaApplyConfiguration.Kind = &value\n\treturn b\n}\n\n// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the APIVersion field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithAPIVersion(value string) *StreamApplyConfiguration {\n\tb.TypeMetaApplyConfiguration.APIVersion = &value\n\treturn b\n}\n\n// WithName sets the Name field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Name field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithName(value string) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Name = &value\n\treturn b\n}\n\n// WithGenerateName sets the GenerateName field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the GenerateName field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithGenerateName(value string) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.GenerateName = &value\n\treturn b\n}\n\n// WithNamespace sets the Namespace field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Namespace field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithNamespace(value string) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Namespace = &value\n\treturn b\n}\n\n// WithUID sets the UID field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the UID field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithUID(value types.UID) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.UID = &value\n\treturn b\n}\n\n// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ResourceVersion field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithResourceVersion(value string) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.ResourceVersion = &value\n\treturn b\n}\n\n// WithGeneration sets the Generation field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Generation field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithGeneration(value int64) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.Generation = &value\n\treturn b\n}\n\n// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the CreationTimestamp field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithCreationTimestamp(value metav1.Time) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.CreationTimestamp = &value\n\treturn b\n}\n\n// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeletionTimestamp field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.DeletionTimestamp = &value\n\treturn b\n}\n\n// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tb.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value\n\treturn b\n}\n\n// WithLabels puts the entries into the Labels field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Labels field,\n// overwriting an existing map entries in Labels field with the same key.\nfunc (b *StreamApplyConfiguration) WithLabels(entries map[string]string) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tif b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 {\n\t\tb.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.ObjectMetaApplyConfiguration.Labels[k] = v\n\t}\n\treturn b\n}\n\n// WithAnnotations puts the entries into the Annotations field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Annotations field,\n// overwriting an existing map entries in Annotations field with the same key.\nfunc (b *StreamApplyConfiguration) WithAnnotations(entries map[string]string) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tif b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 {\n\t\tb.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.ObjectMetaApplyConfiguration.Annotations[k] = v\n\t}\n\treturn b\n}\n\n// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the OwnerReferences field.\nfunc (b *StreamApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tfor i := range values {\n\t\tif values[i] == nil {\n\t\t\tpanic(\"nil value passed to WithOwnerReferences\")\n\t\t}\n\t\tb.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i])\n\t}\n\treturn b\n}\n\n// WithFinalizers adds the given value to the Finalizers field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the Finalizers field.\nfunc (b *StreamApplyConfiguration) WithFinalizers(values ...string) *StreamApplyConfiguration {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\tfor i := range values {\n\t\tb.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i])\n\t}\n\treturn b\n}\n\nfunc (b *StreamApplyConfiguration) ensureObjectMetaApplyConfigurationExists() {\n\tif b.ObjectMetaApplyConfiguration == nil {\n\t\tb.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{}\n\t}\n}\n\n// WithSpec sets the Spec field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Spec field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithSpec(value *StreamSpecApplyConfiguration) *StreamApplyConfiguration {\n\tb.Spec = value\n\treturn b\n}\n\n// WithStatus sets the Status field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Status field is set to the value of the last call.\nfunc (b *StreamApplyConfiguration) WithStatus(value *StatusApplyConfiguration) *StreamApplyConfiguration {\n\tb.Status = value\n\treturn b\n}\n\n// GetKind retrieves the value of the Kind field in the declarative configuration.\nfunc (b *StreamApplyConfiguration) GetKind() *string {\n\treturn b.TypeMetaApplyConfiguration.Kind\n}\n\n// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration.\nfunc (b *StreamApplyConfiguration) GetAPIVersion() *string {\n\treturn b.TypeMetaApplyConfiguration.APIVersion\n}\n\n// GetName retrieves the value of the Name field in the declarative configuration.\nfunc (b *StreamApplyConfiguration) GetName() *string {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\treturn b.ObjectMetaApplyConfiguration.Name\n}\n\n// GetNamespace retrieves the value of the Namespace field in the declarative configuration.\nfunc (b *StreamApplyConfiguration) GetNamespace() *string {\n\tb.ensureObjectMetaApplyConfigurationExists()\n\treturn b.ObjectMetaApplyConfiguration.Namespace\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamplacement.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// StreamPlacementApplyConfiguration represents a declarative configuration of the StreamPlacement type for use\n// with apply.\ntype StreamPlacementApplyConfiguration struct {\n\tCluster *string  `json:\"cluster,omitempty\"`\n\tTags    []string `json:\"tags,omitempty\"`\n}\n\n// StreamPlacementApplyConfiguration constructs a declarative configuration of the StreamPlacement type for use with\n// apply.\nfunc StreamPlacement() *StreamPlacementApplyConfiguration {\n\treturn &StreamPlacementApplyConfiguration{}\n}\n\n// WithCluster sets the Cluster field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Cluster field is set to the value of the last call.\nfunc (b *StreamPlacementApplyConfiguration) WithCluster(value string) *StreamPlacementApplyConfiguration {\n\tb.Cluster = &value\n\treturn b\n}\n\n// WithTags adds the given value to the Tags field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the Tags field.\nfunc (b *StreamPlacementApplyConfiguration) WithTags(values ...string) *StreamPlacementApplyConfiguration {\n\tfor i := range values {\n\t\tb.Tags = append(b.Tags, values[i])\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamsource.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n)\n\n// StreamSourceApplyConfiguration represents a declarative configuration of the StreamSource type for use\n// with apply.\ntype StreamSourceApplyConfiguration struct {\n\tName                  *string                              `json:\"name,omitempty\"`\n\tOptStartSeq           *int                                 `json:\"optStartSeq,omitempty\"`\n\tOptStartTime          *string                              `json:\"optStartTime,omitempty\"`\n\tFilterSubject         *string                              `json:\"filterSubject,omitempty\"`\n\tExternalAPIPrefix     *string                              `json:\"externalApiPrefix,omitempty\"`\n\tExternalDeliverPrefix *string                              `json:\"externalDeliverPrefix,omitempty\"`\n\tSubjectTransforms     []*jetstreamv1beta2.SubjectTransform `json:\"subjectTransforms,omitempty\"`\n}\n\n// StreamSourceApplyConfiguration constructs a declarative configuration of the StreamSource type for use with\n// apply.\nfunc StreamSource() *StreamSourceApplyConfiguration {\n\treturn &StreamSourceApplyConfiguration{}\n}\n\n// WithName sets the Name field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Name field is set to the value of the last call.\nfunc (b *StreamSourceApplyConfiguration) WithName(value string) *StreamSourceApplyConfiguration {\n\tb.Name = &value\n\treturn b\n}\n\n// WithOptStartSeq sets the OptStartSeq field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the OptStartSeq field is set to the value of the last call.\nfunc (b *StreamSourceApplyConfiguration) WithOptStartSeq(value int) *StreamSourceApplyConfiguration {\n\tb.OptStartSeq = &value\n\treturn b\n}\n\n// WithOptStartTime sets the OptStartTime field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the OptStartTime field is set to the value of the last call.\nfunc (b *StreamSourceApplyConfiguration) WithOptStartTime(value string) *StreamSourceApplyConfiguration {\n\tb.OptStartTime = &value\n\treturn b\n}\n\n// WithFilterSubject sets the FilterSubject field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the FilterSubject field is set to the value of the last call.\nfunc (b *StreamSourceApplyConfiguration) WithFilterSubject(value string) *StreamSourceApplyConfiguration {\n\tb.FilterSubject = &value\n\treturn b\n}\n\n// WithExternalAPIPrefix sets the ExternalAPIPrefix field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ExternalAPIPrefix field is set to the value of the last call.\nfunc (b *StreamSourceApplyConfiguration) WithExternalAPIPrefix(value string) *StreamSourceApplyConfiguration {\n\tb.ExternalAPIPrefix = &value\n\treturn b\n}\n\n// WithExternalDeliverPrefix sets the ExternalDeliverPrefix field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ExternalDeliverPrefix field is set to the value of the last call.\nfunc (b *StreamSourceApplyConfiguration) WithExternalDeliverPrefix(value string) *StreamSourceApplyConfiguration {\n\tb.ExternalDeliverPrefix = &value\n\treturn b\n}\n\n// WithSubjectTransforms adds the given value to the SubjectTransforms field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the SubjectTransforms field.\nfunc (b *StreamSourceApplyConfiguration) WithSubjectTransforms(values ...**jetstreamv1beta2.SubjectTransform) *StreamSourceApplyConfiguration {\n\tfor i := range values {\n\t\tif values[i] == nil {\n\t\t\tpanic(\"nil value passed to WithSubjectTransforms\")\n\t\t}\n\t\tb.SubjectTransforms = append(b.SubjectTransforms, *values[i])\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n)\n\n// StreamSpecApplyConfiguration represents a declarative configuration of the StreamSpec type for use\n// with apply.\ntype StreamSpecApplyConfiguration struct {\n\tName                   *string                             `json:\"name,omitempty\"`\n\tDescription            *string                             `json:\"description,omitempty\"`\n\tSubjects               []string                            `json:\"subjects,omitempty\"`\n\tRetention              *string                             `json:\"retention,omitempty\"`\n\tMaxConsumers           *int                                `json:\"maxConsumers,omitempty\"`\n\tMaxMsgsPerSubject      *int                                `json:\"maxMsgsPerSubject,omitempty\"`\n\tMaxMsgs                *int                                `json:\"maxMsgs,omitempty\"`\n\tMaxBytes               *int                                `json:\"maxBytes,omitempty\"`\n\tMaxAge                 *string                             `json:\"maxAge,omitempty\"`\n\tMaxMsgSize             *int                                `json:\"maxMsgSize,omitempty\"`\n\tStorage                *string                             `json:\"storage,omitempty\"`\n\tDiscard                *string                             `json:\"discard,omitempty\"`\n\tReplicas               *int                                `json:\"replicas,omitempty\"`\n\tNoAck                  *bool                               `json:\"noAck,omitempty\"`\n\tDuplicateWindow        *string                             `json:\"duplicateWindow,omitempty\"`\n\tPlacement              *StreamPlacementApplyConfiguration  `json:\"placement,omitempty\"`\n\tMirror                 *StreamSourceApplyConfiguration     `json:\"mirror,omitempty\"`\n\tSources                []*jetstreamv1beta2.StreamSource    `json:\"sources,omitempty\"`\n\tCompression            *string                             `json:\"compression,omitempty\"`\n\tSubjectTransform       *SubjectTransformApplyConfiguration `json:\"subjectTransform,omitempty\"`\n\tRePublish              *RePublishApplyConfiguration        `json:\"republish,omitempty\"`\n\tSealed                 *bool                               `json:\"sealed,omitempty\"`\n\tDenyDelete             *bool                               `json:\"denyDelete,omitempty\"`\n\tDenyPurge              *bool                               `json:\"denyPurge,omitempty\"`\n\tAllowDirect            *bool                               `json:\"allowDirect,omitempty\"`\n\tAllowRollup            *bool                               `json:\"allowRollup,omitempty\"`\n\tMirrorDirect           *bool                               `json:\"mirrorDirect,omitempty\"`\n\tDiscardPerSubject      *bool                               `json:\"discardPerSubject,omitempty\"`\n\tFirstSequence          *uint64                             `json:\"firstSequence,omitempty\"`\n\tMetadata               map[string]string                   `json:\"metadata,omitempty\"`\n\tConsumerLimits         *ConsumerLimitsApplyConfiguration   `json:\"consumerLimits,omitempty\"`\n\tAllowMsgTTL            *bool                               `json:\"allowMsgTtl,omitempty\"`\n\tSubjectDeleteMarkerTTL *string                             `json:\"subjectDeleteMarkerTtl,omitempty\"`\n\tAllowMsgCounter        *bool                               `json:\"allowMsgCounter,omitempty\"`\n\tAllowAtomicPublish     *bool                               `json:\"allowAtomicPublish,omitempty\"`\n\tAllowMsgSchedules      *bool                               `json:\"allowMsgSchedules,omitempty\"`\n\tPersistMode            *string                             `json:\"persistMode,omitempty\"`\n}\n\n// StreamSpecApplyConfiguration constructs a declarative configuration of the StreamSpec type for use with\n// apply.\nfunc StreamSpec() *StreamSpecApplyConfiguration {\n\treturn &StreamSpecApplyConfiguration{}\n}\n\n// WithName sets the Name field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Name field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithName(value string) *StreamSpecApplyConfiguration {\n\tb.Name = &value\n\treturn b\n}\n\n// WithDescription sets the Description field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Description field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithDescription(value string) *StreamSpecApplyConfiguration {\n\tb.Description = &value\n\treturn b\n}\n\n// WithSubjects adds the given value to the Subjects field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the Subjects field.\nfunc (b *StreamSpecApplyConfiguration) WithSubjects(values ...string) *StreamSpecApplyConfiguration {\n\tfor i := range values {\n\t\tb.Subjects = append(b.Subjects, values[i])\n\t}\n\treturn b\n}\n\n// WithRetention sets the Retention field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Retention field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithRetention(value string) *StreamSpecApplyConfiguration {\n\tb.Retention = &value\n\treturn b\n}\n\n// WithMaxConsumers sets the MaxConsumers field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxConsumers field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithMaxConsumers(value int) *StreamSpecApplyConfiguration {\n\tb.MaxConsumers = &value\n\treturn b\n}\n\n// WithMaxMsgsPerSubject sets the MaxMsgsPerSubject field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxMsgsPerSubject field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithMaxMsgsPerSubject(value int) *StreamSpecApplyConfiguration {\n\tb.MaxMsgsPerSubject = &value\n\treturn b\n}\n\n// WithMaxMsgs sets the MaxMsgs field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxMsgs field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithMaxMsgs(value int) *StreamSpecApplyConfiguration {\n\tb.MaxMsgs = &value\n\treturn b\n}\n\n// WithMaxBytes sets the MaxBytes field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxBytes field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithMaxBytes(value int) *StreamSpecApplyConfiguration {\n\tb.MaxBytes = &value\n\treturn b\n}\n\n// WithMaxAge sets the MaxAge field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxAge field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithMaxAge(value string) *StreamSpecApplyConfiguration {\n\tb.MaxAge = &value\n\treturn b\n}\n\n// WithMaxMsgSize sets the MaxMsgSize field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MaxMsgSize field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithMaxMsgSize(value int) *StreamSpecApplyConfiguration {\n\tb.MaxMsgSize = &value\n\treturn b\n}\n\n// WithStorage sets the Storage field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Storage field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithStorage(value string) *StreamSpecApplyConfiguration {\n\tb.Storage = &value\n\treturn b\n}\n\n// WithDiscard sets the Discard field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Discard field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithDiscard(value string) *StreamSpecApplyConfiguration {\n\tb.Discard = &value\n\treturn b\n}\n\n// WithReplicas sets the Replicas field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Replicas field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithReplicas(value int) *StreamSpecApplyConfiguration {\n\tb.Replicas = &value\n\treturn b\n}\n\n// WithNoAck sets the NoAck field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the NoAck field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithNoAck(value bool) *StreamSpecApplyConfiguration {\n\tb.NoAck = &value\n\treturn b\n}\n\n// WithDuplicateWindow sets the DuplicateWindow field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DuplicateWindow field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithDuplicateWindow(value string) *StreamSpecApplyConfiguration {\n\tb.DuplicateWindow = &value\n\treturn b\n}\n\n// WithPlacement sets the Placement field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Placement field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithPlacement(value *StreamPlacementApplyConfiguration) *StreamSpecApplyConfiguration {\n\tb.Placement = value\n\treturn b\n}\n\n// WithMirror sets the Mirror field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Mirror field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithMirror(value *StreamSourceApplyConfiguration) *StreamSpecApplyConfiguration {\n\tb.Mirror = value\n\treturn b\n}\n\n// WithSources adds the given value to the Sources field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the Sources field.\nfunc (b *StreamSpecApplyConfiguration) WithSources(values ...**jetstreamv1beta2.StreamSource) *StreamSpecApplyConfiguration {\n\tfor i := range values {\n\t\tif values[i] == nil {\n\t\t\tpanic(\"nil value passed to WithSources\")\n\t\t}\n\t\tb.Sources = append(b.Sources, *values[i])\n\t}\n\treturn b\n}\n\n// WithCompression sets the Compression field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Compression field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithCompression(value string) *StreamSpecApplyConfiguration {\n\tb.Compression = &value\n\treturn b\n}\n\n// WithSubjectTransform sets the SubjectTransform field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the SubjectTransform field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithSubjectTransform(value *SubjectTransformApplyConfiguration) *StreamSpecApplyConfiguration {\n\tb.SubjectTransform = value\n\treturn b\n}\n\n// WithRePublish sets the RePublish field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the RePublish field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithRePublish(value *RePublishApplyConfiguration) *StreamSpecApplyConfiguration {\n\tb.RePublish = value\n\treturn b\n}\n\n// WithSealed sets the Sealed field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Sealed field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithSealed(value bool) *StreamSpecApplyConfiguration {\n\tb.Sealed = &value\n\treturn b\n}\n\n// WithDenyDelete sets the DenyDelete field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DenyDelete field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithDenyDelete(value bool) *StreamSpecApplyConfiguration {\n\tb.DenyDelete = &value\n\treturn b\n}\n\n// WithDenyPurge sets the DenyPurge field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DenyPurge field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithDenyPurge(value bool) *StreamSpecApplyConfiguration {\n\tb.DenyPurge = &value\n\treturn b\n}\n\n// WithAllowDirect sets the AllowDirect field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the AllowDirect field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithAllowDirect(value bool) *StreamSpecApplyConfiguration {\n\tb.AllowDirect = &value\n\treturn b\n}\n\n// WithAllowRollup sets the AllowRollup field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the AllowRollup field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithAllowRollup(value bool) *StreamSpecApplyConfiguration {\n\tb.AllowRollup = &value\n\treturn b\n}\n\n// WithMirrorDirect sets the MirrorDirect field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the MirrorDirect field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithMirrorDirect(value bool) *StreamSpecApplyConfiguration {\n\tb.MirrorDirect = &value\n\treturn b\n}\n\n// WithDiscardPerSubject sets the DiscardPerSubject field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the DiscardPerSubject field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithDiscardPerSubject(value bool) *StreamSpecApplyConfiguration {\n\tb.DiscardPerSubject = &value\n\treturn b\n}\n\n// WithFirstSequence sets the FirstSequence field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the FirstSequence field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithFirstSequence(value uint64) *StreamSpecApplyConfiguration {\n\tb.FirstSequence = &value\n\treturn b\n}\n\n// WithMetadata puts the entries into the Metadata field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, the entries provided by each call will be put on the Metadata field,\n// overwriting an existing map entries in Metadata field with the same key.\nfunc (b *StreamSpecApplyConfiguration) WithMetadata(entries map[string]string) *StreamSpecApplyConfiguration {\n\tif b.Metadata == nil && len(entries) > 0 {\n\t\tb.Metadata = make(map[string]string, len(entries))\n\t}\n\tfor k, v := range entries {\n\t\tb.Metadata[k] = v\n\t}\n\treturn b\n}\n\n// WithConsumerLimits sets the ConsumerLimits field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ConsumerLimits field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithConsumerLimits(value *ConsumerLimitsApplyConfiguration) *StreamSpecApplyConfiguration {\n\tb.ConsumerLimits = value\n\treturn b\n}\n\n// WithAllowMsgTTL sets the AllowMsgTTL field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the AllowMsgTTL field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithAllowMsgTTL(value bool) *StreamSpecApplyConfiguration {\n\tb.AllowMsgTTL = &value\n\treturn b\n}\n\n// WithSubjectDeleteMarkerTTL sets the SubjectDeleteMarkerTTL field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the SubjectDeleteMarkerTTL field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithSubjectDeleteMarkerTTL(value string) *StreamSpecApplyConfiguration {\n\tb.SubjectDeleteMarkerTTL = &value\n\treturn b\n}\n\n// WithAllowMsgCounter sets the AllowMsgCounter field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the AllowMsgCounter field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithAllowMsgCounter(value bool) *StreamSpecApplyConfiguration {\n\tb.AllowMsgCounter = &value\n\treturn b\n}\n\n// WithAllowAtomicPublish sets the AllowAtomicPublish field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the AllowAtomicPublish field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithAllowAtomicPublish(value bool) *StreamSpecApplyConfiguration {\n\tb.AllowAtomicPublish = &value\n\treturn b\n}\n\n// WithAllowMsgSchedules sets the AllowMsgSchedules field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the AllowMsgSchedules field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithAllowMsgSchedules(value bool) *StreamSpecApplyConfiguration {\n\tb.AllowMsgSchedules = &value\n\treturn b\n}\n\n// WithPersistMode sets the PersistMode field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the PersistMode field is set to the value of the last call.\nfunc (b *StreamSpecApplyConfiguration) WithPersistMode(value string) *StreamSpecApplyConfiguration {\n\tb.PersistMode = &value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/subjecttransform.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// SubjectTransformApplyConfiguration represents a declarative configuration of the SubjectTransform type for use\n// with apply.\ntype SubjectTransformApplyConfiguration struct {\n\tSource *string `json:\"source,omitempty\"`\n\tDest   *string `json:\"dest,omitempty\"`\n}\n\n// SubjectTransformApplyConfiguration constructs a declarative configuration of the SubjectTransform type for use with\n// apply.\nfunc SubjectTransform() *SubjectTransformApplyConfiguration {\n\treturn &SubjectTransformApplyConfiguration{}\n}\n\n// WithSource sets the Source field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Source field is set to the value of the last call.\nfunc (b *SubjectTransformApplyConfiguration) WithSource(value string) *SubjectTransformApplyConfiguration {\n\tb.Source = &value\n\treturn b\n}\n\n// WithDest sets the Dest field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Dest field is set to the value of the last call.\nfunc (b *SubjectTransformApplyConfiguration) WithDest(value string) *SubjectTransformApplyConfiguration {\n\tb.Dest = &value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tls.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// TLSApplyConfiguration represents a declarative configuration of the TLS type for use\n// with apply.\ntype TLSApplyConfiguration struct {\n\tClientCert *string  `json:\"clientCert,omitempty\"`\n\tClientKey  *string  `json:\"clientKey,omitempty\"`\n\tRootCAs    []string `json:\"rootCas,omitempty\"`\n}\n\n// TLSApplyConfiguration constructs a declarative configuration of the TLS type for use with\n// apply.\nfunc TLS() *TLSApplyConfiguration {\n\treturn &TLSApplyConfiguration{}\n}\n\n// WithClientCert sets the ClientCert field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ClientCert field is set to the value of the last call.\nfunc (b *TLSApplyConfiguration) WithClientCert(value string) *TLSApplyConfiguration {\n\tb.ClientCert = &value\n\treturn b\n}\n\n// WithClientKey sets the ClientKey field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ClientKey field is set to the value of the last call.\nfunc (b *TLSApplyConfiguration) WithClientKey(value string) *TLSApplyConfiguration {\n\tb.ClientKey = &value\n\treturn b\n}\n\n// WithRootCAs adds the given value to the RootCAs field in the declarative configuration\n// and returns the receiver, so that objects can be build by chaining \"With\" function invocations.\n// If called multiple times, values provided by each call will be appended to the RootCAs field.\nfunc (b *TLSApplyConfiguration) WithRootCAs(values ...string) *TLSApplyConfiguration {\n\tfor i := range values {\n\t\tb.RootCAs = append(b.RootCAs, values[i])\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tlssecret.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// TLSSecretApplyConfiguration represents a declarative configuration of the TLSSecret type for use\n// with apply.\ntype TLSSecretApplyConfiguration struct {\n\tClientCert *string                      `json:\"cert,omitempty\"`\n\tClientKey  *string                      `json:\"key,omitempty\"`\n\tRootCAs    *string                      `json:\"ca,omitempty\"`\n\tSecret     *SecretRefApplyConfiguration `json:\"secret,omitempty\"`\n}\n\n// TLSSecretApplyConfiguration constructs a declarative configuration of the TLSSecret type for use with\n// apply.\nfunc TLSSecret() *TLSSecretApplyConfiguration {\n\treturn &TLSSecretApplyConfiguration{}\n}\n\n// WithClientCert sets the ClientCert field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ClientCert field is set to the value of the last call.\nfunc (b *TLSSecretApplyConfiguration) WithClientCert(value string) *TLSSecretApplyConfiguration {\n\tb.ClientCert = &value\n\treturn b\n}\n\n// WithClientKey sets the ClientKey field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the ClientKey field is set to the value of the last call.\nfunc (b *TLSSecretApplyConfiguration) WithClientKey(value string) *TLSSecretApplyConfiguration {\n\tb.ClientKey = &value\n\treturn b\n}\n\n// WithRootCAs sets the RootCAs field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the RootCAs field is set to the value of the last call.\nfunc (b *TLSSecretApplyConfiguration) WithRootCAs(value string) *TLSSecretApplyConfiguration {\n\tb.RootCAs = &value\n\treturn b\n}\n\n// WithSecret sets the Secret field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Secret field is set to the value of the last call.\nfunc (b *TLSSecretApplyConfiguration) WithSecret(value *SecretRefApplyConfiguration) *TLSSecretApplyConfiguration {\n\tb.Secret = value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tokensecret.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// TokenSecretApplyConfiguration represents a declarative configuration of the TokenSecret type for use\n// with apply.\ntype TokenSecretApplyConfiguration struct {\n\tToken  *string                      `json:\"token,omitempty\"`\n\tSecret *SecretRefApplyConfiguration `json:\"secret,omitempty\"`\n}\n\n// TokenSecretApplyConfiguration constructs a declarative configuration of the TokenSecret type for use with\n// apply.\nfunc TokenSecret() *TokenSecretApplyConfiguration {\n\treturn &TokenSecretApplyConfiguration{}\n}\n\n// WithToken sets the Token field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Token field is set to the value of the last call.\nfunc (b *TokenSecretApplyConfiguration) WithToken(value string) *TokenSecretApplyConfiguration {\n\tb.Token = &value\n\treturn b\n}\n\n// WithSecret sets the Secret field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Secret field is set to the value of the last call.\nfunc (b *TokenSecretApplyConfiguration) WithSecret(value *SecretRefApplyConfiguration) *TokenSecretApplyConfiguration {\n\tb.Secret = value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/user.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// UserApplyConfiguration represents a declarative configuration of the User type for use\n// with apply.\ntype UserApplyConfiguration struct {\n\tUser     *string                      `json:\"user,omitempty\"`\n\tPassword *string                      `json:\"password,omitempty\"`\n\tSecret   *SecretRefApplyConfiguration `json:\"secret,omitempty\"`\n}\n\n// UserApplyConfiguration constructs a declarative configuration of the User type for use with\n// apply.\nfunc User() *UserApplyConfiguration {\n\treturn &UserApplyConfiguration{}\n}\n\n// WithUser sets the User field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the User field is set to the value of the last call.\nfunc (b *UserApplyConfiguration) WithUser(value string) *UserApplyConfiguration {\n\tb.User = &value\n\treturn b\n}\n\n// WithPassword sets the Password field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Password field is set to the value of the last call.\nfunc (b *UserApplyConfiguration) WithPassword(value string) *UserApplyConfiguration {\n\tb.Password = &value\n\treturn b\n}\n\n// WithSecret sets the Secret field in the declarative configuration to the given value\n// and returns the receiver, so that objects can be built by chaining \"With\" function invocations.\n// If called multiple times, the Secret field is set to the value of the last call.\nfunc (b *UserApplyConfiguration) WithSecret(value *SecretRefApplyConfiguration) *UserApplyConfiguration {\n\tb.Secret = value\n\treturn b\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/applyconfiguration/utils.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by applyconfiguration-gen. DO NOT EDIT.\n\npackage applyconfiguration\n\nimport (\n\tv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tinternal \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/internal\"\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tmanagedfields \"k8s.io/apimachinery/pkg/util/managedfields\"\n)\n\n// ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no\n// apply configuration type exists for the given GroupVersionKind.\nfunc ForKind(kind schema.GroupVersionKind) interface{} {\n\tswitch kind {\n\t// Group=jetstream.nats.io, Version=v1beta2\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"Account\"):\n\t\treturn &jetstreamv1beta2.AccountApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"AccountSpec\"):\n\t\treturn &jetstreamv1beta2.AccountSpecApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"BaseStreamConfig\"):\n\t\treturn &jetstreamv1beta2.BaseStreamConfigApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"Condition\"):\n\t\treturn &jetstreamv1beta2.ConditionApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"ConnectionOpts\"):\n\t\treturn &jetstreamv1beta2.ConnectionOptsApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"Consumer\"):\n\t\treturn &jetstreamv1beta2.ConsumerApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"ConsumerLimits\"):\n\t\treturn &jetstreamv1beta2.ConsumerLimitsApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"ConsumerSpec\"):\n\t\treturn &jetstreamv1beta2.ConsumerSpecApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"CredsSecret\"):\n\t\treturn &jetstreamv1beta2.CredsSecretApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"KeyValue\"):\n\t\treturn &jetstreamv1beta2.KeyValueApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"KeyValueSpec\"):\n\t\treturn &jetstreamv1beta2.KeyValueSpecApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"NKeySecret\"):\n\t\treturn &jetstreamv1beta2.NKeySecretApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"ObjectStore\"):\n\t\treturn &jetstreamv1beta2.ObjectStoreApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"ObjectStoreSpec\"):\n\t\treturn &jetstreamv1beta2.ObjectStoreSpecApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"RePublish\"):\n\t\treturn &jetstreamv1beta2.RePublishApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"SecretRef\"):\n\t\treturn &jetstreamv1beta2.SecretRefApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"Status\"):\n\t\treturn &jetstreamv1beta2.StatusApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"Stream\"):\n\t\treturn &jetstreamv1beta2.StreamApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"StreamPlacement\"):\n\t\treturn &jetstreamv1beta2.StreamPlacementApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"StreamSource\"):\n\t\treturn &jetstreamv1beta2.StreamSourceApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"StreamSpec\"):\n\t\treturn &jetstreamv1beta2.StreamSpecApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"SubjectTransform\"):\n\t\treturn &jetstreamv1beta2.SubjectTransformApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"TLS\"):\n\t\treturn &jetstreamv1beta2.TLSApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"TLSSecret\"):\n\t\treturn &jetstreamv1beta2.TLSSecretApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"TokenSecret\"):\n\t\treturn &jetstreamv1beta2.TokenSecretApplyConfiguration{}\n\tcase v1beta2.SchemeGroupVersion.WithKind(\"User\"):\n\t\treturn &jetstreamv1beta2.UserApplyConfiguration{}\n\n\t}\n\treturn nil\n}\n\nfunc NewTypeConverter(scheme *runtime.Scheme) managedfields.TypeConverter {\n\treturn managedfields.NewSchemeTypeConverter(scheme, internal.Parser())\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/clientset.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage versioned\n\nimport (\n\tfmt \"fmt\"\n\thttp \"net/http\"\n\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2\"\n\tdiscovery \"k8s.io/client-go/discovery\"\n\trest \"k8s.io/client-go/rest\"\n\tflowcontrol \"k8s.io/client-go/util/flowcontrol\"\n)\n\ntype Interface interface {\n\tDiscovery() discovery.DiscoveryInterface\n\tJetstreamV1beta2() jetstreamv1beta2.JetstreamV1beta2Interface\n}\n\n// Clientset contains the clients for groups.\ntype Clientset struct {\n\t*discovery.DiscoveryClient\n\tjetstreamV1beta2 *jetstreamv1beta2.JetstreamV1beta2Client\n}\n\n// JetstreamV1beta2 retrieves the JetstreamV1beta2Client\nfunc (c *Clientset) JetstreamV1beta2() jetstreamv1beta2.JetstreamV1beta2Interface {\n\treturn c.jetstreamV1beta2\n}\n\n// Discovery retrieves the DiscoveryClient\nfunc (c *Clientset) Discovery() discovery.DiscoveryInterface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.DiscoveryClient\n}\n\n// NewForConfig creates a new Clientset for the given config.\n// If config's RateLimiter is not set and QPS and Burst are acceptable,\n// NewForConfig will generate a rate-limiter in configShallowCopy.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*Clientset, error) {\n\tconfigShallowCopy := *c\n\n\tif configShallowCopy.UserAgent == \"\" {\n\t\tconfigShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n\n\t// share the transport between all clients\n\thttpClient, err := rest.HTTPClientFor(&configShallowCopy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn NewForConfigAndClient(&configShallowCopy, httpClient)\n}\n\n// NewForConfigAndClient creates a new Clientset for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\n// If config's RateLimiter is not set and QPS and Burst are acceptable,\n// NewForConfigAndClient will generate a rate-limiter in configShallowCopy.\nfunc NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {\n\tconfigShallowCopy := *c\n\tif configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {\n\t\tif configShallowCopy.Burst <= 0 {\n\t\t\treturn nil, fmt.Errorf(\"burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0\")\n\t\t}\n\t\tconfigShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)\n\t}\n\n\tvar cs Clientset\n\tvar err error\n\tcs.jetstreamV1beta2, err = jetstreamv1beta2.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &cs, nil\n}\n\n// NewForConfigOrDie creates a new Clientset for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *Clientset {\n\tcs, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn cs\n}\n\n// New creates a new Clientset for the given RESTClient.\nfunc New(c rest.Interface) *Clientset {\n\tvar cs Clientset\n\tcs.jetstreamV1beta2 = jetstreamv1beta2.New(c)\n\n\tcs.DiscoveryClient = discovery.NewDiscoveryClient(c)\n\treturn &cs\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/fake/clientset_generated.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tapplyconfiguration \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration\"\n\tclientset \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned\"\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2\"\n\tfakejetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\t\"k8s.io/client-go/discovery\"\n\tfakediscovery \"k8s.io/client-go/discovery/fake\"\n\t\"k8s.io/client-go/testing\"\n)\n\n// NewSimpleClientset returns a clientset that will respond with the provided objects.\n// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,\n// without applying any field management, validations and/or defaults. It shouldn't be considered a replacement\n// for a real clientset and is mostly useful in simple unit tests.\n//\n// DEPRECATED: NewClientset replaces this with support for field management, which significantly improves\n// server side apply testing. NewClientset is only available when apply configurations are generated (e.g.\n// via --with-applyconfig).\nfunc NewSimpleClientset(objects ...runtime.Object) *Clientset {\n\to := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())\n\tfor _, obj := range objects {\n\t\tif err := o.Add(obj); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\tcs := &Clientset{tracker: o}\n\tcs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}\n\tcs.AddReactor(\"*\", \"*\", testing.ObjectReaction(o))\n\tcs.AddWatchReactor(\"*\", func(action testing.Action) (handled bool, ret watch.Interface, err error) {\n\t\tvar opts metav1.ListOptions\n\t\tif watchActcion, ok := action.(testing.WatchActionImpl); ok {\n\t\t\topts = watchActcion.ListOptions\n\t\t}\n\t\tgvr := action.GetResource()\n\t\tns := action.GetNamespace()\n\t\twatch, err := o.Watch(gvr, ns, opts)\n\t\tif err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\t\treturn true, watch, nil\n\t})\n\n\treturn cs\n}\n\n// Clientset implements clientset.Interface. Meant to be embedded into a\n// struct to get a default implementation. This makes faking out just the method\n// you want to test easier.\ntype Clientset struct {\n\ttesting.Fake\n\tdiscovery *fakediscovery.FakeDiscovery\n\ttracker   testing.ObjectTracker\n}\n\nfunc (c *Clientset) Discovery() discovery.DiscoveryInterface {\n\treturn c.discovery\n}\n\nfunc (c *Clientset) Tracker() testing.ObjectTracker {\n\treturn c.tracker\n}\n\n// NewClientset returns a clientset that will respond with the provided objects.\n// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,\n// without applying any validations and/or defaults. It shouldn't be considered a replacement\n// for a real clientset and is mostly useful in simple unit tests.\nfunc NewClientset(objects ...runtime.Object) *Clientset {\n\to := testing.NewFieldManagedObjectTracker(\n\t\tscheme,\n\t\tcodecs.UniversalDecoder(),\n\t\tapplyconfiguration.NewTypeConverter(scheme),\n\t)\n\tfor _, obj := range objects {\n\t\tif err := o.Add(obj); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\tcs := &Clientset{tracker: o}\n\tcs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}\n\tcs.AddReactor(\"*\", \"*\", testing.ObjectReaction(o))\n\tcs.AddWatchReactor(\"*\", func(action testing.Action) (handled bool, ret watch.Interface, err error) {\n\t\tvar opts metav1.ListOptions\n\t\tif watchAction, ok := action.(testing.WatchActionImpl); ok {\n\t\t\topts = watchAction.ListOptions\n\t\t}\n\t\tgvr := action.GetResource()\n\t\tns := action.GetNamespace()\n\t\twatch, err := o.Watch(gvr, ns, opts)\n\t\tif err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\t\treturn true, watch, nil\n\t})\n\n\treturn cs\n}\n\nvar (\n\t_ clientset.Interface = &Clientset{}\n\t_ testing.FakeClient  = &Clientset{}\n)\n\n// JetstreamV1beta2 retrieves the JetstreamV1beta2Client\nfunc (c *Clientset) JetstreamV1beta2() jetstreamv1beta2.JetstreamV1beta2Interface {\n\treturn &fakejetstreamv1beta2.FakeJetstreamV1beta2{Fake: &c.Fake}\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/fake/doc.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated fake clientset.\npackage fake\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/fake/register.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tserializer \"k8s.io/apimachinery/pkg/runtime/serializer\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n)\n\nvar scheme = runtime.NewScheme()\nvar codecs = serializer.NewCodecFactory(scheme)\n\nvar localSchemeBuilder = runtime.SchemeBuilder{\n\tjetstreamv1beta2.AddToScheme,\n}\n\n// AddToScheme adds all types of this clientset into the given scheme. This allows composition\n// of clientsets, like in:\n//\n//\timport (\n//\t  \"k8s.io/client-go/kubernetes\"\n//\t  clientsetscheme \"k8s.io/client-go/kubernetes/scheme\"\n//\t  aggregatorclientsetscheme \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme\"\n//\t)\n//\n//\tkclientset, _ := kubernetes.NewForConfig(c)\n//\t_ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)\n//\n// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types\n// correctly.\nvar AddToScheme = localSchemeBuilder.AddToScheme\n\nfunc init() {\n\tv1.AddToGroupVersion(scheme, schema.GroupVersion{Version: \"v1\"})\n\tutilruntime.Must(AddToScheme(scheme))\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/scheme/doc.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\n// This package contains the scheme of the automatically generated clientset.\npackage scheme\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/scheme/register.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage scheme\n\nimport (\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tserializer \"k8s.io/apimachinery/pkg/runtime/serializer\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n)\n\nvar Scheme = runtime.NewScheme()\nvar Codecs = serializer.NewCodecFactory(Scheme)\nvar ParameterCodec = runtime.NewParameterCodec(Scheme)\nvar localSchemeBuilder = runtime.SchemeBuilder{\n\tjetstreamv1beta2.AddToScheme,\n}\n\n// AddToScheme adds all types of this clientset into the given scheme. This allows composition\n// of clientsets, like in:\n//\n//\timport (\n//\t  \"k8s.io/client-go/kubernetes\"\n//\t  clientsetscheme \"k8s.io/client-go/kubernetes/scheme\"\n//\t  aggregatorclientsetscheme \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme\"\n//\t)\n//\n//\tkclientset, _ := kubernetes.NewForConfig(c)\n//\t_ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)\n//\n// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types\n// correctly.\nvar AddToScheme = localSchemeBuilder.AddToScheme\n\nfunc init() {\n\tv1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: \"v1\"})\n\tutilruntime.Must(AddToScheme(Scheme))\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/account.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tcontext \"context\"\n\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tapplyconfigurationjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2\"\n\tscheme \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// AccountsGetter has a method to return a AccountInterface.\n// A group's client should implement this interface.\ntype AccountsGetter interface {\n\tAccounts(namespace string) AccountInterface\n}\n\n// AccountInterface has methods to work with Account resources.\ntype AccountInterface interface {\n\tCreate(ctx context.Context, account *jetstreamv1beta2.Account, opts v1.CreateOptions) (*jetstreamv1beta2.Account, error)\n\tUpdate(ctx context.Context, account *jetstreamv1beta2.Account, opts v1.UpdateOptions) (*jetstreamv1beta2.Account, error)\n\t// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\n\tUpdateStatus(ctx context.Context, account *jetstreamv1beta2.Account, opts v1.UpdateOptions) (*jetstreamv1beta2.Account, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*jetstreamv1beta2.Account, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*jetstreamv1beta2.AccountList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *jetstreamv1beta2.Account, err error)\n\tApply(ctx context.Context, account *applyconfigurationjetstreamv1beta2.AccountApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.Account, err error)\n\t// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().\n\tApplyStatus(ctx context.Context, account *applyconfigurationjetstreamv1beta2.AccountApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.Account, err error)\n\tAccountExpansion\n}\n\n// accounts implements AccountInterface\ntype accounts struct {\n\t*gentype.ClientWithListAndApply[*jetstreamv1beta2.Account, *jetstreamv1beta2.AccountList, *applyconfigurationjetstreamv1beta2.AccountApplyConfiguration]\n}\n\n// newAccounts returns a Accounts\nfunc newAccounts(c *JetstreamV1beta2Client, namespace string) *accounts {\n\treturn &accounts{\n\t\tgentype.NewClientWithListAndApply[*jetstreamv1beta2.Account, *jetstreamv1beta2.AccountList, *applyconfigurationjetstreamv1beta2.AccountApplyConfiguration](\n\t\t\t\"accounts\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *jetstreamv1beta2.Account { return &jetstreamv1beta2.Account{} },\n\t\t\tfunc() *jetstreamv1beta2.AccountList { return &jetstreamv1beta2.AccountList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/consumer.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tcontext \"context\"\n\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tapplyconfigurationjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2\"\n\tscheme \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// ConsumersGetter has a method to return a ConsumerInterface.\n// A group's client should implement this interface.\ntype ConsumersGetter interface {\n\tConsumers(namespace string) ConsumerInterface\n}\n\n// ConsumerInterface has methods to work with Consumer resources.\ntype ConsumerInterface interface {\n\tCreate(ctx context.Context, consumer *jetstreamv1beta2.Consumer, opts v1.CreateOptions) (*jetstreamv1beta2.Consumer, error)\n\tUpdate(ctx context.Context, consumer *jetstreamv1beta2.Consumer, opts v1.UpdateOptions) (*jetstreamv1beta2.Consumer, error)\n\t// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\n\tUpdateStatus(ctx context.Context, consumer *jetstreamv1beta2.Consumer, opts v1.UpdateOptions) (*jetstreamv1beta2.Consumer, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*jetstreamv1beta2.Consumer, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*jetstreamv1beta2.ConsumerList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *jetstreamv1beta2.Consumer, err error)\n\tApply(ctx context.Context, consumer *applyconfigurationjetstreamv1beta2.ConsumerApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.Consumer, err error)\n\t// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().\n\tApplyStatus(ctx context.Context, consumer *applyconfigurationjetstreamv1beta2.ConsumerApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.Consumer, err error)\n\tConsumerExpansion\n}\n\n// consumers implements ConsumerInterface\ntype consumers struct {\n\t*gentype.ClientWithListAndApply[*jetstreamv1beta2.Consumer, *jetstreamv1beta2.ConsumerList, *applyconfigurationjetstreamv1beta2.ConsumerApplyConfiguration]\n}\n\n// newConsumers returns a Consumers\nfunc newConsumers(c *JetstreamV1beta2Client, namespace string) *consumers {\n\treturn &consumers{\n\t\tgentype.NewClientWithListAndApply[*jetstreamv1beta2.Consumer, *jetstreamv1beta2.ConsumerList, *applyconfigurationjetstreamv1beta2.ConsumerApplyConfiguration](\n\t\t\t\"consumers\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *jetstreamv1beta2.Consumer { return &jetstreamv1beta2.Consumer{} },\n\t\t\tfunc() *jetstreamv1beta2.ConsumerList { return &jetstreamv1beta2.ConsumerList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/doc.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1beta2\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/doc.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_account.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2\"\n\ttypedjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeAccounts implements AccountInterface\ntype fakeAccounts struct {\n\t*gentype.FakeClientWithListAndApply[*v1beta2.Account, *v1beta2.AccountList, *jetstreamv1beta2.AccountApplyConfiguration]\n\tFake *FakeJetstreamV1beta2\n}\n\nfunc newFakeAccounts(fake *FakeJetstreamV1beta2, namespace string) typedjetstreamv1beta2.AccountInterface {\n\treturn &fakeAccounts{\n\t\tgentype.NewFakeClientWithListAndApply[*v1beta2.Account, *v1beta2.AccountList, *jetstreamv1beta2.AccountApplyConfiguration](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1beta2.SchemeGroupVersion.WithResource(\"accounts\"),\n\t\t\tv1beta2.SchemeGroupVersion.WithKind(\"Account\"),\n\t\t\tfunc() *v1beta2.Account { return &v1beta2.Account{} },\n\t\t\tfunc() *v1beta2.AccountList { return &v1beta2.AccountList{} },\n\t\t\tfunc(dst, src *v1beta2.AccountList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1beta2.AccountList) []*v1beta2.Account { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1beta2.AccountList, items []*v1beta2.Account) {\n\t\t\t\tlist.Items = gentype.FromPointerSlice(items)\n\t\t\t},\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_consumer.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2\"\n\ttypedjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeConsumers implements ConsumerInterface\ntype fakeConsumers struct {\n\t*gentype.FakeClientWithListAndApply[*v1beta2.Consumer, *v1beta2.ConsumerList, *jetstreamv1beta2.ConsumerApplyConfiguration]\n\tFake *FakeJetstreamV1beta2\n}\n\nfunc newFakeConsumers(fake *FakeJetstreamV1beta2, namespace string) typedjetstreamv1beta2.ConsumerInterface {\n\treturn &fakeConsumers{\n\t\tgentype.NewFakeClientWithListAndApply[*v1beta2.Consumer, *v1beta2.ConsumerList, *jetstreamv1beta2.ConsumerApplyConfiguration](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1beta2.SchemeGroupVersion.WithResource(\"consumers\"),\n\t\t\tv1beta2.SchemeGroupVersion.WithKind(\"Consumer\"),\n\t\t\tfunc() *v1beta2.Consumer { return &v1beta2.Consumer{} },\n\t\t\tfunc() *v1beta2.ConsumerList { return &v1beta2.ConsumerList{} },\n\t\t\tfunc(dst, src *v1beta2.ConsumerList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1beta2.ConsumerList) []*v1beta2.Consumer { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1beta2.ConsumerList, items []*v1beta2.Consumer) {\n\t\t\t\tlist.Items = gentype.FromPointerSlice(items)\n\t\t\t},\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeJetstreamV1beta2 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeJetstreamV1beta2) Accounts(namespace string) v1beta2.AccountInterface {\n\treturn newFakeAccounts(c, namespace)\n}\n\nfunc (c *FakeJetstreamV1beta2) Consumers(namespace string) v1beta2.ConsumerInterface {\n\treturn newFakeConsumers(c, namespace)\n}\n\nfunc (c *FakeJetstreamV1beta2) KeyValues(namespace string) v1beta2.KeyValueInterface {\n\treturn newFakeKeyValues(c, namespace)\n}\n\nfunc (c *FakeJetstreamV1beta2) ObjectStores(namespace string) v1beta2.ObjectStoreInterface {\n\treturn newFakeObjectStores(c, namespace)\n}\n\nfunc (c *FakeJetstreamV1beta2) Streams(namespace string) v1beta2.StreamInterface {\n\treturn newFakeStreams(c, namespace)\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeJetstreamV1beta2) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_keyvalue.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2\"\n\ttypedjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeKeyValues implements KeyValueInterface\ntype fakeKeyValues struct {\n\t*gentype.FakeClientWithListAndApply[*v1beta2.KeyValue, *v1beta2.KeyValueList, *jetstreamv1beta2.KeyValueApplyConfiguration]\n\tFake *FakeJetstreamV1beta2\n}\n\nfunc newFakeKeyValues(fake *FakeJetstreamV1beta2, namespace string) typedjetstreamv1beta2.KeyValueInterface {\n\treturn &fakeKeyValues{\n\t\tgentype.NewFakeClientWithListAndApply[*v1beta2.KeyValue, *v1beta2.KeyValueList, *jetstreamv1beta2.KeyValueApplyConfiguration](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1beta2.SchemeGroupVersion.WithResource(\"keyvalues\"),\n\t\t\tv1beta2.SchemeGroupVersion.WithKind(\"KeyValue\"),\n\t\t\tfunc() *v1beta2.KeyValue { return &v1beta2.KeyValue{} },\n\t\t\tfunc() *v1beta2.KeyValueList { return &v1beta2.KeyValueList{} },\n\t\t\tfunc(dst, src *v1beta2.KeyValueList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1beta2.KeyValueList) []*v1beta2.KeyValue { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1beta2.KeyValueList, items []*v1beta2.KeyValue) {\n\t\t\t\tlist.Items = gentype.FromPointerSlice(items)\n\t\t\t},\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_objectstore.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2\"\n\ttypedjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeObjectStores implements ObjectStoreInterface\ntype fakeObjectStores struct {\n\t*gentype.FakeClientWithListAndApply[*v1beta2.ObjectStore, *v1beta2.ObjectStoreList, *jetstreamv1beta2.ObjectStoreApplyConfiguration]\n\tFake *FakeJetstreamV1beta2\n}\n\nfunc newFakeObjectStores(fake *FakeJetstreamV1beta2, namespace string) typedjetstreamv1beta2.ObjectStoreInterface {\n\treturn &fakeObjectStores{\n\t\tgentype.NewFakeClientWithListAndApply[*v1beta2.ObjectStore, *v1beta2.ObjectStoreList, *jetstreamv1beta2.ObjectStoreApplyConfiguration](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1beta2.SchemeGroupVersion.WithResource(\"objectstores\"),\n\t\t\tv1beta2.SchemeGroupVersion.WithKind(\"ObjectStore\"),\n\t\t\tfunc() *v1beta2.ObjectStore { return &v1beta2.ObjectStore{} },\n\t\t\tfunc() *v1beta2.ObjectStoreList { return &v1beta2.ObjectStoreList{} },\n\t\t\tfunc(dst, src *v1beta2.ObjectStoreList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1beta2.ObjectStoreList) []*v1beta2.ObjectStore { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1beta2.ObjectStoreList, items []*v1beta2.ObjectStore) {\n\t\t\t\tlist.Items = gentype.FromPointerSlice(items)\n\t\t\t},\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_stream.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2\"\n\ttypedjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// fakeStreams implements StreamInterface\ntype fakeStreams struct {\n\t*gentype.FakeClientWithListAndApply[*v1beta2.Stream, *v1beta2.StreamList, *jetstreamv1beta2.StreamApplyConfiguration]\n\tFake *FakeJetstreamV1beta2\n}\n\nfunc newFakeStreams(fake *FakeJetstreamV1beta2, namespace string) typedjetstreamv1beta2.StreamInterface {\n\treturn &fakeStreams{\n\t\tgentype.NewFakeClientWithListAndApply[*v1beta2.Stream, *v1beta2.StreamList, *jetstreamv1beta2.StreamApplyConfiguration](\n\t\t\tfake.Fake,\n\t\t\tnamespace,\n\t\t\tv1beta2.SchemeGroupVersion.WithResource(\"streams\"),\n\t\t\tv1beta2.SchemeGroupVersion.WithKind(\"Stream\"),\n\t\t\tfunc() *v1beta2.Stream { return &v1beta2.Stream{} },\n\t\t\tfunc() *v1beta2.StreamList { return &v1beta2.StreamList{} },\n\t\t\tfunc(dst, src *v1beta2.StreamList) { dst.ListMeta = src.ListMeta },\n\t\t\tfunc(list *v1beta2.StreamList) []*v1beta2.Stream { return gentype.ToPointerSlice(list.Items) },\n\t\t\tfunc(list *v1beta2.StreamList, items []*v1beta2.Stream) { list.Items = gentype.FromPointerSlice(items) },\n\t\t),\n\t\tfake,\n\t}\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta2\n\ntype AccountExpansion interface{}\n\ntype ConsumerExpansion interface{}\n\ntype KeyValueExpansion interface{}\n\ntype ObjectStoreExpansion interface{}\n\ntype StreamExpansion interface{}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\thttp \"net/http\"\n\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tscheme \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype JetstreamV1beta2Interface interface {\n\tRESTClient() rest.Interface\n\tAccountsGetter\n\tConsumersGetter\n\tKeyValuesGetter\n\tObjectStoresGetter\n\tStreamsGetter\n}\n\n// JetstreamV1beta2Client is used to interact with features provided by the jetstream.nats.io group.\ntype JetstreamV1beta2Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *JetstreamV1beta2Client) Accounts(namespace string) AccountInterface {\n\treturn newAccounts(c, namespace)\n}\n\nfunc (c *JetstreamV1beta2Client) Consumers(namespace string) ConsumerInterface {\n\treturn newConsumers(c, namespace)\n}\n\nfunc (c *JetstreamV1beta2Client) KeyValues(namespace string) KeyValueInterface {\n\treturn newKeyValues(c, namespace)\n}\n\nfunc (c *JetstreamV1beta2Client) ObjectStores(namespace string) ObjectStoreInterface {\n\treturn newObjectStores(c, namespace)\n}\n\nfunc (c *JetstreamV1beta2Client) Streams(namespace string) StreamInterface {\n\treturn newStreams(c, namespace)\n}\n\n// NewForConfig creates a new JetstreamV1beta2Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*JetstreamV1beta2Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new JetstreamV1beta2Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*JetstreamV1beta2Client, error) {\n\tconfig := *c\n\tsetConfigDefaults(&config)\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &JetstreamV1beta2Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new JetstreamV1beta2Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *JetstreamV1beta2Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new JetstreamV1beta2Client for the given RESTClient.\nfunc New(c rest.Interface) *JetstreamV1beta2Client {\n\treturn &JetstreamV1beta2Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) {\n\tgv := jetstreamv1beta2.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *JetstreamV1beta2Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/keyvalue.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tcontext \"context\"\n\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tapplyconfigurationjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2\"\n\tscheme \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// KeyValuesGetter has a method to return a KeyValueInterface.\n// A group's client should implement this interface.\ntype KeyValuesGetter interface {\n\tKeyValues(namespace string) KeyValueInterface\n}\n\n// KeyValueInterface has methods to work with KeyValue resources.\ntype KeyValueInterface interface {\n\tCreate(ctx context.Context, keyValue *jetstreamv1beta2.KeyValue, opts v1.CreateOptions) (*jetstreamv1beta2.KeyValue, error)\n\tUpdate(ctx context.Context, keyValue *jetstreamv1beta2.KeyValue, opts v1.UpdateOptions) (*jetstreamv1beta2.KeyValue, error)\n\t// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\n\tUpdateStatus(ctx context.Context, keyValue *jetstreamv1beta2.KeyValue, opts v1.UpdateOptions) (*jetstreamv1beta2.KeyValue, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*jetstreamv1beta2.KeyValue, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*jetstreamv1beta2.KeyValueList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *jetstreamv1beta2.KeyValue, err error)\n\tApply(ctx context.Context, keyValue *applyconfigurationjetstreamv1beta2.KeyValueApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.KeyValue, err error)\n\t// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().\n\tApplyStatus(ctx context.Context, keyValue *applyconfigurationjetstreamv1beta2.KeyValueApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.KeyValue, err error)\n\tKeyValueExpansion\n}\n\n// keyValues implements KeyValueInterface\ntype keyValues struct {\n\t*gentype.ClientWithListAndApply[*jetstreamv1beta2.KeyValue, *jetstreamv1beta2.KeyValueList, *applyconfigurationjetstreamv1beta2.KeyValueApplyConfiguration]\n}\n\n// newKeyValues returns a KeyValues\nfunc newKeyValues(c *JetstreamV1beta2Client, namespace string) *keyValues {\n\treturn &keyValues{\n\t\tgentype.NewClientWithListAndApply[*jetstreamv1beta2.KeyValue, *jetstreamv1beta2.KeyValueList, *applyconfigurationjetstreamv1beta2.KeyValueApplyConfiguration](\n\t\t\t\"keyvalues\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *jetstreamv1beta2.KeyValue { return &jetstreamv1beta2.KeyValue{} },\n\t\t\tfunc() *jetstreamv1beta2.KeyValueList { return &jetstreamv1beta2.KeyValueList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/objectstore.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tcontext \"context\"\n\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tapplyconfigurationjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2\"\n\tscheme \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// ObjectStoresGetter has a method to return a ObjectStoreInterface.\n// A group's client should implement this interface.\ntype ObjectStoresGetter interface {\n\tObjectStores(namespace string) ObjectStoreInterface\n}\n\n// ObjectStoreInterface has methods to work with ObjectStore resources.\ntype ObjectStoreInterface interface {\n\tCreate(ctx context.Context, objectStore *jetstreamv1beta2.ObjectStore, opts v1.CreateOptions) (*jetstreamv1beta2.ObjectStore, error)\n\tUpdate(ctx context.Context, objectStore *jetstreamv1beta2.ObjectStore, opts v1.UpdateOptions) (*jetstreamv1beta2.ObjectStore, error)\n\t// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\n\tUpdateStatus(ctx context.Context, objectStore *jetstreamv1beta2.ObjectStore, opts v1.UpdateOptions) (*jetstreamv1beta2.ObjectStore, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*jetstreamv1beta2.ObjectStore, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*jetstreamv1beta2.ObjectStoreList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *jetstreamv1beta2.ObjectStore, err error)\n\tApply(ctx context.Context, objectStore *applyconfigurationjetstreamv1beta2.ObjectStoreApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.ObjectStore, err error)\n\t// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().\n\tApplyStatus(ctx context.Context, objectStore *applyconfigurationjetstreamv1beta2.ObjectStoreApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.ObjectStore, err error)\n\tObjectStoreExpansion\n}\n\n// objectStores implements ObjectStoreInterface\ntype objectStores struct {\n\t*gentype.ClientWithListAndApply[*jetstreamv1beta2.ObjectStore, *jetstreamv1beta2.ObjectStoreList, *applyconfigurationjetstreamv1beta2.ObjectStoreApplyConfiguration]\n}\n\n// newObjectStores returns a ObjectStores\nfunc newObjectStores(c *JetstreamV1beta2Client, namespace string) *objectStores {\n\treturn &objectStores{\n\t\tgentype.NewClientWithListAndApply[*jetstreamv1beta2.ObjectStore, *jetstreamv1beta2.ObjectStoreList, *applyconfigurationjetstreamv1beta2.ObjectStoreApplyConfiguration](\n\t\t\t\"objectstores\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *jetstreamv1beta2.ObjectStore { return &jetstreamv1beta2.ObjectStore{} },\n\t\t\tfunc() *jetstreamv1beta2.ObjectStoreList { return &jetstreamv1beta2.ObjectStoreList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/stream.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by client-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tcontext \"context\"\n\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tapplyconfigurationjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2\"\n\tscheme \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tgentype \"k8s.io/client-go/gentype\"\n)\n\n// StreamsGetter has a method to return a StreamInterface.\n// A group's client should implement this interface.\ntype StreamsGetter interface {\n\tStreams(namespace string) StreamInterface\n}\n\n// StreamInterface has methods to work with Stream resources.\ntype StreamInterface interface {\n\tCreate(ctx context.Context, stream *jetstreamv1beta2.Stream, opts v1.CreateOptions) (*jetstreamv1beta2.Stream, error)\n\tUpdate(ctx context.Context, stream *jetstreamv1beta2.Stream, opts v1.UpdateOptions) (*jetstreamv1beta2.Stream, error)\n\t// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\n\tUpdateStatus(ctx context.Context, stream *jetstreamv1beta2.Stream, opts v1.UpdateOptions) (*jetstreamv1beta2.Stream, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*jetstreamv1beta2.Stream, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*jetstreamv1beta2.StreamList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *jetstreamv1beta2.Stream, err error)\n\tApply(ctx context.Context, stream *applyconfigurationjetstreamv1beta2.StreamApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.Stream, err error)\n\t// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().\n\tApplyStatus(ctx context.Context, stream *applyconfigurationjetstreamv1beta2.StreamApplyConfiguration, opts v1.ApplyOptions) (result *jetstreamv1beta2.Stream, err error)\n\tStreamExpansion\n}\n\n// streams implements StreamInterface\ntype streams struct {\n\t*gentype.ClientWithListAndApply[*jetstreamv1beta2.Stream, *jetstreamv1beta2.StreamList, *applyconfigurationjetstreamv1beta2.StreamApplyConfiguration]\n}\n\n// newStreams returns a Streams\nfunc newStreams(c *JetstreamV1beta2Client, namespace string) *streams {\n\treturn &streams{\n\t\tgentype.NewClientWithListAndApply[*jetstreamv1beta2.Stream, *jetstreamv1beta2.StreamList, *applyconfigurationjetstreamv1beta2.StreamApplyConfiguration](\n\t\t\t\"streams\",\n\t\t\tc.RESTClient(),\n\t\t\tscheme.ParameterCodec,\n\t\t\tnamespace,\n\t\t\tfunc() *jetstreamv1beta2.Stream { return &jetstreamv1beta2.Stream{} },\n\t\t\tfunc() *jetstreamv1beta2.StreamList { return &jetstreamv1beta2.StreamList{} },\n\t\t),\n\t}\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/informers/externalversions/factory.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by informer-gen. DO NOT EDIT.\n\npackage externalversions\n\nimport (\n\treflect \"reflect\"\n\tsync \"sync\"\n\ttime \"time\"\n\n\tversioned \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned\"\n\tinternalinterfaces \"github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces\"\n\tjetstream \"github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/jetstream\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// SharedInformerOption defines the functional option type for SharedInformerFactory.\ntype SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory\n\ntype sharedInformerFactory struct {\n\tclient           versioned.Interface\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tlock             sync.Mutex\n\tdefaultResync    time.Duration\n\tcustomResync     map[reflect.Type]time.Duration\n\ttransform        cache.TransformFunc\n\n\tinformers map[reflect.Type]cache.SharedIndexInformer\n\t// startedInformers is used for tracking which informers have been started.\n\t// This allows Start() to be called multiple times safely.\n\tstartedInformers map[reflect.Type]bool\n\t// wg tracks how many goroutines were started.\n\twg sync.WaitGroup\n\t// shuttingDown is true when Shutdown has been called. It may still be running\n\t// because it needs to wait for goroutines.\n\tshuttingDown bool\n}\n\n// WithCustomResyncConfig sets a custom resync period for the specified informer types.\nfunc WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfor k, v := range resyncConfig {\n\t\t\tfactory.customResync[reflect.TypeOf(k)] = v\n\t\t}\n\t\treturn factory\n\t}\n}\n\n// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory.\nfunc WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfactory.tweakListOptions = tweakListOptions\n\t\treturn factory\n\t}\n}\n\n// WithNamespace limits the SharedInformerFactory to the specified namespace.\nfunc WithNamespace(namespace string) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfactory.namespace = namespace\n\t\treturn factory\n\t}\n}\n\n// WithTransform sets a transform on all informers.\nfunc WithTransform(transform cache.TransformFunc) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfactory.transform = transform\n\t\treturn factory\n\t}\n}\n\n// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces.\nfunc NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory {\n\treturn NewSharedInformerFactoryWithOptions(client, defaultResync)\n}\n\n// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory.\n// Listers obtained via this SharedInformerFactory will be subject to the same filters\n// as specified here.\n// Deprecated: Please use NewSharedInformerFactoryWithOptions instead\nfunc NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory {\n\treturn NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions))\n}\n\n// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options.\nfunc NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory {\n\tfactory := &sharedInformerFactory{\n\t\tclient:           client,\n\t\tnamespace:        v1.NamespaceAll,\n\t\tdefaultResync:    defaultResync,\n\t\tinformers:        make(map[reflect.Type]cache.SharedIndexInformer),\n\t\tstartedInformers: make(map[reflect.Type]bool),\n\t\tcustomResync:     make(map[reflect.Type]time.Duration),\n\t}\n\n\t// Apply all options\n\tfor _, opt := range options {\n\t\tfactory = opt(factory)\n\t}\n\n\treturn factory\n}\n\nfunc (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\n\tif f.shuttingDown {\n\t\treturn\n\t}\n\n\tfor informerType, informer := range f.informers {\n\t\tif !f.startedInformers[informerType] {\n\t\t\tf.wg.Add(1)\n\t\t\t// We need a new variable in each loop iteration,\n\t\t\t// otherwise the goroutine would use the loop variable\n\t\t\t// and that keeps changing.\n\t\t\tinformer := informer\n\t\t\tgo func() {\n\t\t\t\tdefer f.wg.Done()\n\t\t\t\tinformer.Run(stopCh)\n\t\t\t}()\n\t\t\tf.startedInformers[informerType] = true\n\t\t}\n\t}\n}\n\nfunc (f *sharedInformerFactory) Shutdown() {\n\tf.lock.Lock()\n\tf.shuttingDown = true\n\tf.lock.Unlock()\n\n\t// Will return immediately if there is nothing to wait for.\n\tf.wg.Wait()\n}\n\nfunc (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool {\n\tinformers := func() map[reflect.Type]cache.SharedIndexInformer {\n\t\tf.lock.Lock()\n\t\tdefer f.lock.Unlock()\n\n\t\tinformers := map[reflect.Type]cache.SharedIndexInformer{}\n\t\tfor informerType, informer := range f.informers {\n\t\t\tif f.startedInformers[informerType] {\n\t\t\t\tinformers[informerType] = informer\n\t\t\t}\n\t\t}\n\t\treturn informers\n\t}()\n\n\tres := map[reflect.Type]bool{}\n\tfor informType, informer := range informers {\n\t\tres[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)\n\t}\n\treturn res\n}\n\n// InformerFor returns the SharedIndexInformer for obj using an internal\n// client.\nfunc (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\n\tinformerType := reflect.TypeOf(obj)\n\tinformer, exists := f.informers[informerType]\n\tif exists {\n\t\treturn informer\n\t}\n\n\tresyncPeriod, exists := f.customResync[informerType]\n\tif !exists {\n\t\tresyncPeriod = f.defaultResync\n\t}\n\n\tinformer = newFunc(f.client, resyncPeriod)\n\tinformer.SetTransform(f.transform)\n\tf.informers[informerType] = informer\n\n\treturn informer\n}\n\n// SharedInformerFactory provides shared informers for resources in all known\n// API group versions.\n//\n// It is typically used like this:\n//\n//\tctx, cancel := context.Background()\n//\tdefer cancel()\n//\tfactory := NewSharedInformerFactory(client, resyncPeriod)\n//\tdefer factory.WaitForStop()    // Returns immediately if nothing was started.\n//\tgenericInformer := factory.ForResource(resource)\n//\ttypedInformer := factory.SomeAPIGroup().V1().SomeType()\n//\tfactory.Start(ctx.Done())          // Start processing these informers.\n//\tsynced := factory.WaitForCacheSync(ctx.Done())\n//\tfor v, ok := range synced {\n//\t    if !ok {\n//\t        fmt.Fprintf(os.Stderr, \"caches failed to sync: %v\", v)\n//\t        return\n//\t    }\n//\t}\n//\n//\t// Creating informers can also be created after Start, but then\n//\t// Start must be called again:\n//\tanotherGenericInformer := factory.ForResource(resource)\n//\tfactory.Start(ctx.Done())\ntype SharedInformerFactory interface {\n\tinternalinterfaces.SharedInformerFactory\n\n\t// Start initializes all requested informers. They are handled in goroutines\n\t// which run until the stop channel gets closed.\n\t// Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync.\n\tStart(stopCh <-chan struct{})\n\n\t// Shutdown marks a factory as shutting down. At that point no new\n\t// informers can be started anymore and Start will return without\n\t// doing anything.\n\t//\n\t// In addition, Shutdown blocks until all goroutines have terminated. For that\n\t// to happen, the close channel(s) that they were started with must be closed,\n\t// either before Shutdown gets called or while it is waiting.\n\t//\n\t// Shutdown may be called multiple times, even concurrently. All such calls will\n\t// block until all goroutines have terminated.\n\tShutdown()\n\n\t// WaitForCacheSync blocks until all started informers' caches were synced\n\t// or the stop channel gets closed.\n\tWaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool\n\n\t// ForResource gives generic access to a shared informer of the matching type.\n\tForResource(resource schema.GroupVersionResource) (GenericInformer, error)\n\n\t// InformerFor returns the SharedIndexInformer for obj using an internal\n\t// client.\n\tInformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer\n\n\tJetstream() jetstream.Interface\n}\n\nfunc (f *sharedInformerFactory) Jetstream() jetstream.Interface {\n\treturn jetstream.New(f, f.namespace, f.tweakListOptions)\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/informers/externalversions/generic.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by informer-gen. DO NOT EDIT.\n\npackage externalversions\n\nimport (\n\tfmt \"fmt\"\n\n\tv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// GenericInformer is type of SharedIndexInformer which will locate and delegate to other\n// sharedInformers based on type\ntype GenericInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() cache.GenericLister\n}\n\ntype genericInformer struct {\n\tinformer cache.SharedIndexInformer\n\tresource schema.GroupResource\n}\n\n// Informer returns the SharedIndexInformer.\nfunc (f *genericInformer) Informer() cache.SharedIndexInformer {\n\treturn f.informer\n}\n\n// Lister returns the GenericLister.\nfunc (f *genericInformer) Lister() cache.GenericLister {\n\treturn cache.NewGenericLister(f.Informer().GetIndexer(), f.resource)\n}\n\n// ForResource gives generic access to a shared informer of the matching type\n// TODO extend this to unknown resources with a client pool\nfunc (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {\n\tswitch resource {\n\t// Group=jetstream.nats.io, Version=v1beta2\n\tcase v1beta2.SchemeGroupVersion.WithResource(\"accounts\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().Accounts().Informer()}, nil\n\tcase v1beta2.SchemeGroupVersion.WithResource(\"consumers\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().Consumers().Informer()}, nil\n\tcase v1beta2.SchemeGroupVersion.WithResource(\"keyvalues\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().KeyValues().Informer()}, nil\n\tcase v1beta2.SchemeGroupVersion.WithResource(\"objectstores\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().ObjectStores().Informer()}, nil\n\tcase v1beta2.SchemeGroupVersion.WithResource(\"streams\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Jetstream().V1beta2().Streams().Informer()}, nil\n\n\t}\n\n\treturn nil, fmt.Errorf(\"no informer found for %v\", resource)\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/informers/externalversions/internalinterfaces/factory_interfaces.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by informer-gen. DO NOT EDIT.\n\npackage internalinterfaces\n\nimport (\n\ttime \"time\"\n\n\tversioned \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer.\ntype NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer\n\n// SharedInformerFactory a small interface to allow for adding an informer without an import cycle\ntype SharedInformerFactory interface {\n\tStart(stopCh <-chan struct{})\n\tInformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer\n}\n\n// TweakListOptionsFunc is a function that transforms a v1.ListOptions.\ntype TweakListOptionsFunc func(*v1.ListOptions)\n"
  },
  {
    "path": "pkg/jetstream/generated/informers/externalversions/jetstream/interface.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by informer-gen. DO NOT EDIT.\n\npackage jetstream\n\nimport (\n\tinternalinterfaces \"github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces\"\n\tv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2\"\n)\n\n// Interface provides access to each of this group's versions.\ntype Interface interface {\n\t// V1beta2 provides access to shared informers for resources in V1beta2.\n\tV1beta2() v1beta2.Interface\n}\n\ntype group struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// V1beta2 returns a new v1beta2.Interface.\nfunc (g *group) V1beta2() v1beta2.Interface {\n\treturn v1beta2.New(g.factory, g.namespace, g.tweakListOptions)\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/account.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapisjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tversioned \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned\"\n\tinternalinterfaces \"github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces\"\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// AccountInformer provides access to a shared informer and lister for\n// Accounts.\ntype AccountInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() jetstreamv1beta2.AccountLister\n}\n\ntype accountInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewAccountInformer constructs a new informer for Account type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewAccountInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredAccountInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredAccountInformer constructs a new informer for Account type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredAccountInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().Accounts(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().Accounts(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().Accounts(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().Accounts(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t},\n\t\t&apisjetstreamv1beta2.Account{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *accountInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredAccountInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *accountInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apisjetstreamv1beta2.Account{}, f.defaultInformer)\n}\n\nfunc (f *accountInformer) Lister() jetstreamv1beta2.AccountLister {\n\treturn jetstreamv1beta2.NewAccountLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/consumer.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapisjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tversioned \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned\"\n\tinternalinterfaces \"github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces\"\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ConsumerInformer provides access to a shared informer and lister for\n// Consumers.\ntype ConsumerInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() jetstreamv1beta2.ConsumerLister\n}\n\ntype consumerInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewConsumerInformer constructs a new informer for Consumer type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewConsumerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredConsumerInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredConsumerInformer constructs a new informer for Consumer type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredConsumerInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().Consumers(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().Consumers(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().Consumers(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().Consumers(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t},\n\t\t&apisjetstreamv1beta2.Consumer{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *consumerInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredConsumerInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *consumerInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apisjetstreamv1beta2.Consumer{}, f.defaultInformer)\n}\n\nfunc (f *consumerInformer) Lister() jetstreamv1beta2.ConsumerLister {\n\treturn jetstreamv1beta2.NewConsumerLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tinternalinterfaces \"github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// Accounts returns a AccountInformer.\n\tAccounts() AccountInformer\n\t// Consumers returns a ConsumerInformer.\n\tConsumers() ConsumerInformer\n\t// KeyValues returns a KeyValueInformer.\n\tKeyValues() KeyValueInformer\n\t// ObjectStores returns a ObjectStoreInformer.\n\tObjectStores() ObjectStoreInformer\n\t// Streams returns a StreamInformer.\n\tStreams() StreamInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// Accounts returns a AccountInformer.\nfunc (v *version) Accounts() AccountInformer {\n\treturn &accountInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n\n// Consumers returns a ConsumerInformer.\nfunc (v *version) Consumers() ConsumerInformer {\n\treturn &consumerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n\n// KeyValues returns a KeyValueInformer.\nfunc (v *version) KeyValues() KeyValueInformer {\n\treturn &keyValueInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n\n// ObjectStores returns a ObjectStoreInformer.\nfunc (v *version) ObjectStores() ObjectStoreInformer {\n\treturn &objectStoreInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n\n// Streams returns a StreamInformer.\nfunc (v *version) Streams() StreamInformer {\n\treturn &streamInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/keyvalue.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapisjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tversioned \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned\"\n\tinternalinterfaces \"github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces\"\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// KeyValueInformer provides access to a shared informer and lister for\n// KeyValues.\ntype KeyValueInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() jetstreamv1beta2.KeyValueLister\n}\n\ntype keyValueInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewKeyValueInformer constructs a new informer for KeyValue type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewKeyValueInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredKeyValueInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredKeyValueInformer constructs a new informer for KeyValue type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredKeyValueInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().KeyValues(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().KeyValues(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().KeyValues(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().KeyValues(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t},\n\t\t&apisjetstreamv1beta2.KeyValue{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *keyValueInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredKeyValueInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *keyValueInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apisjetstreamv1beta2.KeyValue{}, f.defaultInformer)\n}\n\nfunc (f *keyValueInformer) Lister() jetstreamv1beta2.KeyValueLister {\n\treturn jetstreamv1beta2.NewKeyValueLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/objectstore.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapisjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tversioned \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned\"\n\tinternalinterfaces \"github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces\"\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ObjectStoreInformer provides access to a shared informer and lister for\n// ObjectStores.\ntype ObjectStoreInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() jetstreamv1beta2.ObjectStoreLister\n}\n\ntype objectStoreInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewObjectStoreInformer constructs a new informer for ObjectStore type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewObjectStoreInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredObjectStoreInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredObjectStoreInformer constructs a new informer for ObjectStore type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredObjectStoreInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().ObjectStores(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().ObjectStores(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().ObjectStores(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().ObjectStores(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t},\n\t\t&apisjetstreamv1beta2.ObjectStore{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *objectStoreInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredObjectStoreInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *objectStoreInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apisjetstreamv1beta2.ObjectStore{}, f.defaultInformer)\n}\n\nfunc (f *objectStoreInformer) Lister() jetstreamv1beta2.ObjectStoreLister {\n\treturn jetstreamv1beta2.NewObjectStoreLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/stream.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tcontext \"context\"\n\ttime \"time\"\n\n\tapisjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tversioned \"github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned\"\n\tinternalinterfaces \"github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions/internalinterfaces\"\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// StreamInformer provides access to a shared informer and lister for\n// Streams.\ntype StreamInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() jetstreamv1beta2.StreamLister\n}\n\ntype streamInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewStreamInformer constructs a new informer for Stream type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewStreamInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredStreamInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredStreamInformer constructs a new informer for Stream type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredStreamInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().Streams(namespace).List(context.Background(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().Streams(namespace).Watch(context.Background(), options)\n\t\t\t},\n\t\t\tListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().Streams(namespace).List(ctx, options)\n\t\t\t},\n\t\t\tWatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.JetstreamV1beta2().Streams(namespace).Watch(ctx, options)\n\t\t\t},\n\t\t},\n\t\t&apisjetstreamv1beta2.Stream{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *streamInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredStreamInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *streamInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&apisjetstreamv1beta2.Stream{}, f.defaultInformer)\n}\n\nfunc (f *streamInformer) Lister() jetstreamv1beta2.StreamLister {\n\treturn jetstreamv1beta2.NewStreamLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/listers/jetstream/v1beta2/account.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// AccountLister helps list Accounts.\n// All objects returned here must be treated as read-only.\ntype AccountLister interface {\n\t// List lists all Accounts in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*jetstreamv1beta2.Account, err error)\n\t// Accounts returns an object that can list and get Accounts.\n\tAccounts(namespace string) AccountNamespaceLister\n\tAccountListerExpansion\n}\n\n// accountLister implements the AccountLister interface.\ntype accountLister struct {\n\tlisters.ResourceIndexer[*jetstreamv1beta2.Account]\n}\n\n// NewAccountLister returns a new AccountLister.\nfunc NewAccountLister(indexer cache.Indexer) AccountLister {\n\treturn &accountLister{listers.New[*jetstreamv1beta2.Account](indexer, jetstreamv1beta2.Resource(\"account\"))}\n}\n\n// Accounts returns an object that can list and get Accounts.\nfunc (s *accountLister) Accounts(namespace string) AccountNamespaceLister {\n\treturn accountNamespaceLister{listers.NewNamespaced[*jetstreamv1beta2.Account](s.ResourceIndexer, namespace)}\n}\n\n// AccountNamespaceLister helps list and get Accounts.\n// All objects returned here must be treated as read-only.\ntype AccountNamespaceLister interface {\n\t// List lists all Accounts in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*jetstreamv1beta2.Account, err error)\n\t// Get retrieves the Account from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*jetstreamv1beta2.Account, error)\n\tAccountNamespaceListerExpansion\n}\n\n// accountNamespaceLister implements the AccountNamespaceLister\n// interface.\ntype accountNamespaceLister struct {\n\tlisters.ResourceIndexer[*jetstreamv1beta2.Account]\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/listers/jetstream/v1beta2/consumer.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ConsumerLister helps list Consumers.\n// All objects returned here must be treated as read-only.\ntype ConsumerLister interface {\n\t// List lists all Consumers in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*jetstreamv1beta2.Consumer, err error)\n\t// Consumers returns an object that can list and get Consumers.\n\tConsumers(namespace string) ConsumerNamespaceLister\n\tConsumerListerExpansion\n}\n\n// consumerLister implements the ConsumerLister interface.\ntype consumerLister struct {\n\tlisters.ResourceIndexer[*jetstreamv1beta2.Consumer]\n}\n\n// NewConsumerLister returns a new ConsumerLister.\nfunc NewConsumerLister(indexer cache.Indexer) ConsumerLister {\n\treturn &consumerLister{listers.New[*jetstreamv1beta2.Consumer](indexer, jetstreamv1beta2.Resource(\"consumer\"))}\n}\n\n// Consumers returns an object that can list and get Consumers.\nfunc (s *consumerLister) Consumers(namespace string) ConsumerNamespaceLister {\n\treturn consumerNamespaceLister{listers.NewNamespaced[*jetstreamv1beta2.Consumer](s.ResourceIndexer, namespace)}\n}\n\n// ConsumerNamespaceLister helps list and get Consumers.\n// All objects returned here must be treated as read-only.\ntype ConsumerNamespaceLister interface {\n\t// List lists all Consumers in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*jetstreamv1beta2.Consumer, err error)\n\t// Get retrieves the Consumer from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*jetstreamv1beta2.Consumer, error)\n\tConsumerNamespaceListerExpansion\n}\n\n// consumerNamespaceLister implements the ConsumerNamespaceLister\n// interface.\ntype consumerNamespaceLister struct {\n\tlisters.ResourceIndexer[*jetstreamv1beta2.Consumer]\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta2\n\n// AccountListerExpansion allows custom methods to be added to\n// AccountLister.\ntype AccountListerExpansion interface{}\n\n// AccountNamespaceListerExpansion allows custom methods to be added to\n// AccountNamespaceLister.\ntype AccountNamespaceListerExpansion interface{}\n\n// ConsumerListerExpansion allows custom methods to be added to\n// ConsumerLister.\ntype ConsumerListerExpansion interface{}\n\n// ConsumerNamespaceListerExpansion allows custom methods to be added to\n// ConsumerNamespaceLister.\ntype ConsumerNamespaceListerExpansion interface{}\n\n// KeyValueListerExpansion allows custom methods to be added to\n// KeyValueLister.\ntype KeyValueListerExpansion interface{}\n\n// KeyValueNamespaceListerExpansion allows custom methods to be added to\n// KeyValueNamespaceLister.\ntype KeyValueNamespaceListerExpansion interface{}\n\n// ObjectStoreListerExpansion allows custom methods to be added to\n// ObjectStoreLister.\ntype ObjectStoreListerExpansion interface{}\n\n// ObjectStoreNamespaceListerExpansion allows custom methods to be added to\n// ObjectStoreNamespaceLister.\ntype ObjectStoreNamespaceListerExpansion interface{}\n\n// StreamListerExpansion allows custom methods to be added to\n// StreamLister.\ntype StreamListerExpansion interface{}\n\n// StreamNamespaceListerExpansion allows custom methods to be added to\n// StreamNamespaceLister.\ntype StreamNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "pkg/jetstream/generated/listers/jetstream/v1beta2/keyvalue.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// KeyValueLister helps list KeyValues.\n// All objects returned here must be treated as read-only.\ntype KeyValueLister interface {\n\t// List lists all KeyValues in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*jetstreamv1beta2.KeyValue, err error)\n\t// KeyValues returns an object that can list and get KeyValues.\n\tKeyValues(namespace string) KeyValueNamespaceLister\n\tKeyValueListerExpansion\n}\n\n// keyValueLister implements the KeyValueLister interface.\ntype keyValueLister struct {\n\tlisters.ResourceIndexer[*jetstreamv1beta2.KeyValue]\n}\n\n// NewKeyValueLister returns a new KeyValueLister.\nfunc NewKeyValueLister(indexer cache.Indexer) KeyValueLister {\n\treturn &keyValueLister{listers.New[*jetstreamv1beta2.KeyValue](indexer, jetstreamv1beta2.Resource(\"keyvalue\"))}\n}\n\n// KeyValues returns an object that can list and get KeyValues.\nfunc (s *keyValueLister) KeyValues(namespace string) KeyValueNamespaceLister {\n\treturn keyValueNamespaceLister{listers.NewNamespaced[*jetstreamv1beta2.KeyValue](s.ResourceIndexer, namespace)}\n}\n\n// KeyValueNamespaceLister helps list and get KeyValues.\n// All objects returned here must be treated as read-only.\ntype KeyValueNamespaceLister interface {\n\t// List lists all KeyValues in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*jetstreamv1beta2.KeyValue, err error)\n\t// Get retrieves the KeyValue from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*jetstreamv1beta2.KeyValue, error)\n\tKeyValueNamespaceListerExpansion\n}\n\n// keyValueNamespaceLister implements the KeyValueNamespaceLister\n// interface.\ntype keyValueNamespaceLister struct {\n\tlisters.ResourceIndexer[*jetstreamv1beta2.KeyValue]\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/listers/jetstream/v1beta2/objectstore.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// ObjectStoreLister helps list ObjectStores.\n// All objects returned here must be treated as read-only.\ntype ObjectStoreLister interface {\n\t// List lists all ObjectStores in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*jetstreamv1beta2.ObjectStore, err error)\n\t// ObjectStores returns an object that can list and get ObjectStores.\n\tObjectStores(namespace string) ObjectStoreNamespaceLister\n\tObjectStoreListerExpansion\n}\n\n// objectStoreLister implements the ObjectStoreLister interface.\ntype objectStoreLister struct {\n\tlisters.ResourceIndexer[*jetstreamv1beta2.ObjectStore]\n}\n\n// NewObjectStoreLister returns a new ObjectStoreLister.\nfunc NewObjectStoreLister(indexer cache.Indexer) ObjectStoreLister {\n\treturn &objectStoreLister{listers.New[*jetstreamv1beta2.ObjectStore](indexer, jetstreamv1beta2.Resource(\"objectstore\"))}\n}\n\n// ObjectStores returns an object that can list and get ObjectStores.\nfunc (s *objectStoreLister) ObjectStores(namespace string) ObjectStoreNamespaceLister {\n\treturn objectStoreNamespaceLister{listers.NewNamespaced[*jetstreamv1beta2.ObjectStore](s.ResourceIndexer, namespace)}\n}\n\n// ObjectStoreNamespaceLister helps list and get ObjectStores.\n// All objects returned here must be treated as read-only.\ntype ObjectStoreNamespaceLister interface {\n\t// List lists all ObjectStores in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*jetstreamv1beta2.ObjectStore, err error)\n\t// Get retrieves the ObjectStore from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*jetstreamv1beta2.ObjectStore, error)\n\tObjectStoreNamespaceListerExpansion\n}\n\n// objectStoreNamespaceLister implements the ObjectStoreNamespaceLister\n// interface.\ntype objectStoreNamespaceLister struct {\n\tlisters.ResourceIndexer[*jetstreamv1beta2.ObjectStore]\n}\n"
  },
  {
    "path": "pkg/jetstream/generated/listers/jetstream/v1beta2/stream.go",
    "content": "// Copyright 2025 The NATS 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//\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// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1beta2\n\nimport (\n\tjetstreamv1beta2 \"github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\tlisters \"k8s.io/client-go/listers\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// StreamLister helps list Streams.\n// All objects returned here must be treated as read-only.\ntype StreamLister interface {\n\t// List lists all Streams in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*jetstreamv1beta2.Stream, err error)\n\t// Streams returns an object that can list and get Streams.\n\tStreams(namespace string) StreamNamespaceLister\n\tStreamListerExpansion\n}\n\n// streamLister implements the StreamLister interface.\ntype streamLister struct {\n\tlisters.ResourceIndexer[*jetstreamv1beta2.Stream]\n}\n\n// NewStreamLister returns a new StreamLister.\nfunc NewStreamLister(indexer cache.Indexer) StreamLister {\n\treturn &streamLister{listers.New[*jetstreamv1beta2.Stream](indexer, jetstreamv1beta2.Resource(\"stream\"))}\n}\n\n// Streams returns an object that can list and get Streams.\nfunc (s *streamLister) Streams(namespace string) StreamNamespaceLister {\n\treturn streamNamespaceLister{listers.NewNamespaced[*jetstreamv1beta2.Stream](s.ResourceIndexer, namespace)}\n}\n\n// StreamNamespaceLister helps list and get Streams.\n// All objects returned here must be treated as read-only.\ntype StreamNamespaceLister interface {\n\t// List lists all Streams in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*jetstreamv1beta2.Stream, err error)\n\t// Get retrieves the Stream from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*jetstreamv1beta2.Stream, error)\n\tStreamNamespaceListerExpansion\n}\n\n// streamNamespaceLister implements the StreamNamespaceLister\n// interface.\ntype streamNamespaceLister struct {\n\tlisters.ResourceIndexer[*jetstreamv1beta2.Stream]\n}\n"
  },
  {
    "path": "pkg/k8scodegen/file-header.txt",
    "content": "// Copyright 2025 The NATS 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//\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"
  },
  {
    "path": "pkg/k8scodegen/k8scodegen.go",
    "content": "package k8scodegen\n\nimport _ \"k8s.io/code-generator\"\n"
  },
  {
    "path": "pkg/natsreloader/natsreloader.go",
    "content": "// Copyright 2020-2023 The NATS 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//\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\npackage natsreloader\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/fsnotify/fsnotify\"\n)\n\nconst errorFmt = \"Error: %s\\n\"\n\nfunc isInotifyExhausted(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\terrStr := strings.ToLower(err.Error())\n\treturn strings.Contains(errStr, \"no space left on device\") ||\n\t\tstrings.Contains(errStr, \"too many open files\") ||\n\t\tstrings.Contains(errStr, \"inotify\") ||\n\t\tstrings.Contains(errStr, \"watch limit\")\n}\n\nfunc createInotifyExhaustedError(originalErr error, watchedFileCount int) error {\n\treturn fmt.Errorf(`inotify file watching system is exhausted (original error: %v)\n\nThis typically occurs on high-density Kubernetes nodes where many pods are using file watchers.\n\nDIAGNOSIS:\n  - Trying to watch %d files\n  - System may have exhausted inotify watches or instances\n  - Check current limits: cat /proc/sys/fs/inotify/max_user_watches\n  - Check current usage: find /proc/*/fd -lname anon_inode:inotify 2>/dev/null | wc -l\n\nSOLUTIONS:\n  1. Increase inotify limits (cluster admin):\n     echo 'fs.inotify.max_user_watches=1048576' >> /etc/sysctl.conf\n     sysctl -p\n\n  2. Use polling fallback mode:\n     Add --force-poll flag to use polling instead of inotify\n\n  3. Reduce file watching:\n     Minimize the number of configuration files being watched\n\nFor more information, see: https://github.com/nats-io/nack/issues/264`, originalErr, watchedFileCount)\n}\n\n// Config represents the configuration of the reloader.\ntype Config struct {\n\tPidFile           string\n\tWatchedFiles      []string\n\tMaxRetries        int\n\tRetryWaitSecs     int\n\tSignal            os.Signal\n\tForcePoll         bool\n\tPollInterval      time.Duration\n\tMaxWatcherRetries int\n}\n\n// Reloader monitors the state from a single server config file\n// and sends signal on updates.\ntype Reloader struct {\n\t*Config\n\n\t// proc represents the NATS Server process which will\n\t// be signaled.\n\tproc *os.Process\n\n\t// pid is the last known PID from the NATS Server.\n\tpid int\n\n\t// quit shutsdown the reloader.\n\tquit func()\n}\n\nfunc (r *Reloader) waitForProcess() error {\n\tvar proc *os.Process\n\tvar pid int\n\tattempts := 0\n\n\tstartTime := time.Now()\n\tfor {\n\t\tpidfile, err := os.ReadFile(r.PidFile)\n\t\tif err != nil {\n\t\t\tgoto WaitAndRetry\n\t\t}\n\n\t\tpid, err = strconv.Atoi(string(pidfile))\n\t\tif err != nil {\n\t\t\tgoto WaitAndRetry\n\t\t}\n\n\t\t// This always succeeds regardless of the process existing or not.\n\t\tproc, err = os.FindProcess(pid)\n\t\tif err != nil {\n\t\t\tgoto WaitAndRetry\n\t\t}\n\n\t\t// Check if the process is still alive.\n\t\terr = proc.Signal(syscall.Signal(0))\n\t\tif err != nil {\n\t\t\tgoto WaitAndRetry\n\t\t}\n\t\tbreak\n\n\tWaitAndRetry:\n\t\tlog.Printf(\"Error while monitoring pid %v: %v\", pid, err)\n\t\tattempts++\n\t\tif attempts > r.MaxRetries {\n\t\t\treturn fmt.Errorf(\"too many errors attempting to find server process\")\n\t\t}\n\t\ttime.Sleep(time.Duration(r.RetryWaitSecs) * time.Second)\n\t}\n\n\tif attempts > 0 {\n\t\tlog.Printf(\"Found pid from pidfile %q after %v failed attempts (took %.3fs)\",\n\t\t\tr.PidFile, attempts, time.Since(startTime).Seconds())\n\t}\n\tr.proc = proc\n\treturn nil\n}\n\nfunc removeDuplicateStrings(s []string) []string {\n\tif len(s) < 1 {\n\t\treturn s\n\t}\n\n\tsort.Strings(s)\n\tprev := 1\n\tfor curr := 1; curr < len(s); curr++ {\n\t\tif s[curr-1] != s[curr] {\n\t\t\ts[prev] = s[curr]\n\t\t\tprev++\n\t\t}\n\t}\n\n\treturn s[:prev]\n}\n\nfunc getFileDigest(filePath string) ([]byte, error) {\n\th := sha256.New()\n\tf, err := os.Open(filePath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer f.Close()\n\tif _, err := io.Copy(h, f); err != nil {\n\t\treturn nil, err\n\t}\n\treturn h.Sum(nil), nil\n}\n\nfunc handleEvent(event fsnotify.Event, lastConfigAppliedCache map[string][]byte, updatedFiles, deletedFiles []string) ([]string, []string) {\n\tif event.Has(fsnotify.Remove) {\n\t\t// We don't get a Remove event for the directory itself, so\n\t\t// we need to detect that separately.\n\t\treturn updatedFiles, append(deletedFiles, event.Name)\n\t}\n\t_, err := os.Stat(event.Name)\n\tif err != nil {\n\t\t// Beware that this means that we won't reconfigure if a file\n\t\t// is permanently removed.  We want to support transient\n\t\t// disappearance, waiting for the new content, and have not set\n\t\t// up any sort of longer-term timers to detect permanent\n\t\t// deletion.\n\t\t// If you really need this, then switch a file to be empty\n\t\t// before removing if afterwards.\n\t\treturn updatedFiles, deletedFiles\n\t}\n\n\tif len(updatedFiles) > 0 {\n\t\treturn updatedFiles, deletedFiles\n\t}\n\tdigest, err := getFileDigest(event.Name)\n\tif err != nil {\n\t\tlog.Printf(errorFmt, err)\n\t\treturn updatedFiles, deletedFiles\n\t}\n\n\tlastConfigHash, ok := lastConfigAppliedCache[event.Name]\n\tif ok && bytes.Equal(lastConfigHash, digest) {\n\t\treturn updatedFiles, deletedFiles\n\t}\n\n\tlog.Printf(\"Changed config; file=%q existing=%v total-files=%d\",\n\t\tevent.Name, ok, len(lastConfigAppliedCache))\n\tlastConfigAppliedCache[event.Name] = digest\n\treturn append(updatedFiles, event.Name), deletedFiles\n}\n\n// handleEvents handles all events in the queue. It returns the updated and deleted files and can contain duplicates.\nfunc handleEvents(configWatcher *fsnotify.Watcher, event fsnotify.Event, lastConfigAppliedCache map[string][]byte) ([]string, []string) {\n\tupdatedFiles, deletedFiles := handleEvent(event, lastConfigAppliedCache, make([]string, 0, 16), make([]string, 0, 16))\n\tfor {\n\t\tselect {\n\t\tcase event := <-configWatcher.Events:\n\t\t\tupdatedFiles, deletedFiles = handleEvent(event, lastConfigAppliedCache, updatedFiles, deletedFiles)\n\t\tdefault:\n\t\t\treturn updatedFiles, deletedFiles\n\t\t}\n\t}\n}\n\nfunc handleDeletedFiles(deletedFiles []string, configWatcher *fsnotify.Watcher, lastConfigAppliedCache map[string][]byte) ([]string, []string) {\n\tif len(deletedFiles) > 0 {\n\t\tlog.Printf(\"Tracking files %v\", deletedFiles)\n\t}\n\tnewDeletedFiles := make([]string, 0, len(deletedFiles))\n\tupdated := make([]string, 0, len(deletedFiles))\n\tfor _, f := range deletedFiles {\n\t\tif err := configWatcher.Add(f); err != nil {\n\t\t\tnewDeletedFiles = append(newDeletedFiles, f)\n\t\t} else {\n\t\t\tupdated, _ = handleEvent(fsnotify.Event{Name: f, Op: fsnotify.Create}, lastConfigAppliedCache, updated, nil)\n\t\t}\n\t}\n\treturn removeDuplicateStrings(updated), newDeletedFiles\n}\n\nfunc (r *Reloader) createWatcherWithRetry() (*fsnotify.Watcher, error) {\n\tmaxRetries := r.MaxWatcherRetries\n\tif maxRetries == 0 {\n\t\tmaxRetries = 3\n\t}\n\n\tvar lastErr error\n\tfor attempt := 0; attempt <= maxRetries; attempt++ {\n\t\tif attempt > 0 {\n\t\t\twaitTime := time.Duration(r.RetryWaitSecs) * time.Second\n\t\t\tif waitTime == 0 {\n\t\t\t\twaitTime = 4 * time.Second\n\t\t\t}\n\n\t\t\twaitTime = retryJitter(waitTime)\n\t\t\tlog.Printf(\"Retrying watcher creation in %.2fs (attempt %d/%d)\", waitTime.Seconds(), attempt+1, maxRetries+1)\n\t\t\ttime.Sleep(waitTime)\n\t\t}\n\n\t\twatcher, err := fsnotify.NewWatcher()\n\t\tif err == nil {\n\t\t\tif attempt > 0 {\n\t\t\t\tlog.Printf(\"Successfully created watcher after %d retries\", attempt)\n\t\t\t}\n\t\t\treturn watcher, nil\n\t\t}\n\n\t\tlastErr = err\n\t\tlog.Printf(\"Failed to create watcher (attempt %d/%d): %v\", attempt+1, maxRetries+1, err)\n\n\t\tif isInotifyExhausted(err) {\n\t\t\treturn nil, createInotifyExhaustedError(err, len(r.WatchedFiles))\n\t\t}\n\t}\n\n\t// All retries failed\n\treturn nil, fmt.Errorf(\"failed to create watcher after %d attempts, last error: %v\", maxRetries+1, lastErr)\n}\n\nfunc (r *Reloader) init() (*fsnotify.Watcher, map[string][]byte, error) {\n\terr := r.waitForProcess()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif r.ForcePoll {\n\t\tlog.Printf(\"Using polling mode (forced)\")\n\t\treturn nil, nil, nil\n\t}\n\n\tconfigWatcher, err := r.createWatcherWithRetry()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\twatchedFiles := make([]string, 0)\n\n\tfor _, c := range r.WatchedFiles {\n\t\tif !strings.HasSuffix(c, \".conf\") {\n\t\t\tcontinue\n\t\t}\n\t\tchildFiles, err := getServerFiles(c)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\twatchedFiles = append(watchedFiles, childFiles...)\n\t}\n\n\tr.WatchedFiles = append(r.WatchedFiles, watchedFiles...)\n\n\t// Follow configuration updates in the directory where\n\t// the config file is located and trigger reload when\n\t// it is either recreated or written into.\n\tfor i := range r.WatchedFiles {\n\t\t// Ensure our paths are canonical\n\t\tr.WatchedFiles[i], _ = filepath.Abs(r.WatchedFiles[i])\n\t}\n\tr.WatchedFiles = removeDuplicateStrings(r.WatchedFiles)\n\t// Follow configuration file updates and trigger reload when\n\t// it is either recreated or written into.\n\tfor i := range r.WatchedFiles {\n\t\t// Watch files individually for https://github.com/kubernetes/kubernetes/issues/112677\n\t\tif err := configWatcher.Add(r.WatchedFiles[i]); err != nil {\n\t\t\t_ = configWatcher.Close()\n\n\t\t\t// Check if this is an inotify exhaustion error\n\t\t\tif isInotifyExhausted(err) {\n\t\t\t\treturn nil, nil, createInotifyExhaustedError(err, len(r.WatchedFiles))\n\t\t\t}\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tlog.Printf(\"Watching file: %v\", r.WatchedFiles[i])\n\t}\n\n\t// lastConfigAppliedCache is the last config update\n\t// applied by us.\n\tlastConfigAppliedCache := make(map[string][]byte)\n\n\t// Preload config hashes, so we know their digests\n\t// up front and avoid potentially reloading when unnecessary.\n\tfor _, configFile := range r.WatchedFiles {\n\t\tdigest, err := getFileDigest(configFile)\n\t\tif err != nil {\n\t\t\t_ = configWatcher.Close()\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tlastConfigAppliedCache[configFile] = digest\n\t}\n\tlog.Printf(\"Live, ready to kick pid %v on config changes (files=%d)\",\n\t\tr.proc.Pid, len(lastConfigAppliedCache))\n\n\tif len(lastConfigAppliedCache) == 0 {\n\t\tlog.Printf(\"Error: no watched config files cached; input spec was: %#v\",\n\t\t\tr.WatchedFiles)\n\t}\n\treturn configWatcher, lastConfigAppliedCache, nil\n}\n\nfunc (r *Reloader) reload(updatedFiles []string) error {\n\tattempts := 0\n\tfor {\n\t\terr := r.waitForProcess()\n\t\tif err != nil {\n\t\t\tgoto Retry\n\t\t}\n\n\t\tlog.Printf(\"Sending pid %v '%s' signal to reload changes from: %s\", r.proc.Pid, r.Signal.String(), updatedFiles)\n\t\terr = r.proc.Signal(r.Signal)\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\n\tRetry:\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error during reload: %s\", err)\n\t\t}\n\t\tif attempts > r.MaxRetries {\n\t\t\treturn fmt.Errorf(\"too many errors (%v) attempting to signal server to reload: %w\", attempts, err)\n\t\t}\n\t\tdelay := retryJitter(time.Duration(r.RetryWaitSecs) * time.Second)\n\t\tlog.Printf(\"Wait and retrying in %.3fs ...\", delay.Seconds())\n\t\ttime.Sleep(delay)\n\t\tattempts++\n\t}\n}\n\n// Run starts the main loop.\nfunc (r *Reloader) Run(ctx context.Context) error {\n\tctx, cancel := context.WithCancel(ctx)\n\tr.quit = func() {\n\t\tcancel()\n\t}\n\n\tconfigWatcher, lastConfigAppliedCache, err := r.init()\n\tif err != nil {\n\t\tif isInotifyExhausted(err) {\n\t\t\tlog.Printf(\"inotify unavailable, falling back to polling mode\")\n\t\t\tr.ForcePoll = true\n\t\t\treturn r.runPollingMode(ctx)\n\t\t}\n\t\treturn err\n\t}\n\n\tif r.ForcePoll || configWatcher == nil {\n\t\treturn r.runPollingMode(ctx)\n\t}\n\n\tdefer configWatcher.Close()\n\n\t// We use a ticker to re-add deleted files to the watcher\n\tt := time.NewTicker(time.Second)\n\tt.Stop()\n\tdefer t.Stop()\n\tvar tickerRunning bool\n\tvar deletedFiles []string\n\tvar updatedFiles []string\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tcase <-t.C:\n\t\t\tupdatedFiles, deletedFiles = handleDeletedFiles(deletedFiles, configWatcher, lastConfigAppliedCache)\n\t\t\tif len(deletedFiles) == 0 {\n\t\t\t\tlog.Printf(\"All monitored files detected.\")\n\t\t\t\tt.Stop()\n\t\t\t\ttickerRunning = false\n\t\t\t}\n\t\t\tif len(updatedFiles) > 0 {\n\t\t\t\t// Send signal to reload the config.\n\t\t\t\tlog.Printf(\"Updated files: %v\", updatedFiles)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue\n\t\tcase event := <-configWatcher.Events:\n\t\t\tupdated, deleted := handleEvents(configWatcher, event, lastConfigAppliedCache)\n\t\t\tupdatedFiles = removeDuplicateStrings(updated)\n\t\t\tdeletedFiles = removeDuplicateStrings(append(deletedFiles, deleted...))\n\t\t\tif !tickerRunning {\n\t\t\t\t// Start the ticker to re-add deleted files.\n\t\t\t\tlog.Printf(\"Starting ticker to re-add all tracked files.\")\n\t\t\t\tt.Reset(time.Second)\n\t\t\t\ttickerRunning = true\n\t\t\t}\n\t\t\tif len(updatedFiles) > 0 {\n\t\t\t\t// Send signal to reload the config\n\t\t\t\tlog.Printf(\"Updated files: %v\", updatedFiles)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue\n\t\tcase err := <-configWatcher.Errors:\n\t\t\tlog.Printf(errorFmt, err)\n\t\t\tcontinue\n\t\t}\n\t\t// Configuration was updated, try to do reload for a few times\n\t\t// otherwise give up and wait for next event.\n\t\terr := r.reload(updatedFiles)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tupdatedFiles = nil\n\t}\n}\n\n// Stop shutsdown the process.\nfunc (r *Reloader) Stop() error {\n\tlog.Println(\"Shutting down...\")\n\tr.quit()\n\treturn nil\n}\n\n// NewReloader returns a configured NATS server reloader.\nfunc NewReloader(config *Config) (*Reloader, error) {\n\treturn &Reloader{\n\t\tConfig: config,\n\t}, nil\n}\n\n// retryJitter helps avoid trying things at synchronized times, thus improving\n// resiliency in aggregate.\nfunc retryJitter(base time.Duration) time.Duration {\n\tb := float64(base)\n\t// 10% +/-\n\toffset := rand.Float64()*0.2 - 0.1\n\treturn time.Duration(b + offset)\n}\n\nfunc (r *Reloader) pollForChanges(lastConfigAppliedCache map[string][]byte) ([]string, error) {\n\tvar updatedFiles []string\n\n\tfor _, configFile := range r.WatchedFiles {\n\t\t// Check if file still exists\n\t\tif _, err := os.Stat(configFile); os.IsNotExist(err) {\n\t\t\t// File was deleted, remove from cache and treat as an update\n\t\t\t// to trigger a reload with the remaining configuration files\n\t\t\tif _, exists := lastConfigAppliedCache[configFile]; exists {\n\t\t\t\tlog.Printf(\"Detected deleted config file (polling); file=%q\", configFile)\n\t\t\t\tdelete(lastConfigAppliedCache, configFile)\n\t\t\t\tupdatedFiles = append(updatedFiles, configFile)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tdigest, err := getFileDigest(configFile)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error reading file %s: %v\", configFile, err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Check if file has changed\n\t\tlastDigest, exists := lastConfigAppliedCache[configFile]\n\t\tif !exists || !bytes.Equal(lastDigest, digest) {\n\t\t\tlog.Printf(\"Changed config (polling); file=%q existing=%v\", configFile, exists)\n\t\t\tlastConfigAppliedCache[configFile] = digest\n\t\t\tupdatedFiles = append(updatedFiles, configFile)\n\t\t}\n\t}\n\n\treturn updatedFiles, nil\n}\n\nfunc (r *Reloader) runPollingMode(ctx context.Context) error {\n\tlastConfigAppliedCache := make(map[string][]byte)\n\n\twatchedFiles := make([]string, 0)\n\tfor _, c := range r.WatchedFiles {\n\t\t// Only try to parse config files\n\t\tif !strings.HasSuffix(c, \".conf\") {\n\t\t\tcontinue\n\t\t}\n\t\tchildFiles, err := getServerFiles(c)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\twatchedFiles = append(watchedFiles, childFiles...)\n\t}\n\n\tr.WatchedFiles = append(r.WatchedFiles, watchedFiles...)\n\n\t// Ensure our paths are canonical\n\tfor i := range r.WatchedFiles {\n\t\tr.WatchedFiles[i], _ = filepath.Abs(r.WatchedFiles[i])\n\t}\n\tr.WatchedFiles = removeDuplicateStrings(r.WatchedFiles)\n\n\tfor _, configFile := range r.WatchedFiles {\n\t\tdigest, err := getFileDigest(configFile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlastConfigAppliedCache[configFile] = digest\n\t\tlog.Printf(\"Polling file: %v\", configFile)\n\t}\n\n\tlog.Printf(\"Live, ready to kick pid %v on config changes (files=%d, polling mode)\",\n\t\tr.proc.Pid, len(lastConfigAppliedCache))\n\n\tpollInterval := r.PollInterval\n\tif pollInterval == 0 {\n\t\tpollInterval = 5 * time.Second\n\t}\n\n\tticker := time.NewTicker(pollInterval)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tcase <-ticker.C:\n\t\t\tupdatedFiles, err := r.pollForChanges(lastConfigAppliedCache)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"Error polling for changes: %v\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif len(updatedFiles) > 0 {\n\t\t\t\tlog.Printf(\"Updated files (polling): %v\", updatedFiles)\n\t\t\t\terr := r.reload(updatedFiles)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc getServerFiles(configFile string) ([]string, error) {\n\tfilePaths, err := getIncludePaths(configFile, make(map[string]interface{}))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcertPaths, err := getCertPaths(filePaths)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfilePaths = append(filePaths, certPaths...)\n\tsort.Strings(filePaths)\n\n\treturn filePaths, nil\n}\n\nfunc getIncludePaths(configFile string, checked map[string]interface{}) ([]string, error) {\n\tif _, ok := checked[configFile]; ok {\n\t\treturn []string{}, nil\n\t}\n\n\tconfigFile, err := filepath.Abs(configFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfilePaths := []string{configFile}\n\tchecked[configFile] = nil\n\n\tparentDirectory := filepath.Dir(configFile)\n\tincludeRegex := regexp.MustCompile(`(?m)^\\s*include\\s+(['\"]?[^'\";\\n]*)`)\n\n\tcontent, err := os.ReadFile(configFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tincludeMatches := includeRegex.FindAllStringSubmatch(string(content), -1)\n\tfor _, match := range includeMatches {\n\t\tmatchStr := match[1]\n\t\tif strings.HasPrefix(matchStr, \"$\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tmatchStr = strings.TrimPrefix(matchStr, \"'\")\n\t\tmatchStr = strings.TrimPrefix(matchStr, \"\\\"\")\n\n\t\t// Include filepaths in NATS config are always relative\n\t\tfullyQualifiedPath := filepath.Join(parentDirectory, matchStr)\n\t\tfullyQualifiedPath = filepath.Clean(fullyQualifiedPath)\n\n\t\tif _, err := os.Stat(fullyQualifiedPath); os.IsNotExist(err) {\n\t\t\treturn nil, fmt.Errorf(\"%s does not exist\", fullyQualifiedPath)\n\t\t}\n\n\t\t// Recursive call to make sure we catch any nested includes\n\t\t// Using map[string]interface{} as a set to avoid loops\n\t\tincludePaths, err := getIncludePaths(fullyQualifiedPath, checked)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfilePaths = append(filePaths, includePaths...)\n\t}\n\n\treturn filePaths, nil\n}\n\nfunc getCertPaths(configPaths []string) ([]string, error) {\n\tcertPaths := []string{}\n\tcertRegex := regexp.MustCompile(`(?m)^\\s*(cert_file|key_file|ca_file)\\s*:\\s*(['\"]?[^'\";\\n]*)\"?`)\n\n\tfor _, configPath := range configPaths {\n\t\tcontent, err := os.ReadFile(configPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tcertMatches := certRegex.FindAllStringSubmatch(string(content), -1)\n\t\tfor _, match := range certMatches {\n\t\t\tmatchStr := match[2]\n\t\t\tif strings.HasPrefix(matchStr, \"$\") {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmatchStr = strings.TrimPrefix(matchStr, \"'\")\n\t\t\tmatchStr = strings.TrimPrefix(matchStr, \"\\\"\")\n\n\t\t\tfullyQualifiedPath, err := filepath.Abs(matchStr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif _, err := os.Stat(fullyQualifiedPath); os.IsNotExist(err) {\n\t\t\t\treturn nil, fmt.Errorf(\"%s does not exist\", fullyQualifiedPath)\n\t\t\t}\n\n\t\t\tcertPaths = append(certPaths, fullyQualifiedPath)\n\t\t}\n\t}\n\n\treturn certPaths, nil\n}\n"
  },
  {
    "path": "pkg/natsreloader/natsreloader_test.go",
    "content": "// Copyright 2020-2023 The NATS 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//\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\npackage natsreloader\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n)\n\nconst (\n\ttestConfig_0 = `\njetstream {\n\tstore_dir: data/jetstream\n\tmax_mem: 10G\n\tmax_file: 10G\n}\noperator: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiI3Sk1aNEQ0RE1WU1hGWDRYWExCTVVITjY1MjdaQlhaV0dZUUtBQVk0TVRQQTZOSEdHS1NBIiwiaWF0IjoxNjgyNTAzMzg4LCJpc3MiOiJPQ1hXU0tWNU5UTUxCUjI0NlZaQ0laSzVZRlBVTVNPVjZVWk5JNDRPTFVVUUlCNkE0VU1RWE1USiIsIm5hbWUiOiJuYXRzLXRlc3QtMDEiLCJzdWIiOiJPQ1hXU0tWNU5UTUxCUjI0NlZaQ0laSzVZRlBVTVNPVjZVWk5JNDRPTFVVUUlCNkE0VU1RWE1USiIsIm5hdHMiOnsic2lnbmluZ19rZXlzIjpbIk9DS1oyNkpJUDdCN1BHQk43QTdEVEVHVk9NUlNHNE5XVFZURjdPQ0pOSVdRS0xZT0YzWDJBTlFKIl0sImFjY291bnRfc2VydmVyX3VybCI6Im5hdHM6Ly9sb2NhbGhvc3Q6NDIyMiIsIm9wZXJhdG9yX3NlcnZpY2VfdXJscyI6WyJuYXRzOi8vbG9jYWxob3N0OjQyMjIiXSwic3lzdGVtX2FjY291bnQiOiJBQk5ITEY2NVlEWkxGWUlIUVVVU0pXWlZSUVc0UE8zVFFRT0VTNlA3WTRUQ1BQWVVTNkhIVzJFUyIsInR5cGUiOiJvcGVyYXRvciIsInZlcnNpb24iOjJ9fQ.LjVkEnA3Fg3F20cPZm5FShZQKWPiU4pLdhh2s0cj_zhxA88wXgNfUo_SPs59JE97qvpR7AOWksP5dzxMZJ2iBQ\n# System Account named SYS\nsystem_account: ABNHLF65YDZLFYIHQUUSJWZVRQW4PO3TQQOES6P7Y4TCPPYUS6HHW2ES\n\nresolver {\n\ttype: full\n\tdir: './'\n}\n\njetstream {\n\tstore_dir: data/jetstream\n\tmax_mem: 10G\n\tmax_file: 10G\n}\n\ninclude './testConfig_1.conf'`\n\n\ttestConfig_1 = `include ./testConfig_2.conf`\n\n\ttestConfig_2 = `\ntls: {\n\tcert_file: \"./test.pem\"\n\tkey_file: \"./testkey.pem\"\n}\n`\n\tincludeTest_0 = `\ninclude nats_0.conf\ninclude  nats_1.conf;\t// semicolon terminated\ninclude \"nats_2.conf\"\t// double-quoted\ninclude  \"nats_3.conf\"; // double-quoted and semicolon terminated\ninclude 'nats_4.conf'\t// single-quoted\ninclude  'nats_5.conf'; // single-quoted and semicolon terminated\ninclude $NATS;        \t// ignore variable\ninclude \"$NATS_6.conf\"  // filename starting with $\ninclude includeTest_1.conf\n`\n\tincludeTest_1 = `\ntls: {\n\tcert_file: ./nats_0.pem\n\tkey_file: 'nats_0.key'\n}\ntls: {\n\tcert_file: \"./nats_1.pem\"\n\tkey_file: $test\n}\ntls: {\n\tcert_file: \"$nats_2.pem\";\n\tkey_file: 'nats_1.key';\n}\n`\n)\n\nvar (\n\tconfigContents    = `port = 2222`\n\tnewConfigContents = `port = 2222\nsomeOtherThing = \"bar\"\n`\n)\n\nfunc TestReloader(t *testing.T) {\n\t// Setup a pidfile that points to us\n\tpid := os.Getpid()\n\tpidfile, err := os.CreateTemp(os.TempDir(), \"nats-pid-\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tp := fmt.Sprintf(\"%d\", pid)\n\tif _, err := pidfile.WriteString(p); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(pidfile.Name())\n\n\t// Create tempfile with contents, then update it\n\tnconfig := &Config{\n\t\tPidFile:      pidfile.Name(),\n\t\tWatchedFiles: []string{},\n\t\tSignal:       syscall.SIGHUP,\n\t}\n\n\tvar configFiles []*os.File\n\tfor i := 0; i < 2; i++ {\n\t\tconfigFile, err := os.CreateTemp(os.TempDir(), \"nats-conf-\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer os.Remove(configFile.Name())\n\n\t\tif _, err := configFile.WriteString(configContents); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tconfigFiles = append(configFiles, configFile)\n\t\tnconfig.WatchedFiles = append(nconfig.WatchedFiles, configFile.Name())\n\t}\n\n\tr, err := NewReloader(nconfig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsignals := 0\n\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\tvar sigsMu sync.Mutex\n\n\t// Signal handling.\n\tgo func() {\n\t\tc := make(chan os.Signal, 1)\n\t\tsignal.Notify(c, syscall.SIGHUP)\n\n\t\t// Success when receiving the first signal\n\t\tfor range c {\n\t\t\tsigsMu.Lock()\n\t\t\tsignals++\n\t\t\tsigsMu.Unlock()\n\t\t}\n\t}()\n\n\tgo func() {\n\t\t// This is terrible, but we need this thread to wait until r.Run(ctx) has finished starting up\n\t\t// before we start mucking with the file.\n\t\t// There isn't any other good way to synchronize on this happening.\n\t\ttime.Sleep(200 * time.Millisecond)\n\t\tfor _, configfile := range configFiles {\n\t\t\tfor i := 0; i < 5; i++ {\n\t\t\t\t// Append some more stuff to the config\n\t\t\t\tif _, err := configfile.WriteAt([]byte(newConfigContents), 0); err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\t}\n\t\t}\n\n\t\t// Create some random file in the same directory, shouldn't trigger an\n\t\t// additional server signal.\n\t\tconfigFile, err := os.CreateTemp(os.TempDir(), \"foo\")\n\t\tif err != nil {\n\t\t\tt.Log(err)\n\t\t\treturn\n\t\t}\n\t\tdefer os.Remove(configFile.Name())\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\tcancel()\n\t}()\n\n\terr = r.Run(ctx)\n\tif err != nil && !errors.Is(err, context.Canceled) {\n\t\tt.Fatal(err)\n\t}\n\t// We should have gotten only one signal for each configuration file\n\tsigsMu.Lock()\n\tgot := signals\n\tsigsMu.Unlock()\n\texpected := len(configFiles)\n\tif got != expected {\n\t\tt.Fatalf(\"Wrong number of signals received. Expected: %v, got: %v\", expected, got)\n\t}\n}\n\nfunc TestInclude(t *testing.T) {\n\tdirectory, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdummyFiles := []string{\n\t\t\"nats_0.conf\",\n\t\t\"nats_1.conf\",\n\t\t\"nats_2.conf\",\n\t\t\"nats_3.conf\",\n\t\t\"nats_4.conf\",\n\t\t\"nats_5.conf\",\n\t\t\"$NATS_6.conf\",\n\t\t\"nats_0.pem\",\n\t\t\"nats_1.pem\",\n\t\t\"$nats_2.pem\",\n\t\t\"nats_0.key\",\n\t\t\"nats_1.key\",\n\t}\n\n\tfor _, f := range dummyFiles {\n\t\tp := filepath.Join(directory, f)\n\t\terr = writeFile(\"\", p)\n\t\tdefer os.Remove(p)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\tincludeTestConf_0 := filepath.Join(directory, \"includeTest_0.conf\")\n\terr = writeFile(includeTest_0, includeTestConf_0)\n\tdefer os.Remove(includeTestConf_0)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tincludeTestConf_1 := filepath.Join(directory, \"includeTest_1.conf\")\n\terr = writeFile(includeTest_1, includeTestConf_1)\n\tdefer os.Remove(includeTestConf_1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tincludes, err := getServerFiles(\"includeTest_0.conf\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tincludePaths := make([]string, 0)\n\tfor _, p := range includes {\n\t\tincludePaths = append(includePaths, filepath.Base(p))\n\t}\n\n\tdummyFiles = append(dummyFiles, \"includeTest_0.conf\")\n\tdummyFiles = append(dummyFiles, \"includeTest_1.conf\")\n\n\tsort.Strings(dummyFiles)\n\tsort.Strings(includePaths)\n\n\tfor i, p := range dummyFiles {\n\t\tif p != includePaths[i] {\n\t\t\tt.Fatal(\"Expected include paths do not match\")\n\t\t}\n\t}\n}\n\nfunc TestFileFinder(t *testing.T) {\n\tdirectory, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconfFile := filepath.Join(directory, \"testConfig_0.conf\")\n\terr = writeFile(testConfig_0, confFile)\n\tdefer os.Remove(confFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfFile = filepath.Join(directory, \"testConfig_1.conf\")\n\terr = writeFile(testConfig_1, confFile)\n\tdefer os.Remove(confFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfFile = filepath.Join(directory, \"testConfig_2.conf\")\n\terr = writeFile(testConfig_2, confFile)\n\tdefer os.Remove(confFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfFile = filepath.Join(directory, \"test.pem\")\n\terr = writeFile(\"test\", confFile)\n\tdefer os.Remove(confFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfFile = filepath.Join(directory, \"testkey.pem\")\n\terr = writeFile(\"test\", confFile)\n\tdefer os.Remove(confFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpid := os.Getpid()\n\tpidFile := filepath.Join(directory, \"nats.pid\")\n\terr = writeFile(fmt.Sprintf(\"%d\", pid), pidFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(pidFile)\n\n\tnconfig := &Config{\n\t\tPidFile:      pidFile,\n\t\tWatchedFiles: []string{filepath.Join(directory, \"testConfig_0.conf\")},\n\t\tSignal:       syscall.SIGHUP,\n\t}\n\n\tr, err := NewReloader(nconfig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tgo func() {\n\t\terr = r.Run(ctx)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\ttime.Sleep(time.Second)\n\n\tcancel()\n\n\texpectedWatchedFiles := []string{\n\t\t\"/testConfig_0.conf\",\n\t\t\"/testConfig_1.conf\",\n\t\t\"/testConfig_2.conf\",\n\t\t\"/test.pem\",\n\t\t\"/testkey.pem\",\n\t}\n\n\twatchedFiles := r.WatchedFiles\n\n\tsort.Strings(expectedWatchedFiles)\n\tsort.Strings(watchedFiles)\n\n\tif len(watchedFiles) > len(expectedWatchedFiles) {\n\t\tt.Fatal(\"Unexpected number of watched files\")\n\t}\n\n\tfor i, e := range expectedWatchedFiles {\n\t\tf := strings.TrimPrefix(watchedFiles[i], directory)\n\t\tif f != e {\n\t\t\tt.Fatal(\"Expected watched file list does not match\")\n\t\t}\n\n\t}\n}\n\nfunc writeFile(content, path string) error {\n\tparentDirectory := filepath.Dir(path)\n\tif _, err := os.Stat(parentDirectory); errors.Is(err, fs.ErrNotExist) {\n\t\terr = os.MkdirAll(parentDirectory, 0o755)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfile, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o644)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\n\t_, err = file.WriteString(content)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc TestReloaderInotifyExhaustion(t *testing.T) {\n\tpid := os.Getpid()\n\tpidfile, err := os.CreateTemp(os.TempDir(), \"nats-pid-\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(pidfile.Name())\n\n\tp := fmt.Sprintf(\"%d\", pid)\n\tif _, err := pidfile.WriteString(p); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create a config file\n\tconfigFile, err := os.CreateTemp(os.TempDir(), \"nats-conf-\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(configFile.Name())\n\n\tif _, err := configFile.WriteString(configContents); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create configuration that will fail during init\n\tnconfig := &Config{\n\t\tPidFile:      pidfile.Name(),\n\t\tWatchedFiles: []string{configFile.Name()},\n\t\tSignal:       syscall.SIGHUP,\n\t}\n\n\tr, err := NewReloader(nconfig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create many temporary files to potentially exhaust resources\n\tvar manyFiles []string\n\tfor i := 0; i < 10; i++ {\n\t\ttempFile, err := os.CreateTemp(os.TempDir(), fmt.Sprintf(\"nats-test-%d-\", i))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer os.Remove(tempFile.Name())\n\n\t\tif _, err := tempFile.WriteString(configContents); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tmanyFiles = append(manyFiles, tempFile.Name())\n\t}\n\n\t// Update config to watch many files\n\tnconfig.WatchedFiles = append(nconfig.WatchedFiles, manyFiles...)\n\n\t// Try to run the reloader - this should work with current implementation\n\t// but demonstrates the scenario where it could fail\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\n\terr = r.Run(ctx)\n\tif err != nil && !errors.Is(err, context.Canceled) {\n\t\t// This is where we would expect to see the \"no space left on device\" error\n\t\t// in a real inotify exhaustion scenario\n\t\tt.Logf(\"Expected error in inotify exhaustion scenario: %v\", err)\n\n\t\t// Check if error message is misleading (current problem)\n\t\tif strings.Contains(err.Error(), \"no space left on device\") {\n\t\t\tt.Logf(\"ERROR: Misleading error message detected - this is the bug we're fixing\")\n\t\t\tt.Logf(\"Error message should explain inotify exhaustion, not disk space\")\n\t\t}\n\t}\n}\n\nfunc TestReloaderPollingMode(t *testing.T) {\n\t// Test that polling mode works correctly\n\n\t// Setup a pidfile that points to us\n\tpid := os.Getpid()\n\tpidfile, err := os.CreateTemp(os.TempDir(), \"nats-pid-\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(pidfile.Name())\n\n\tp := fmt.Sprintf(\"%d\", pid)\n\tif _, err := pidfile.WriteString(p); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create a config file\n\tconfigFile, err := os.CreateTemp(os.TempDir(), \"nats-conf-\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(configFile.Name())\n\n\tif _, err := configFile.WriteString(configContents); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create configuration that forces polling mode\n\tnconfig := &Config{\n\t\tPidFile:      pidfile.Name(),\n\t\tWatchedFiles: []string{configFile.Name()},\n\t\tSignal:       syscall.SIGHUP,\n\t\tForcePoll:    true,\n\t\tPollInterval: 100 * time.Millisecond, // Fast polling for testing\n\t}\n\n\tr, err := NewReloader(nconfig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsignals := 0\n\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\tdefer cancel()\n\n\tvar sigsMu sync.Mutex\n\n\t// Signal handling\n\tgo func() {\n\t\tc := make(chan os.Signal, 1)\n\t\tsignal.Notify(c, syscall.SIGHUP)\n\n\t\tfor range c {\n\t\t\tsigsMu.Lock()\n\t\t\tsignals++\n\t\t\tsigsMu.Unlock()\n\t\t}\n\t}()\n\n\tgo func() {\n\t\t// Wait for polling to start\n\t\ttime.Sleep(200 * time.Millisecond)\n\n\t\t// Modify the config file\n\t\tif _, err := configFile.WriteAt([]byte(newConfigContents), 0); err != nil {\n\t\t\tt.Logf(\"Failed to write config: %v\", err)\n\t\t\treturn\n\t\t}\n\n\t\t// Wait for polling to detect the change\n\t\ttime.Sleep(200 * time.Millisecond)\n\t\tcancel()\n\t}()\n\n\terr = r.Run(ctx)\n\tif err != nil && !errors.Is(err, context.Canceled) {\n\t\tt.Fatal(err)\n\t}\n\n\t// We should have gotten at least one signal\n\tsigsMu.Lock()\n\tgot := signals\n\tsigsMu.Unlock()\n\tif got == 0 {\n\t\tt.Fatal(\"Expected at least one signal in polling mode, got 0\")\n\t}\n\n\tt.Logf(\"Successfully received %d signals in polling mode\", got)\n}\n\nfunc TestReloaderPollingModeFileDeletion(t *testing.T) {\n\t// Test that file deletion is handled correctly in polling mode\n\n\t// Setup a pidfile that points to us\n\tpid := os.Getpid()\n\tpidfile, err := os.CreateTemp(os.TempDir(), \"nats-pid-\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(pidfile.Name())\n\n\tp := fmt.Sprintf(\"%d\", pid)\n\tif _, err := pidfile.WriteString(p); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create two config files\n\tconfigFile1, err := os.CreateTemp(os.TempDir(), \"nats-conf-1-\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfigFile1Path := configFile1.Name()\n\tdefer os.Remove(configFile1Path)\n\n\tif _, err := configFile1.WriteString(configContents); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconfigFile2, err := os.CreateTemp(os.TempDir(), \"nats-conf-2-\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfigFile2Path := configFile2.Name()\n\n\tif _, err := configFile2.WriteString(configContents); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfigFile2.Close()\n\n\t// Create configuration that forces polling mode\n\tnconfig := &Config{\n\t\tPidFile:      pidfile.Name(),\n\t\tWatchedFiles: []string{configFile1Path, configFile2Path},\n\t\tSignal:       syscall.SIGHUP,\n\t\tForcePoll:    true,\n\t\tPollInterval: 100 * time.Millisecond, // Fast polling for testing\n\t}\n\n\tr, err := NewReloader(nconfig)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsignals := 0\n\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\tdefer cancel()\n\n\tvar sigsMu sync.Mutex\n\n\t// Signal handling\n\tgo func() {\n\t\tc := make(chan os.Signal, 1)\n\t\tsignal.Notify(c, syscall.SIGHUP)\n\n\t\tfor range c {\n\t\t\tsigsMu.Lock()\n\t\t\tsignals++\n\t\t\tsigsMu.Unlock()\n\t\t\tt.Logf(\"Received signal #%d\", signals)\n\t\t}\n\t}()\n\n\tgo func() {\n\t\t// Wait for polling to start\n\t\ttime.Sleep(200 * time.Millisecond)\n\n\t\t// Delete the second config file\n\t\tt.Logf(\"Deleting config file: %s\", configFile2Path)\n\t\tif err := os.Remove(configFile2Path); err != nil {\n\t\t\tt.Logf(\"Failed to delete config: %v\", err)\n\t\t\treturn\n\t\t}\n\n\t\t// Wait for polling to detect the deletion\n\t\ttime.Sleep(300 * time.Millisecond)\n\n\t\t// Modify the first config file to ensure we can still detect changes\n\t\tt.Logf(\"Modifying remaining config file: %s\", configFile1Path)\n\t\tif err := os.WriteFile(configFile1Path, []byte(newConfigContents), 0o644); err != nil {\n\t\t\tt.Logf(\"Failed to write config: %v\", err)\n\t\t\treturn\n\t\t}\n\n\t\t// Wait for polling to detect the change\n\t\ttime.Sleep(300 * time.Millisecond)\n\t\tcancel()\n\t}()\n\n\terr = r.Run(ctx)\n\tif err != nil && !errors.Is(err, context.Canceled) {\n\t\tt.Fatal(err)\n\t}\n\n\t// We should have gotten at least 2 signals:\n\t// 1. For the file deletion\n\t// 2. For the file modification\n\tsigsMu.Lock()\n\tgot := signals\n\tsigsMu.Unlock()\n\tif got < 2 {\n\t\tt.Fatalf(\"Expected at least 2 signals in polling mode (deletion + modification), got %d\", got)\n\t}\n\n\tt.Logf(\"Successfully received %d signals in polling mode with file deletion\", got)\n}\n"
  },
  {
    "path": "tests/Dockerfile",
    "content": "# This Dockerfile enables straight-forward building of the jetstream-controller\n# This is currently not used in the CI/CD pipeline but can be used for local builds of the controller image\nFROM golang:1.25.4-alpine@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb AS builder\nARG TARGETOS\nARG TARGETARCH\n\nWORKDIR /workspace\n\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n\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/jetstream-controller/ cmd/jetstream-controller/\nCOPY pkg/ pkg/\nCOPY internal/ internal/\nCOPY controllers/ controllers/\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 jetstream-controller cmd/jetstream-controller/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-debian12:nonroot\nWORKDIR /\nCOPY --from=builder /workspace/jetstream-controller .\nUSER 65532:65532\n\nENTRYPOINT [\"/jetstream-controller\"]\n"
  },
  {
    "path": "tests/nack-control-loop.yaml",
    "content": "jetstream:\n  enabled: true\n\n  image:\n    repository: nack\n    tag: test\n\n  # Enable controller-runtime mode\n  additionalArgs:\n    - --control-loop\n\n  nats:\n   url: nats://nats:4222\n\nnamespaced: true\n"
  },
  {
    "path": "tests/nack-legacy.yaml",
    "content": "jetstream:\n  enabled: true\n\n  image:\n    repository: nack\n    tag: test\n\n  nats:\n   url: nats://nats:4222\n\nnamespaced: true\n"
  },
  {
    "path": "tests/nats.yaml",
    "content": "---\nglobal:\n  labels:\n    app: main-jetstream\n\nnatsBox:\n  enabled: false\n\nconfig:\n  cluster:\n    enabled: false\n\n  gateway:\n    enabled: false\n\n  jetstream:\n    enabled: true\n\n    memoryStore:\n      enabled: true\n      maxSize: 256Mi\n\n    fileStore:\n      enabled: true\n      pvc:\n        enabled: true\n        size: 256Mi\n"
  },
  {
    "path": "tests/stream-creation/00-nack.yaml",
    "content": "apiVersion: kuttl.dev/v1beta1\nkind: TestStep\nunitTest: false\ncommands:\n  - command: helm uninstall --namespace $NAMESPACE nats\n    ignoreFailure: true\n  - command: helm uninstall --namespace $NAMESPACE nack\n    ignoreFailure: true\n  - command: helm repo add nats https://nats-io.github.io/k8s/helm/charts --force-update\n  - command: helm upgrade --install --wait --namespace $NAMESPACE nats nats/nats -f ../nats.yaml\n  - command: helm upgrade --install --wait --namespace $NAMESPACE nack nats/nack --skip-crds -f ../nack.yaml\n"
  },
  {
    "path": "tests/stream-creation/01-stream.yaml",
    "content": "apiVersion: kuttl.dev/v1beta1\nkind: TestStep\napply:\n  - rides-stream.yaml\nassert:\n  - asserted-rides-stream.yaml\nunitTest: false\n"
  },
  {
    "path": "tests/stream-creation/02-natscli-stream.yaml",
    "content": "apiVersion: kuttl.dev/v1beta1\nkind: TestStep\napply:\n  - natscli.yaml\nassert:\n  - asserted-natscli.yaml\nunitTest: false\n"
  },
  {
    "path": "tests/stream-creation/asserted-natscli.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    run: natscli\n  name: natscli\nstatus:\n  phase: Succeeded\n"
  },
  {
    "path": "tests/stream-creation/asserted-rides-stream.yaml",
    "content": "apiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: rides\nspec:\n  allowDirect: false\n  allowMsgTtl: false\n  allowRollup: false\n  compression: \"\"\n  creds: \"\"\n  denyDelete: false\n  discard: old\n  discardPerSubject: false\n  firstSequence: 0\n  maxAge: \"\"\n  maxBytes: -1\n  maxConsumers: -1\n  maxMsgSize: -1\n  maxMsgs: -1\n  maxMsgsPerSubject: 0\n  name: rides\n  nkey: \"\"\n  noAck: false\n  preventDelete: false\n  preventUpdate: false\n  replicas: 1\n  retention: limits\n  servers: []\n  storage: memory\n  subjectDeleteMarkerTtl: \"\"\n  subjects:\n    - rides.>\n"
  },
  {
    "path": "tests/stream-creation/natscli.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n  labels:\n    run: natscli\n  name: natscli\nspec:\n  restartPolicy: Never\n  containers:\n  - image: natsio/nats-box\n    name: natscli\n    command:\n      - nats\n    args:\n      - -s\n      - nats://nats:4222\n      - stream\n      - info\n      - rides\n"
  },
  {
    "path": "tests/stream-creation/rides-stream.yaml",
    "content": "apiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n  name: rides\nspec:\n  name: rides\n  subjects:\n    - \"rides.>\"\n  storage: memory\n  replicas: 1\n"
  }
]