Showing preview only (986K chars total). Download the full file or copy to clipboard to get everything.
Repository: nats-io/nack
Branch: main
Commit: f6dad096d5d4
Files: 177
Total size: 926.2 KB
Directory structure:
gitextract_mbhvqodb/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── config.yml
│ │ ├── defect.yml
│ │ └── proposal.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── claude.yml
│ ├── deps-release-detect.yaml
│ ├── deps-release-tag.yaml
│ ├── e2e.yaml
│ ├── release.yaml
│ └── test.yaml
├── .gitignore
├── .goreleaser.yml
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── cicd/
│ ├── Dockerfile
│ ├── Dockerfile_goreleaser
│ ├── assets/
│ │ └── entrypoint.sh
│ └── tag-deps-version.txt
├── cmd/
│ ├── jetstream-controller/
│ │ └── main.go
│ ├── nats-boot-config/
│ │ └── main.go
│ └── nats-server-config-reloader/
│ └── main.go
├── controllers/
│ └── jetstream/
│ ├── conn_pool.go
│ ├── conn_pool_test.go
│ ├── consumer.go
│ ├── consumer_test.go
│ ├── controller.go
│ ├── controller_test.go
│ ├── jsmclient.go
│ ├── jsmclient_test.go
│ ├── stream.go
│ └── stream_test.go
├── dependencies.md
├── deploy/
│ ├── crds.yml
│ ├── examples/
│ │ ├── consumer_pull.yml
│ │ ├── consumer_push.yml
│ │ ├── stream.yml
│ │ ├── stream_mirror.yml
│ │ ├── stream_placement.yml
│ │ ├── stream_servers.yml
│ │ └── stream_sources.yml
│ └── rbac.yml
├── docker-bake.hcl
├── docs/
│ └── api.md
├── examples/
│ └── secure/
│ ├── client-tls.yaml
│ ├── issuer.yaml
│ ├── nack/
│ │ ├── account-foo.yaml
│ │ ├── nats-account-a.yaml
│ │ ├── nats-consumer-bar-a.yaml
│ │ ├── nats-stream-foo-a.yaml
│ │ └── stream-foo.yaml
│ ├── nack-a-client-tls.yaml
│ ├── nack-b-client-tls.yaml
│ ├── nats-helm.yaml
│ └── server-tls.yaml
├── go.mod
├── go.sum
├── internal/
│ └── controller/
│ ├── account_controller.go
│ ├── account_controller_test.go
│ ├── client.go
│ ├── connection_pool.go
│ ├── connection_pool_test.go
│ ├── consumer_controller.go
│ ├── consumer_controller_test.go
│ ├── helpers_test.go
│ ├── jetstream_controller.go
│ ├── jetstream_controller_test.go
│ ├── keyvalue_controller.go
│ ├── keyvalue_controller_test.go
│ ├── objectstore_controller.go
│ ├── objectstore_controller_test.go
│ ├── register.go
│ ├── stream_controller.go
│ ├── stream_controller_test.go
│ ├── suite_test.go
│ └── types.go
├── kuttl-test.yaml
├── pkg/
│ ├── bootconfig/
│ │ └── bootconfig.go
│ ├── jetstream/
│ │ ├── apis/
│ │ │ └── jetstream/
│ │ │ ├── register.go
│ │ │ ├── v1beta1/
│ │ │ │ ├── consumertypes.go
│ │ │ │ ├── doc.go
│ │ │ │ ├── register.go
│ │ │ │ ├── streamtemplatetypes.go
│ │ │ │ ├── streamtypes.go
│ │ │ │ ├── types.go
│ │ │ │ └── zz_generated.deepcopy.go
│ │ │ └── v1beta2/
│ │ │ ├── accounttypes.go
│ │ │ ├── consumertypes.go
│ │ │ ├── doc.go
│ │ │ ├── keyvaluetypes.go
│ │ │ ├── objectstoretypes.go
│ │ │ ├── register.go
│ │ │ ├── streamtypes.go
│ │ │ ├── types.go
│ │ │ └── zz_generated.deepcopy.go
│ │ └── generated/
│ │ ├── applyconfiguration/
│ │ │ ├── internal/
│ │ │ │ └── internal.go
│ │ │ ├── jetstream/
│ │ │ │ └── v1beta2/
│ │ │ │ ├── account.go
│ │ │ │ ├── accountspec.go
│ │ │ │ ├── basestreamconfig.go
│ │ │ │ ├── condition.go
│ │ │ │ ├── connectionopts.go
│ │ │ │ ├── consumer.go
│ │ │ │ ├── consumerlimits.go
│ │ │ │ ├── consumerspec.go
│ │ │ │ ├── credssecret.go
│ │ │ │ ├── keyvalue.go
│ │ │ │ ├── keyvaluespec.go
│ │ │ │ ├── nkeysecret.go
│ │ │ │ ├── objectstore.go
│ │ │ │ ├── objectstorespec.go
│ │ │ │ ├── republish.go
│ │ │ │ ├── secretref.go
│ │ │ │ ├── status.go
│ │ │ │ ├── stream.go
│ │ │ │ ├── streamplacement.go
│ │ │ │ ├── streamsource.go
│ │ │ │ ├── streamspec.go
│ │ │ │ ├── subjecttransform.go
│ │ │ │ ├── tls.go
│ │ │ │ ├── tlssecret.go
│ │ │ │ ├── tokensecret.go
│ │ │ │ └── user.go
│ │ │ └── utils.go
│ │ ├── clientset/
│ │ │ └── versioned/
│ │ │ ├── clientset.go
│ │ │ ├── fake/
│ │ │ │ ├── clientset_generated.go
│ │ │ │ ├── doc.go
│ │ │ │ └── register.go
│ │ │ ├── scheme/
│ │ │ │ ├── doc.go
│ │ │ │ └── register.go
│ │ │ └── typed/
│ │ │ └── jetstream/
│ │ │ └── v1beta2/
│ │ │ ├── account.go
│ │ │ ├── consumer.go
│ │ │ ├── doc.go
│ │ │ ├── fake/
│ │ │ │ ├── doc.go
│ │ │ │ ├── fake_account.go
│ │ │ │ ├── fake_consumer.go
│ │ │ │ ├── fake_jetstream_client.go
│ │ │ │ ├── fake_keyvalue.go
│ │ │ │ ├── fake_objectstore.go
│ │ │ │ └── fake_stream.go
│ │ │ ├── generated_expansion.go
│ │ │ ├── jetstream_client.go
│ │ │ ├── keyvalue.go
│ │ │ ├── objectstore.go
│ │ │ └── stream.go
│ │ ├── informers/
│ │ │ └── externalversions/
│ │ │ ├── factory.go
│ │ │ ├── generic.go
│ │ │ ├── internalinterfaces/
│ │ │ │ └── factory_interfaces.go
│ │ │ └── jetstream/
│ │ │ ├── interface.go
│ │ │ └── v1beta2/
│ │ │ ├── account.go
│ │ │ ├── consumer.go
│ │ │ ├── interface.go
│ │ │ ├── keyvalue.go
│ │ │ ├── objectstore.go
│ │ │ └── stream.go
│ │ └── listers/
│ │ └── jetstream/
│ │ └── v1beta2/
│ │ ├── account.go
│ │ ├── consumer.go
│ │ ├── expansion_generated.go
│ │ ├── keyvalue.go
│ │ ├── objectstore.go
│ │ └── stream.go
│ ├── k8scodegen/
│ │ ├── file-header.txt
│ │ └── k8scodegen.go
│ └── natsreloader/
│ ├── natsreloader.go
│ └── natsreloader_test.go
└── tests/
├── Dockerfile
├── nack-control-loop.yaml
├── nack-legacy.yaml
├── nats.yaml
└── stream-creation/
├── 00-nack.yaml
├── 01-stream.yaml
├── 02-natscli-stream.yaml
├── asserted-natscli.yaml
├── asserted-rides-stream.yaml
├── natscli.yaml
└── rides-stream.yaml
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Discussion
url: https://github.com/nats-io/nack/discussions
about: Ideal for ideas, feedback, or longer form questions.
- name: Chat
url: https://slack.nats.io
about: Ideal for short, one-off questions, general conversation, and meeting other NATS users!
================================================
FILE: .github/ISSUE_TEMPLATE/defect.yml
================================================
---
name: Defect
description: Report a defect, such as a bug or regression.
labels:
- defect
body:
- type: textarea
id: versions
attributes:
label: What version were you using?
description: Include the server version (`nats-server --version`) and any client versions when observing the issue.
validations:
required: true
- type: textarea
id: environment
attributes:
label: What environment was the server running in?
description: This pertains to the operating system, CPU architecture, and/or Docker image that was used.
validations:
required: true
- type: textarea
id: steps
attributes:
label: Is this defect reproducible?
description: Provide best-effort steps to showcase the defect.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Given the capability you are leveraging, describe your expectation?
description: This may be the expected behavior or performance characteristics.
validations:
required: true
- type: textarea
id: actual
attributes:
label: Given the expectation, what is the defect you are observing?
description: This may be an unexpected behavior or regression in performance.
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/proposal.yml
================================================
---
name: Proposal
description: Propose an enhancement or new feature.
labels:
- proposal
body:
- type: textarea
id: usecase
attributes:
label: What motivated this proposal?
description: Describe the use case justifying this request.
validations:
required: true
- type: textarea
id: change
attributes:
label: What is the proposed change?
description: This could be a behavior change, enhanced API, or a branch new feature.
validations:
required: true
- type: textarea
id: benefits
attributes:
label: Who benefits from this change?
description: Describe how this not only benefits you.
validations:
required: false
- type: textarea
id: alternates
attributes:
label: What alternatives have you evaluated?
description: This could be using existing features or relying on an external dependency.
validations:
required: false
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
# version updates: enabled
# security updates: enabled
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
- package-ecosystem: docker
directory: /cicd
schedule:
interval: daily
cooldown:
default-days: 7
# version updates: disabled
# security updates: enabled
# https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates#overriding-the-default-behavior-with-a-configuration-file
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 0
cooldown:
default-days: 7
================================================
FILE: .github/workflows/claude.yml
================================================
name: Claude Code
# GITHUB_TOKEN needs contents:read and actions:read — required by
# claude-code-action for restoring trusted config files from the base branch.
# All other GitHub API access uses the App token.
permissions:
contents: read
actions: read
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
pull_request_target:
types: [opened, reopened]
jobs:
claude:
uses: synadia-io/ai-workflows/.github/workflows/claude.yml@v2
with:
gh_app_id: ${{ vars.CLAUDE_GH_APP_ID }}
checkout_mode: 'base'
review_focus: |
Additionally focus on:
- Kubernetes controller reconciliation correctness (idempotency, status updates, error handling)
- Proper use of controller-runtime patterns (watches, predicates, ownership references)
- Go error handling (wrapped errors, sentinel errors, no swallowed errors)
secrets:
claude_oauth_token: ${{ secrets.CLAUDE_OAUTH_TOKEN }}
gh_app_private_key: ${{ secrets.CLAUDE_GH_APP_PRIVATE_KEY }}
================================================
FILE: .github/workflows/deps-release-detect.yaml
================================================
name: Deps Release
on: 'pull_request'
permissions:
contents: write
jobs:
detect:
name: Detect
runs-on: ubuntu-latest
if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
git checkout -b "$GITHUB_HEAD_REF"
- name: Dependabot metadata
id: dependabot-metadata
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
- name: Install node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 18
- name: Install semver
run: |-
npm install -g semver
- name: Bump
run: |-
set -e
push=0
config='[
{
"directory": "cicd",
"dependencyName": "alpine"
}
]'
deps_file="./cicd/tag-deps-version.txt"
deps="${STEPS_DEPENDABOT_METADATA_OUTPUTS_UPDATED_DEPENDENCIES_JSON}"
for i in $(seq 0 "$(("$(echo "$config" | jq length) - 1"))"); do
directory="$(echo "$config" | jq -r ".[$i].directory")"
dependencyName="$(echo "$config" | jq -r ".[$i].dependencyName")"
match="$(echo "$deps" | jq ".[] | select(.directory == \"/$directory\" and .dependencyName == \"$dependencyName\")")"
if [ -z "$match" ]; then
continue
fi
updateType="$(echo "$match" | jq -r ".updateType")"
prevVersion="$(echo "$match" | jq -r ".prevVersion")"
newVersion="$(echo "$match" | jq -r ".newVersion")"
echo "directory : $directory"
echo "dependencyName : $dependencyName"
echo "updateType : $updateType"
echo "prevVersion : $prevVersion"
echo "newVersion : $newVersion"
tagPrevVersion="$(git ls-remote 2>/dev/null \
| grep -oE 'refs/tags/v[0-9]+\.[0-9]+\.[0-9]+' \
| cut -d'/' -f3 \
| xargs semver \
| tail -n 1)"
tagNewVersion="$(semver -i patch "$tagPrevVersion")"
echo "$tagPrevVersion" > "$deps_file"
echo "$tagNewVersion" >> "$deps_file"
git add "$deps_file"
if git commit -m "bump dependency release to $tagNewVersion"; then
push=1
fi
done
if [ "$push" = "1" ]; then
git push -u origin "$GITHUB_HEAD_REF"
fi
env:
STEPS_DEPENDABOT_METADATA_OUTPUTS_UPDATED_DEPENDENCIES_JSON: ${{ steps.dependabot-metadata.outputs.updated-dependencies-json }}
================================================
FILE: .github/workflows/deps-release-tag.yaml
================================================
name: Deps Release
on:
push:
branches:
- main
permissions:
actions: write
contents: write
jobs:
tag:
name: Tag
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- id: tag
name: Determine tag
run: |
deps_file="./cicd/tag-deps-version.txt"
old_version="$(head -n 1 "$deps_file")"
old_ref_name="v$old_version"
new_version="$(tail -n 1 "$deps_file")"
new_ref_name="v$new_version"
create=true
if [ "$(git ls-remote origin "refs/tags/$new_ref_name" | wc -l)" = "1" ]; then
create=false
fi
echo "old-version=$old_version" | tee -a "$GITHUB_OUTPUT"
echo "old-ref-name=$old_ref_name" | tee -a "$GITHUB_OUTPUT"
echo "new-version=$new_version" | tee -a "$GITHUB_OUTPUT"
echo "new-ref-name=$new_ref_name" | tee -a "$GITHUB_OUTPUT"
echo "create=$create" | tee -a "$GITHUB_OUTPUT"
- if: ${{ fromJSON(steps.tag.outputs.create) }}
name: Tag
run: |
commit="$(git rev-parse HEAD)"
git fetch origin refs/tags/"${STEPS_TAG_OUTPUTS_OLD_REF_NAME}"
git checkout -b deps "${STEPS_TAG_OUTPUTS_OLD_REF_NAME}"
git restore --source="$commit" ./cicd ./.github/workflows/release.yaml
git add ./cicd ./.github/workflows/release.yaml
if git commit -m "bump dependency release to ${STEPS_TAG_OUTPUTS_NEW_VERSION}"; then
git tag "${STEPS_TAG_OUTPUTS_NEW_REF_NAME}"
git push origin "${STEPS_TAG_OUTPUTS_NEW_REF_NAME}"
fi
env:
STEPS_TAG_OUTPUTS_OLD_REF_NAME: ${{ steps.tag.outputs.old-ref-name }}
STEPS_TAG_OUTPUTS_NEW_VERSION: ${{ steps.tag.outputs.new-version }}
STEPS_TAG_OUTPUTS_NEW_REF_NAME: ${{ steps.tag.outputs.new-ref-name }}
- if: ${{ fromJSON(steps.tag.outputs.create) }}
name: Trigger Release
run: gh workflow run release.yaml --ref "${STEPS_TAG_OUTPUTS_NEW_REF_NAME}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
STEPS_TAG_OUTPUTS_NEW_REF_NAME: ${{ steps.tag.outputs.new-ref-name }}
================================================
FILE: .github/workflows/e2e.yaml
================================================
name: e2e
on:
push:
branches:
- main
pull_request:
jobs:
e2e:
name: e2e
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: install kuttl
run: |
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
chmod +x /usr/local/bin/kubectl-kuttl
- name: create kind cluster
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with:
install_only: true
- name: set up helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1
- name: run e2e test
run: make test-e2e
================================================
FILE: .github/workflows/release.yaml
================================================
name: Release
on:
workflow_dispatch:
push:
tags:
- v[0-9]+.[0-9]+.[0-9]+
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout Source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
cache: false
- name: Setup QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Setup Docker Hub
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_CLI_TOKEN }}
- name: Get Image Tags
id: tags
run: |
version=$(sed 's/^v//' <<< ${GITHUB_REF_NAME})
echo tags="latest,${version}" >> $GITHUB_OUTPUT
- name: Build and Push
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
with:
source: .
files: docker-bake.hcl
push: true
set: goreleaser.args.GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
env:
TAGS: "${{ steps.tags.outputs.tags }}"
REGISTRY: "natsio"
- name: Attach Release Files
run: gh release upload ${GITHUB_REF_NAME} deploy/crds.yml deploy/rbac.yml
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/test.yaml
================================================
name: Test
on:
push:
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
jobs:
test:
name: Test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout Source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Go
id: setup-go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
- name: Build
run: make build
- name: Test
run: make test
================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
/jetstream-controller.docker
/jetstream-controller
/nats-server-config-reloader
/nats-server-config-reloader.docker
/nats-boot-config
/nats-boot-config.docker
/tools
/bin
/.idea
/kubeconfig
# E2E test generated config
/tests/nack.yaml
================================================
FILE: .goreleaser.yml
================================================
version: 2
project_name: nack
release:
name_template: 'Release {{.Tag}}'
draft: true
skip_upload: true
github:
owner: nats-io
name: nack
builds:
- id: jetstream-controller
main: ./cmd/jetstream-controller
binary: jetstream-controller
ldflags: &common_ldflags
- -s -w -X main.Version={{ if index .Env "VERSION" }}{{ .Env.VERSION }}{{ else }}{{ .Version }}{{ end }} -X main.GitInfo={{.ShortCommit}} -X main.BuildTime={{.Date}}
tags:
- timetzdata
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
- arm
goarm:
- 6
- 7
- id: nats-boot-config
main: ./cmd/nats-boot-config
binary: nats-boot-config
ldflags: *common_ldflags
tags:
- timetzdata
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
- arm
goarm:
- 6
- 7
- id: nats-server-config-reloader
main: ./cmd/nats-server-config-reloader
binary: nats-server-config-reloader
ldflags: *common_ldflags
tags:
- timetzdata
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
- arm
goarm:
- 6
- 7
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
NACK (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.
## Build & Test Commands
```bash
make build # Build all binaries
make jetstream-controller # Build main controller (with race detector)
make nats-server-config-reloader # Build config reloader sidecar
make nats-boot-config # Build boot config utility
make test # Run unit tests (go vet + envtest + go test)
make test-e2e # Run E2E tests with kuttl (requires kind)
make generate # Regenerate K8s clientset and deepcopy code
make clean # Remove built binaries
```
Run a single test package:
```bash
go test -race -cover -count=1 -timeout 30s ./internal/controller/...
go test -race -cover -count=1 -timeout 30s ./controllers/jetstream/...
```
Run a single test:
```bash
go test -race -count=1 -timeout 30s -run TestMyFunction ./internal/controller/...
```
Format code: `go fmt ./...`
## Architecture
### Two Controller Modes
The `jetstream-controller` binary runs in one of two modes:
- **Legacy mode** (default): Event-driven queue processing using custom informer factories. Supports only Stream and Consumer. Code in `controllers/jetstream/`.
- **Control-loop mode** (`--control-loop`): Full controller-runtime reconciliation loop. Supports all resource types (Stream, Consumer, Account, KeyValue, ObjectStore). Code in `internal/controller/`.
Entry point: `cmd/jetstream-controller/main.go` — the `--control-loop` flag selects which mode to run.
### CRD Types
All CRDs are API version `jetstream.nats.io/v1beta2`, defined in `pkg/jetstream/apis/jetstream/v1beta2/`:
- `streamtypes.go`, `consumertypes.go`, `accounttypes.go`, `keyvaluetypes.go`, `objectstoretypes.go`
Generated client code lives in `pkg/jetstream/generated/` — do not edit manually, run `make generate`.
### Controller Patterns
Controllers follow standard Kubernetes operator patterns:
- **Finalizers** for safe deletion cleanup (defined in `internal/controller/types.go`)
- **Status conditions** (Ready/Errored) tracked on each resource
- **State annotations** for reconciliation state tracking (Ready, Reconciling, Errored, Finalizing)
- **Idempotent reconciliation** — operations must be safe to retry
- **Owner references** for parent-child relationships (e.g., Consumer → Stream)
### Other Components
- `pkg/natsreloader/` — watches config files and sends SIGHUP to reload NATS server
- `pkg/bootconfig/` — init container for node-level network config
## Key Dependencies
- `sigs.k8s.io/controller-runtime` — Kubernetes controller framework (control-loop mode)
- `k8s.io/client-go` — Kubernetes client (legacy mode)
- `github.com/nats-io/nats.go` — NATS client
- `github.com/nats-io/jsm.go` — JetStream management
## Review Focus Areas
When reviewing changes, pay attention to:
- Kubernetes controller reconciliation correctness (idempotency, status updates, error handling)
- Proper use of controller-runtime patterns (watches, predicates, ownership references)
- Go error handling (wrapped errors, sentinel errors, no swallowed errors)
## Local Development
```bash
# Build and run against a local kubeconfig
make jetstream-controller
./jetstream-controller -kubeconfig ~/.kube/config -s nats://localhost:4222
# Start a local JetStream-enabled NATS server
nats-server -DV -js
# Increase log verbosity (klog flags)
./jetstream-controller -kubeconfig ~/.kube/config -s nats://localhost:4222 -v=10
```
================================================
FILE: CONTRIBUTING.md
================================================
> [!WARNING]
> This contribution guide is work in progress and is meant to be a location where more developers can contribute.
# Development
The codebase is currently fragmented into the refactored solution using today's standards for creating controllers (when
using the `--control-loop` argument) and the old variant. The old variant is found in `controllers` directory, while the
new code is found in `internal`.
## E2E testing
You may run the entire e2e suite with the accompanying updated image using:
```bash
make test-e2e
```
This command will:
1. Build a local Docker image with your changes
2. Run the full test suite in **legacy controller mode** (using `controllers/` implementation)
3. Run the full test suite in **control-loop mode** (using `internal/controller/` implementation with `--control-loop` flag)
This ensures both controller implementations are tested with your changes.
**Requirements:**
- `kind` must be installed (install via `make install-kind`)
- `kubectl-kuttl` must be installed (install via `kubectl krew install kuttl`)
# CRD Updates
## Generating types
```bash
make generate
```
will update the generated go structs after having updated the types.
## CRD and docs
CRD updates & accompanying documentation is currently updated manually.
TODO to automate this.
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
export GO111MODULE := on
SHELL=/usr/bin/env bash
ENVTEST_K8S_VERSION = 1.32.0
now := $(shell date -u +%Y-%m-%dT%H:%M:%S%z)
gitBranch := $(shell git rev-parse --abbrev-ref HEAD)
gitCommit := $(shell git rev-parse --short HEAD)
repoDirty := $(shell git diff --quiet || echo "-dirty")
VERSION ?= version-not-set
linkerVars := -X main.BuildTime=$(now) -X main.GitInfo=$(gitBranch)-$(gitCommit)$(repoDirty) -X main.Version=$(VERSION)
drepo ?= natsio
jetstreamSrc := $(shell find cmd/jetstream-controller pkg/jetstream internal/controller controllers/jetstream -name "*.go") pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go
configReloaderSrc := $(shell find cmd/nats-server-config-reloader/ pkg/natsreloader/ -name "*.go")
bootConfigSrc := $(shell find cmd/nats-boot-config/ pkg/bootconfig/ -name "*.go")
# You might override this so as to use a more recent version, to update old
# generated imports, and so migrate away from old import paths and get back to
# a consistent import tree.
codeGeneratorDir ?=
default:
# Try these (read Makefile for more recipes):
# make jetstream-controller
# make nats-server-config-reloader
# make nats-boot-config
generate: fetch-modules pkg/k8scodegen/file-header.txt
rm -rf pkg/jetstream/generated
D="$(codeGeneratorDir)"; : "$${D:=`go list -m -f '{{.Dir}}' k8s.io/code-generator`}"; \
source "$$D/kube_codegen.sh" ; \
kube::codegen::gen_helpers \
--boilerplate pkg/k8scodegen/file-header.txt \
pkg/jetstream/apis; \
kube::codegen::gen_client \
--with-watch \
--with-applyconfig \
--boilerplate pkg/k8scodegen/file-header.txt \
--output-dir pkg/jetstream/generated \
--output-pkg github.com/nats-io/nack/pkg/jetstream/generated \
--one-input-api jetstream/v1beta2 \
pkg/jetstream/apis
jetstream-controller: $(jetstreamSrc)
go build -race -o $@ \
-ldflags "$(linkerVars)" \
github.com/nats-io/nack/cmd/jetstream-controller
jetstream-controller.docker: $(jetstreamSrc)
CGO_ENABLED=0 GOOS=linux go build -o $@ \
-ldflags "-s -w $(linkerVars)" \
-tags timetzdata \
github.com/nats-io/nack/cmd/jetstream-controller
.PHONY: jetstream-controller-docker
jetstream-controller-docker:
ifneq ($(ver),)
REGISTRY="$(drepo)" \
TAGS="$(ver)" \
docker buildx bake --load \
--set goreleaser.args.VERSION=$(ver) \
jetstream-controller
else
# Missing version, try this.
# make jetstream-controller-docker ver=1.2.3
exit 1
endif
.PHONY: jetstream-controller-dockerx
jetstream-controller-dockerx:
ifneq ($(ver),)
# Ensure 'docker buildx ls' shows correct platforms.
REGISTRY="$(drepo)" \
TAGS="$(ver)" \
PUSH=true \
docker buildx bake --push \
--set goreleaser.args.VERSION=$(ver) \
jetstream-controller
else
# Missing version, try this.
# make jetstream-controller-dockerx ver=1.2.3
exit 1
endif
nats-server-config-reloader: $(configReloaderSrc)
go build -race -o $@ \
-ldflags "$(linkerVars)" \
github.com/nats-io/nack/cmd/nats-server-config-reloader
nats-server-config-reloader.docker: $(configReloaderSrc)
CGO_ENABLED=0 GOOS=linux go build -o $@ \
-ldflags "-s -w $(linkerVars)" \
-tags timetzdata \
github.com/nats-io/nack/cmd/nats-server-config-reloader
.PHONY: nats-server-config-reloader-docker
nats-server-config-reloader-docker:
ifneq ($(ver),)
REGISTRY="$(drepo)" \
TAGS="$(ver)" \
docker buildx bake --load \
--set goreleaser.args.VERSION=$(ver) \
nats-server-config-reloader
else
# Missing version, try this.
# make nats-server-config-reloader-docker ver=1.2.3
exit 1
endif
.PHONY: nats-server-config-reloader-dockerx
nats-server-config-reloader-dockerx:
ifneq ($(ver),)
# Ensure 'docker buildx ls' shows correct platforms.
REGISTRY="$(drepo)" \
TAGS="$(ver)" \
PUSH=true \
docker buildx bake --push \
--set goreleaser.args.VERSION=$(ver) \
nats-server-config-reloader
else
# Missing version, try this.
# make nats-server-config-reloader-dockerx ver=1.2.3
exit 1
endif
nats-boot-config: $(bootConfigSrc)
go build -race -o $@ \
-ldflags "$(linkerVars)" \
github.com/nats-io/nack/cmd/nats-boot-config
nats-boot-config.docker: $(bootConfigSrc)
CGO_ENABLED=0 GOOS=linux go build -o $@ \
-ldflags "-s -w $(linkerVars)" \
-tags timetzdata \
github.com/nats-io/nack/cmd/nats-boot-config
.PHONY: nats-boot-config-docker
nats-boot-config-docker:
ifneq ($(ver),)
REGISTRY="$(drepo)" \
TAGS="$(ver)" \
docker buildx bake --load \
--set goreleaser.args.VERSION=$(ver) \
nats-boot-config
else
# Missing version, try this.
# make nats-boot-config-docker ver=1.2.3
exit 1
endif
.PHONY: nats-boot-config-dockerx
nats-boot-config-dockerx:
ifneq ($(ver),)
# Ensure 'docker buildx ls' shows correct platforms.
REGISTRY="$(drepo)" \
TAGS="$(ver)" \
PUSH=true \
docker buildx bake --push \
--set goreleaser.args.VERSION=$(ver) \
nats-boot-config
else
# Missing version, try this.
# make nats-boot-config-dockerx ver=1.2.3
exit 1
endif
.PHONY: fetch-modules
# This will error if we have removed some code to be regenerated, so we instead silence it and force success
fetch-modules:
go list -f '{{with .Module}}{{end}}' all >/dev/null 2>&1 || true
.PHONY: build
build: jetstream-controller nats-server-config-reloader nats-boot-config
# Setup envtest tools based on a operator-sdk project makefile
LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary (ideally with version)
# $2 - package url which can be installed
# $3 - specific version of package
define go-install-tool
@[ -f $(1) ] || { \
set -e; \
package=$(2)@$(3) ;\
echo "Downloading $${package}" ;\
GOBIN=$(LOCALBIN) go install $${package} ;\
mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\
}
endef
ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION)
ENVTEST_VERSION ?= release-0.20
.PHONY: envtest
envtest: $(ENVTEST) ## Download setup-envtest locally if necessary.
$(ENVTEST): $(LOCALBIN)
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))
.PHONY: test
test: envtest
go vet ./controllers/... ./pkg/natsreloader/... ./internal/controller/...
$(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path ## Get k8s binaries
go test -race -cover -count=1 -timeout 30s ./controllers/... ./pkg/natsreloader/... ./internal/controller/...
.PHONY: test-e2e
test-e2e:
@echo "Running e2e tests with kuttl..."
@command -v kubectl-kuttl >/dev/null 2>&1 || { echo "kuttl not installed. Install: kubectl krew install kuttl"; exit 1; }
kind delete cluster || true
kind create cluster
docker build -t nack:test -f tests/Dockerfile .
kind load docker-image nack:test
@echo "\n=== Testing LEGACY controller mode ==="
cp tests/nack-legacy.yaml tests/nack.yaml && kubectl kuttl test
@echo "\n=== Testing CONTROL-LOOP controller mode ==="
cp tests/nack-control-loop.yaml tests/nack.yaml && kubectl kuttl test
rm -f tests/nack.yaml
.PHONY: clean
clean:
rm -f jetstream-controller jetstream-controller.docker \
nats-server-config-reloader nats-server-config-reloader.docker \
nats-boot-config nats-boot-config.docker
.PHONY: install-kind
install-kind:
go install sigs.k8s.io/kind@v0.30.0
================================================
FILE: README.md
================================================
<img width="800" alt="nack-large" src="https://user-images.githubusercontent.com/26195/92535603-71ad9a80-f1ec-11ea-8959-cdc22b31b84a.png">
[](https://www.apache.org/licenses/LICENSE-2.0)
[](https://github.com/nats-io/nack/actions/workflows/release.yaml)
[](https://github.com/nats-io/nack/actions/workflows/e2e.yaml)
[](https://github.com/nats-io/nack/actions/workflows/test.yaml)
[NATS](https://nats.io) Controllers for Kubernetes (NACK)
## Table of Contents
- [JetStream Controller](#jetstream-controller)
- [Controller Modes](#controller-modes)
- [Getting Started](#getting-started)
- [Managing Multiple NATS Systems and Accounts](#managing-multiple-nats-systems-and-accounts)
- [Creating NATS Resources](#creating-nats-resources)
- [Getting Started with Accounts](#getting-started-with-accounts)
- [Local Development](#local-development)
- [NATS Server Config Reloader](#nats-server-config-reloader)
- [NATS Boot Config](#nats-boot-config)
## JetStream Controller
The JetStream controllers allows you to manage [NATS JetStream](https://docs.nats.io/nats-concepts/jetstream) resources via Kubernetes CRDs.
### Controller Modes
NACK supports two controller modes with different capabilities:
| Mode | Streams | Consumers | Key/Value | Object Store | Accounts |
|------|---------|-----------|-----------|--------------|----------|
| **Legacy (default)** | ✅ | ✅ | ❌ | ❌ | ❌ |
| **Control-loop** (`--control-loop`) | ✅ | ✅ | ✅ | ✅ | ✅ |
> **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.
Resources managed by NACK controllers are expected to _exclusively_ be managed by NACK, and configuration state will be enforced if mutated by an external client.
## [API Reference](docs/api.md)
The API reference documents all available CRD fields for Streams, Consumers, KeyValue, ObjectStore, and Account resources.
### Getting started
Install with Helm:
```sh
helm repo add nats https://nats-io.github.io/k8s/helm/charts/
helm repo update
helm upgrade --install nats nats/nats \
--set config.jetstream.enabled=true \
--set config.jetstream.memoryStore.enabled=true \
--set config.cluster.enabled=true --wait
helm upgrade --install nack nats/nack \
--set jetstream.nats.url=nats://nats.default.svc.cluster.local:4222 --wait
```
#### (Optional) Enable Experimental `controller-runtime` Controllers
> **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.
>
> The `jetstream-controller` logs will contain a diff of any changes the controller has made.
```sh
helm upgrade nack nats/nack \
--set jetstream.nats.url=nats://nats.default.svc.cluster.local:4222 \
--set jetstream.additionalArgs={--control-loop} --wait
```
### Managing Multiple NATS Systems and Accounts
There are several approaches for managing multiple NATS Systems with NACK within one Kubernetes cluster. These options are not mutually exclusive.
#### 1. Run Multiple Namespaced Controllers
You 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.
```sh
helm upgrade --install nack nats/nack \
--create-namespace --namespace nats \
--set namespaced=true \
--set jetstream.nats.url=nats://nats.nats.svc.cluster.local:4222 --wait
```
#### 2. Use the Accounts Resource
The Accounts resource acts as a connection config for other resources. You may define multiple accounts for the same, or for distinct, NATS Systems.
```yaml
---
apiVersion: jetstream.nats.io/v1beta2
kind: Account
metadata:
name: a
spec:
name: a
creds:
secret:
name: account-a-creds
servers:
- nats://nats.nats-a.svc.cluster.local
---
apiVersion: jetstream.nats.io/v1beta2
kind: Account
metadata:
name: b
spec:
name: b
creds:
secret:
name: account-b-creds
servers:
- nats://nats.nats-b.svc.cluster.local
---
apiVersion: jetstream.nats.io/v1beta2
kind: Stream
metadata:
name: foo-a
spec:
name: foo
subjects: ["foo", "foo.>"]
storage: file
replicas: 3
maxAge: 1h
account: a
---
apiVersion: jetstream.nats.io/v1beta2
kind: Stream
metadata:
name: foo-b
spec:
name: foo
subjects: ["foo", "foo.>"]
storage: file
replicas: 3
maxAge: 1h
account: b
```
The 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.
This 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`.
See more details in the [Getting Started with Accounts](#getting-started-with-accounts) section.
#### 3. Define Connection Config in the CRD Manifest
You may define some connection options within the resource manifests directly. If not running in the newer `--control-loop` mode, set `--crd-connect`.
If running with `--control-loop`, resource-level connection config will always override any global config.
> **Note**: The `--crd-connect` flag is not required if running with `--control-loop`.
```sh
helm upgrade nack nats/nack \
--set jetstream.additionalArgs={--crd-connect} --wait
```
#### Example Stream:
```yaml
apiVersion: jetstream.nats.io/v1beta2
kind: Stream
metadata:
name: bar
spec:
name: bar
subjects: ["bar", "bar.>"]
storage: file
replicas: 3
maxAge: 1h
servers:
- nats://nats.nats.svc.cluster.local:4222
```
### Creating NATS Resources
Let's create a stream and a couple of consumers:
```yaml
---
apiVersion: jetstream.nats.io/v1beta2
kind: Stream
metadata:
name: mystream
spec:
name: mystream
subjects: ["orders.*"]
storage: memory
maxAge: 1h
---
apiVersion: jetstream.nats.io/v1beta2
kind: Consumer
metadata:
name: my-push-consumer
spec:
streamName: mystream
durableName: my-push-consumer
deliverSubject: my-push-consumer.orders
deliverPolicy: last
ackPolicy: none
replayPolicy: instant
---
apiVersion: jetstream.nats.io/v1beta2
kind: Consumer
metadata:
name: my-pull-consumer
spec:
streamName: mystream
durableName: my-pull-consumer
deliverPolicy: all
filterSubject: orders.received
maxDeliver: 20
ackPolicy: explicit
---
# Note: KeyValue requires control-loop mode to be enabled
apiVersion: jetstream.nats.io/v1beta2
kind: KeyValue
metadata:
name: my-key-value
spec:
bucket: my-key-value
history: 20
storage: file
maxBytes: 2048
compression: true
---
# Note: ObjectStore requires control-loop mode to be enabled
apiVersion: jetstream.nats.io/v1beta2
kind: ObjectStore
metadata:
name: my-object-store
spec:
bucket: my-object-store
storage: file
replicas: 1
maxBytes: 536870912 # 512 MB
compression: true
```
```sh
# Create a stream.
$ kubectl apply -f https://raw.githubusercontent.com/nats-io/nack/main/deploy/examples/stream.yml
# Check if it was successfully created.
$ kubectl get streams
NAME STATE STREAM NAME SUBJECTS
mystream Ready mystream [orders.*]
# Create a push-based consumer
$ kubectl apply -f https://raw.githubusercontent.com/nats-io/nack/main/deploy/examples/consumer_push.yml
# Create a pull based consumer
$ kubectl apply -f https://raw.githubusercontent.com/nats-io/nack/main/deploy/examples/consumer_pull.yml
# Check if they were successfully created.
$ kubectl get consumers
NAME STATE STREAM CONSUMER ACK POLICY
my-pull-consumer Ready mystream my-pull-consumer explicit
my-push-consumer Ready mystream my-push-consumer none
# If you end up in an Errored state, run kubectl describe for more info.
# kubectl describe streams mystream
# kubectl describe consumers my-pull-consumer
```
Now we're ready to use Streams and Consumers. Let's start off with writing some
data into `mystream`.
```sh
# Run nats-box that includes the NATS management utilities, and exec into it.
$ kubectl exec -it deployment/nats-box -- /bin/sh -l
# Publish a couple of messages from nats-box
nats-box:~$ nats pub orders.received "order 1"
nats-box:~$ nats pub orders.received "order 2"
```
First, we'll read the data using a pull-based consumer.
From the above `my-pull-consumer` Consumer CRD, we have set the filterSubject
of `orders.received`. You can double check with the following command:
```sh
$ kubectl get consumer my-pull-consumer -o jsonpath={.spec.filterSubject}
orders.received
```
So that's the subject my-pull-consumer will pull messages from.
```sh
# Pull first message.
nats-box:~$ nats consumer next mystream my-pull-consumer
--- subject: orders.received / delivered: 1 / stream seq: 1 / consumer seq: 1
order 1
Acknowledged message
# Pull next message.
nats-box:~$ nats consumer next mystream my-pull-consumer
--- subject: orders.received / delivered: 1 / stream seq: 2 / consumer seq: 2
order 2
Acknowledged message
```
Next, let's read data using a push-based consumer.
From the above `my-push-consumer` Consumer CRD, we have set the deliverSubject
of `my-push-consumer.orders`, as you can confirm with the following command:
```sh
$ kubectl get consumer my-push-consumer -o jsonpath={.spec.deliverSubject}
my-push-consumer.orders
```
So pushed messages will arrive on that subject. This time all messages arrive automatically.
```sh
nats-box:~$ nats sub my-push-consumer.orders
17:57:24 Subscribing on my-push-consumer.orders
[#1] Received JetStream message: consumer: mystream > my-push-consumer / subject: orders.received /
delivered: 1 / consumer seq: 1 / stream seq: 1 / ack: false
order 1
[#2] Received JetStream message: consumer: mystream > my-push-consumer / subject: orders.received /
delivered: 1 / consumer seq: 2 / stream seq: 2 / ack: false
order 2
```
### Getting Started with Accounts
You can create an Account resource with the following CRD. The Account resource
can be used to specify server and TLS information.
> **Note** The `Account` resource does not create or manage NATS accounts. It functions as a connection and authentication config for the managed resources.
The [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).
```yaml
---
apiVersion: jetstream.nats.io/v1beta2
kind: Account
metadata:
name: a
spec:
name: a
servers:
- nats://nats:4222
tls:
secret:
name: nack-a-tls
ca: "ca.crt"
cert: "tls.crt"
key: "tls.key"
```
You can then link an Account to a Stream so that the Stream uses the Account
information for its creation.
```yaml
---
apiVersion: jetstream.nats.io/v1beta2
kind: Stream
metadata:
name: foo
spec:
name: foo
subjects: ["foo", "foo.>"]
storage: file
replicas: 1
account: a # <-- Create stream using account A information
```
The following is an example of how to get Accounts working with a custom NATS
Server URL and TLS certificates.
```sh
# Install cert-manager
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.17.0/cert-manager.yaml
# Install TLS certs
cd examples/secure
# Install certificate issuer
kubectl apply -f issuer.yaml
# Install account A cert
kubectl apply -f nack-a-client-tls.yaml
# Install server cert
kubectl apply -f server-tls.yaml
# Install nats-box cert
kubectl apply -f client-tls.yaml
# Install NATS cluster
helm upgrade --install -f nats-helm.yaml nats nats/nats
# Verify pods are healthy
kubectl get pods
# Install JetStream Controller from nack
helm upgrade --install nack nats/nack --set jetstream.enabled=true
# Verify pods are healthy
kubectl get pods
# Create account A resource
kubectl apply -f nack/nats-account-a.yaml
# Create stream using account A
kubectl apply -f nack/nats-stream-foo-a.yaml
# Create consumer using account A
kubectl apply -f nack/nats-consumer-bar-a.yaml
```
After Accounts, Streams, and Consumers are created, let's log into the nats-box
container to run the management CLI.
```sh
# Get container shell
kubectl exec -it deployment/nats-box -- /bin/sh -l
```
There should now be some Streams available, verify with `nats` command.
```sh
# List streams
nats stream ls
```
You can now publish messages on a Stream.
```sh
# Push message
nats pub foo hi
```
And pull messages from a Consumer.
```sh
# Pull message
nats consumer next foo bar
```
### Local Development
```sh
# First, build the jetstream controller.
make jetstream-controller
# Next, run the controller like this
./jetstream-controller -kubeconfig ~/.kube/config -s nats://localhost:4222
# Pro tip: jetstream-controller uses klog just like kubectl or kube-apiserver.
# This means you can change the verbosity of logs with the -v flag.
#
# For example, this prints raw HTTP requests and responses.
# ./jetstream-controller -v=10
# You'll probably want to start a local Jetstream-enabled NATS server, unless
# you use a public one.
nats-server -DV -js
```
Build Docker image
```sh
make jetstream-controller-docker ver=1.2.3
```
## NATS Server Config Reloader
This is a sidecar that you can use to automatically reload your NATS Server
configuration file.
### Installing with Helm
For more information see the
[Chart repo](https://github.com/nats-io/k8s/tree/main/helm/charts/nats).
```sh
helm repo add nats https://nats-io.github.io/k8s/helm/charts/
helm upgrade --install nats nats/nats
```
### Configuring
```yaml
reloader:
enabled: true
image: natsio/nats-server-config-reloader:0.16.1
pullPolicy: IfNotPresent
```
### Local Development
```sh
# First, build the config reloader.
make nats-server-config-reloader
# Next, run the reloader like this
./nats-server-config-reloader
```
Build Docker image
```sh
make nats-server-config-reloader-docker ver=1.2.3
```
## NATS Boot Config
A helper utility used during NATS server pod initialization to generate and manage boot-time configuration.
### Installing with Helm
For more information see the
[Chart repo](https://github.com/nats-io/k8s/tree/main/helm/charts/nats).
```sh
helm repo add nats https://nats-io.github.io/k8s/helm/charts/
helm upgrade --install nats nats/nats
```
### Configuring
```yaml
bootconfig:
image: natsio/nats-boot-config:0.16.1
pullPolicy: IfNotPresent
```
### Local Development
```sh
# First, build the project.
make nats-boot-config
# Next, run the project like this
./nats-boot-config
```
Build Docker image
```sh
make nats-boot-config-docker ver=1.2.3
```
================================================
FILE: cicd/Dockerfile
================================================
#syntax=docker/dockerfile:1.13
ARG GO_APP
FROM alpine:3.23.3 AS deps
ARG GO_APP
ARG GORELEASER_DIST_DIR=/go/src/dist
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
RUN mkdir -p /go/bin /go/src ${GORELEASER_DIST_DIR}
COPY --from=build ${GORELEASER_DIST_DIR}/ ${GORELEASER_DIST_DIR}
RUN <<EOT
set -e
apk add --no-cache ca-certificates jq
cd ${GORELEASER_DIST_DIR}/..
if [[ ${TARGETARCH} == "arm" ]]; then VARIANT=$(echo ${TARGETVARIANT} | sed 's/^v//'); fi
BIN_PATH=$(jq -r ".[] |select(.type == \"Binary\" and \
.name == \"${GO_APP}\" and \
.goos == \"${TARGETOS}\" and \
.goarch == \"${TARGETARCH}\" and \
(.goarm == \"${VARIANT}\" or .goarm == null)) | .path" < /go/src/dist/artifacts.json)
cp ${BIN_PATH} /go/bin
EOT
FROM alpine:3.23.3
ARG GO_APP
ENV GO_APP ${GO_APP}
COPY --from=deps --chmod=755 /go/bin/${GO_APP} /usr/local/bin/${GO_APP}
COPY --from=deps /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=assets entrypoint.sh /entrypoint.sh
RUN ln -s /usr/local/bin/${GO_APP} /${GO_APP} && chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
================================================
FILE: cicd/Dockerfile_goreleaser
================================================
#syntax=docker/dockerfile:1.13
FROM --platform=$BUILDPLATFORM golang:1.25.6-bookworm AS build
RUN <<EOT
set -e
echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' > /etc/apt/sources.list.d/goreleaser.list
apt-get update
apt-get install -y goreleaser
rm -rf /var/lib/apt/lists/*
EOT
FROM build
ARG CI
ARG PUSH
ARG GITHUB_TOKEN
ARG TAGS
ARG VERSION
COPY --from=src . /go/src
RUN <<EOT
set -e
cd /go/src
FLAGS="--clean"
if [ -z ${GITHUB_TOKEN} ]; then
if [ ${CI} != "true" ]; then FLAGS="${FLAGS} --skip=validate"; fi
if [ ${PUSH} != "true" ]; then FLAGS="${FLAGS} --single-target"; fi
goreleaser build ${FLAGS}
else
goreleaser release ${FLAGS}
fi
EOT
================================================
FILE: cicd/assets/entrypoint.sh
================================================
#!/bin/sh
exec "/${GO_APP}" "$@"
================================================
FILE: cicd/tag-deps-version.txt
================================================
0.21.0
0.21.1
================================================
FILE: cmd/jetstream-controller/main.go
================================================
// Copyright 2020-2023 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"errors"
"flag"
"fmt"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
"github.com/nats-io/nack/controllers/jetstream"
"github.com/nats-io/nack/internal/controller"
v1beta2 "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2"
clientset "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
klog "k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/cache"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log"
)
var (
BuildTime = "build-time-not-set"
GitInfo = "gitinfo-not-set"
Version = "not-set"
)
func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
os.Exit(1)
}
}
func run() error {
klog.InitFlags(nil)
// Opt into the new klog behavior so that -stderrthreshold is honored even
// when -logtostderr=true (the default).
// Ref: kubernetes/klog#212, kubernetes/klog#432
_ = flag.Set("legacy_stderr_threshold_behavior", "false")
_ = flag.Set("stderrthreshold", "INFO")
// Explicitly register controller-runtime flags
ctrl.RegisterFlags(nil)
namespace := flag.String("namespace", v1.NamespaceAll, "Restrict to a namespace")
version := flag.Bool("version", false, "Print the version and exit")
creds := flag.String("creds", "", "NATS Credentials")
nkey := flag.String("nkey", "", "NATS NKey")
cert := flag.String("tlscert", "", "NATS TLS public certificate")
key := flag.String("tlskey", "", "NATS TLS private key")
ca := flag.String("tlsca", "", "NATS TLS certificate authority chain")
tlsfirst := flag.Bool("tlsfirst", false, "If enabled, forces explicit TLS without waiting for Server INFO")
server := flag.String("s", "", "NATS Server URL")
crdConnect := 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")
cleanupPeriod := flag.Duration("cleanup-period", 30*time.Second, "Period to run object cleanup")
readOnly := flag.Bool("read-only", false, "Starts the controller without causing changes to the NATS resources")
cacheDir := flag.String("cache-dir", "", "Directory to store cached credential and TLS files")
controlLoop := flag.Bool("control-loop", false, "Experimental: Run controller with a full reconciliation control loop")
controlLoopSyncInterval := flag.Duration("sync-interval", time.Minute, "Interval to perform scheduled reconcile")
healthProbeBindAddress := flag.String("health-probe-bind-address", ":8081", "The address the probe endpoint binds to")
flag.Parse()
if *version {
fmt.Printf("%s version %s (%s), built %s\n", os.Args[0], Version, GitInfo, BuildTime)
return nil
}
if *server == "" && !*crdConnect {
return errors.New("NATS Server URL is required")
}
config, err := ctrl.GetConfig()
if err != nil {
return fmt.Errorf("get kubernetes rest config: %w", err)
}
if *controlLoop {
klog.Warning("Starting JetStream controller in experimental control loop mode")
natsCfg := &controller.NatsConfig{
ClientName: "jetstream-controller",
Credentials: *creds,
NKey: *nkey,
ServerURL: *server,
CAs: []string{},
Certificate: *cert,
Key: *key,
TLSFirst: *tlsfirst,
}
if *ca != "" {
natsCfg.CAs = []string{*ca}
}
controllerCfg := &controller.Config{
ReadOnly: *readOnly,
Namespace: *namespace,
CacheDir: *cacheDir,
RequeueInterval: *controlLoopSyncInterval,
HealthProbeBindAddress: *healthProbeBindAddress,
}
return runControlLoop(config, natsCfg, controllerCfg)
}
// K8S API Client.
kc, err := kubernetes.NewForConfig(config)
if err != nil {
return err
}
// JetStream CRDs client.
jc, err := clientset.NewForConfig(config)
if err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctrl := jetstream.NewController(jetstream.Options{
// FIXME: Move context to be param from Run
// to avoid keeping state in options.
Ctx: ctx,
NATSCredentials: *creds,
NATSNKey: *nkey,
NATSServerURL: *server,
NATSCA: *ca,
NATSCertificate: *cert,
NATSKey: *key,
NATSTLSFirst: *tlsfirst,
KubeIface: kc,
JetstreamIface: jc,
Namespace: *namespace,
CRDConnect: *crdConnect,
CleanupPeriod: *cleanupPeriod,
ReadOnly: *readOnly,
})
klog.Infof("Starting %s v%s...", os.Args[0], Version)
klog.Infof("Running in LEGACY mode")
if *readOnly {
klog.Infof("Running in read-only mode: JetStream state in server will not be changed")
}
go handleSignals(cancel)
return ctrl.Run()
}
func runControlLoop(config *rest.Config, natsCfg *controller.NatsConfig, controllerCfg *controller.Config) error {
// Setup scheme
scheme := runtime.NewScheme()
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(v1beta2.AddToScheme(scheme))
log.SetLogger(klog.NewKlogr())
ctrlOpts := ctrl.Options{
Scheme: scheme,
Logger: log.Log,
HealthProbeBindAddress: controllerCfg.HealthProbeBindAddress,
}
if controllerCfg.Namespace != "" {
ctrlOpts.Cache = cache.Options{
DefaultNamespaces: map[string]cache.Config{
controllerCfg.Namespace: {},
},
}
}
mgr, err := ctrl.NewManager(config, ctrlOpts)
if err != nil {
return fmt.Errorf("unable to start manager: %w", err)
}
if controllerCfg.CacheDir == "" {
cacheDir, err := os.MkdirTemp(".", "nack")
if err != nil {
return fmt.Errorf("create cache dir: %w", err)
}
defer os.RemoveAll(cacheDir)
cacheDir, err = filepath.Abs(cacheDir)
if err != nil {
return fmt.Errorf("get absolute cache dir: %w", err)
}
controllerCfg.CacheDir = cacheDir
} else {
if _, err := os.Stat(controllerCfg.CacheDir); os.IsNotExist(err) {
err = os.MkdirAll(controllerCfg.CacheDir, 0o755)
if err != nil {
return fmt.Errorf("create cache dir: %w", err)
}
}
}
err = controller.RegisterAll(mgr, natsCfg, controllerCfg)
if err != nil {
return fmt.Errorf("register jetstream controllers: %w", err)
}
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
return fmt.Errorf("unable to set up health check: %w", err)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
return fmt.Errorf("unable to set up ready check: %w", err)
}
klog.Info("starting manager")
klog.Infof("Running in CONTROL-LOOP mode")
if controllerCfg.ReadOnly {
klog.Infof("Running in read-only mode: JetStream state in server will not be changed")
}
return mgr.Start(ctrl.SetupSignalHandler())
}
func handleSignals(cancel context.CancelFunc) {
sigc := make(chan os.Signal, 2)
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
for sig := range sigc {
switch sig {
case syscall.SIGINT:
os.Exit(130)
case syscall.SIGTERM:
cancel()
return
}
}
}
================================================
FILE: cmd/nats-boot-config/main.go
================================================
// Copyright 2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"flag"
"fmt"
"os"
"github.com/nats-io/nack/pkg/bootconfig"
log "github.com/sirupsen/logrus"
)
var (
BuildTime = "build-time-not-set"
GitInfo = "gitinfo-not-set"
Version = "version-not-set"
)
func main() {
fs := flag.NewFlagSet("nats-pod-bootconfig", flag.ExitOnError)
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: nats-pod-bootconfig [options...]\n\n")
fs.PrintDefaults()
fmt.Fprintf(os.Stderr, "\n")
}
var opts *bootconfig.Options = &bootconfig.Options{}
var showHelp, showVersion bool
fs.BoolVar(&showHelp, "h", false, "Show help")
fs.BoolVar(&showVersion, "v", false, "Show version")
fs.StringVar(&opts.ClientAdvertiseFileName, "f", "client_advertise.conf", "File name where the client advertise address will be written into")
fs.StringVar(&opts.GatewayAdvertiseFileName, "gf", "gateway_advertise.conf", "File name where the gateway advertise address will be written into")
fs.StringVar(&opts.TargetTag, "t", "nats.io/node-external-ip", "Tag that will be looked up from a node")
fs.Parse(os.Args[1:])
switch {
case showHelp:
flag.Usage()
os.Exit(0)
case showVersion:
fmt.Fprintf(os.Stderr, "NATS Server Pod boot config v%s (%s, %s)\n", Version, GitInfo, BuildTime)
os.Exit(0)
}
if os.Getenv("DEBUG") == "true" {
log.SetLevel(log.DebugLevel)
}
formatter := &log.TextFormatter{
FullTimestamp: true,
}
log.SetFormatter(formatter)
controller := bootconfig.NewController(opts)
log.Infof("Starting NATS Server boot config v%s (%s, %s)", Version, GitInfo, BuildTime)
err := controller.Run(context.Background())
if err != nil && err != context.Canceled {
log.Fatalf(err.Error())
}
}
================================================
FILE: cmd/nats-server-config-reloader/main.go
================================================
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/nats-io/nack/pkg/natsreloader"
)
var (
BuildTime = "build-time-not-set"
GitInfo = "gitinfo-not-set"
Version = "version-not-set"
)
// StringSet is a wrapper for []string to allow using it with the flags package.
type StringSet []string
func (s *StringSet) String() string {
return strings.Join([]string(*s), ", ")
}
// Set appends the value provided to the list of strings.
func (s *StringSet) Set(val string) error {
*s = append(*s, val)
return nil
}
func main() {
fs := flag.NewFlagSet("nats-server-config-reloader", flag.ExitOnError)
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: nats-server-config-reloader [options...]\n\n")
fs.PrintDefaults()
fmt.Fprintf(os.Stderr, "\n")
}
// Help and version
var (
showHelp bool
showVersion bool
fileSet StringSet
customSignal int
forcePoll bool
pollInterval time.Duration
maxWatcherRetries int
)
nconfig := &natsreloader.Config{}
fs.BoolVar(&showHelp, "h", false, "Show help")
fs.BoolVar(&showHelp, "help", false, "Show help")
fs.BoolVar(&showVersion, "v", false, "Show version")
fs.BoolVar(&showVersion, "version", false, "Show version")
fs.StringVar(&nconfig.PidFile, "P", "/var/run/nats/nats.pid", "NATS Server Pid File")
fs.StringVar(&nconfig.PidFile, "pid", "/var/run/nats/nats.pid", "NATS Server Pid File")
fs.Var(&fileSet, "c", "NATS Server Config File (may be repeated to specify more than one)")
fs.Var(&fileSet, "config", "NATS Server Config File (may be repeated to specify more than one)")
fs.IntVar(&nconfig.MaxRetries, "max-retries", 30, "Max attempts to trigger reload")
fs.IntVar(&nconfig.RetryWaitSecs, "retry-wait-secs", 4, "Time to back off when reloading fails before retrying")
fs.IntVar(&customSignal, "signal", 1, "Signal to send to the NATS Server process (default SIGHUP 1)")
fs.BoolVar(&forcePoll, "force-poll", false, "Force polling mode instead of inotify file watching")
fs.DurationVar(&pollInterval, "poll-interval", 5*time.Second, "Polling interval when using polling mode (default 5s)")
fs.IntVar(&maxWatcherRetries, "max-watcher-retries", 3, "Max retries for creating inotify watcher on transient failures")
fs.Parse(os.Args[1:])
nconfig.WatchedFiles = fileSet
if len(fileSet) == 0 {
nconfig.WatchedFiles = []string{"/etc/nats-config/nats.conf"}
}
nconfig.Signal = syscall.Signal(customSignal)
nconfig.ForcePoll = forcePoll
nconfig.PollInterval = pollInterval
nconfig.MaxWatcherRetries = maxWatcherRetries
switch {
case showHelp:
flag.Usage()
os.Exit(0)
case showVersion:
fmt.Fprintf(os.Stderr, "NATS Server Config Reloader v%s (%s, %s)\n", Version, GitInfo, BuildTime)
os.Exit(0)
}
r, err := natsreloader.NewReloader(nconfig)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
os.Exit(1)
}
// Signal handling.
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
for sig := range c {
log.Printf("Trapped \"%v\" signal\n", sig)
switch sig {
case syscall.SIGINT:
log.Println("Exiting...")
os.Exit(0)
return
case syscall.SIGTERM:
r.Stop()
return
}
}
}()
log.Printf("Starting NATS Server Reloader v%s\n", Version)
err = r.Run(context.Background())
if err != nil && err != context.Canceled {
fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
os.Exit(1)
}
}
================================================
FILE: controllers/jetstream/conn_pool.go
================================================
package jetstream
import (
"crypto/sha256"
"crypto/tls"
"encoding/json"
"fmt"
"os"
"sync"
"github.com/nats-io/nats.go"
"github.com/sirupsen/logrus"
"golang.org/x/sync/singleflight"
)
type natsContext struct {
Name string `json:"name"`
URL string `json:"url"`
JWT string `json:"jwt"`
Seed string `json:"seed"`
Credentials string `json:"credential"`
Nkey string `json:"nkey"`
Token string `json:"token"`
Username string `json:"username"`
Password string `json:"password"`
TLSCAs []string `json:"tls_ca"`
TLSCert string `json:"tls_cert"`
TLSKey string `json:"tls_key"`
}
func (c *natsContext) copy() *natsContext {
if c == nil {
return nil
}
cp := *c
return &cp
}
func (c *natsContext) hash() (string, error) {
b, err := json.Marshal(c)
if err != nil {
return "", fmt.Errorf("error marshaling context to json: %v", err)
}
if c.Nkey != "" {
fb, err := os.ReadFile(c.Nkey)
if err != nil {
return "", fmt.Errorf("error opening nkey file %s: %v", c.Nkey, err)
}
b = append(b, fb...)
}
if c.Credentials != "" {
fb, err := os.ReadFile(c.Credentials)
if err != nil {
return "", fmt.Errorf("error opening creds file %s: %v", c.Credentials, err)
}
b = append(b, fb...)
}
if len(c.TLSCAs) > 0 {
for _, cert := range c.TLSCAs {
fb, err := os.ReadFile(cert)
if err != nil {
return "", fmt.Errorf("error opening ca file %s: %v", cert, err)
}
b = append(b, fb...)
}
}
if c.TLSCert != "" {
fb, err := os.ReadFile(c.TLSCert)
if err != nil {
return "", fmt.Errorf("error opening cert file %s: %v", c.TLSCert, err)
}
b = append(b, fb...)
}
if c.TLSKey != "" {
fb, err := os.ReadFile(c.TLSKey)
if err != nil {
return "", fmt.Errorf("error opening key file %s: %v", c.TLSKey, err)
}
b = append(b, fb...)
}
hash := sha256.New()
hash.Write(b)
return fmt.Sprintf("%x", hash.Sum(nil)), nil
}
type natsContextDefaults struct {
Name string
URL string
TLSCAs []string
TLSCert string
TLSKey string
TLSConfig *tls.Config
}
type pooledNatsConn struct {
nc *nats.Conn
cp *natsConnPool
key string
count uint64
closed bool
}
func (pc *pooledNatsConn) ReturnToPool() {
pc.cp.Lock()
pc.count--
if pc.count == 0 {
if pooledConn, ok := pc.cp.cache[pc.key]; ok && pc == pooledConn {
delete(pc.cp.cache, pc.key)
}
pc.closed = true
pc.cp.Unlock()
pc.nc.Close()
return
}
pc.cp.Unlock()
}
type natsConnPool struct {
sync.Mutex
cache map[string]*pooledNatsConn
logger *logrus.Logger
group *singleflight.Group
natsDefaults *natsContextDefaults
natsOpts []nats.Option
}
func newNatsConnPool(logger *logrus.Logger, natsDefaults *natsContextDefaults, natsOpts []nats.Option) *natsConnPool {
return &natsConnPool{
cache: map[string]*pooledNatsConn{},
group: &singleflight.Group{},
logger: logger,
natsDefaults: natsDefaults,
natsOpts: natsOpts,
}
}
const getPooledConnMaxTries = 10
// Get returns a *pooledNatsConn
func (cp *natsConnPool) Get(cfg *natsContext) (*pooledNatsConn, error) {
if cfg == nil {
return nil, fmt.Errorf("nats context must not be nil")
}
// copy cfg
cfg = cfg.copy()
// set defaults
if cfg.Name == "" {
cfg.Name = cp.natsDefaults.Name
}
if cfg.URL == "" {
cfg.URL = cp.natsDefaults.URL
}
if len(cfg.TLSCAs) == 0 {
cfg.TLSCAs = cp.natsDefaults.TLSCAs
}
if cfg.TLSCert == "" {
cfg.TLSCert = cp.natsDefaults.TLSCert
}
if cfg.TLSKey == "" {
cfg.TLSKey = cp.natsDefaults.TLSKey
}
// get hash
key, err := cfg.hash()
if err != nil {
return nil, err
}
for i := 0; i < getPooledConnMaxTries; i++ {
connection, err := cp.getPooledConn(key, cfg)
if err != nil {
return nil, err
}
cp.Lock()
if connection.closed {
// ReturnToPool closed this while lock not held, try again
cp.Unlock()
continue
}
// increment count out of the pool
connection.count++
cp.Unlock()
return connection, nil
}
return nil, fmt.Errorf("failed to get pooled connection after %d attempts", getPooledConnMaxTries)
}
// getPooledConn gets or establishes a *pooledNatsConn in a singleflight group, but does not increment its count
func (cp *natsConnPool) getPooledConn(key string, cfg *natsContext) (*pooledNatsConn, error) {
conn, err, _ := cp.group.Do(key, func() (interface{}, error) {
cp.Lock()
pooledConn, ok := cp.cache[key]
if ok && pooledConn.nc.IsConnected() {
cp.Unlock()
return pooledConn, nil
}
cp.Unlock()
opts := cp.natsOpts
opts = append(opts, func(options *nats.Options) error {
if cfg.Name != "" {
options.Name = cfg.Name
}
if cfg.Token != "" {
options.Token = cfg.Token
}
if cfg.Username != "" {
options.User = cfg.Username
}
if cfg.Password != "" {
options.Password = cfg.Password
}
return nil
})
if cfg.JWT != "" && cfg.Seed != "" {
opts = append(opts, nats.UserJWTAndSeed(cfg.JWT, cfg.Seed))
}
if cfg.Nkey != "" {
opt, err := nats.NkeyOptionFromSeed(cfg.Nkey)
if err != nil {
return nil, fmt.Errorf("unable to load nkey: %v", err)
}
opts = append(opts, opt)
}
if cfg.Credentials != "" {
opts = append(opts, nats.UserCredentials(cfg.Credentials))
}
if len(cfg.TLSCAs) > 0 {
opts = append(opts, nats.RootCAs(cfg.TLSCAs...))
}
if cfg.TLSCert != "" && cfg.TLSKey != "" {
opts = append(opts, nats.ClientCert(cfg.TLSCert, cfg.TLSKey))
}
nc, err := nats.Connect(cfg.URL, opts...)
if err != nil {
return nil, err
}
cp.logger.Infof("%s connected to NATS Deployment: %s", cfg.Name, nc.ConnectedAddr())
connection := &pooledNatsConn{
nc: nc,
cp: cp,
key: key,
}
cp.Lock()
cp.cache[key] = connection
cp.Unlock()
return connection, err
})
if err != nil {
return nil, err
}
connection, ok := conn.(*pooledNatsConn)
if !ok {
return nil, fmt.Errorf("not a pooledNatsConn")
}
return connection, nil
}
================================================
FILE: controllers/jetstream/conn_pool_test.go
================================================
package jetstream
import (
"sync"
"testing"
"time"
"github.com/nats-io/nats.go"
natsservertest "github.com/nats-io/nats-server/v2/test"
"github.com/sirupsen/logrus"
testifyAssert "github.com/stretchr/testify/assert"
)
func TestConnPool(t *testing.T) {
t.Parallel()
s := natsservertest.RunRandClientPortServer()
defer s.Shutdown()
o1 := &natsContext{
Name: "Client 1",
}
o2 := &natsContext{
Name: "Client 1",
}
o3 := &natsContext{
Name: "Client 2",
}
natsDefaults := &natsContextDefaults{
URL: s.ClientURL(),
}
natsOptions := []nats.Option{
nats.MaxReconnects(10240),
}
cp := newNatsConnPool(logrus.New(), natsDefaults, natsOptions)
var c1, c2, c3 *pooledNatsConn
var c1e, c2e, c3e error
wg := &sync.WaitGroup{}
wg.Add(3)
go func() {
c1, c1e = cp.Get(o1)
wg.Done()
}()
go func() {
c2, c2e = cp.Get(o2)
wg.Done()
}()
go func() {
c3, c3e = cp.Get(o3)
wg.Done()
}()
wg.Wait()
assert := testifyAssert.New(t)
if assert.NoError(c1e) && assert.NoError(c2e) {
assert.Same(c1, c2)
}
if assert.NoError(c3e) {
assert.NotSame(c1, c3)
assert.NotSame(c2, c3)
}
c1.ReturnToPool()
c3.ReturnToPool()
time.Sleep(1 * time.Second)
assert.False(c1.nc.IsClosed())
assert.False(c2.nc.IsClosed())
assert.True(c3.nc.IsClosed())
c4, c4e := cp.Get(o1)
if assert.NoError(c4e) {
assert.Same(c2, c4)
}
c2.ReturnToPool()
c4.ReturnToPool()
time.Sleep(1 * time.Second)
assert.True(c1.nc.IsClosed())
assert.True(c2.nc.IsClosed())
assert.True(c4.nc.IsClosed())
c5, c5e := cp.Get(o1)
if assert.NoError(c5e) {
assert.NotSame(c1, c5)
}
c5.ReturnToPool()
time.Sleep(1 * time.Second)
assert.True(c5.nc.IsClosed())
}
================================================
FILE: controllers/jetstream/consumer.go
================================================
package jetstream
import (
"context"
"errors"
"fmt"
"strconv"
"time"
"github.com/nats-io/jsm.go"
jsmapi "github.com/nats-io/jsm.go/api"
apis "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2"
typed "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2"
k8sapi "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
k8smeta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/util/retry"
klog "k8s.io/klog/v2"
)
func (c *Controller) runConsumerQueue() {
for {
processQueueNext(c.cnsQueue, c.RealJSMC, c.processConsumer)
}
}
func (c *Controller) processConsumer(ns, name string, jsmClient jsmClientFunc) (err error) {
cns, err := c.cnsLister.Consumers(ns).Get(name)
if err != nil && k8serrors.IsNotFound(err) {
return nil
} else if err != nil {
return err
}
return c.processConsumerObject(cns, jsmClient)
}
func (c *Controller) processConsumerObject(cns *apis.Consumer, jsm jsmClientFunc) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("failed to process consumer: %w", err)
}
}()
ns := cns.Namespace
spec := cns.Spec
ifc := c.ji.Consumers(ns)
acc, err := c.getAccountOverrides(spec.Account, ns)
if err != nil {
return err
}
defer func() {
if err == nil {
return
}
if _, serr := setConsumerErrored(c.ctx, cns, ifc, err); serr != nil {
err = fmt.Errorf("%s: %w", err, serr)
}
}()
type operator func(ctx context.Context, c jsmClient, spec apis.ConsumerSpec) (err error)
natsClientUtil := func(op operator) error {
return c.runWithJsmc(jsm, acc, &jsmcSpecOverrides{
servers: spec.Servers,
tls: spec.TLS,
creds: spec.Creds,
nkey: spec.Nkey,
}, cns, func(jsmc jsmClient) error {
return op(c.ctx, jsmc, spec)
})
}
deleteOK := cns.GetDeletionTimestamp() != nil
newGeneration := cns.Generation != cns.Status.ObservedGeneration
consumerOK := true
err = natsClientUtil(consumerExists)
var apierr jsmapi.ApiError
if errors.As(err, &apierr) && apierr.NotFoundError() {
consumerOK = false
} else if err != nil {
return err
}
updateOK := (consumerOK && !deleteOK && newGeneration)
createOK := (!consumerOK && !deleteOK) || (!updateOK && !deleteOK && newGeneration)
switch {
case createOK:
c.normalEvent(cns, "Creating",
fmt.Sprintf("Creating consumer %q on stream %q", spec.DurableName, spec.StreamName))
if err := natsClientUtil(createConsumer); err != nil {
return err
}
if _, err := setConsumerOK(c.ctx, cns, ifc); err != nil {
return err
}
c.normalEvent(cns, "Created",
fmt.Sprintf("Created consumer %q on stream %q", spec.DurableName, spec.StreamName))
case updateOK:
if cns.Spec.PreventUpdate {
c.normalEvent(cns, "SkipUpdate", fmt.Sprintf("Skip updating consumer %q on stream %q", spec.DurableName, spec.StreamName))
if _, err := setConsumerOK(c.ctx, cns, ifc); err != nil {
return err
}
return nil
}
c.normalEvent(cns, "Updating", fmt.Sprintf("Updating consumer %q on stream %q", spec.DurableName, spec.StreamName))
if err := natsClientUtil(updateConsumer); err != nil {
return err
}
if _, err := setConsumerOK(c.ctx, cns, ifc); err != nil {
return err
}
c.normalEvent(cns, "Updated", fmt.Sprintf("Updated consumer %q on stream %q", spec.DurableName, spec.StreamName))
case deleteOK:
if cns.Spec.PreventDelete {
c.normalEvent(cns, "SkipDelete", fmt.Sprintf("Skip deleting consumer %q on stream %q", spec.DurableName, spec.StreamName))
if _, err := setConsumerOK(c.ctx, cns, ifc); err != nil {
return err
}
return nil
}
c.normalEvent(cns, "Deleting", fmt.Sprintf("Deleting consumer %q on stream %q", spec.DurableName, spec.StreamName))
if err := natsClientUtil(deleteConsumer); err != nil {
return err
}
default:
c.normalEvent(cns, "Noop", fmt.Sprintf("Nothing done for consumer %q (prevent-delete=%v, prevent-update=%v)",
spec.DurableName, spec.PreventDelete, spec.PreventUpdate,
))
if _, err := setConsumerOK(c.ctx, cns, ifc); err != nil {
return err
}
}
return nil
}
func consumerExists(ctx context.Context, c jsmClient, spec apis.ConsumerSpec) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("failed to check if consumer exists: %w", err)
}
}()
_, err = c.LoadConsumer(ctx, spec.StreamName, spec.DurableName)
return err
}
func createConsumer(ctx context.Context, c jsmClient, spec apis.ConsumerSpec) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("failed to create consumer %q on stream %q: %w", spec.DurableName, spec.StreamName, err)
}
}()
opts, err := consumerSpecToOpts(spec)
if err != nil {
return
}
_, err = c.NewConsumer(ctx, spec.StreamName, opts)
return
}
func updateConsumer(ctx context.Context, c jsmClient, spec apis.ConsumerSpec) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("failed to update consumer %q on stream %q: %w", spec.DurableName, spec.StreamName, err)
}
}()
js, err := c.LoadConsumer(ctx, spec.StreamName, spec.DurableName)
if err != nil {
return
}
opts, err := consumerSpecToOpts(spec)
if err != nil {
return
}
err = js.UpdateConfiguration(opts...)
return
}
func consumerSpecToOpts(spec apis.ConsumerSpec) ([]jsm.ConsumerOption, error) {
opts := []jsm.ConsumerOption{
jsm.DurableName(spec.DurableName),
jsm.DeliverySubject(spec.DeliverSubject),
jsm.RateLimitBitsPerSecond(uint64(spec.RateLimitBps)),
jsm.MaxAckPending(uint(spec.MaxAckPending)),
jsm.ConsumerDescription(spec.Description),
jsm.DeliverGroup(spec.DeliverGroup),
jsm.MaxWaiting(uint(spec.MaxWaiting)),
jsm.MaxRequestBatch(uint(spec.MaxRequestBatch)),
jsm.MaxRequestMaxBytes(spec.MaxRequestMaxBytes),
jsm.ConsumerOverrideReplicas(spec.Replicas),
}
if spec.FilterSubject != "" && len(spec.FilterSubjects) > 0 {
return nil, fmt.Errorf("cannot specify both FilterSubject and FilterSubjects")
}
if spec.FilterSubject != "" {
opts = append(opts, jsm.FilterStreamBySubject(spec.FilterSubject))
} else if len(spec.FilterSubjects) > 0 {
opts = append(opts, jsm.FilterStreamBySubject(spec.FilterSubjects...))
}
switch spec.DeliverPolicy {
case "all":
opts = append(opts, jsm.DeliverAllAvailable())
case "last":
opts = append(opts, jsm.StartWithLastReceived())
case "new":
opts = append(opts, jsm.StartWithNextReceived())
case "byStartSequence":
opts = append(opts, jsm.StartAtSequence(uint64(spec.OptStartSeq)))
case "byStartTime":
if spec.OptStartTime == "" {
return nil, fmt.Errorf("'optStartTime' is required for deliver policy 'byStartTime'")
}
t, err := time.Parse(time.RFC3339, spec.OptStartTime)
if err != nil {
return nil, err
}
opts = append(opts, jsm.StartAtTime(t))
case "lastPerSubject":
opts = append(opts, jsm.DeliverLastPerSubject())
case "":
default:
return nil, fmt.Errorf("invalid value for 'deliverPolicy': '%s'. Must be one of 'all', 'last', 'new', 'lastPerSubject', 'byStartSequence', 'byStartTime'", spec.DeliverPolicy)
}
switch spec.AckPolicy {
case "none":
opts = append(opts, jsm.AcknowledgeNone())
case "all":
opts = append(opts, jsm.AcknowledgeAll())
case "explicit":
opts = append(opts, jsm.AcknowledgeExplicit())
case "":
default:
return nil, fmt.Errorf("invalid value for 'ackPolicy': '%s'. Must be one of 'none', 'all', 'explicit'", spec.AckPolicy)
}
if spec.AckWait != "" {
d, err := time.ParseDuration(spec.AckWait)
if err != nil {
return nil, err
}
opts = append(opts, jsm.AckWait(d))
}
switch spec.ReplayPolicy {
case "instant":
opts = append(opts, jsm.ReplayInstantly())
case "original":
opts = append(opts, jsm.ReplayAsReceived())
case "":
default:
return nil, fmt.Errorf("invalid value for 'replayPolicy': '%s'. Must be one of 'instant', 'original'", spec.ReplayPolicy)
}
if spec.SampleFreq != "" {
n, err := strconv.Atoi(spec.SampleFreq)
if err != nil {
return nil, err
}
opts = append(opts, jsm.SamplePercent(n))
}
if spec.FlowControl {
opts = append(opts, jsm.PushFlowControl())
}
if spec.HeartbeatInterval != "" {
d, err := time.ParseDuration(spec.HeartbeatInterval)
if err != nil {
return nil, err
}
opts = append(opts, jsm.IdleHeartbeat(d))
}
if len(spec.BackOff) > 0 {
backoffs := make([]time.Duration, 0)
for _, backoff := range spec.BackOff {
dur, err := time.ParseDuration(backoff)
if err != nil {
return nil, err
}
backoffs = append(backoffs, dur)
}
opts = append(opts, jsm.BackoffIntervals(backoffs...))
}
if spec.HeadersOnly {
opts = append(opts, jsm.DeliverHeadersOnly())
} else {
opts = append(opts, jsm.DeliverBodies())
}
if spec.MaxRequestExpires != "" {
dur, err := time.ParseDuration(spec.MaxRequestExpires)
if err != nil {
return nil, err
}
opts = append(opts, jsm.MaxRequestExpires(dur))
}
if spec.MemStorage {
opts = append(opts, jsm.ConsumerOverrideMemoryStorage())
}
if spec.MaxDeliver != 0 {
opts = append(opts, jsm.MaxDeliveryAttempts(spec.MaxDeliver))
}
if spec.Metadata != nil {
opts = append(opts, jsm.ConsumerMetadata(spec.Metadata))
}
if spec.InactiveThreshold != "" {
dur, err := time.ParseDuration(spec.InactiveThreshold)
if err != nil {
return nil, err
}
opts = append(opts, jsm.InactiveThreshold(dur))
}
// Handle PauseUntil for pausing consumer
if spec.PauseUntil != "" {
t, err := time.Parse(time.RFC3339, spec.PauseUntil)
if err != nil {
return nil, fmt.Errorf("invalid pauseUntil time: %w", err)
}
opts = append(opts, jsm.PauseUntil(t))
}
// Handle PriorityPolicy with PriorityGroups and PinnedTTL
switch spec.PriorityPolicy {
case "", "none":
// Default is none, no need to set
case "pinned_client":
if spec.PinnedTTL != "" {
dur, err := time.ParseDuration(spec.PinnedTTL)
if err != nil {
return nil, fmt.Errorf("invalid pinnedTTL duration: %w", err)
}
opts = append(opts, jsm.PinnedClientPriorityGroups(dur, spec.PriorityGroups...))
}
case "overflow":
opts = append(opts, jsm.OverflowPriorityGroups(spec.PriorityGroups...))
case "prioritized":
opts = append(opts, jsm.PrioritizedPriorityGroups(spec.PriorityGroups...))
default:
return nil, fmt.Errorf("invalid priority policy: %s", spec.PriorityPolicy)
}
return opts, nil
}
func deleteConsumer(ctx context.Context, c jsmClient, spec apis.ConsumerSpec) (err error) {
stream, consumer := spec.StreamName, spec.DurableName
defer func() {
if err != nil {
err = fmt.Errorf("failed to delete consumer %q on stream %q: %w", consumer, stream, err)
}
}()
if spec.PreventDelete {
klog.Infof("Consumer %q is configured to preventDelete on stream %q:", stream, consumer)
return nil
}
var apierr jsmapi.ApiError
cn, err := c.LoadConsumer(ctx, stream, consumer)
if errors.As(err, &apierr) && apierr.NotFoundError() {
return nil
} else if err != nil {
return err
}
return cn.Delete()
}
func setConsumerOK(ctx context.Context, s *apis.Consumer, i typed.ConsumerInterface) (*apis.Consumer, error) {
sc := s.DeepCopy()
sc.Status.ObservedGeneration = s.Generation
sc.Status.Conditions = UpsertCondition(sc.Status.Conditions, apis.Condition{
Type: readyCondType,
Status: k8sapi.ConditionTrue,
LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),
Reason: "Created",
Message: "Consumer successfully created",
})
var res *apis.Consumer
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
var err error
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
res, err = i.UpdateStatus(ctx, sc, k8smeta.UpdateOptions{})
if err != nil {
return fmt.Errorf("failed to set consumer %q status: %w", s.Spec.DurableName, err)
}
return nil
})
return res, err
}
func setConsumerErrored(ctx context.Context, s *apis.Consumer, sif typed.ConsumerInterface, err error) (*apis.Consumer, error) {
if err == nil {
return s, nil
}
sc := s.DeepCopy()
sc.Status.Conditions = UpsertCondition(sc.Status.Conditions, apis.Condition{
Type: readyCondType,
Status: k8sapi.ConditionFalse,
LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),
Reason: "Errored",
Message: err.Error(),
})
var res *apis.Consumer
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
var err error
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
res, err = sif.UpdateStatus(ctx, sc, k8smeta.UpdateOptions{})
if err != nil {
return fmt.Errorf("failed to set consumer errored status: %w", err)
}
return nil
})
return res, err
}
================================================
FILE: controllers/jetstream/consumer_test.go
================================================
package jetstream
import (
"context"
"errors"
"strings"
"testing"
"time"
jsmapi "github.com/nats-io/jsm.go/api"
apis "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2"
clientsetfake "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/fake"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
k8smeta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
k8sclientsetfake "k8s.io/client-go/kubernetes/fake"
k8stesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/record"
)
func TestProcessConsumer(t *testing.T) {
t.Parallel()
updateObject := func(a k8stesting.Action) (handled bool, o runtime.Object, err error) {
ua, ok := a.(k8stesting.UpdateAction)
if !ok {
return false, nil, nil
}
return true, ua.GetObject(), nil
}
t.Run("create consumer", func(t *testing.T) {
t.Parallel()
jc := clientsetfake.NewSimpleClientset()
wantEvents := 2
rec := record.NewFakeRecorder(wantEvents)
ctrl := NewController(Options{
Ctx: context.Background(),
KubeIface: k8sclientsetfake.NewSimpleClientset(),
JetstreamIface: jc,
Recorder: rec,
})
ns, name := "default", "my-consumer"
informer := ctrl.informerFactory.Jetstream().V1beta2().Consumers()
err := informer.Informer().GetStore().Add(&apis.Consumer{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: ns,
Name: name,
Generation: 1,
},
Spec: apis.ConsumerSpec{
DurableName: name,
DeliverPolicy: "byStartTime",
OptStartTime: time.Now().Format(time.RFC3339),
AckPolicy: "explicit",
AckWait: "1m",
ReplayPolicy: "original",
SampleFreq: "50",
HeartbeatInterval: "30s",
BackOff: []string{"500ms", "1s"},
HeadersOnly: true,
MaxRequestExpires: "5m",
MemStorage: true,
},
})
if err != nil {
t.Fatal(err)
}
jc.PrependReactor("update", "consumers", updateObject)
notFoundErr := jsmapi.ApiError{Code: 404}
jsmc := &mockJsmClient{
loadConsumerErr: notFoundErr,
newConsumerErr: nil,
newConsumer: &mockConsumer{},
}
if err := ctrl.processConsumer(ns, name, func(n *natsContext) (jsmClient, error) {
return jsmc, nil
}); err != nil {
t.Fatal(err)
}
if got := len(rec.Events); got != wantEvents {
t.Error("unexpected number of events")
t.Fatalf("got=%d; want=%d", got, wantEvents)
}
for i := 0; i < len(rec.Events); i++ {
gotEvent := <-rec.Events
if !strings.Contains(gotEvent, "Creat") {
t.Error("unexpected event")
t.Fatalf("got=%s; want=%s", gotEvent, "Creating/Created...")
}
}
})
t.Run("create consumer, invalid configuration", func(t *testing.T) {
t.Parallel()
jc := clientsetfake.NewSimpleClientset()
wantEvents := 1
rec := record.NewFakeRecorder(wantEvents)
ctrl := NewController(Options{
Ctx: context.Background(),
KubeIface: k8sclientsetfake.NewSimpleClientset(),
JetstreamIface: jc,
Recorder: rec,
})
ns, name := "default", "my-consumer"
informer := ctrl.informerFactory.Jetstream().V1beta2().Consumers()
err := informer.Informer().GetStore().Add(&apis.Consumer{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: ns,
Name: name,
Generation: 1,
},
Spec: apis.ConsumerSpec{
DurableName: name,
DeliverPolicy: "invalid",
},
})
if err != nil {
t.Fatal(err)
}
jc.PrependReactor("update", "consumers", updateObject)
notFoundErr := jsmapi.ApiError{Code: 404}
jsmc := &mockJsmClient{
loadConsumerErr: notFoundErr,
newConsumerErr: nil,
newConsumer: &mockConsumer{},
}
if err := ctrl.processConsumer(ns, name, testWrapJSMC(jsmc)); err == nil || !strings.Contains(err.Error(), `failed to create consumer "my-consumer" on stream `) {
t.Fatal(err)
}
if got := len(rec.Events); got != wantEvents {
t.Error("unexpected number of events")
t.Fatalf("got=%d; want=%d", got, wantEvents)
}
gotEvent := <-rec.Events
if !strings.Contains(gotEvent, "Creating") {
t.Error("unexpected event")
t.Fatalf("got=%s; want=%s", gotEvent, "Creating...")
}
})
t.Run("update consumer", func(t *testing.T) {
t.Parallel()
jc := clientsetfake.NewSimpleClientset()
wantEvents := 2
rec := record.NewFakeRecorder(wantEvents)
ctrl := NewController(Options{
Ctx: context.Background(),
KubeIface: k8sclientsetfake.NewSimpleClientset(),
JetstreamIface: jc,
Recorder: rec,
})
ns, name := "default", "my-consumer"
informer := ctrl.informerFactory.Jetstream().V1beta2().Consumers()
err := informer.Informer().GetStore().Add(&apis.Consumer{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: ns,
Name: name,
Generation: 2,
},
Spec: apis.ConsumerSpec{
DurableName: name,
},
Status: apis.Status{
ObservedGeneration: 1,
},
})
if err != nil {
t.Fatal(err)
}
jc.PrependReactor("update", "consumers", updateObject)
jsmc := &mockJsmClient{
loadConsumerErr: nil,
loadConsumer: &mockConsumer{},
}
if err := ctrl.processConsumer(ns, name, testWrapJSMC(jsmc)); err != nil {
t.Fatal(err)
}
if got := len(rec.Events); got != wantEvents {
t.Error("unexpected number of events")
t.Fatalf("got=%d; want=%d", got, wantEvents)
}
for i := 0; i < len(rec.Events); i++ {
gotEvent := <-rec.Events
if !strings.Contains(gotEvent, "Updat") {
t.Error("unexpected event")
t.Fatalf("got=%s; want=%s", gotEvent, "Updating/Updated...")
}
}
})
t.Run("delete consumer", func(t *testing.T) {
t.Parallel()
jc := clientsetfake.NewSimpleClientset()
wantEvents := 1
rec := record.NewFakeRecorder(wantEvents)
ctrl := NewController(Options{
Ctx: context.Background(),
KubeIface: k8sclientsetfake.NewSimpleClientset(),
JetstreamIface: jc,
Recorder: rec,
})
ts := k8smeta.Unix(1600216923, 0)
ns, name := "default", "my-consumer"
informer := ctrl.informerFactory.Jetstream().V1beta2().Consumers()
err := informer.Informer().GetStore().Add(&apis.Consumer{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: ns,
Name: name,
DeletionTimestamp: &ts,
},
Spec: apis.ConsumerSpec{
DurableName: name,
},
})
if err != nil {
t.Fatal(err)
}
jc.PrependReactor("update", "consumers", updateObject)
jsmc := &mockJsmClient{
loadConsumerErr: nil,
loadConsumer: &mockConsumer{},
}
if err := ctrl.processConsumer(ns, name, testWrapJSMC(jsmc)); err != nil {
t.Fatal(err)
}
if got := len(rec.Events); got != wantEvents {
t.Error("unexpected number of events")
t.Fatalf("got=%d; want=%d", got, wantEvents)
}
gotEvent := <-rec.Events
if !strings.Contains(gotEvent, "Deleting") {
t.Error("unexpected event")
t.Fatalf("got=%s; want=%s", gotEvent, "Deleting...")
}
})
t.Run("process error", func(t *testing.T) {
t.Parallel()
jc := clientsetfake.NewSimpleClientset()
wantEvents := 1
rec := record.NewFakeRecorder(wantEvents)
ctrl := NewController(Options{
Ctx: context.Background(),
KubeIface: k8sclientsetfake.NewSimpleClientset(),
JetstreamIface: jc,
Recorder: rec,
})
ns, name := "default", "my-consumer"
informer := ctrl.informerFactory.Jetstream().V1beta2().Consumers()
err := informer.Informer().GetStore().Add(&apis.Consumer{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: ns,
Name: name,
Generation: 1,
},
Spec: apis.ConsumerSpec{
DurableName: name,
},
})
if err != nil {
t.Fatal(err)
}
jc.PrependReactor("update", "consumers", func(a k8stesting.Action) (handled bool, o runtime.Object, err error) {
ua, ok := a.(k8stesting.UpdateAction)
if !ok {
return false, nil, nil
}
obj := ua.GetObject()
str, ok := obj.(*apis.Consumer)
if !ok {
t.Error("unexpected object type")
t.Fatalf("got=%T; want=%T", obj, &apis.Consumer{})
}
if got, want := len(str.Status.Conditions), 1; got != want {
t.Error("unexpected number of conditions")
t.Fatalf("got=%d; want=%d", got, want)
}
if got, want := str.Status.Conditions[0].Reason, "Errored"; got != want {
t.Error("unexpected condition reason")
t.Fatalf("got=%s; want=%s", got, want)
}
return true, obj, nil
})
jsmc := &mockJsmClient{
loadConsumerErr: errors.New("failed to load consumer"),
}
if err := ctrl.processConsumer(ns, name, testWrapJSMC(jsmc)); err == nil {
t.Fatal("unexpected success")
}
})
}
func TestConsumerSpecToOpts(t *testing.T) {
tests := map[string]struct {
name string
given apis.ConsumerSpec
expected jsmapi.ConsumerConfig
errCheck func(t *testing.T, err error)
}{
"valid consumer spec": {
given: apis.ConsumerSpec{
DurableName: "my-consumer",
DeliverPolicy: "byStartSequence",
OptStartSeq: 10,
AckPolicy: "explicit",
AckWait: "1m",
ReplayPolicy: "original",
SampleFreq: "50",
HeartbeatInterval: "30s",
BackOff: []string{"500ms", "1s"},
HeadersOnly: true,
MaxRequestExpires: "5m",
MemStorage: true,
},
expected: jsmapi.ConsumerConfig{
AckPolicy: jsmapi.AckExplicit,
AckWait: 1 * time.Minute,
DeliverPolicy: jsmapi.DeliverByStartSequence,
Durable: "my-consumer",
Heartbeat: 30 * time.Second,
BackOff: []time.Duration{500 * time.Millisecond, 1 * time.Second},
OptStartSeq: 10,
ReplayPolicy: jsmapi.ReplayOriginal,
SampleFrequency: "50%",
HeadersOnly: true,
MaxRequestExpires: 5 * time.Minute,
MemoryStorage: true,
},
},
"valid consumer spec, defaults only": {
given: apis.ConsumerSpec{
DurableName: "my-consumer",
},
expected: jsmapi.ConsumerConfig{
Durable: "my-consumer",
},
},
"invalid deliver policy value": {
given: apis.ConsumerSpec{
DurableName: "my-consumer",
DeliverPolicy: "invalid",
},
errCheck: func(t *testing.T, err error) {
require.Error(t, err)
require.Contains(t, err.Error(), "invalid value for 'deliverPolicy': 'invalid'")
},
},
"missing start time for deliver policy byStartTime": {
given: apis.ConsumerSpec{
DurableName: "my-consumer",
DeliverPolicy: "byStartTime",
},
errCheck: func(t *testing.T, err error) {
require.Error(t, err)
require.Contains(t, err.Error(), "'optStartTime' is required for deliver policy 'byStartTime'")
},
},
"deliver policy lastPerSubject": {
given: apis.ConsumerSpec{
DurableName: "my-consumer",
DeliverPolicy: "lastPerSubject",
},
expected: jsmapi.ConsumerConfig{
Durable: "my-consumer",
DeliverPolicy: jsmapi.DeliverLastPerSubject,
},
},
"invalid ack policy": {
given: apis.ConsumerSpec{
DurableName: "my-consumer",
AckPolicy: "invalid",
},
errCheck: func(t *testing.T, err error) {
require.Error(t, err)
require.Contains(t, err.Error(), "invalid value for 'ackPolicy': 'invalid'")
},
},
"invalid replay policy": {
given: apis.ConsumerSpec{
DurableName: "my-consumer",
ReplayPolicy: "invalid",
},
errCheck: func(t *testing.T, err error) {
require.Error(t, err)
require.Contains(t, err.Error(), "invalid value for 'replayPolicy': 'invalid'")
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
res, err := consumerSpecToOpts(test.given)
if test.errCheck != nil {
test.errCheck(t, err)
return
}
require.NoError(t, err)
var config jsmapi.ConsumerConfig
for _, opt := range res {
err := opt(&config)
require.NoError(t, err)
}
assert.Equal(t, test.expected, config)
})
}
}
func testWrapJSMC(jsm jsmClient) jsmClientFunc {
return func(n *natsContext) (jsmClient, error) {
return jsm, nil
}
}
================================================
FILE: controllers/jetstream/controller.go
================================================
// Copyright 2020-2022 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package jetstream
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/nats-io/jsm.go"
jsmapi "github.com/nats-io/jsm.go/api"
"github.com/nats-io/nats.go"
"github.com/sirupsen/logrus"
apis "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2"
clientset "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned"
scheme "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/scheme"
typed "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2"
informers "github.com/nats-io/nack/pkg/jetstream/generated/informers/externalversions"
listers "github.com/nats-io/nack/pkg/jetstream/generated/listers/jetstream/v1beta2"
k8sapi "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
k8smeta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
k8sscheme "k8s.io/client-go/kubernetes/scheme"
k8styped "k8s.io/client-go/kubernetes/typed/core/v1"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
klog "k8s.io/klog/v2"
)
const (
// maxQueueRetries is the max times an item will be retried. An item will
// be pulled maxQueueRetries+1 times from the queue. On pull number
// maxQueueRetries+1, if it fails again, it won't be retried.
maxQueueRetries = 10
// readyCondType is the Ready condition type.
readyCondType = "Ready"
)
type Options struct {
Ctx context.Context
KubeIface kubernetes.Interface
JetstreamIface clientset.Interface
NATSClientName string
NATSCredentials string
NATSNKey string
NATSServerURL string
NATSCA string
NATSCertificate string
NATSKey string
NATSTLSFirst bool
Namespace string
CRDConnect bool
CleanupPeriod time.Duration
ReadOnly bool
Recorder record.EventRecorder
}
type Controller struct {
ctx context.Context
opts Options
connPool *natsConnPool
ki k8styped.CoreV1Interface
ji typed.JetstreamV1beta2Interface
informerFactory informers.SharedInformerFactory
rec record.EventRecorder
strLister listers.StreamLister
strSynced cache.InformerSynced
strQueue workqueue.TypedRateLimitingInterface[any]
cnsLister listers.ConsumerLister
cnsSynced cache.InformerSynced
cnsQueue workqueue.TypedRateLimitingInterface[any]
accLister listers.AccountLister
// Informers for unsupported resources (KeyValue and ObjectStore)
// These are only used to emit warnings in legacy mode
kvLister listers.KeyValueLister
kvSynced cache.InformerSynced
osLister listers.ObjectStoreLister
osSynced cache.InformerSynced
// cacheDir is where the downloaded TLS certs from the server
// will be stored temporarily.
cacheDir string
}
func NewController(opt Options) *Controller {
resyncPeriod := 30 * time.Second
informerFactory := informers.NewSharedInformerFactoryWithOptions(opt.JetstreamIface, resyncPeriod, informers.WithNamespace(opt.Namespace))
streamInformer := informerFactory.Jetstream().V1beta2().Streams()
consumerInformer := informerFactory.Jetstream().V1beta2().Consumers()
accountInformer := informerFactory.Jetstream().V1beta2().Accounts()
keyValueInformer := informerFactory.Jetstream().V1beta2().KeyValues()
objectStoreInformer := informerFactory.Jetstream().V1beta2().ObjectStores()
if opt.Recorder == nil {
utilruntime.Must(scheme.AddToScheme(k8sscheme.Scheme))
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(klog.Infof)
eventBroadcaster.StartRecordingToSink(&k8styped.EventSinkImpl{
Interface: opt.KubeIface.CoreV1().Events(""),
})
opt.Recorder = eventBroadcaster.NewRecorder(k8sscheme.Scheme, k8sapi.EventSource{
Component: "jetstream-controller",
})
}
if opt.NATSClientName == "" {
opt.NATSClientName = "jetstream-controller"
}
ji := opt.JetstreamIface.JetstreamV1beta2()
streamQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any](), "Streams")
consumerQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any](), "Consumers")
streamInformer.Informer().AddEventHandler(
eventHandlers(
streamQueue,
),
)
consumerInformer.Informer().AddEventHandler(
eventHandlers(
consumerQueue,
),
)
// Add warning handlers for unsupported resources in legacy mode
keyValueInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
if kv, ok := obj.(*apis.KeyValue); ok {
klog.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)
opt.Recorder.Event(kv, k8sapi.EventTypeWarning, "NotSupported", "KeyValue resources require --control-loop mode. The NATS KV bucket will NOT be created.")
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
if kv, ok := newObj.(*apis.KeyValue); ok {
klog.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)
opt.Recorder.Event(kv, k8sapi.EventTypeWarning, "NotSupported", "KeyValue resources require --control-loop mode. Updates will NOT be applied.")
}
},
},
)
objectStoreInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
if os, ok := obj.(*apis.ObjectStore); ok {
klog.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)
opt.Recorder.Event(os, k8sapi.EventTypeWarning, "NotSupported", "ObjectStore resources require --control-loop mode. The NATS object store will NOT be created.")
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
if os, ok := newObj.(*apis.ObjectStore); ok {
klog.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)
opt.Recorder.Event(os, k8sapi.EventTypeWarning, "NotSupported", "ObjectStore resources require --control-loop mode. Updates will NOT be applied.")
}
},
},
)
cacheDir, err := os.MkdirTemp(".", "nack")
if err != nil {
panic(err)
}
defer os.RemoveAll(cacheDir)
return &Controller{
ctx: opt.Ctx,
opts: opt,
ki: opt.KubeIface.CoreV1(),
ji: ji,
informerFactory: informerFactory,
rec: opt.Recorder,
strLister: streamInformer.Lister(),
strSynced: streamInformer.Informer().HasSynced,
strQueue: streamQueue,
cnsLister: consumerInformer.Lister(),
cnsSynced: consumerInformer.Informer().HasSynced,
cnsQueue: consumerQueue,
accLister: accountInformer.Lister(),
kvLister: keyValueInformer.Lister(),
kvSynced: keyValueInformer.Informer().HasSynced,
osLister: objectStoreInformer.Lister(),
osSynced: objectStoreInformer.Informer().HasSynced,
cacheDir: cacheDir,
}
}
func (c *Controller) Run() error {
// Connect to NATS.
opts := make([]nats.Option, 0)
// Always attempt to have a connection to NATS.
opts = append(opts, nats.MaxReconnects(-1))
if c.opts.NATSTLSFirst {
opts = append(opts, nats.TLSHandshakeFirst())
}
natsCtxDefaults := &natsContextDefaults{Name: c.opts.NATSClientName}
if !c.opts.CRDConnect {
// Use JWT/NKEYS based credentials if present.
if c.opts.NATSCredentials != "" {
opts = append(opts, nats.UserCredentials(c.opts.NATSCredentials))
} else if c.opts.NATSNKey != "" {
opt, err := nats.NkeyOptionFromSeed(c.opts.NATSNKey)
if err != nil {
return nil
}
opts = append(opts, opt)
}
if c.opts.NATSCertificate != "" && c.opts.NATSKey != "" {
natsCtxDefaults.TLSCert = c.opts.NATSCertificate
natsCtxDefaults.TLSKey = c.opts.NATSKey
}
if c.opts.NATSCA != "" {
natsCtxDefaults.TLSCAs = []string{c.opts.NATSCA}
}
natsCtxDefaults.URL = c.opts.NATSServerURL
ncp := newNatsConnPool(logrus.New(), natsCtxDefaults, opts)
pooledNc, err := ncp.Get(&natsContext{})
if err != nil {
return fmt.Errorf("failed to connect to nats: %w", err)
}
pooledNc.ReturnToPool()
c.connPool = ncp
} else {
c.connPool = newNatsConnPool(logrus.New(), natsCtxDefaults, opts)
}
defer utilruntime.HandleCrash()
defer c.strQueue.ShutDown()
defer c.cnsQueue.ShutDown()
c.informerFactory.Start(c.ctx.Done())
if !cache.WaitForCacheSync(c.ctx.Done(), c.strSynced) {
return fmt.Errorf("failed to wait for stream cache sync")
}
if !cache.WaitForCacheSync(c.ctx.Done(), c.cnsSynced) {
return fmt.Errorf("failed to wait for consumer cache sync")
}
// Also wait for KeyValue and ObjectStore caches to sync so we can emit warnings
if !cache.WaitForCacheSync(c.ctx.Done(), c.kvSynced) {
return fmt.Errorf("failed to wait for keyvalue cache sync")
}
if !cache.WaitForCacheSync(c.ctx.Done(), c.osSynced) {
return fmt.Errorf("failed to wait for objectstore cache sync")
}
go wait.Until(c.runStreamQueue, time.Second, c.ctx.Done())
go wait.Until(c.runConsumerQueue, time.Second, c.ctx.Done())
go c.cleanupStreams()
go c.cleanupConsumers()
<-c.ctx.Done()
// Gracefully shutdown.
return nil
}
// RealJSMC creates a new JSM client from pooled nats connections
// Providing a blank string for servers, defaults to c.opts.NATSServerUrls
// call deferred jsmC.Close() on returned instance to return the nats connection to pool
func (c *Controller) RealJSMC(cfg *natsContext) (jsmClient, error) {
if cfg == nil {
cfg = &natsContext{}
}
pooledNc, err := c.connPool.Get(cfg)
if err != nil {
return nil, err
}
jm, err := jsm.New(pooledNc.nc)
if err != nil {
return nil, err
}
jsmc := &realJsmClient{pooledNc: pooledNc, jm: jm}
return jsmc, nil
}
func selectMissingStreamsFromList(prev, cur map[string]*apis.Stream) []*apis.Stream {
var deleted []*apis.Stream
for name, ps := range prev {
if _, ok := cur[name]; !ok {
deleted = append(deleted, ps)
}
}
return deleted
}
func streamsMap(ss []*apis.Stream) map[string]*apis.Stream {
m := make(map[string]*apis.Stream)
for _, s := range ss {
m[fmt.Sprintf("%s/%s", s.Namespace, s.Name)] = s
}
return m
}
func (c *Controller) cleanupStreams() error {
if c.opts.ReadOnly {
return nil
}
tick := time.NewTicker(c.opts.CleanupPeriod)
defer tick.Stop()
// Track the Stream CRDs that may have been created.
var prevStreams map[string]*apis.Stream
for {
select {
case <-c.ctx.Done():
return c.ctx.Err()
case <-tick.C:
streams, err := c.strLister.List(labels.Everything())
if err != nil {
klog.Infof("failed to list streams for cleanup: %s", err)
continue
}
sm := streamsMap(streams)
missing := selectMissingStreamsFromList(prevStreams, sm)
for _, s := range missing {
// A stream that we were tracking but that for some reason
// was not part of the latest list shared by informer.
// Need to double check whether the stream is present before
// considering deletion.
klog.Infof("stream %s/%s might be missing, looking it up...", s.Namespace, s.Name)
ctx, done := context.WithTimeout(context.Background(), 10*time.Second)
defer done()
_, err := c.ji.Streams(s.Namespace).Get(ctx, s.Name, k8smeta.GetOptions{})
if err != nil {
if k8serrors.IsNotFound(err) {
klog.Infof("stream %s/%s was not found anymore, deleting from JetStream", s.Namespace, s.Name)
t := k8smeta.NewTime(time.Now())
s.DeletionTimestamp = &t
if err := c.processStreamObject(s, c.RealJSMC); err != nil && !k8serrors.IsNotFound(err) {
klog.Infof("failed to delete stream %s/%s: %s", s.Namespace, s.Name, err)
continue
}
klog.Infof("deleted stream %s/%s from JetStream", s.Namespace, s.Name)
} else {
klog.Warningf("error looking up stream %s/%s", s.Namespace, s.Name)
}
} else {
klog.Infof("found stream %s/%s, no further action needed", s.Namespace, s.Name)
}
}
prevStreams = sm
}
}
}
func selectMissingConsumersFromList(prev, cur map[string]*apis.Consumer) []*apis.Consumer {
var deleted []*apis.Consumer
for name, ps := range prev {
if _, ok := cur[name]; !ok {
deleted = append(deleted, ps)
}
}
return deleted
}
func consumerMap(cs []*apis.Consumer) map[string]*apis.Consumer {
m := make(map[string]*apis.Consumer)
for _, c := range cs {
m[fmt.Sprintf("%s/%s", c.Namespace, c.Name)] = c
}
return m
}
func (c *Controller) cleanupConsumers() error {
if c.opts.ReadOnly {
return nil
}
tick := time.NewTicker(c.opts.CleanupPeriod)
defer tick.Stop()
// Track consumers that may have been deleted.
var prevConsumers map[string]*apis.Consumer
for {
select {
case <-c.ctx.Done():
return c.ctx.Err()
case <-tick.C:
consumers, err := c.cnsLister.List(labels.Everything())
if err != nil {
klog.Infof("failed to list consumers for cleanup: %s", err)
continue
}
cm := consumerMap(consumers)
missing := selectMissingConsumersFromList(prevConsumers, cm)
for _, cns := range missing {
// A consumer that we were tracking but that for some reason
// was not part of the latest list shared by informer.
// Need to double check whether the consumer is present before
// considering deletion.
klog.Infof("consumer %s/%s might be missing, looking it up...", cns.Namespace, cns.Name)
ctx, done := context.WithTimeout(context.Background(), 10*time.Second)
defer done()
_, err := c.ji.Consumers(cns.Namespace).Get(ctx, cns.Name, k8smeta.GetOptions{})
if err != nil {
if k8serrors.IsNotFound(err) {
klog.Infof("consumer %s/%s was not found anymore, deleting from JetStream", cns.Namespace, cns.Name)
t := k8smeta.NewTime(time.Now())
cns.DeletionTimestamp = &t
if err := c.processConsumerObject(cns, c.RealJSMC); err != nil && !k8serrors.IsNotFound(err) {
klog.Infof("failed to delete consumer %s/%s: %s", cns.Namespace, cns.Name, err)
continue
}
klog.Infof("deleted consumer %s/%s from JetStream", cns.Namespace, cns.Name)
} else {
klog.Warningf("error looking up consumer %s/%s", cns.Namespace, cns.Name)
}
} else {
klog.Infof("found consumer %s/%s, no further action needed", cns.Namespace, cns.Name)
}
}
prevConsumers = cm
}
}
}
func (c *Controller) normalEvent(o runtime.Object, reason, message string) {
if c.rec != nil {
c.rec.Event(o, k8sapi.EventTypeNormal, reason, message)
}
}
func (c *Controller) warningEvent(o runtime.Object, reason, message string) {
if c.rec != nil {
c.rec.Event(o, k8sapi.EventTypeWarning, reason, message)
}
}
type accountOverrides struct {
remoteClientCert string
remoteClientKey string
remoteRootCA string
servers []string
userCreds string
nkey string
user string
password string
token string
}
func (c *Controller) getAccountOverrides(account string, ns string) (*accountOverrides, error) {
overrides := &accountOverrides{}
if account == "" || !c.opts.CRDConnect {
return overrides, nil
}
// Lookup the account using the REST client.
ctx, done := context.WithTimeout(context.Background(), 5*time.Second)
defer done()
acc, err := c.ji.Accounts(ns).Get(ctx, account, k8smeta.GetOptions{})
if err != nil {
return nil, err
}
overrides.servers = acc.Spec.Servers
// Lookup the TLS secrets
if acc.Spec.TLS != nil && acc.Spec.TLS.Secret != nil {
secretName := acc.Spec.TLS.Secret.Name
secret, err := c.ki.Secrets(ns).Get(c.ctx, secretName, k8smeta.GetOptions{})
if err != nil {
return nil, err
}
// Write this to the cacheDir.
accDir := filepath.Join(c.cacheDir, ns, account)
if err := os.MkdirAll(accDir, 0o755); err != nil {
return nil, err
}
var certData, keyData []byte
var certPath, keyPath string
for k, v := range secret.Data {
switch k {
case acc.Spec.TLS.ClientCert:
certPath = filepath.Join(accDir, k)
certData = v
case acc.Spec.TLS.ClientKey:
keyPath = filepath.Join(accDir, k)
keyData = v
case acc.Spec.TLS.RootCAs:
overrides.remoteRootCA = filepath.Join(accDir, k)
if err := os.WriteFile(overrides.remoteRootCA, v, 0o644); err != nil {
return nil, err
}
}
}
if certData != nil && keyData != nil {
overrides.remoteClientCert = certPath
overrides.remoteClientKey = keyPath
if err := os.WriteFile(certPath, certData, 0o644); err != nil {
return nil, err
}
if err := os.WriteFile(keyPath, keyData, 0o644); err != nil {
return nil, err
}
}
}
// Lookup the UserCredentials.
if acc.Spec.Creds != nil && acc.Spec.Creds.Secret != nil {
secretName := acc.Spec.Creds.Secret.Name
secret, err := c.ki.Secrets(ns).Get(c.ctx, secretName, k8smeta.GetOptions{})
if err != nil {
return nil, err
}
// Write the user credentials to the cache dir.
accDir := filepath.Join(c.cacheDir, ns, account)
if err := os.MkdirAll(accDir, 0o755); err != nil {
return nil, err
}
if credsBytes, ok := secret.Data[acc.Spec.Creds.File]; ok {
overrides.userCreds = filepath.Join(accDir, acc.Spec.Creds.File)
if err := os.WriteFile(overrides.userCreds, credsBytes, 0o644); err != nil {
return nil, err
}
}
}
// Lookup the NKey seed.
if acc.Spec.NKey != nil && acc.Spec.NKey.Secret != nil {
secretName := acc.Spec.NKey.Secret.Name
secret, err := c.ki.Secrets(ns).Get(c.ctx, secretName, k8smeta.GetOptions{})
if err != nil {
return nil, err
}
if nkeyBytes, ok := secret.Data[acc.Spec.NKey.Seed]; ok {
overrides.nkey = string(nkeyBytes)
}
}
// Lookup the Token.
if acc.Spec.Token != nil {
secretName := acc.Spec.Token.Secret.Name
secret, err := c.ki.Secrets(ns).Get(c.ctx, secretName, k8smeta.GetOptions{})
if err != nil {
return nil, err
}
if token, ok := secret.Data[acc.Spec.Token.Token]; ok {
overrides.token = string(token)
}
}
// Lookup the User.
if acc.Spec.User != nil {
secretName := acc.Spec.User.Secret.Name
secret, err := c.ki.Secrets(ns).Get(c.ctx, secretName, k8smeta.GetOptions{})
if err != nil {
return nil, err
}
userBytes := secret.Data[acc.Spec.User.User]
passwordBytes := secret.Data[acc.Spec.User.Password]
if userBytes != nil && passwordBytes != nil {
overrides.user = string(userBytes)
overrides.password = string(passwordBytes)
}
}
return overrides, nil
}
type jsmcSpecOverrides struct {
servers []string
tls *apis.TLS
creds string
nkey string
}
func (c *Controller) runWithJsmc(jsm jsmClientFunc, acc *accountOverrides, spec *jsmcSpecOverrides, o runtime.Object, op func(jsmClient) error) error {
if !c.opts.CRDConnect {
jsmc, err := jsm(&natsContext{})
if err != nil {
return err
}
return op(jsmc)
}
// Create a new client
natsCtx := &natsContext{}
// Use JWT/NKEYS/user-password/token based credentials if present.
if spec.creds != "" {
natsCtx.Credentials = spec.creds
} else if spec.nkey != "" {
natsCtx.Nkey = spec.nkey
}
if spec.tls != nil {
if spec.tls.ClientCert != "" && spec.tls.ClientKey != "" {
natsCtx.TLSCert = spec.tls.ClientCert
natsCtx.TLSKey = spec.tls.ClientKey
}
}
// Use fetched secrets for the account and server if defined.
if acc.remoteClientCert != "" && acc.remoteClientKey != "" {
natsCtx.TLSCert = acc.remoteClientCert
natsCtx.TLSKey = acc.remoteClientKey
}
if acc.remoteRootCA != "" {
natsCtx.TLSCAs = []string{acc.remoteRootCA}
}
if acc.userCreds != "" {
natsCtx.Credentials = acc.userCreds
} else if acc.nkey != "" {
natsCtx.Nkey = acc.nkey
}
if acc.user != "" && acc.password != "" {
natsCtx.Username = acc.user
natsCtx.Password = acc.password
} else if acc.token != "" {
natsCtx.Token = acc.token
}
if spec.tls != nil && len(spec.tls.RootCAs) > 0 {
natsCtx.TLSCAs = spec.tls.RootCAs
}
natsServers := strings.Join(append(spec.servers, acc.servers...), ",")
natsCtx.URL = natsServers
c.normalEvent(o, "Connecting", "Connecting to new nats-servers")
jsmc, err := jsm(natsCtx)
if err != nil {
return fmt.Errorf("failed to connect to nats-servers(%s): %w", natsServers, err)
}
defer jsmc.Close()
return op(jsmc)
}
func splitNamespaceName(item interface{}) (ns string, name string, err error) {
defer func() {
if err != nil {
err = fmt.Errorf("failed to split namespace-name: %w", err)
}
}()
key, ok := item.(string)
if !ok {
return "", "", fmt.Errorf("unexpected type: got=%T, want=%T", item, key)
}
ns, name, err = cache.SplitMetaNamespaceKey(key)
if err != nil {
return "", "", err
}
return ns, name, nil
}
func getStorageType(s string) (jsmapi.StorageType, error) {
switch s {
case strings.ToLower(jsmapi.FileStorage.String()):
return jsmapi.FileStorage, nil
case strings.ToLower(jsmapi.MemoryStorage.String()):
return jsmapi.MemoryStorage, nil
default:
return 0, fmt.Errorf("invalid jetstream storage option: %s", s)
}
}
func enqueueWork(q workqueue.TypedRateLimitingInterface[any], item interface{}) (err error) {
key, err := cache.MetaNamespaceKeyFunc(item)
if err != nil {
return fmt.Errorf("failed to enqueue work: %w", err)
}
q.Add(key)
return nil
}
type (
jsmClientFunc func(*natsContext) (jsmClient, error)
processorFunc func(ns, name string, jmsClient jsmClientFunc) error
)
func processQueueNext(q workqueue.TypedRateLimitingInterface[any], jmsClient jsmClientFunc, process processorFunc) {
item, shutdown := q.Get()
if shutdown {
return
}
defer q.Done(item)
ns, name, err := splitNamespaceName(item)
if err != nil {
// Probably junk, clean it up.
utilruntime.HandleError(err)
q.Forget(item)
return
}
err = process(ns, name, jmsClient)
if err == nil {
// Item processed successfully, don't requeue.
q.Forget(item)
return
}
utilruntime.HandleError(err)
if q.NumRequeues(item) < maxQueueRetries {
// Failed to process item, try again.
q.AddRateLimited(item)
return
}
// If we haven't been able to recover by this point, then just stop.
// The user should have enough info in kubectl describe to debug.
q.Forget(item)
}
func UpsertCondition(cs []apis.Condition, next apis.Condition) []apis.Condition {
for i := 0; i < len(cs); i++ {
if cs[i].Type != next.Type {
continue
}
cs[i] = next
return cs
}
return append(cs, next)
}
func shouldEnqueue(prevObj, nextObj interface{}) bool {
type crd interface {
GetDeletionTimestamp() *k8smeta.Time
GetSpec() interface{}
}
prev, ok := prevObj.(crd)
if !ok {
return false
}
next, ok := nextObj.(crd)
if !ok {
return false
}
markedDelete := next.GetDeletionTimestamp() != nil
specChanged := !equality.Semantic.DeepEqual(prev.GetSpec(), next.GetSpec())
return markedDelete || specChanged
}
func eventHandlers(q workqueue.TypedRateLimitingInterface[any]) cache.ResourceEventHandlerFuncs {
return cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
if err := enqueueWork(q, obj); err != nil {
utilruntime.HandleError(err)
}
},
UpdateFunc: func(prev, next interface{}) {
if !shouldEnqueue(prev, next) {
return
}
if err := enqueueWork(q, next); err != nil {
utilruntime.HandleError(err)
}
},
DeleteFunc: func(obj interface{}) {
if err := enqueueWork(q, obj); err != nil {
utilruntime.HandleError(err)
}
},
}
}
================================================
FILE: controllers/jetstream/controller_test.go
================================================
package jetstream
import (
"context"
"fmt"
"os"
"testing"
"time"
jsmapi "github.com/nats-io/jsm.go/api"
apis "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2"
k8sapis "k8s.io/api/core/v1"
k8smeta "k8s.io/apimachinery/pkg/apis/meta/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/util/workqueue"
)
func TestMain(m *testing.M) {
// Disable error logs.
utilruntime.ErrorHandlers = []utilruntime.ErrorHandler{
func(ctx context.Context, err error, msg string, args ...any) {},
}
os.Exit(m.Run())
}
func TestGetStorageType(t *testing.T) {
t.Parallel()
cases := []struct {
storage string
wantType jsmapi.StorageType
wantErr bool
}{
{storage: "memory", wantType: jsmapi.MemoryStorage},
{storage: "file", wantType: jsmapi.FileStorage},
{storage: "junk", wantErr: true},
}
for _, c := range cases {
c := c
t.Run(c.storage, func(t *testing.T) {
t.Parallel()
got, err := getStorageType(c.storage)
if err != nil && !c.wantErr {
t.Error("unexpected error")
t.Fatalf("got=%s; want=nil", err)
} else if err == nil && c.wantErr {
t.Error("unexpected success")
t.Fatalf("got=nil; want=err")
}
if got != c.wantType {
t.Error("unexpected storage type")
t.Fatalf("got=%v; want=%v", got, c.wantType)
}
})
}
}
func TestEnqueueWork(t *testing.T) {
t.Parallel()
limiter := workqueue.DefaultTypedControllerRateLimiter[any]()
q := workqueue.NewNamedRateLimitingQueue(limiter, "StreamsTest")
defer q.ShutDown()
s := &apis.Stream{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: "default",
Name: "my-stream",
},
}
if err := enqueueWork(q, s); err != nil {
t.Fatal(err)
}
if got, want := q.Len(), 1; got != want {
t.Error("unexpected queue length")
t.Fatalf("got=%d; want=%d", got, want)
}
wantItem := fmt.Sprintf("%s/%s", s.Namespace, s.Name)
gotItem, _ := q.Get()
if gotItem != wantItem {
t.Error("unexpected queue item")
t.Fatalf("got=%s; want=%s", gotItem, wantItem)
}
}
func TestProcessQueueNext(t *testing.T) {
t.Parallel()
t.Run("bad item key", func(t *testing.T) {
t.Parallel()
limiter := workqueue.DefaultTypedControllerRateLimiter[any]()
q := workqueue.NewNamedRateLimitingQueue(limiter, "StreamsTest")
defer q.ShutDown()
key := "this/is/a/bad/key"
q.Add(key)
processQueueNext(q, testWrapJSMC(&mockJsmClient{}), func(ns, name string, c jsmClientFunc) error {
return nil
})
if got, want := q.Len(), 0; got != want {
t.Error("unexpected number of items in queue")
t.Fatalf("got=%d; want=%d", got, want)
}
if got, want := q.NumRequeues(key), 0; got != want {
t.Error("unexpected number of requeues")
t.Fatalf("got=%d; want=%d", got, want)
}
})
t.Run("process error", func(t *testing.T) {
t.Parallel()
limiter := workqueue.DefaultTypedControllerRateLimiter[any]()
q := workqueue.NewNamedRateLimitingQueue(limiter, "StreamsTest")
defer q.ShutDown()
ns, name := "default", "mystream"
key := fmt.Sprintf("%s/%s", ns, name)
q.Add(key)
maxGets := maxQueueRetries + 1
numRequeues := -1
for i := 0; i < maxGets; i++ {
if i == maxGets-1 {
numRequeues = q.NumRequeues(key)
}
processQueueNext(q, testWrapJSMC(&mockJsmClient{}), func(ns, name string, c jsmClientFunc) error {
return fmt.Errorf("processing error")
})
}
if got, want := q.Len(), 0; got != want {
t.Error("unexpected number of items in queue")
t.Fatalf("got=%d; want=%d", got, want)
}
if got, want := numRequeues, 10; got != want {
t.Error("unexpected number of requeues")
t.Fatalf("got=%d; want=%d", got, want)
}
})
t.Run("process ok", func(t *testing.T) {
t.Parallel()
limiter := workqueue.DefaultTypedControllerRateLimiter[any]()
q := workqueue.NewNamedRateLimitingQueue(limiter, "StreamsTest")
defer q.ShutDown()
ns, name := "default", "mystream"
key := fmt.Sprintf("%s/%s", ns, name)
q.Add(key)
numRequeues := q.NumRequeues(key)
processQueueNext(q, testWrapJSMC(&mockJsmClient{}), func(ns, name string, c jsmClientFunc) error {
return nil
})
if got, want := q.Len(), 0; got != want {
t.Error("unexpected number of items in queue")
t.Fatalf("got=%d; want=%d", got, want)
}
if got, want := numRequeues, 0; got != want {
t.Error("unexpected number of requeues")
t.Fatalf("got=%d; want=%d", got, want)
}
})
}
func TestUpsertCondition(t *testing.T) {
t.Parallel()
var cs []apis.Condition
cs = UpsertCondition(cs, apis.Condition{
Type: readyCondType,
Status: k8sapis.ConditionTrue,
LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),
Reason: "Synced",
Message: "Stream is synced with spec",
})
if got, want := len(cs), 1; got != want {
t.Error("unexpected len conditions")
t.Fatalf("got=%d; want=%d", got, want)
}
if got, want := cs[0].Reason, "Synced"; got != want {
t.Error("unexpected reason")
t.Fatalf("got=%s; want=%s", got, want)
}
cs = UpsertCondition(cs, apis.Condition{
Type: readyCondType,
Status: k8sapis.ConditionFalse,
LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),
Reason: "Errored",
Message: "invalid foo",
})
if got, want := len(cs), 1; got != want {
t.Error("unexpected len conditions")
t.Fatalf("got=%d; want=%d", got, want)
}
if got, want := cs[0].Reason, "Errored"; got != want {
t.Error("unexpected reason")
t.Fatalf("got=%s; want=%s", got, want)
}
cs = UpsertCondition(cs, apis.Condition{
Type: "Foo",
Status: k8sapis.ConditionTrue,
LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),
Reason: "Bar",
Message: "bar ok",
})
if got, want := len(cs), 2; got != want {
t.Error("unexpected len conditions")
t.Fatalf("got=%d; want=%d", got, want)
}
if got, want := cs[1].Reason, "Bar"; got != want {
t.Error("unexpected reason")
t.Fatalf("got=%s; want=%s", got, want)
}
}
func TestShouldEnqueue(t *testing.T) {
t.Parallel()
ts := k8smeta.NewTime(time.Now())
cases := []struct {
name string
prev interface{}
next interface{}
want bool
}{
{
name: "stream deleted",
prev: &apis.Stream{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: "default",
Name: "obj-name",
},
},
next: &apis.Stream{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: "default",
Name: "obj-name",
DeletionTimestamp: &ts,
},
},
want: true,
},
{
name: "stream spec changed",
prev: &apis.Stream{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: "default",
Name: "obj-name",
},
Spec: apis.StreamSpec{
Name: "foo",
},
},
next: &apis.Stream{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: "default",
Name: "obj-name",
},
Spec: apis.StreamSpec{
Name: "bar",
},
},
want: true,
},
{
name: "consumer deleted",
prev: &apis.Consumer{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: "default",
Name: "obj-name",
},
},
next: &apis.Consumer{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: "default",
Name: "obj-name",
DeletionTimestamp: &ts,
},
},
want: true,
},
{
name: "consumer spec changed",
prev: &apis.Consumer{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: "default",
Name: "obj-name",
},
Spec: apis.ConsumerSpec{
DurableName: "foo",
},
},
next: &apis.Consumer{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: "default",
Name: "obj-name",
},
Spec: apis.ConsumerSpec{
DurableName: "bar",
},
},
want: true,
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
got := shouldEnqueue(c.prev, c.next)
if got != c.want {
t.Fatalf("got=%t; want=%t", got, c.want)
}
})
}
}
================================================
FILE: controllers/jetstream/jsmclient.go
================================================
package jetstream
import (
"context"
"github.com/nats-io/jsm.go"
jsmapi "github.com/nats-io/jsm.go/api"
"github.com/nats-io/nats.go"
"github.com/sirupsen/logrus"
)
type jsmClient interface {
Connect(servers string, opts ...nats.Option) error
Close()
LoadStream(ctx context.Context, name string) (jsmStream, error)
NewStream(ctx context.Context, name string, opts []jsm.StreamOption) (jsmStream, error)
LoadConsumer(ctx context.Context, stream, consumer string) (jsmConsumer, error)
NewConsumer(ctx context.Context, stream string, opts []jsm.ConsumerOption) (jsmConsumer, error)
}
type jsmStream interface {
UpdateConfiguration(cnf jsmapi.StreamConfig, opts ...jsm.StreamOption) error
Delete() error
}
type jsmConsumer interface {
UpdateConfiguration(opts ...jsm.ConsumerOption) error
Delete() error
}
type realJsmClient struct {
pooledNc *pooledNatsConn
jm *jsm.Manager
}
func (c *realJsmClient) Connect(servers string, opts ...nats.Option) error {
connPool := newNatsConnPool(logrus.New(), &natsContextDefaults{URL: servers}, opts)
pooledNc, err := connPool.Get(&natsContext{})
if err != nil {
return err
}
c.pooledNc = pooledNc
m, err := jsm.New(pooledNc.nc)
if err != nil {
return err
}
c.jm = m
return nil
}
func (c *realJsmClient) Close() {
c.pooledNc.ReturnToPool()
}
func (c *realJsmClient) LoadStream(_ context.Context, name string) (jsmStream, error) {
return c.jm.LoadStream(name)
}
func (c *realJsmClient) NewStream(_ context.Context, name string, opts []jsm.StreamOption) (jsmStream, error) {
return c.jm.NewStream(name, opts...)
}
func (c *realJsmClient) LoadConsumer(_ context.Context, stream, consumer string) (jsmConsumer, error) {
return c.jm.LoadConsumer(stream, consumer)
}
func (c *realJsmClient) NewConsumer(_ context.Context, stream string, opts []jsm.ConsumerOption) (jsmConsumer, error) {
return c.jm.NewConsumer(stream, opts...)
}
================================================
FILE: controllers/jetstream/jsmclient_test.go
================================================
package jetstream
import (
"context"
"github.com/nats-io/jsm.go"
jsmapi "github.com/nats-io/jsm.go/api"
"github.com/nats-io/nats.go"
)
type mockStream struct {
deleteErr error
capturedConfig *jsmapi.StreamConfig
updateConfigCallback func(cnf jsmapi.StreamConfig) error
}
func (m *mockStream) UpdateConfiguration(cnf jsmapi.StreamConfig, opts ...jsm.StreamOption) error {
if m.capturedConfig != nil {
*m.capturedConfig = cnf
}
if m.updateConfigCallback != nil {
return m.updateConfigCallback(cnf)
}
return nil
}
func (m *mockStream) Delete() error {
return m.deleteErr
}
type mockConsumer struct {
deleteErr error
}
func (m *mockConsumer) UpdateConfiguration(opts ...jsm.ConsumerOption) error {
return nil
}
func (m *mockConsumer) Delete() error {
return m.deleteErr
}
type mockJsmClient struct {
connectErr error
loadStream jsmStream
loadStreamErr error
newStream jsmStream
newStreamErr error
loadConsumer jsmConsumer
loadConsumerErr error
newConsumer jsmConsumer
newConsumerErr error
}
func (c *mockJsmClient) Connect(servers string, opts ...nats.Option) error {
return c.connectErr
}
func (c *mockJsmClient) Close() {}
func (c *mockJsmClient) LoadStream(ctx context.Context, name string) (jsmStream, error) {
return c.loadStream, c.loadStreamErr
}
func (c *mockJsmClient) NewStream(ctx context.Context, name string, opt []jsm.StreamOption) (jsmStream, error) {
return c.newStream, c.newStreamErr
}
func (c *mockJsmClient) LoadConsumer(ctx context.Context, stream, consumer string) (jsmConsumer, error) {
return c.loadConsumer, c.loadConsumerErr
}
func (c *mockJsmClient) NewConsumer(ctx context.Context, stream string, opts []jsm.ConsumerOption) (jsmConsumer, error) {
return c.newConsumer, c.newConsumerErr
}
================================================
FILE: controllers/jetstream/stream.go
================================================
// Copyright 2020 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package jetstream
import (
"context"
"errors"
"fmt"
"time"
jsm "github.com/nats-io/jsm.go"
jsmapi "github.com/nats-io/jsm.go/api"
apis "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2"
typed "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2"
k8sapi "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
k8smeta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/util/retry"
klog "k8s.io/klog/v2"
)
func (c *Controller) runStreamQueue() {
for {
processQueueNext(c.strQueue, c.RealJSMC, c.processStream)
}
}
func (c *Controller) processStream(ns, name string, jsm jsmClientFunc) (err error) {
str, err := c.strLister.Streams(ns).Get(name)
if err != nil && k8serrors.IsNotFound(err) {
return nil
} else if err != nil {
return err
}
return c.processStreamObject(str, jsm)
}
func (c *Controller) processStreamObject(str *apis.Stream, jsm jsmClientFunc) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("failed to process stream: %w", err)
}
}()
spec := str.Spec
ifc := c.ji.Streams(str.Namespace)
ns := str.Namespace
readOnly := c.opts.ReadOnly
acc, err := c.getAccountOverrides(spec.Account, ns)
if err != nil {
return err
}
defer func() {
if err == nil {
return
}
if _, serr := setStreamErrored(c.ctx, str, ifc, err); serr != nil {
err = fmt.Errorf("%s: %w", err, serr)
}
}()
type operator func(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err error)
natsClientUtil := func(op operator) error {
return c.runWithJsmc(jsm, acc, &jsmcSpecOverrides{
servers: spec.Servers,
tls: spec.TLS,
creds: spec.Creds,
nkey: spec.Nkey,
}, str, func(jsmc jsmClient) error {
return op(c.ctx, jsmc, spec)
})
}
deleteOK := str.GetDeletionTimestamp() != nil
newGeneration := str.Generation != str.Status.ObservedGeneration
strOK := true
err = natsClientUtil(streamExists)
var apierr jsmapi.ApiError
if errors.As(err, &apierr) && apierr.NotFoundError() {
strOK = false
} else if err != nil {
return err
}
updateOK := (strOK && !deleteOK && newGeneration)
createOK := (!strOK && !deleteOK) || (!updateOK && !deleteOK && newGeneration)
switch {
case createOK:
if readOnly {
c.normalEvent(str, "SkipCreate", fmt.Sprintf("Skip creating stream %q", spec.Name))
return nil
}
c.normalEvent(str, "Creating", fmt.Sprintf("Creating stream %q", spec.Name))
if err := natsClientUtil(createStream); err != nil {
return err
}
if _, err := setStreamOK(c.ctx, str, ifc); err != nil {
return err
}
c.normalEvent(str, "Created", fmt.Sprintf("Created stream %q", spec.Name))
case updateOK:
if str.Spec.PreventUpdate || readOnly {
c.normalEvent(str, "SkipUpdate", fmt.Sprintf("Skip updating stream %q", spec.Name))
if _, err := setStreamOK(c.ctx, str, ifc); err != nil {
return err
}
return nil
}
c.normalEvent(str, "Updating", fmt.Sprintf("Updating stream %q", spec.Name))
if err := natsClientUtil(updateStream); err != nil {
return err
}
if _, err := setStreamOK(c.ctx, str, ifc); err != nil {
return err
}
c.normalEvent(str, "Updated", fmt.Sprintf("Updated stream %q", spec.Name))
return nil
case deleteOK:
if str.Spec.PreventDelete || readOnly {
c.normalEvent(str, "SkipDelete", fmt.Sprintf("Skip deleting stream %q", spec.Name))
if _, err := setStreamOK(c.ctx, str, ifc); err != nil {
return err
}
return nil
}
c.normalEvent(str, "Deleting", fmt.Sprintf("Deleting stream %q", spec.Name))
if err := natsClientUtil(deleteStream); err != nil {
return err
}
default:
c.normalEvent(str, "Noop", fmt.Sprintf("Nothing done for stream %q (prevent-delete=%v, prevent-update=%v)",
spec.Name, spec.PreventDelete, spec.PreventUpdate,
))
// Noop events only update the status of the CRD.
if _, err := setStreamOK(c.ctx, str, ifc); err != nil {
return err
}
}
return nil
}
func streamExists(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("failed to check if stream exists: %w", err)
}
}()
_, err = c.LoadStream(ctx, spec.Name)
return err
}
func createStream(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("failed to create stream %q: %w", spec.Name, err)
}
}()
maxAge, err := getDurationFromString(spec.MaxAge)
if err != nil {
return err
}
duplicates, err := getDuplicates(spec.DuplicateWindow)
if err != nil {
return err
}
opts := []jsm.StreamOption{
jsm.Subjects(spec.Subjects...),
jsm.MaxConsumers(spec.MaxConsumers),
jsm.MaxMessageSize(int32(spec.MaxMsgSize)),
jsm.MaxMessages(int64(spec.MaxMsgs)),
jsm.Replicas(spec.Replicas),
jsm.DuplicateWindow(duplicates),
jsm.MaxAge(maxAge),
jsm.MaxBytes(int64(spec.MaxBytes)),
}
switch spec.Retention {
case "limits":
opts = append(opts, jsm.LimitsRetention())
case "interest":
opts = append(opts, jsm.InterestRetention())
case "workqueue":
opts = append(opts, jsm.WorkQueueRetention())
}
switch spec.Storage {
case "file":
opts = append(opts, jsm.FileStorage())
case "memory":
opts = append(opts, jsm.MemoryStorage())
}
switch spec.Discard {
case "old":
opts = append(opts, jsm.DiscardOld())
case "new":
opts = append(opts, jsm.DiscardNew())
}
switch spec.Compression {
case "s2":
opts = append(opts, jsm.Compression(jsmapi.S2Compression))
case "none":
opts = append(opts, jsm.Compression(jsmapi.NoCompression))
}
if spec.NoAck {
opts = append(opts, jsm.NoAck())
}
if spec.Description != "" {
opts = append(opts, func(o *jsmapi.StreamConfig) error {
o.Description = spec.Description
return nil
})
}
if spec.MaxMsgsPerSubject > 0 {
opts = append(opts, func(o *jsmapi.StreamConfig) error {
o.MaxMsgsPer = int64(spec.MaxMsgsPerSubject)
return nil
})
}
if spec.Mirror != nil {
ss, err := getStreamSource(spec.Mirror)
if err != nil {
return err
}
opts = append(opts, func(o *jsmapi.StreamConfig) error {
o.Mirror = ss
return nil
})
}
if spec.Placement != nil {
opts = append(opts, func(o *jsmapi.StreamConfig) error {
o.Placement = &jsmapi.Placement{
Cluster: spec.Placement.Cluster,
Tags: spec.Placement.Tags,
}
return nil
})
}
var srcs []*jsmapi.StreamSource
for _, ss := range spec.Sources {
jss, err := getStreamSource(ss)
if err != nil {
return err
}
srcs = append(srcs, jss)
}
opts = append(opts, func(o *jsmapi.StreamConfig) error {
o.Sources = srcs
return nil
})
if spec.RePublish != nil {
opts = append(opts, jsm.Republish(&jsmapi.RePublish{
Source: spec.RePublish.Source,
Destination: spec.RePublish.Destination,
}))
}
if spec.SubjectTransform != nil {
opts = append(opts, func(o *jsmapi.StreamConfig) error {
o.SubjectTransform = &jsmapi.SubjectTransformConfig{
Source: spec.SubjectTransform.Source,
Destination: spec.SubjectTransform.Dest,
}
return nil
})
}
if spec.AllowDirect {
opts = append(opts, jsm.AllowDirect())
}
if spec.AllowRollup {
opts = append(opts, jsm.AllowRollup())
}
if spec.DenyDelete {
opts = append(opts, jsm.DenyDelete())
}
if spec.DenyPurge {
opts = append(opts, jsm.DenyPurge())
}
if spec.DiscardPerSubject {
opts = append(opts, jsm.DiscardNewPerSubject())
}
if spec.FirstSequence != 0 {
opts = append(opts, jsm.FirstSequence(spec.FirstSequence))
}
if spec.Metadata != nil {
opts = append(opts, jsm.StreamMetadata(spec.Metadata))
}
if spec.AllowMsgTTL {
opts = append(opts, jsm.AllowMsgTTL())
}
if spec.SubjectDeleteMarkerTTL != "" {
d, err := time.ParseDuration(spec.SubjectDeleteMarkerTTL)
if err != nil {
return fmt.Errorf("parse subject delete marker TTL: %w", err)
}
opts = append(opts, jsm.SubjectDeleteMarkerTTL(d))
}
if spec.AllowMsgCounter {
opts = append(opts, func(o *jsmapi.StreamConfig) error {
o.AllowMsgCounter = true
return nil
})
}
if spec.AllowAtomicPublish {
opts = append(opts, func(o *jsmapi.StreamConfig) error {
o.AllowAtomicPublish = true
return nil
})
}
if spec.AllowMsgSchedules {
opts = append(opts, func(o *jsmapi.StreamConfig) error {
o.AllowMsgSchedules = true
return nil
})
}
if spec.PersistMode == "async" {
opts = append(opts, func(o *jsmapi.StreamConfig) error {
o.PersistMode = jsmapi.AsyncPersistMode
return nil
})
} else if spec.PersistMode == "default" {
opts = append(opts, func(o *jsmapi.StreamConfig) error {
o.PersistMode = jsmapi.DefaultPersistMode
return nil
})
}
_, err = c.NewStream(ctx, spec.Name, opts)
return err
}
func updateStream(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("failed to update stream %q: %w", spec.Name, err)
}
}()
js, err := c.LoadStream(ctx, spec.Name)
if err != nil {
return err
}
maxAge, err := getDurationFromString(spec.MaxAge)
if err != nil {
return err
}
subjectDeleteMarkerTTL, err := getDurationFromString(spec.SubjectDeleteMarkerTTL)
if err != nil {
return err
}
retention := getRetention(spec.Retention)
storage := getStorage(spec.Storage)
discard := getDiscard(spec.Discard)
duplicates, err := getDuplicates(spec.DuplicateWindow)
if err != nil {
return err
}
var subjectTransform *jsmapi.SubjectTransformConfig
if spec.SubjectTransform != nil {
subjectTransform = &jsmapi.SubjectTransformConfig{
Source: spec.SubjectTransform.Source,
Destination: spec.SubjectTransform.Dest,
}
}
config := jsmapi.StreamConfig{
Name: spec.Name,
Description: spec.Description,
Retention: retention,
Subjects: spec.Subjects,
MaxConsumers: spec.MaxConsumers,
MaxMsgs: int64(spec.MaxMsgs),
MaxBytes: int64(spec.MaxBytes),
MaxMsgsPer: int64(spec.MaxMsgsPerSubject),
MaxAge: maxAge,
MaxMsgSize: int32(spec.MaxMsgSize),
Storage: storage,
Discard: discard,
DiscardNewPer: spec.DiscardPerSubject,
Replicas: spec.Replicas,
NoAck: spec.NoAck,
Duplicates: duplicates,
AllowDirect: spec.AllowDirect,
DenyDelete: spec.DenyDelete,
DenyPurge: spec.DenyPurge,
RollupAllowed: spec.AllowRollup,
FirstSeq: spec.FirstSequence,
SubjectTransform: subjectTransform,
AllowMsgTTL: spec.AllowMsgTTL,
SubjectDeleteMarkerTTL: subjectDeleteMarkerTTL,
AllowMsgCounter: spec.AllowMsgCounter,
AllowAtomicPublish: spec.AllowAtomicPublish,
AllowMsgSchedules: spec.AllowMsgSchedules,
}
if spec.RePublish != nil {
config.RePublish = &jsmapi.RePublish{
Source: spec.RePublish.Source,
Destination: spec.RePublish.Destination,
HeadersOnly: spec.RePublish.HeadersOnly,
}
}
if spec.Mirror != nil {
ss, err := getStreamSource(spec.Mirror)
if err != nil {
return err
}
config.Mirror = ss
}
config.Sources = make([]*jsmapi.StreamSource, len(spec.Sources))
for i, ss := range spec.Sources {
jss, err := getStreamSource(ss)
if err != nil {
return err
}
config.Sources[i] = jss
}
if spec.Placement != nil {
config.Placement = &jsmapi.Placement{
Cluster: spec.Placement.Cluster,
Tags: spec.Placement.Tags,
}
}
if spec.Metadata != nil {
config.Metadata = spec.Metadata
}
switch spec.Compression {
case "s2":
config.Compression = jsmapi.S2Compression
case "none":
config.Compression = jsmapi.NoCompression
}
// Handle PersistMode
if spec.PersistMode == "async" {
config.PersistMode = jsmapi.AsyncPersistMode
} else if spec.PersistMode == "default" {
config.PersistMode = jsmapi.DefaultPersistMode
}
return js.UpdateConfiguration(config)
}
func deleteStream(ctx context.Context, c jsmClient, spec apis.StreamSpec) (err error) {
name := spec.Name
defer func() {
if err != nil {
err = fmt.Errorf("failed to delete stream %q: %w", name, err)
}
}()
if spec.PreventDelete {
klog.Infof("Stream %q is configured to preventDelete:\n", name)
return nil
}
var apierr jsmapi.ApiError
str, err := c.LoadStream(ctx, name)
if errors.As(err, &apierr) && apierr.NotFoundError() {
return nil
} else if err != nil {
return err
}
return str.Delete()
}
func setStreamErrored(ctx context.Context, s *apis.Stream, sif typed.StreamInterface, err error) (*apis.Stream, error) {
if err == nil {
return s, nil
}
sc := s.DeepCopy()
sc.Status.Conditions = UpsertCondition(sc.Status.Conditions, apis.Condition{
Type: readyCondType,
Status: k8sapi.ConditionFalse,
LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),
Reason: "Errored",
Message: err.Error(),
})
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
var res *apis.Stream
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
var err error
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
res, err = sif.UpdateStatus(ctx, sc, k8smeta.UpdateOptions{})
if err != nil {
return fmt.Errorf("failed to set stream errored status: %w", err)
}
return nil
})
return res, err
}
func setStreamOK(ctx context.Context, s *apis.Stream, i typed.StreamInterface) (*apis.Stream, error) {
sc := s.DeepCopy()
sc.Status.ObservedGeneration = s.Generation
sc.Status.Conditions = UpsertCondition(sc.Status.Conditions, apis.Condition{
Type: readyCondType,
Status: k8sapi.ConditionTrue,
LastTransitionTime: time.Now().UTC().Format(time.RFC3339Nano),
Reason: "Created",
Message: "Stream successfully created",
})
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
var res *apis.Stream
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
var err error
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
res, err = i.UpdateStatus(ctx, sc, k8smeta.UpdateOptions{})
if err != nil {
return fmt.Errorf("failed to set stream %q status: %w", s.Spec.Name, err)
}
return nil
})
return res, err
}
func getDurationFromString(v string) (time.Duration, error) {
if v == "" {
return time.Duration(0), nil
}
return time.ParseDuration(v)
}
func getRetention(v string) jsmapi.RetentionPolicy {
retention := jsmapi.LimitsPolicy
switch v {
case "interest":
retention = jsmapi.InterestPolicy
case "workqueue":
retention = jsmapi.WorkQueuePolicy
}
return retention
}
func getStorage(v string) jsmapi.StorageType {
storage := jsmapi.MemoryStorage
switch v {
case "file":
storage = jsmapi.FileStorage
}
return storage
}
func getDiscard(v string) jsmapi.DiscardPolicy {
discard := jsmapi.DiscardOld
switch v {
case "new":
discard = jsmapi.DiscardNew
}
return discard
}
func getDuplicates(v string) (time.Duration, error) {
if v == "" {
return time.Duration(0), nil
}
return time.ParseDuration(v)
}
func getStreamSource(ss *apis.StreamSource) (*jsmapi.StreamSource, error) {
jss := &jsmapi.StreamSource{
Name: ss.Name,
FilterSubject: ss.FilterSubject,
}
if ss.OptStartSeq > 0 {
jss.OptStartSeq = uint64(ss.OptStartSeq)
} else if ss.OptStartTime != "" {
t, err := time.Parse(time.RFC3339, ss.OptStartTime)
if err != nil {
return nil, err
}
jss.OptStartTime = &t
}
if ss.ExternalAPIPrefix != "" || ss.ExternalDeliverPrefix != "" {
jss.External = &jsmapi.ExternalStream{
ApiPrefix: ss.ExternalAPIPrefix,
DeliverPrefix: ss.ExternalDeliverPrefix,
}
}
for _, transform := range ss.SubjectTransforms {
jss.SubjectTransforms = append(jss.SubjectTransforms, jsmapi.SubjectTransformConfig{
Source: transform.Source,
Destination: transform.Dest,
})
}
return jss, nil
}
================================================
FILE: controllers/jetstream/stream_test.go
================================================
package jetstream
import (
"context"
"errors"
"strings"
"testing"
jsmapi "github.com/nats-io/jsm.go/api"
apis "github.com/nats-io/nack/pkg/jetstream/apis/jetstream/v1beta2"
clientsetfake "github.com/nats-io/nack/pkg/jetstream/generated/clientset/versioned/fake"
k8smeta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
k8sclientsetfake "k8s.io/client-go/kubernetes/fake"
k8stesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/record"
)
func TestProcessStream(t *testing.T) {
t.Parallel()
updateObject := func(a k8stesting.Action) (handled bool, o runtime.Object, err error) {
ua, ok := a.(k8stesting.UpdateAction)
if !ok {
return false, nil, nil
}
return true, ua.GetObject(), nil
}
t.Run("create stream", func(t *testing.T) {
t.Parallel()
jc := clientsetfake.NewSimpleClientset()
wantEvents := 2
rec := record.NewFakeRecorder(wantEvents)
ctrl := NewController(Options{
Ctx: context.Background(),
KubeIface: k8sclientsetfake.NewSimpleClientset(),
JetstreamIface: jc,
Recorder: rec,
})
ns, name := "default", "my-stream"
informer := ctrl.informerFactory.Jetstream().V1beta2().Streams()
err := informer.Informer().GetStore().Add(&apis.Stream{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: ns,
Name: name,
Generation: 1,
},
Spec: apis.StreamSpec{
Name: name,
MaxAge: "1h",
Storage: "memory",
},
})
if err != nil {
t.Fatal(err)
}
jc.PrependReactor("update", "streams", updateObject)
notFoundErr := jsmapi.ApiError{Code: 404}
jsmc := &mockJsmClient{
loadStreamErr: notFoundErr,
}
if err := ctrl.processStream(ns, name, testWrapJSMC(jsmc)); err != nil {
t.Fatal(err)
}
if got := len(rec.Events); got != wantEvents {
t.Error("unexpected number of events")
t.Fatalf("got=%d; want=%d", got, wantEvents)
}
<-rec.Events
<-rec.Events
for i := 0; i < len(rec.Events); i++ {
gotEvent := <-rec.Events
if !strings.Contains(gotEvent, "Creat") {
t.Error("unexpected event")
t.Fatalf("got=%s; want=%s", gotEvent, "Creating/Created...")
}
}
})
t.Run("update stream", func(t *testing.T) {
t.Parallel()
jc := clientsetfake.NewSimpleClientset()
wantEvents := 2
rec := record.NewFakeRecorder(wantEvents)
ctrl := NewController(Options{
Ctx: context.Background(),
KubeIface: k8sclientsetfake.NewSimpleClientset(),
JetstreamIface: jc,
Recorder: rec,
})
ns, name := "default", "my-stream"
informer := ctrl.informerFactory.Jetstream().V1beta2().Streams()
err := informer.Informer().GetStore().Add(&apis.Stream{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: ns,
Name: name,
Generation: 2,
},
Spec: apis.StreamSpec{
Name: name,
MaxAge: "1h",
Storage: "memory",
AllowMsgSchedules: true,
},
Status: apis.Status{
ObservedGeneration: 1,
},
})
if err != nil {
t.Fatal(err)
}
jc.PrependReactor("update", "streams", updateObject)
// Capture the config that gets passed to UpdateConfiguration
var capturedConfig jsmapi.StreamConfig
jsmc := &mockJsmClient{
loadStreamErr: nil,
loadStream: &mockStream{
capturedConfig: &capturedConfig,
},
}
if err := ctrl.processStream(ns, name, testWrapJSMC(jsmc)); err != nil {
t.Fatal(err)
}
// Verify that AllowMsgSchedules was set in the config
if !capturedConfig.AllowMsgSchedules {
t.Errorf("AllowMsgSchedules not set in stream config during update: got=%v, want=true", capturedConfig.AllowMsgSchedules)
}
if got := len(rec.Events); got != wantEvents {
t.Error("unexpected number of events")
t.Fatalf("got=%d; want=%d", got, wantEvents)
}
for i := 0; i < len(rec.Events); i++ {
gotEvent := <-rec.Events
if !strings.Contains(gotEvent, "Updat") {
t.Error("unexpected event")
t.Fatalf("got=%s; want=%s", gotEvent, "Updating/Updated...")
}
}
})
t.Run("delete stream", func(t *testing.T) {
t.Parallel()
jc := clientsetfake.NewSimpleClientset()
wantEvents := 1
rec := record.NewFakeRecorder(wantEvents)
ctrl := NewController(Options{
Ctx: context.Background(),
KubeIface: k8sclientsetfake.NewSimpleClientset(),
JetstreamIface: jc,
Recorder: rec,
})
ts := k8smeta.Unix(1600216923, 0)
ns, name := "default", "my-stream"
informer := ctrl.informerFactory.Jetstream().V1beta2().Streams()
err := informer.Informer().GetStore().Add(&apis.Stream{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: ns,
Name: name,
Generation: 2,
DeletionTimestamp: &ts,
},
Spec: apis.StreamSpec{
Name: name,
MaxAge: "1h",
Storage: "memory",
},
Status: apis.Status{
ObservedGeneration: 1,
},
})
if err != nil {
t.Fatal(err)
}
jc.PrependReactor("update", "streams", updateObject)
jsmc := &mockJsmClient{
loadStreamErr: nil,
loadStream: &mockStream{},
}
if err := ctrl.processStream(ns, name, testWrapJSMC(jsmc)); err != nil {
t.Fatal(err)
}
if got := len(rec.Events); got != wantEvents {
t.Error("unexpected number of events")
t.Fatalf("got=%d; want=%d", got, wantEvents)
}
for i := 0; i < len(rec.Events); i++ {
gotEvent := <-rec.Events
if !strings.Contains(gotEvent, "Delet") {
t.Error("unexpected event")
t.Fatalf("got=%s; want=%s", gotEvent, "Deleting/Deleted...")
}
}
})
t.Run("process error", func(t *testing.T) {
t.Parallel()
jc := clientsetfake.NewSimpleClientset()
wantEvents := 4
rec := record.NewFakeRecorder(wantEvents)
ctrl := NewController(Options{
Ctx: context.Background(),
KubeIface: k8sclientsetfake.NewSimpleClientset(),
JetstreamIface: jc,
Recorder: rec,
})
ns, name := "default", "my-stream"
informer := ctrl.informerFactory.Jetstream().V1beta2().Streams()
err := informer.Informer().GetStore().Add(&apis.Stream{
ObjectMeta: k8smeta.ObjectMeta{
Namespace: ns,
Name: name,
Generation: 1,
},
Spec: apis.StreamSpec{
Name: name,
MaxAge: "1h",
Storage: "memory",
},
})
if err != nil {
t.Fatal(err)
}
jc.PrependReactor("update", "streams", func(a k8stesting.Action) (handled bool, o runtime.Object, err error) {
ua, ok := a.(k8stesting.UpdateAction)
if !ok {
return false, nil, nil
}
obj := ua.GetObject()
str, ok := obj.(*apis.Stream)
if !ok {
t.Error("unexpected object type")
t.Fatalf("got=%T; want=%T", obj, &apis.Stream{})
}
if got, want := len(str.Status.Conditions), 1; got != want {
t.Error("unexpected number of conditions")
t.Fatalf("got=%d; want=%d", got, want)
}
if got, want := str.Status.Conditions[0].Reason, "Errored"; got != want {
t.Error("unexpected condition reason")
t.Fatalf("got=%s; want=%s", got, want)
}
return true, obj, nil
})
jsmc := &mockJsmClient{
loadStreamErr: errors.New("failed to load stream"),
}
if err := ctrl.processStream(ns, name, testWrapJSMC(jsmc)); err == nil {
t.Fatal("unexpected success")
}
})
}
================================================
FILE: dependencies.md
================================================
# External Dependencies
This file lists the dependencies used in this repository.
| Dependency | License |
|--------------------------------------|--------------|
| github.com/fsnotify/fsnotify | BSD-3-Clause |
| github.com/nats-io/jsm.go | Apache-2.0 |
| github.com/nats-io/nats.go | Apache-2.0 |
| github.com/sirupsen/logrus | MIT |
| github.com/stretchr/testify | MIT |
| k8s.io/api | Apache-2.0 |
| k8s.io/apimachinery | Apache-2.0 |
| k8s.io/client-go | Apache-2.0 |
| k8s.io/code-generator | Apache-2.0 |
| k8s.io/klog/v2 | Apache-2.0 |
| sigs.k8s.io/structured-merge-diff/v4 | Apache-2.0 |
================================================
FILE: deploy/crds.yml
================================================
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: streams.jetstream.nats.io
spec:
group: jetstream.nats.io
scope: Namespaced
names:
kind: Stream
singular: stream
plural: streams
versions:
- name: v1beta2
served: true
storage: true
subresources:
status: {}
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
name:
description: A unique name for the Stream.
type: string
pattern: '^[^.*>]*$'
minLength: 1
description:
description: The description of the stream.
type: string
subjects:
description: A list of subjects to consume, supports wildcards.
type: array
minLength: 1
items:
type: string
minLength: 1
retention:
description: How messages are retained in the Stream, once this is exceeded old messages are removed.
type: string
enum:
- limits
- interest
- workqueue
default: limits
maxConsumers:
description: How many Consumers can be defined for a given Stream. -1 for unlimited.
type: integer
minimum: -1
default: -1
maxMsgs:
description: How many messages may be in a Stream, oldest messages will be removed if the Stream exceeds this size. -1 for unlimited.
type: integer
minimum: -1
default: -1
maxBytes:
description: How big the Stream may be, when the combined stream size exceeds this old messages are removed. -1 for unlimited.
type: integer
minimum: -1
default: -1
discard:
description: When a Stream reach it's limits either old messages are deleted or new ones are denied.
type: string
enum:
- old
- new
default: old
discardPerSubject:
description: Applies discard policy on a per-subject basis. Requires discard policy 'new' and 'maxMsgs' to be set.
type: boolean
default: false
maxAge:
description: Maximum age of any message in the stream, expressed in Go's time.Duration format. Empty for unlimited.
type: string
default: ''
maxMsgsPerSubject:
description: The maximum number of messages per subject.
type: integer
default: 0
maxMsgSize:
description: The largest message that will be accepted by the Stream. -1 for unlimited.
type: integer
minimum: -1
default: -1
storage:
description: The storage backend to use for the Stream.
type: string
enum:
- file
- memory
default: memory
replicas:
description: How many replicas to keep for each message.
type: integer
minimum: 1
default: 1
noAck:
description: Disables acknowledging messages that are received by the Stream.
type: boolean
default: false
duplicateWindow:
description: The duration window to track duplicate messages for.
type: string
placement:
description: A stream's placement.
type: object
properties:
cluster:
type: string
tags:
type: array
items:
type: string
mirror:
description: A stream mirror.
type: object
properties:
name:
type: string
optStartSeq:
type: integer
optStartTime:
description: Time format must be RFC3339.
type: string
filterSubject:
type: string
externalApiPrefix:
type: string
externalDeliverPrefix:
type: string
subjectTransforms:
description: List of subject transforms for this mirror.
type: array
items:
description: A subject transform pair.
type: object
properties:
source:
description: Source subject.
type: string
dest:
description: Destination subject.
type: string
sources:
description: A stream's sources.
type: array
items:
type: object
properties:
name:
type: string
optStartSeq:
type: integer
optStartTime:
description: Time format must be RFC3339.
type: string
filterSubject:
type: string
externalApiPrefix:
type: string
externalDeliverPrefix:
type: string
subjectTransforms:
description: List of subject transforms for this mirror.
type: array
items:
description: A subject transform pair.
type: object
properties:
source:
description: Source subject.
type: string
dest:
description: Destination subject.
type: string
sealed:
description: Seal an existing stream so no new messages may be added.
type: boolean
default: false
denyDelete:
description: When true, restricts the ability to delete messages from a stream via the API. Cannot be changed once set to true.
type: boolean
default: false
denyPurge:
description: When true, restricts the ability to purge a stream via the API. Cannot be changed once set to true.
type: boolean
default: false
allowRollup:
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.
type: boolean
default: false
compression:
description: Stream specific compression.
type: string
enum:
- s2
- none
- ''
default: ''
firstSequence:
description: Sequence number from which the Stream will start.
type: number
default: 0
subjectTransform:
description: SubjectTransform is for applying a subject transform (to matching messages) when a new message is received.
type: object
properties:
source:
type: string
description: Source subject.
dest:
type: string
description: Destination subject to transform into.
republish:
description: Republish configuration of the stream.
type: object
properties:
destination:
type: string
description: Messages will be additionally published to this subject.
source:
type: string
description: Messages will be published from this subject to the destination subject.
allowDirect:
description: When true, allow higher performance, direct access to get individual messages.
type: boolean
default: false
mirrorDirect:
description: When true, enables direct access to messages from the origin stream.
type: boolean
default: false
allowMsgTtl:
description: When true, allows header initiated per-message TTLs. If disabled, then the `NATS-TTL` header will be ignored.
type: boolean
default: false
subjectDeleteMarkerTtl:
description: Enables and sets a duration for adding server markers for delete, purge and max age limits.
type: string
default: ''
allowMsgCounter:
description: When true, enables message counters for the stream.
type: boolean
default: false
allowAtomicPublish:
description: When true, enables atomic batch publishing.
type: boolean
default: false
allowMsgSchedules:
description: When true, enables message scheduling.
type: boolean
default: false
persistMode:
description: Configures stream persistence settings (async or default).
type: string
default: ''
consumerLimits:
type: object
properties:
inactiveThreshold:
description: The duration of inactivity after which a consumer is considered inactive.
type: string
maxAckPending:
description: Maximum number of outstanding unacknowledged messages.
type: integer
metadata:
description: Additional Stream metadata.
type: object
additionalProperties:
type: string
account:
description: Name of the account to which the Stream belongs.
type: string
pattern: '^[^.*>]*$'
creds:
description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on this path.
type: string
default: ''
nkey:
description: NATS user NKey for connecting to servers.
type: string
default: ''
preventDelete:
description: When true, the managed Stream will not be deleted when the resource is deleted.
type: boolean
default: false
preventUpdate:
description: When true, the managed Stream will not be updated when the resource is updated.
type: boolean
default: false
servers:
description: A list of servers for creating stream.
type: array
items:
type: string
default: []
tls:
description: A client's TLS certs and keys.
type: object
properties:
clientCert:
description: A client's cert filepath. Should be mounted.
type: string
clientKey:
description: A client's key filepath. Should be mounted.
type: string
rootCas:
description: A list of filepaths to CAs. Should be mounted.
type: array
items:
type: string
tlsFirst:
description: When true, the KV Store will initiate TLS before server INFO.
type: boolean
default: false
jsDomain:
description: The JetStream domain to use for the stream.
type: string
status:
type: object
properties:
observedGeneration:
type: integer
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
lastTransitionTime:
type: string
reason:
type: string
message:
type: string
additionalPrinterColumns:
- name: State
type: string
description: The current state of the stream.
jsonPath: .status.conditions[?(@.type == 'Ready')].reason
- name: Stream Name
type: string
description: The name of the JetStream Stream.
jsonPath: .spec.name
- name: Subjects
type: string
description: The subjects this Stream produces.
jsonPath: .spec.subjects
- name: v1beta1
served: false
storage: false
subresources:
status: {}
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
name:
description: A unique name for the Stream.
type: string
pattern: '^[^.*>]*$'
minLength: 1
subjects:
description: A list of subjects to consume, supports wildcards.
type: array
minLength: 1
items:
type: string
minLength: 1
retention:
description: How messages are retained in the Stream, once this is exceeded old messages are removed.
type: string
enum:
- limits
- interest
- workqueue
default: limits
maxConsumers:
description: How many Consumers can be defined for a given Stream. -1 for unlimited.
type: integer
minimum: -1
default: -1
maxMsgs:
description: How many messages may be in a Stream, oldest messages will be removed if the Stream exceeds this size. -1 for unlimited.
type: integer
minimum: -1
default: -1
maxBytes:
description: How big the Stream may be, when the combined stream size exceeds this old messages are removed. -1 for unlimited.
type: integer
minimum: -1
default: -1
maxAge:
description: Maximum age of any message in the stream, expressed in Go's time.Duration format. Empty for unlimited.
type: string
default: ''
maxMsgSize:
description: The largest message that will be accepted by the Stream. -1 for unlimited.
type: integer
minimum: -1
default: -1
storage:
description: The storage backend to use for the Stream.
type: string
enum:
- file
- memory
default: memory
replicas:
description: How many replicas to keep for each message.
type: integer
minimum: 1
default: 1
noAck:
description: Disables acknowledging messages that are received by the Stream.
type: boolean
default: false
discard:
description: When a Stream reach it's limits either old messages are deleted or new ones are denied.
type: string
enum:
- old
- new
default: old
duplicateWindow:
description: The duration window to track duplicate messages for.
type: string
description:
description: The description of the stream.
type: string
maxMsgsPerSubject:
description: The maximum number of messages per subject.
type: integer
default: 0
mirror:
description: A stream mirror.
type: object
properties:
name:
type: string
optStartSeq:
type: integer
optStartTime:
description: Time format must be RFC3339.
type: string
filterSubject:
type: string
externalApiPrefix:
type: string
externalDeliverPrefix:
type: string
placement:
description: A stream's placement.
type: object
properties:
cluster:
type: string
tags:
type: array
items:
type: string
sources:
description: A stream's sources.
type: array
items:
type: object
properties:
name:
type: string
optStartSeq:
type: integer
optStartTime:
description: Time format must be RFC3339.
type: string
filterSubject:
type: string
externalApiPrefix:
type: string
externalDeliverPrefix:
type: string
status:
type: object
properties:
observedGeneration:
type: integer
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
lastTransitionTime:
type: string
reason:
type: string
message:
type: string
additionalPrinterColumns:
- name: State
type: string
description: The current state of the stream.
jsonPath: .status.conditions[?(@.type == 'Ready')].reason
- name: Stream Name
type: string
description: The name of the JetStream Stream.
jsonPath: .spec.name
- name: Subjects
type: string
description: The subjects this Stream produces.
jsonPath: .spec.subjects
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: consumers.jetstream.nats.io
spec:
group: jetstream.nats.io
scope: Namespaced
names:
kind: Consumer
singular: consumer
plural: consumers
versions:
- name: v1beta2
served: true
storage: true
subresources:
status: {}
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
durableName:
description: The name of the Consumer.
type: string
pattern: '^[^.*>]+$'
minLength: 1
streamName:
description: The name of the Stream to create the Consumer in.
type: string
deliverPolicy:
type: string
enum:
- all
- last
- new
# Requires optStartSeq
- byStartSequence
# Requires optStartTime
- byStartTime
- lastPerSubject
default: all
optStartSeq:
type: integer
minimum: 0
optStartTime:
description: Time format must be RFC3339.
type: string
deliverSubject:
description: The subject to deliver observed messages, when not set, a pull-based Consumer is created.
type: string
ackPolicy:
description: How messages should be acknowledged.
type: string
enum:
- none
- all
- explicit
default: none
ackWait:
description: How long to allow messages to remain un-acknowledged before attempting redelivery.
type: string
default: 1ns
maxDeliver:
type: integer
minimum: -1
backoff:
description: List of durations representing a retry time scale for NaK'd or retried messages.
type: array
items:
type: string
filterSubject:
description: Select only a specific incoming subjects, supports wildcards.
type: string
filterSubjects:
description: List of incoming subjects, supports wildcards. Available since 2.10.
type: array
items:
type: string
replayPolicy:
description: How messages are sent.
type: string
enum:
- instant
- original
default: instant
sampleFreq:
description: What percentage of acknowledgements should be samples for observability.
type: string
maxWaiting:
description: The number of pulls that can be outstanding on a pull consumer, pulls received after this is reached are ignored.
type: integer
rateLimitBps:
description: Rate at which messages will be delivered to clients, expressed in bit per second.
type: integer
maxAckPending:
description: Maximum pending Acks before consumers are paused.
type: integer
deliverGroup:
description: The name of a queue group.
type: string
description:
description: The description of the consumer.
type: string
flowControl:
description: Enables flow control.
type: boolean
default: false
headersOnly:
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.
type: boolean
default: false
heartbeatInterval:
description: The interval used to deliver idle heartbeats for push-based consumers, in Go's time.Duration format.
type: string
maxRequestBatch:
description: The largest batch property that may be specified when doing a pull on a Pull Consumer.
type: integer
maxRequestExpires:
description: The maximum expires duration that may be set when doing a pull on a Pull Consumer.
type: string
maxRequestMaxBytes:
description: The maximum max_bytes value that maybe set when dong a pull on a Pull Consumer.
type: integer
inactiveThreshold:
description: The idle time an Ephemeral Consumer allows before it is removed.
type: string
pauseUntil:
description: RFC3339 timestamp until which the consumer should be paused.
type: string
default: ''
priorityPolicy:
description: Priority policy for consumer (pinned_client, overflow, prioritized, or none).
type: string
default: ''
pinnedTtl:
description: TTL for pinned client when using pinned_client priority policy.
type: string
default: ''
priorityGroups:
description: List of priority groups for the consumer. For now, only one group is supported.
type: array
items:
type: string
replicas:
description: When set do not inherit the replica count from the stream but specifically set it to this amount.
type: integer
memStorage:
description: Force the consumer state to be kept in memory rather than inherit the setting from the stream.
type: boolean
default: false
metadata:
description: Additional Consumer metadata.
type: object
additionalProperties:
type: string
account:
description: Name of the account to which the Consumer belongs.
type: string
pattern: '^[^.*>]*$'
creds:
description: NATS user credentials for connecting to servers. Please make sure your controller has mounted the creds on its path.
type: string
default: ''
nkey:
description: NATS user NKey for connecting to servers.
type: string
default: ''
preventDelete:
description: When true, the managed Consumer will not be deleted when the resource is deleted.
type: boolean
default: false
preventUpdate:
description: When true, the managed Consumer will not be updated when the resource is updated.
type: boolean
default: false
servers:
description: A list of servers for creating consumer.
type: array
items:
type: string
default: []
tls:
description: A client's TLS certs and keys.
type: object
properties:
clientCert:
description: A client's cert filepath. Should be mounted.
type: string
clientKey:
description: A client's key filepath. Should be mounted.
type: string
rootCas:
description: A list of filepaths to CAs. Should be mounted.
type: array
items:
type: string
tlsFirst:
description: When true, the KV Store will initiate TLS before server INFO.
type: boolean
default: false
jsDomain:
description: The JetStream domain to use for the consumer.
type: string
status:
type: object
properties:
observedGeneration:
type: integer
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
lastTransitionTime:
type: string
reason:
type: string
message:
type: string
additionalPrinterColumns:
- name: State
type: string
description: The current state of the consumer.
jsonPath: .status.conditions[?(@.type == 'Ready')].reason
- name: Stream
type: string
description: The name of the JetStream Stream.
jsonPath: .spec.streamName
- name: Consumer
type: string
description: The name of the JetStream Consumer.
jsonPath: .spec.durableName
- name: Ack Policy
type: string
description: The ack policy.
jsonPath: .spec.ackPolicy
- name: v1beta1
served: false
storage: false
subresources:
status: {}
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
streamName:
description: The name of the Stream to create the Consumer in.
type: string
deliverPolicy:
type: string
enum:
- all
- last
- new
# Requires optStartSeq
- byStartSequence
# Requires optStartTime
- byStartTime
default: all
optStartSeq:
type: integer
minimum: 0
optStartTime:
description: Time format must be RFC3339.
type: string
durableName:
description: The name of the Consumer.
type: string
pattern: '^[^.*>]+$'
minLength: 1
deliverSubject:
description: The subject to deliver observed messages, when not set, a pull-based Consumer is created.
type: string
ackPolicy:
description: How messages should be acknowledged.
type: string
enum:
- none
- all
- explicit
default: none
ackWait:
description: How long to allow messages to remain un-acknowledged before attempting redelivery.
type: string
default: 1ns
maxDeliver:
type: integer
minimum: -1
filterSubject:
description: Select only a specific incoming subjects, supports wildcards.
type: string
replayPolicy:
description: How messages are sent.
type: string
enum:
- instant
- original
default: instant
sampleFreq:
description: What percentage of acknowledgements should be samples for observability.
type: string
rateLimitBps:
description: Rate at which messages will be delivered to clients, expressed in bit per second.
type: integer
maxAckPending:
description: Maximum pending Acks before consumers are paused.
type: integer
deliverGroup:
description: The name of a queue group.
type: string
description:
description: The description of the consumer.
type: string
flowControl:
description: Enables flow control.
type: boolean
default: false
heartbeatInterval:
description: The interval used to deliver idle heartbeats for push-based consumers, in Go's time.Duration fo
gitextract_mbhvqodb/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── config.yml
│ │ ├── defect.yml
│ │ └── proposal.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── claude.yml
│ ├── deps-release-detect.yaml
│ ├── deps-release-tag.yaml
│ ├── e2e.yaml
│ ├── release.yaml
│ └── test.yaml
├── .gitignore
├── .goreleaser.yml
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── cicd/
│ ├── Dockerfile
│ ├── Dockerfile_goreleaser
│ ├── assets/
│ │ └── entrypoint.sh
│ └── tag-deps-version.txt
├── cmd/
│ ├── jetstream-controller/
│ │ └── main.go
│ ├── nats-boot-config/
│ │ └── main.go
│ └── nats-server-config-reloader/
│ └── main.go
├── controllers/
│ └── jetstream/
│ ├── conn_pool.go
│ ├── conn_pool_test.go
│ ├── consumer.go
│ ├── consumer_test.go
│ ├── controller.go
│ ├── controller_test.go
│ ├── jsmclient.go
│ ├── jsmclient_test.go
│ ├── stream.go
│ └── stream_test.go
├── dependencies.md
├── deploy/
│ ├── crds.yml
│ ├── examples/
│ │ ├── consumer_pull.yml
│ │ ├── consumer_push.yml
│ │ ├── stream.yml
│ │ ├── stream_mirror.yml
│ │ ├── stream_placement.yml
│ │ ├── stream_servers.yml
│ │ └── stream_sources.yml
│ └── rbac.yml
├── docker-bake.hcl
├── docs/
│ └── api.md
├── examples/
│ └── secure/
│ ├── client-tls.yaml
│ ├── issuer.yaml
│ ├── nack/
│ │ ├── account-foo.yaml
│ │ ├── nats-account-a.yaml
│ │ ├── nats-consumer-bar-a.yaml
│ │ ├── nats-stream-foo-a.yaml
│ │ └── stream-foo.yaml
│ ├── nack-a-client-tls.yaml
│ ├── nack-b-client-tls.yaml
│ ├── nats-helm.yaml
│ └── server-tls.yaml
├── go.mod
├── go.sum
├── internal/
│ └── controller/
│ ├── account_controller.go
│ ├── account_controller_test.go
│ ├── client.go
│ ├── connection_pool.go
│ ├── connection_pool_test.go
│ ├── consumer_controller.go
│ ├── consumer_controller_test.go
│ ├── helpers_test.go
│ ├── jetstream_controller.go
│ ├── jetstream_controller_test.go
│ ├── keyvalue_controller.go
│ ├── keyvalue_controller_test.go
│ ├── objectstore_controller.go
│ ├── objectstore_controller_test.go
│ ├── register.go
│ ├── stream_controller.go
│ ├── stream_controller_test.go
│ ├── suite_test.go
│ └── types.go
├── kuttl-test.yaml
├── pkg/
│ ├── bootconfig/
│ │ └── bootconfig.go
│ ├── jetstream/
│ │ ├── apis/
│ │ │ └── jetstream/
│ │ │ ├── register.go
│ │ │ ├── v1beta1/
│ │ │ │ ├── consumertypes.go
│ │ │ │ ├── doc.go
│ │ │ │ ├── register.go
│ │ │ │ ├── streamtemplatetypes.go
│ │ │ │ ├── streamtypes.go
│ │ │ │ ├── types.go
│ │ │ │ └── zz_generated.deepcopy.go
│ │ │ └── v1beta2/
│ │ │ ├── accounttypes.go
│ │ │ ├── consumertypes.go
│ │ │ ├── doc.go
│ │ │ ├── keyvaluetypes.go
│ │ │ ├── objectstoretypes.go
│ │ │ ├── register.go
│ │ │ ├── streamtypes.go
│ │ │ ├── types.go
│ │ │ └── zz_generated.deepcopy.go
│ │ └── generated/
│ │ ├── applyconfiguration/
│ │ │ ├── internal/
│ │ │ │ └── internal.go
│ │ │ ├── jetstream/
│ │ │ │ └── v1beta2/
│ │ │ │ ├── account.go
│ │ │ │ ├── accountspec.go
│ │ │ │ ├── basestreamconfig.go
│ │ │ │ ├── condition.go
│ │ │ │ ├── connectionopts.go
│ │ │ │ ├── consumer.go
│ │ │ │ ├── consumerlimits.go
│ │ │ │ ├── consumerspec.go
│ │ │ │ ├── credssecret.go
│ │ │ │ ├── keyvalue.go
│ │ │ │ ├── keyvaluespec.go
│ │ │ │ ├── nkeysecret.go
│ │ │ │ ├── objectstore.go
│ │ │ │ ├── objectstorespec.go
│ │ │ │ ├── republish.go
│ │ │ │ ├── secretref.go
│ │ │ │ ├── status.go
│ │ │ │ ├── stream.go
│ │ │ │ ├── streamplacement.go
│ │ │ │ ├── streamsource.go
│ │ │ │ ├── streamspec.go
│ │ │ │ ├── subjecttransform.go
│ │ │ │ ├── tls.go
│ │ │ │ ├── tlssecret.go
│ │ │ │ ├── tokensecret.go
│ │ │ │ └── user.go
│ │ │ └── utils.go
│ │ ├── clientset/
│ │ │ └── versioned/
│ │ │ ├── clientset.go
│ │ │ ├── fake/
│ │ │ │ ├── clientset_generated.go
│ │ │ │ ├── doc.go
│ │ │ │ └── register.go
│ │ │ ├── scheme/
│ │ │ │ ├── doc.go
│ │ │ │ └── register.go
│ │ │ └── typed/
│ │ │ └── jetstream/
│ │ │ └── v1beta2/
│ │ │ ├── account.go
│ │ │ ├── consumer.go
│ │ │ ├── doc.go
│ │ │ ├── fake/
│ │ │ │ ├── doc.go
│ │ │ │ ├── fake_account.go
│ │ │ │ ├── fake_consumer.go
│ │ │ │ ├── fake_jetstream_client.go
│ │ │ │ ├── fake_keyvalue.go
│ │ │ │ ├── fake_objectstore.go
│ │ │ │ └── fake_stream.go
│ │ │ ├── generated_expansion.go
│ │ │ ├── jetstream_client.go
│ │ │ ├── keyvalue.go
│ │ │ ├── objectstore.go
│ │ │ └── stream.go
│ │ ├── informers/
│ │ │ └── externalversions/
│ │ │ ├── factory.go
│ │ │ ├── generic.go
│ │ │ ├── internalinterfaces/
│ │ │ │ └── factory_interfaces.go
│ │ │ └── jetstream/
│ │ │ ├── interface.go
│ │ │ └── v1beta2/
│ │ │ ├── account.go
│ │ │ ├── consumer.go
│ │ │ ├── interface.go
│ │ │ ├── keyvalue.go
│ │ │ ├── objectstore.go
│ │ │ └── stream.go
│ │ └── listers/
│ │ └── jetstream/
│ │ └── v1beta2/
│ │ ├── account.go
│ │ ├── consumer.go
│ │ ├── expansion_generated.go
│ │ ├── keyvalue.go
│ │ ├── objectstore.go
│ │ └── stream.go
│ ├── k8scodegen/
│ │ ├── file-header.txt
│ │ └── k8scodegen.go
│ └── natsreloader/
│ ├── natsreloader.go
│ └── natsreloader_test.go
└── tests/
├── Dockerfile
├── nack-control-loop.yaml
├── nack-legacy.yaml
├── nats.yaml
└── stream-creation/
├── 00-nack.yaml
├── 01-stream.yaml
├── 02-natscli-stream.yaml
├── asserted-natscli.yaml
├── asserted-rides-stream.yaml
├── natscli.yaml
└── rides-stream.yaml
SYMBOL INDEX (916 symbols across 110 files)
FILE: cmd/jetstream-controller/main.go
function main (line 51) | func main() {
function run (line 58) | func run() error {
function runControlLoop (line 174) | func runControlLoop(config *rest.Config, natsCfg *controller.NatsConfig,...
function handleSignals (line 241) | func handleSignals(cancel context.CancelFunc) {
FILE: cmd/nats-boot-config/main.go
function main (line 32) | func main() {
FILE: cmd/nats-server-config-reloader/main.go
type StringSet (line 24) | type StringSet
method String (line 26) | func (s *StringSet) String() string {
method Set (line 31) | func (s *StringSet) Set(val string) error {
function main (line 36) | func main() {
FILE: controllers/jetstream/conn_pool.go
type natsContext (line 16) | type natsContext struct
method copy (line 31) | func (c *natsContext) copy() *natsContext {
method hash (line 39) | func (c *natsContext) hash() (string, error) {
type natsContextDefaults (line 86) | type natsContextDefaults struct
type pooledNatsConn (line 95) | type pooledNatsConn struct
method ReturnToPool (line 103) | func (pc *pooledNatsConn) ReturnToPool() {
type natsConnPool (line 118) | type natsConnPool struct
method Get (line 140) | func (cp *natsConnPool) Get(cfg *natsContext) (*pooledNatsConn, error) {
method getPooledConn (line 194) | func (cp *natsConnPool) getPooledConn(key string, cfg *natsContext) (*...
function newNatsConnPool (line 127) | func newNatsConnPool(logger *logrus.Logger, natsDefaults *natsContextDef...
constant getPooledConnMaxTries (line 137) | getPooledConnMaxTries = 10
FILE: controllers/jetstream/conn_pool_test.go
function TestConnPool (line 15) | func TestConnPool(t *testing.T) {
FILE: controllers/jetstream/consumer.go
method runConsumerQueue (line 21) | func (c *Controller) runConsumerQueue() {
method processConsumer (line 27) | func (c *Controller) processConsumer(ns, name string, jsmClient jsmClien...
method processConsumerObject (line 38) | func (c *Controller) processConsumerObject(cns *apis.Consumer, jsm jsmCl...
function consumerExists (line 144) | func consumerExists(ctx context.Context, c jsmClient, spec apis.Consumer...
function createConsumer (line 155) | func createConsumer(ctx context.Context, c jsmClient, spec apis.Consumer...
function updateConsumer (line 170) | func updateConsumer(ctx context.Context, c jsmClient, spec apis.Consumer...
function consumerSpecToOpts (line 191) | func consumerSpecToOpts(spec apis.ConsumerSpec) ([]jsm.ConsumerOption, e...
function deleteConsumer (line 368) | func deleteConsumer(ctx context.Context, c jsmClient, spec apis.Consumer...
function setConsumerOK (line 392) | func setConsumerOK(ctx context.Context, s *apis.Consumer, i typed.Consum...
function setConsumerErrored (line 418) | func setConsumerErrored(ctx context.Context, s *apis.Consumer, sif typed...
FILE: controllers/jetstream/consumer_test.go
function TestProcessConsumer (line 23) | func TestProcessConsumer(t *testing.T) {
function TestConsumerSpecToOpts (line 333) | func TestConsumerSpecToOpts(t *testing.T) {
function testWrapJSMC (line 448) | func testWrapJSMC(jsm jsmClient) jsmClientFunc {
FILE: controllers/jetstream/controller.go
constant maxQueueRetries (line 58) | maxQueueRetries = 10
constant readyCondType (line 61) | readyCondType = "Ready"
type Options (line 64) | type Options struct
type Controller (line 89) | type Controller struct
method Run (line 235) | func (c *Controller) Run() error {
method RealJSMC (line 311) | func (c *Controller) RealJSMC(cfg *natsContext) (jsmClient, error) {
method cleanupStreams (line 345) | func (c *Controller) cleanupStreams() error {
method cleanupConsumers (line 415) | func (c *Controller) cleanupConsumers() error {
method normalEvent (line 467) | func (c *Controller) normalEvent(o runtime.Object, reason, message str...
method warningEvent (line 473) | func (c *Controller) warningEvent(o runtime.Object, reason, message st...
method getAccountOverrides (line 491) | func (c *Controller) getAccountOverrides(account string, ns string) (*...
method runWithJsmc (line 628) | func (c *Controller) runWithJsmc(jsm jsmClientFunc, acc *accountOverri...
function NewController (line 122) | func NewController(opt Options) *Controller {
function selectMissingStreamsFromList (line 327) | func selectMissingStreamsFromList(prev, cur map[string]*apis.Stream) []*...
function streamsMap (line 337) | func streamsMap(ss []*apis.Stream) map[string]*apis.Stream {
function selectMissingConsumersFromList (line 397) | func selectMissingConsumersFromList(prev, cur map[string]*apis.Consumer)...
function consumerMap (line 407) | func consumerMap(cs []*apis.Consumer) map[string]*apis.Consumer {
type accountOverrides (line 479) | type accountOverrides struct
type jsmcSpecOverrides (line 621) | type jsmcSpecOverrides struct
function splitNamespaceName (line 691) | func splitNamespaceName(item interface{}) (ns string, name string, err e...
function getStorageType (line 711) | func getStorageType(s string) (jsmapi.StorageType, error) {
function enqueueWork (line 722) | func enqueueWork(q workqueue.TypedRateLimitingInterface[any], item inter...
type jsmClientFunc (line 733) | type jsmClientFunc
type processorFunc (line 734) | type processorFunc
function processQueueNext (line 737) | func processQueueNext(q workqueue.TypedRateLimitingInterface[any], jmsCl...
function UpsertCondition (line 772) | func UpsertCondition(cs []apis.Condition, next apis.Condition) []apis.Co...
function shouldEnqueue (line 785) | func shouldEnqueue(prevObj, nextObj interface{}) bool {
function eventHandlers (line 807) | func eventHandlers(q workqueue.TypedRateLimitingInterface[any]) cache.Re...
FILE: controllers/jetstream/controller_test.go
function TestMain (line 19) | func TestMain(m *testing.M) {
function TestGetStorageType (line 28) | func TestGetStorageType(t *testing.T) {
function TestEnqueueWork (line 63) | func TestEnqueueWork(t *testing.T) {
function TestProcessQueueNext (line 94) | func TestProcessQueueNext(t *testing.T) {
function TestUpsertCondition (line 184) | func TestUpsertCondition(t *testing.T) {
function TestShouldEnqueue (line 238) | func TestShouldEnqueue(t *testing.T) {
FILE: controllers/jetstream/jsmclient.go
type jsmClient (line 12) | type jsmClient interface
type jsmStream (line 23) | type jsmStream interface
type jsmConsumer (line 28) | type jsmConsumer interface
type realJsmClient (line 33) | type realJsmClient struct
method Connect (line 38) | func (c *realJsmClient) Connect(servers string, opts ...nats.Option) e...
method Close (line 55) | func (c *realJsmClient) Close() {
method LoadStream (line 59) | func (c *realJsmClient) LoadStream(_ context.Context, name string) (js...
method NewStream (line 63) | func (c *realJsmClient) NewStream(_ context.Context, name string, opts...
method LoadConsumer (line 67) | func (c *realJsmClient) LoadConsumer(_ context.Context, stream, consum...
method NewConsumer (line 71) | func (c *realJsmClient) NewConsumer(_ context.Context, stream string, ...
FILE: controllers/jetstream/jsmclient_test.go
type mockStream (line 11) | type mockStream struct
method UpdateConfiguration (line 17) | func (m *mockStream) UpdateConfiguration(cnf jsmapi.StreamConfig, opts...
method Delete (line 27) | func (m *mockStream) Delete() error {
type mockConsumer (line 31) | type mockConsumer struct
method UpdateConfiguration (line 35) | func (m *mockConsumer) UpdateConfiguration(opts ...jsm.ConsumerOption)...
method Delete (line 39) | func (m *mockConsumer) Delete() error {
type mockJsmClient (line 43) | type mockJsmClient struct
method Connect (line 57) | func (c *mockJsmClient) Connect(servers string, opts ...nats.Option) e...
method Close (line 61) | func (c *mockJsmClient) Close() {}
method LoadStream (line 63) | func (c *mockJsmClient) LoadStream(ctx context.Context, name string) (...
method NewStream (line 67) | func (c *mockJsmClient) NewStream(ctx context.Context, name string, op...
method LoadConsumer (line 71) | func (c *mockJsmClient) LoadConsumer(ctx context.Context, stream, cons...
method NewConsumer (line 75) | func (c *mockJsmClient) NewConsumer(ctx context.Context, stream string...
FILE: controllers/jetstream/stream.go
method runStreamQueue (line 33) | func (c *Controller) runStreamQueue() {
method processStream (line 39) | func (c *Controller) processStream(ns, name string, jsm jsmClientFunc) (...
method processStreamObject (line 50) | func (c *Controller) processStreamObject(str *apis.Stream, jsm jsmClient...
function streamExists (line 161) | func streamExists(ctx context.Context, c jsmClient, spec apis.StreamSpec...
function createStream (line 172) | func createStream(ctx context.Context, c jsmClient, spec apis.StreamSpec...
function updateStream (line 377) | func updateStream(ctx context.Context, c jsmClient, spec apis.StreamSpec...
function deleteStream (line 497) | func deleteStream(ctx context.Context, c jsmClient, spec apis.StreamSpec...
function setStreamErrored (line 521) | func setStreamErrored(ctx context.Context, s *apis.Stream, sif typed.Str...
function setStreamOK (line 552) | func setStreamOK(ctx context.Context, s *apis.Stream, i typed.StreamInte...
function getDurationFromString (line 581) | func getDurationFromString(v string) (time.Duration, error) {
function getRetention (line 589) | func getRetention(v string) jsmapi.RetentionPolicy {
function getStorage (line 600) | func getStorage(v string) jsmapi.StorageType {
function getDiscard (line 609) | func getDiscard(v string) jsmapi.DiscardPolicy {
function getDuplicates (line 618) | func getDuplicates(v string) (time.Duration, error) {
function getStreamSource (line 626) | func getStreamSource(ss *apis.StreamSource) (*jsmapi.StreamSource, error) {
FILE: controllers/jetstream/stream_test.go
function TestProcessStream (line 21) | func TestProcessStream(t *testing.T) {
FILE: internal/controller/account_controller.go
type AccountReconciler (line 40) | type AccountReconciler struct
method Reconcile (line 62) | func (r *AccountReconciler) Reconcile(ctx context.Context, req ctrl.Re...
method findDependentResources (line 159) | func (r *AccountReconciler) findDependentResources(ctx context.Context...
method SetupWithManager (line 224) | func (r *AccountReconciler) SetupWithManager(mgr ctrl.Manager) error {
type JetStreamResource (line 45) | type JetStreamResource interface
type JetStreamResourceList (line 50) | type JetStreamResourceList
FILE: internal/controller/client.go
type NatsConfig (line 15) | type NatsConfig struct
method Copy (line 30) | func (o *NatsConfig) Copy() *NatsConfig {
method Hash (line 39) | func (o *NatsConfig) Hash() (string, error) {
method Overlay (line 92) | func (o *NatsConfig) Overlay(overlay *NatsConfig) {
method HasAuth (line 136) | func (o *NatsConfig) HasAuth() bool {
method UnsetAuth (line 140) | func (o *NatsConfig) UnsetAuth() {
method buildOptions (line 149) | func (o *NatsConfig) buildOptions() ([]nats.Option, error) {
type Closable (line 195) | type Closable interface
function CreateJSMClient (line 199) | func CreateJSMClient(conn *pooledConnection, pedantic bool, domain strin...
function CreateJetStreamClient (line 233) | func CreateJetStreamClient(conn *pooledConnection, pedantic bool, domain...
function createNatsConn (line 251) | func createNatsConn(cfg *NatsConfig) (*nats.Conn, error) {
FILE: internal/controller/connection_pool.go
type pooledConnection (line 10) | type pooledConnection struct
method Close (line 17) | func (pc *pooledConnection) Close() {
type connectionPool (line 25) | type connectionPool struct
method Get (line 38) | func (p *connectionPool) Get(c *NatsConfig, pedantic bool) (*pooledCon...
method release (line 73) | func (p *connectionPool) release(hash string) {
function newConnPool (line 31) | func newConnPool(gracePeriod time.Duration) *connectionPool {
FILE: internal/controller/connection_pool_test.go
function TestConnPool (line 12) | func TestConnPool(t *testing.T) {
FILE: internal/controller/consumer_controller.go
type ConsumerReconciler (line 46) | type ConsumerReconciler struct
method Reconcile (line 57) | func (r *ConsumerReconciler) Reconcile(ctx context.Context, req ctrl.R...
method deleteConsumer (line 139) | func (r *ConsumerReconciler) deleteConsumer(ctx context.Context, log l...
method createOrUpdate (line 196) | func (r *ConsumerReconciler) createOrUpdate(ctx context.Context, log k...
method SetupWithManager (line 525) | func (r *ConsumerReconciler) SetupWithManager(mgr ctrl.Manager) error {
function getStoredConsumerState (line 311) | func getStoredConsumerState(consumer *api.Consumer) (*jsmapi.ConsumerCon...
function getServerConsumerState (line 325) | func getServerConsumerState(js *jsm.Manager, consumer *api.Consumer) (*j...
function consumerSpecToConfig (line 338) | func consumerSpecToConfig(spec *api.ConsumerSpec) ([]jsm.ConsumerOption,...
FILE: internal/controller/consumer_controller_test.go
function Test_consumerSpecToConfig (line 726) | func Test_consumerSpecToConfig(t *testing.T) {
FILE: internal/controller/helpers_test.go
function assertReadyStateMatches (line 15) | func assertReadyStateMatches(condition api.Condition, status v1.Conditio...
function CreateTestServer (line 29) | func CreateTestServer() *server.Server {
FILE: internal/controller/jetstream_controller.go
constant JSConsumerNotFoundErr (line 29) | JSConsumerNotFoundErr uint16 = 10014
constant JSStreamNotFoundErr (line 30) | JSStreamNotFoundErr uint16 = 10059
type JetStreamController (line 35) | type JetStreamController interface
function NewJSController (line 58) | func NewJSController(k8sClient client.Client, natsConfig *NatsConfig, co...
type jsController (line 68) | type jsController struct
method RequeueInterval (line 77) | func (c *jsController) RequeueInterval() time.Duration {
method ReadOnly (line 89) | func (c *jsController) ReadOnly() bool {
method ValidNamespace (line 93) | func (c *jsController) ValidNamespace(namespace string) bool {
method WithJSMClient (line 98) | func (c *jsController) WithJSMClient(opts api.ConnectionOpts, ns strin...
method WithJetStreamClient (line 118) | func (c *jsController) WithJetStreamClient(opts api.ConnectionOpts, ns...
method natsConfigFromOpts (line 139) | func (c *jsController) natsConfigFromOpts(opts api.ConnectionOpts, ns ...
function natsConfigFromOpts (line 392) | func natsConfigFromOpts(opts api.ConnectionOpts) *NatsConfig {
function updateReadyCondition (line 432) | func updateReadyCondition(conditions []api.Condition, status v1.Conditio...
function jsonString (line 464) | func jsonString(v string) []byte {
function compareConfigState (line 468) | func compareConfigState(actual any, desired any) string {
function versionComponents (line 472) | func versionComponents(version string) (major, minor, patch int, err err...
FILE: internal/controller/jetstream_controller_test.go
function Test_updateReadyCondition (line 12) | func Test_updateReadyCondition(t *testing.T) {
FILE: internal/controller/keyvalue_controller.go
constant kvStreamPrefix (line 40) | kvStreamPrefix = "KV_"
type KeyValueReconciler (line 44) | type KeyValueReconciler struct
method Reconcile (line 60) | func (r *KeyValueReconciler) Reconcile(ctx context.Context, req ctrl.R...
method deleteKeyValue (line 135) | func (r *KeyValueReconciler) deleteKeyValue(ctx context.Context, log l...
method createOrUpdate (line 185) | func (r *KeyValueReconciler) createOrUpdate(ctx context.Context, log l...
method SetupWithManager (line 400) | func (r *KeyValueReconciler) SetupWithManager(mgr ctrl.Manager) error {
function getStoredKeyValueState (line 301) | func getStoredKeyValueState(keyValue *api.KeyValue) (*jetstream.StreamCo...
function getServerKeyValueState (line 315) | func getServerKeyValueState(ctx context.Context, js jetstream.JetStream,...
function keyValueSpecToConfig (line 328) | func keyValueSpecToConfig(spec *api.KeyValueSpec) (jetstream.KeyValueCon...
FILE: internal/controller/keyvalue_controller_test.go
function Test_mapKVSpecToConfig (line 685) | func Test_mapKVSpecToConfig(t *testing.T) {
FILE: internal/controller/objectstore_controller.go
constant objStreamPrefix (line 40) | objStreamPrefix = "OBJ_"
type ObjectStoreReconciler (line 44) | type ObjectStoreReconciler struct
method Reconcile (line 61) | func (r *ObjectStoreReconciler) Reconcile(ctx context.Context, req ctr...
method deleteObjectStore (line 133) | func (r *ObjectStoreReconciler) deleteObjectStore(ctx context.Context,...
method createOrUpdate (line 183) | func (r *ObjectStoreReconciler) createOrUpdate(ctx context.Context, lo...
method SetupWithManager (line 366) | func (r *ObjectStoreReconciler) SetupWithManager(mgr ctrl.Manager) err...
function getStoredObjectStoreState (line 299) | func getStoredObjectStoreState(objectStore *api.ObjectStore) (*jetstream...
function getServerObjectStoreState (line 313) | func getServerObjectStoreState(ctx context.Context, js jetstream.JetStre...
function objectStoreSpecToConfig (line 326) | func objectStoreSpecToConfig(spec *api.ObjectStoreSpec) (jetstream.Objec...
FILE: internal/controller/objectstore_controller_test.go
function Test_mapobjectstoreSpecToConfig (line 675) | func Test_mapobjectstoreSpecToConfig(t *testing.T) {
FILE: internal/controller/register.go
type Config (line 15) | type Config struct
function RegisterAll (line 26) | func RegisterAll(mgr ctrl.Manager, clientConfig *NatsConfig, config *Con...
FILE: internal/controller/stream_controller.go
type StreamReconciler (line 42) | type StreamReconciler struct
method Reconcile (line 59) | func (r *StreamReconciler) Reconcile(ctx context.Context, req ctrl.Req...
method deleteStream (line 134) | func (r *StreamReconciler) deleteStream(ctx context.Context, log logr....
method createOrUpdate (line 184) | func (r *StreamReconciler) createOrUpdate(ctx context.Context, log log...
method SetupWithManager (line 649) | func (r *StreamReconciler) SetupWithManager(mgr ctrl.Manager) error {
function getStoredStreamState (line 299) | func getStoredStreamState(stream *api.Stream) (*jsmapi.StreamConfig, err...
function getServerStreamState (line 313) | func getServerStreamState(jsm *jsm.Manager, stream *api.Stream) (*jsmapi...
function streamSpecToConfig (line 326) | func streamSpecToConfig(spec *api.StreamSpec, currentConfig *jsmapi.Stre...
function mapStreamSource (line 578) | func mapStreamSource(ss *api.StreamSource) (*jetstream.StreamSource, err...
function mapJSMStreamSource (line 613) | func mapJSMStreamSource(ss *api.StreamSource) (*jsmapi.StreamSource, err...
FILE: internal/controller/stream_controller_test.go
function Test_mapSpecToConfig (line 729) | func Test_mapSpecToConfig(t *testing.T) {
function TestStreamUpdateWithoutPlacement (line 947) | func TestStreamUpdateWithoutPlacement(t *testing.T) {
FILE: internal/controller/suite_test.go
function TestControllers (line 55) | func TestControllers(t *testing.T) {
FILE: internal/controller/types.go
constant readyCondType (line 4) | readyCondType = "Ready"
constant accountFinalizer (line 5) | accountFinalizer = "account.nats.io/finalizer"
constant streamFinalizer (line 6) | streamFinalizer = "stream.nats.io/finalizer"
constant keyValueFinalizer (line 7) | keyValueFinalizer = "kv.nats.io/finalizer"
constant objectStoreFinalizer (line 8) | objectStoreFinalizer = "objectstore.nats.io/finalizer"
constant consumerFinalizer (line 9) | consumerFinalizer = "consumer.nats.io/finalizer"
constant stateAnnotationConsumer (line 11) | stateAnnotationConsumer = "consumer.nats.io/state"
constant stateAnnotationKV (line 12) | stateAnnotationKV = "kv.nats.io/state"
constant stateAnnotationObj (line 13) | stateAnnotationObj = "objectstore.nats.io/state"
constant stateAnnotationStream (line 14) | stateAnnotationStream = "stream.nats.io/state"
constant stateReady (line 16) | stateReady = "Ready"
constant stateReconciling (line 17) | stateReconciling = "Reconciling"
constant stateErrored (line 18) | stateErrored = "Errored"
constant stateFinalizing (line 19) | stateFinalizing = "Finalizing"
FILE: pkg/bootconfig/bootconfig.go
type Options (line 29) | type Options struct
type Controller (line 47) | type Controller struct
method SetupClients (line 67) | func (c *Controller) SetupClients(cfg *k8srestapi.Config) error {
method Run (line 77) | func (c *Controller) Run(ctx context.Context) error {
function NewController (line 59) | func NewController(opts *Options) *Controller {
FILE: pkg/jetstream/apis/jetstream/register.go
constant GroupName (line 5) | GroupName = "jetstream.nats.io"
FILE: pkg/jetstream/apis/jetstream/v1beta1/consumertypes.go
type Consumer (line 11) | type Consumer struct
method GetSpec (line 19) | func (c *Consumer) GetSpec() interface{} {
type ConsumerSpec (line 24) | type ConsumerSpec struct
type ConsumerList (line 49) | type ConsumerList struct
FILE: pkg/jetstream/apis/jetstream/v1beta1/register.go
function Kind (line 22) | func Kind(kind string) schema.GroupKind {
function Resource (line 27) | func Resource(resource string) schema.GroupResource {
function addKnownTypes (line 32) | func addKnownTypes(scheme *runtime.Scheme) error {
FILE: pkg/jetstream/apis/jetstream/v1beta1/streamtemplatetypes.go
type StreamTemplate (line 11) | type StreamTemplate struct
method GetSpec (line 19) | func (s *StreamTemplate) GetSpec() interface{} {
type StreamTemplateSpec (line 24) | type StreamTemplateSpec struct
type StreamTemplateList (line 33) | type StreamTemplateList struct
FILE: pkg/jetstream/apis/jetstream/v1beta1/streamtypes.go
type Stream (line 11) | type Stream struct
method GetSpec (line 19) | func (s *Stream) GetSpec() interface{} {
type StreamSpec (line 24) | type StreamSpec struct
type StreamPlacement (line 47) | type StreamPlacement struct
type StreamSource (line 52) | type StreamSource struct
type StreamList (line 65) | type StreamList struct
FILE: pkg/jetstream/apis/jetstream/v1beta1/types.go
type CredentialsSecret (line 7) | type CredentialsSecret struct
type Status (line 12) | type Status struct
type Condition (line 17) | type Condition struct
FILE: pkg/jetstream/apis/jetstream/v1beta1/zz_generated.deepcopy.go
method DeepCopyInto (line 26) | func (in *Condition) DeepCopyInto(out *Condition) {
method DeepCopy (line 32) | func (in *Condition) DeepCopy() *Condition {
method DeepCopyInto (line 42) | func (in *Consumer) DeepCopyInto(out *Consumer) {
method DeepCopy (line 52) | func (in *Consumer) DeepCopy() *Consumer {
method DeepCopyObject (line 62) | func (in *Consumer) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 70) | func (in *ConsumerList) DeepCopyInto(out *ConsumerList) {
method DeepCopy (line 85) | func (in *ConsumerList) DeepCopy() *ConsumerList {
method DeepCopyObject (line 95) | func (in *ConsumerList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 103) | func (in *ConsumerSpec) DeepCopyInto(out *ConsumerSpec) {
method DeepCopy (line 109) | func (in *ConsumerSpec) DeepCopy() *ConsumerSpec {
method DeepCopyInto (line 119) | func (in *CredentialsSecret) DeepCopyInto(out *CredentialsSecret) {
method DeepCopy (line 125) | func (in *CredentialsSecret) DeepCopy() *CredentialsSecret {
method DeepCopyInto (line 135) | func (in *Status) DeepCopyInto(out *Status) {
method DeepCopy (line 146) | func (in *Status) DeepCopy() *Status {
method DeepCopyInto (line 156) | func (in *Stream) DeepCopyInto(out *Stream) {
method DeepCopy (line 166) | func (in *Stream) DeepCopy() *Stream {
method DeepCopyObject (line 176) | func (in *Stream) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 184) | func (in *StreamList) DeepCopyInto(out *StreamList) {
method DeepCopy (line 199) | func (in *StreamList) DeepCopy() *StreamList {
method DeepCopyObject (line 209) | func (in *StreamList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 217) | func (in *StreamPlacement) DeepCopyInto(out *StreamPlacement) {
method DeepCopy (line 228) | func (in *StreamPlacement) DeepCopy() *StreamPlacement {
method DeepCopyInto (line 238) | func (in *StreamSource) DeepCopyInto(out *StreamSource) {
method DeepCopy (line 244) | func (in *StreamSource) DeepCopy() *StreamSource {
method DeepCopyInto (line 254) | func (in *StreamSpec) DeepCopyInto(out *StreamSpec) {
method DeepCopy (line 286) | func (in *StreamSpec) DeepCopy() *StreamSpec {
method DeepCopyInto (line 296) | func (in *StreamTemplate) DeepCopyInto(out *StreamTemplate) {
method DeepCopy (line 306) | func (in *StreamTemplate) DeepCopy() *StreamTemplate {
method DeepCopyObject (line 316) | func (in *StreamTemplate) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 324) | func (in *StreamTemplateList) DeepCopyInto(out *StreamTemplateList) {
method DeepCopy (line 339) | func (in *StreamTemplateList) DeepCopy() *StreamTemplateList {
method DeepCopyObject (line 349) | func (in *StreamTemplateList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 357) | func (in *StreamTemplateSpec) DeepCopyInto(out *StreamTemplateSpec) {
method DeepCopy (line 364) | func (in *StreamTemplateSpec) DeepCopy() *StreamTemplateSpec {
FILE: pkg/jetstream/apis/jetstream/v1beta2/accounttypes.go
type Account (line 11) | type Account struct
method GetSpec (line 19) | func (c *Account) GetSpec() interface{} {
type AccountSpec (line 24) | type AccountSpec struct
type AccountList (line 36) | type AccountList struct
FILE: pkg/jetstream/apis/jetstream/v1beta2/consumertypes.go
type Consumer (line 11) | type Consumer struct
method GetSpec (line 19) | func (c *Consumer) GetSpec() interface{} {
type ConsumerSpec (line 24) | type ConsumerSpec struct
type ConsumerList (line 65) | type ConsumerList struct
FILE: pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go
type KeyValue (line 13) | type KeyValue struct
method GetSpec (line 21) | func (s *KeyValue) GetSpec() interface{} {
type KeyValueSpec (line 26) | type KeyValueSpec struct
type KeyValueList (line 50) | type KeyValueList struct
FILE: pkg/jetstream/apis/jetstream/v1beta2/objectstoretypes.go
type ObjectStore (line 11) | type ObjectStore struct
method GetSpec (line 19) | func (s *ObjectStore) GetSpec() interface{} {
type ObjectStoreSpec (line 24) | type ObjectStoreSpec struct
type ObjectStoreList (line 40) | type ObjectStoreList struct
FILE: pkg/jetstream/apis/jetstream/v1beta2/register.go
function Kind (line 22) | func Kind(kind string) schema.GroupKind {
function Resource (line 27) | func Resource(resource string) schema.GroupResource {
function addKnownTypes (line 32) | func addKnownTypes(scheme *runtime.Scheme) error {
FILE: pkg/jetstream/apis/jetstream/v1beta2/streamtypes.go
type Stream (line 11) | type Stream struct
method GetSpec (line 19) | func (s *Stream) GetSpec() interface{} {
type StreamSpec (line 24) | type StreamSpec struct
type SubjectTransform (line 65) | type SubjectTransform struct
type StreamPlacement (line 70) | type StreamPlacement struct
type StreamSource (line 75) | type StreamSource struct
type RePublish (line 87) | type RePublish struct
type StreamList (line 96) | type StreamList struct
FILE: pkg/jetstream/apis/jetstream/v1beta2/types.go
type CredentialsSecret (line 7) | type CredentialsSecret struct
type Status (line 12) | type Status struct
type Condition (line 17) | type Condition struct
type BaseStreamConfig (line 25) | type BaseStreamConfig struct
type ConnectionOpts (line 31) | type ConnectionOpts struct
type ConsumerLimits (line 41) | type ConsumerLimits struct
type TLS (line 46) | type TLS struct
type TLSSecret (line 52) | type TLSSecret struct
type CredsSecret (line 59) | type CredsSecret struct
type NKeySecret (line 64) | type NKeySecret struct
type TokenSecret (line 69) | type TokenSecret struct
type User (line 74) | type User struct
type SecretRef (line 80) | type SecretRef struct
FILE: pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go
method DeepCopyInto (line 26) | func (in *Account) DeepCopyInto(out *Account) {
method DeepCopy (line 36) | func (in *Account) DeepCopy() *Account {
method DeepCopyObject (line 46) | func (in *Account) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 54) | func (in *AccountList) DeepCopyInto(out *AccountList) {
method DeepCopy (line 69) | func (in *AccountList) DeepCopy() *AccountList {
method DeepCopyObject (line 79) | func (in *AccountList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 87) | func (in *AccountSpec) DeepCopyInto(out *AccountSpec) {
method DeepCopy (line 123) | func (in *AccountSpec) DeepCopy() *AccountSpec {
method DeepCopyInto (line 133) | func (in *BaseStreamConfig) DeepCopyInto(out *BaseStreamConfig) {
method DeepCopy (line 140) | func (in *BaseStreamConfig) DeepCopy() *BaseStreamConfig {
method DeepCopyInto (line 150) | func (in *Condition) DeepCopyInto(out *Condition) {
method DeepCopy (line 156) | func (in *Condition) DeepCopy() *Condition {
method DeepCopyInto (line 166) | func (in *ConnectionOpts) DeepCopyInto(out *ConnectionOpts) {
method DeepCopy (line 182) | func (in *ConnectionOpts) DeepCopy() *ConnectionOpts {
method DeepCopyInto (line 192) | func (in *Consumer) DeepCopyInto(out *Consumer) {
method DeepCopy (line 202) | func (in *Consumer) DeepCopy() *Consumer {
method DeepCopyObject (line 212) | func (in *Consumer) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 220) | func (in *ConsumerLimits) DeepCopyInto(out *ConsumerLimits) {
method DeepCopy (line 226) | func (in *ConsumerLimits) DeepCopy() *ConsumerLimits {
method DeepCopyInto (line 236) | func (in *ConsumerList) DeepCopyInto(out *ConsumerList) {
method DeepCopy (line 251) | func (in *ConsumerList) DeepCopy() *ConsumerList {
method DeepCopyObject (line 261) | func (in *ConsumerList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 269) | func (in *ConsumerSpec) DeepCopyInto(out *ConsumerSpec) {
method DeepCopy (line 298) | func (in *ConsumerSpec) DeepCopy() *ConsumerSpec {
method DeepCopyInto (line 308) | func (in *CredentialsSecret) DeepCopyInto(out *CredentialsSecret) {
method DeepCopy (line 314) | func (in *CredentialsSecret) DeepCopy() *CredentialsSecret {
method DeepCopyInto (line 324) | func (in *CredsSecret) DeepCopyInto(out *CredsSecret) {
method DeepCopy (line 335) | func (in *CredsSecret) DeepCopy() *CredsSecret {
method DeepCopyInto (line 345) | func (in *KeyValue) DeepCopyInto(out *KeyValue) {
method DeepCopy (line 355) | func (in *KeyValue) DeepCopy() *KeyValue {
method DeepCopyObject (line 365) | func (in *KeyValue) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 373) | func (in *KeyValueList) DeepCopyInto(out *KeyValueList) {
method DeepCopy (line 388) | func (in *KeyValueList) DeepCopy() *KeyValueList {
method DeepCopyObject (line 398) | func (in *KeyValueList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 406) | func (in *KeyValueSpec) DeepCopyInto(out *KeyValueSpec) {
method DeepCopy (line 439) | func (in *KeyValueSpec) DeepCopy() *KeyValueSpec {
method DeepCopyInto (line 449) | func (in *NKeySecret) DeepCopyInto(out *NKeySecret) {
method DeepCopy (line 460) | func (in *NKeySecret) DeepCopy() *NKeySecret {
method DeepCopyInto (line 470) | func (in *ObjectStore) DeepCopyInto(out *ObjectStore) {
method DeepCopy (line 480) | func (in *ObjectStore) DeepCopy() *ObjectStore {
method DeepCopyObject (line 490) | func (in *ObjectStore) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 498) | func (in *ObjectStoreList) DeepCopyInto(out *ObjectStoreList) {
method DeepCopy (line 513) | func (in *ObjectStoreList) DeepCopy() *ObjectStoreList {
method DeepCopyObject (line 523) | func (in *ObjectStoreList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 531) | func (in *ObjectStoreSpec) DeepCopyInto(out *ObjectStoreSpec) {
method DeepCopy (line 550) | func (in *ObjectStoreSpec) DeepCopy() *ObjectStoreSpec {
method DeepCopyInto (line 560) | func (in *RePublish) DeepCopyInto(out *RePublish) {
method DeepCopy (line 566) | func (in *RePublish) DeepCopy() *RePublish {
method DeepCopyInto (line 576) | func (in *SecretRef) DeepCopyInto(out *SecretRef) {
method DeepCopy (line 582) | func (in *SecretRef) DeepCopy() *SecretRef {
method DeepCopyInto (line 592) | func (in *Status) DeepCopyInto(out *Status) {
method DeepCopy (line 603) | func (in *Status) DeepCopy() *Status {
method DeepCopyInto (line 613) | func (in *Stream) DeepCopyInto(out *Stream) {
method DeepCopy (line 623) | func (in *Stream) DeepCopy() *Stream {
method DeepCopyObject (line 633) | func (in *Stream) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 641) | func (in *StreamList) DeepCopyInto(out *StreamList) {
method DeepCopy (line 656) | func (in *StreamList) DeepCopy() *StreamList {
method DeepCopyObject (line 666) | func (in *StreamList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 674) | func (in *StreamPlacement) DeepCopyInto(out *StreamPlacement) {
method DeepCopy (line 685) | func (in *StreamPlacement) DeepCopy() *StreamPlacement {
method DeepCopyInto (line 695) | func (in *StreamSource) DeepCopyInto(out *StreamSource) {
method DeepCopy (line 712) | func (in *StreamSource) DeepCopy() *StreamSource {
method DeepCopyInto (line 722) | func (in *StreamSpec) DeepCopyInto(out *StreamSpec) {
method DeepCopy (line 777) | func (in *StreamSpec) DeepCopy() *StreamSpec {
method DeepCopyInto (line 787) | func (in *SubjectTransform) DeepCopyInto(out *SubjectTransform) {
method DeepCopy (line 793) | func (in *SubjectTransform) DeepCopy() *SubjectTransform {
method DeepCopyInto (line 803) | func (in *TLS) DeepCopyInto(out *TLS) {
method DeepCopy (line 814) | func (in *TLS) DeepCopy() *TLS {
method DeepCopyInto (line 824) | func (in *TLSSecret) DeepCopyInto(out *TLSSecret) {
method DeepCopy (line 835) | func (in *TLSSecret) DeepCopy() *TLSSecret {
method DeepCopyInto (line 845) | func (in *TokenSecret) DeepCopyInto(out *TokenSecret) {
method DeepCopy (line 852) | func (in *TokenSecret) DeepCopy() *TokenSecret {
method DeepCopyInto (line 862) | func (in *User) DeepCopyInto(out *User) {
method DeepCopy (line 869) | func (in *User) DeepCopy() *User {
FILE: pkg/jetstream/generated/applyconfiguration/internal/internal.go
function Parser (line 25) | func Parser() *typed.Parser {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/account.go
type AccountApplyConfiguration (line 26) | type AccountApplyConfiguration struct
method IsApplyConfiguration (line 43) | func (b AccountApplyConfiguration) IsApplyConfiguration() {}
method WithKind (line 48) | func (b *AccountApplyConfiguration) WithKind(value string) *AccountApp...
method WithAPIVersion (line 56) | func (b *AccountApplyConfiguration) WithAPIVersion(value string) *Acco...
method WithName (line 64) | func (b *AccountApplyConfiguration) WithName(value string) *AccountApp...
method WithGenerateName (line 73) | func (b *AccountApplyConfiguration) WithGenerateName(value string) *Ac...
method WithNamespace (line 82) | func (b *AccountApplyConfiguration) WithNamespace(value string) *Accou...
method WithUID (line 91) | func (b *AccountApplyConfiguration) WithUID(value types.UID) *AccountA...
method WithResourceVersion (line 100) | func (b *AccountApplyConfiguration) WithResourceVersion(value string) ...
method WithGeneration (line 109) | func (b *AccountApplyConfiguration) WithGeneration(value int64) *Accou...
method WithCreationTimestamp (line 118) | func (b *AccountApplyConfiguration) WithCreationTimestamp(value metav1...
method WithDeletionTimestamp (line 127) | func (b *AccountApplyConfiguration) WithDeletionTimestamp(value metav1...
method WithDeletionGracePeriodSeconds (line 136) | func (b *AccountApplyConfiguration) WithDeletionGracePeriodSeconds(val...
method WithLabels (line 146) | func (b *AccountApplyConfiguration) WithLabels(entries map[string]stri...
method WithAnnotations (line 161) | func (b *AccountApplyConfiguration) WithAnnotations(entries map[string...
method WithOwnerReferences (line 175) | func (b *AccountApplyConfiguration) WithOwnerReferences(values ...*v1....
method WithFinalizers (line 189) | func (b *AccountApplyConfiguration) WithFinalizers(values ...string) *...
method ensureObjectMetaApplyConfigurationExists (line 197) | func (b *AccountApplyConfiguration) ensureObjectMetaApplyConfiguration...
method WithSpec (line 206) | func (b *AccountApplyConfiguration) WithSpec(value *AccountSpecApplyCo...
method WithStatus (line 214) | func (b *AccountApplyConfiguration) WithStatus(value *StatusApplyConfi...
method GetKind (line 220) | func (b *AccountApplyConfiguration) GetKind() *string {
method GetAPIVersion (line 225) | func (b *AccountApplyConfiguration) GetAPIVersion() *string {
method GetName (line 230) | func (b *AccountApplyConfiguration) GetName() *string {
method GetNamespace (line 236) | func (b *AccountApplyConfiguration) GetNamespace() *string {
function Account (line 35) | func Account(name, namespace string) *AccountApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/accountspec.go
type AccountSpecApplyConfiguration (line 20) | type AccountSpecApplyConfiguration struct
method WithServers (line 38) | func (b *AccountSpecApplyConfiguration) WithServers(values ...string) ...
method WithTLS (line 48) | func (b *AccountSpecApplyConfiguration) WithTLS(value *TLSSecretApplyC...
method WithCreds (line 56) | func (b *AccountSpecApplyConfiguration) WithCreds(value *CredsSecretAp...
method WithNKey (line 64) | func (b *AccountSpecApplyConfiguration) WithNKey(value *NKeySecretAppl...
method WithToken (line 72) | func (b *AccountSpecApplyConfiguration) WithToken(value *TokenSecretAp...
method WithUser (line 80) | func (b *AccountSpecApplyConfiguration) WithUser(value *UserApplyConfi...
function AccountSpec (line 31) | func AccountSpec() *AccountSpecApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/basestreamconfig.go
type BaseStreamConfigApplyConfiguration (line 20) | type BaseStreamConfigApplyConfiguration struct
method WithPreventDelete (line 34) | func (b *BaseStreamConfigApplyConfiguration) WithPreventDelete(value b...
method WithPreventUpdate (line 42) | func (b *BaseStreamConfigApplyConfiguration) WithPreventUpdate(value b...
function BaseStreamConfig (line 27) | func BaseStreamConfig() *BaseStreamConfigApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/condition.go
type ConditionApplyConfiguration (line 24) | type ConditionApplyConfiguration struct
method WithType (line 41) | func (b *ConditionApplyConfiguration) WithType(value string) *Conditio...
method WithStatus (line 49) | func (b *ConditionApplyConfiguration) WithStatus(value v1.ConditionSta...
method WithReason (line 57) | func (b *ConditionApplyConfiguration) WithReason(value string) *Condit...
method WithMessage (line 65) | func (b *ConditionApplyConfiguration) WithMessage(value string) *Condi...
method WithLastTransitionTime (line 73) | func (b *ConditionApplyConfiguration) WithLastTransitionTime(value str...
function Condition (line 34) | func Condition() *ConditionApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/connectionopts.go
type ConnectionOptsApplyConfiguration (line 20) | type ConnectionOptsApplyConfiguration struct
method WithAccount (line 39) | func (b *ConnectionOptsApplyConfiguration) WithAccount(value string) *...
method WithCreds (line 47) | func (b *ConnectionOptsApplyConfiguration) WithCreds(value string) *Co...
method WithNkey (line 55) | func (b *ConnectionOptsApplyConfiguration) WithNkey(value string) *Con...
method WithServers (line 63) | func (b *ConnectionOptsApplyConfiguration) WithServers(values ...strin...
method WithTLS (line 73) | func (b *ConnectionOptsApplyConfiguration) WithTLS(value *TLSApplyConf...
method WithTLSFirst (line 81) | func (b *ConnectionOptsApplyConfiguration) WithTLSFirst(value bool) *C...
method WithJsDomain (line 89) | func (b *ConnectionOptsApplyConfiguration) WithJsDomain(value string) ...
function ConnectionOpts (line 32) | func ConnectionOpts() *ConnectionOptsApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumer.go
type ConsumerApplyConfiguration (line 26) | type ConsumerApplyConfiguration struct
method IsApplyConfiguration (line 43) | func (b ConsumerApplyConfiguration) IsApplyConfiguration() {}
method WithKind (line 48) | func (b *ConsumerApplyConfiguration) WithKind(value string) *ConsumerA...
method WithAPIVersion (line 56) | func (b *ConsumerApplyConfiguration) WithAPIVersion(value string) *Con...
method WithName (line 64) | func (b *ConsumerApplyConfiguration) WithName(value string) *ConsumerA...
method WithGenerateName (line 73) | func (b *ConsumerApplyConfiguration) WithGenerateName(value string) *C...
method WithNamespace (line 82) | func (b *ConsumerApplyConfiguration) WithNamespace(value string) *Cons...
method WithUID (line 91) | func (b *ConsumerApplyConfiguration) WithUID(value types.UID) *Consume...
method WithResourceVersion (line 100) | func (b *ConsumerApplyConfiguration) WithResourceVersion(value string)...
method WithGeneration (line 109) | func (b *ConsumerApplyConfiguration) WithGeneration(value int64) *Cons...
method WithCreationTimestamp (line 118) | func (b *ConsumerApplyConfiguration) WithCreationTimestamp(value metav...
method WithDeletionTimestamp (line 127) | func (b *ConsumerApplyConfiguration) WithDeletionTimestamp(value metav...
method WithDeletionGracePeriodSeconds (line 136) | func (b *ConsumerApplyConfiguration) WithDeletionGracePeriodSeconds(va...
method WithLabels (line 146) | func (b *ConsumerApplyConfiguration) WithLabels(entries map[string]str...
method WithAnnotations (line 161) | func (b *ConsumerApplyConfiguration) WithAnnotations(entries map[strin...
method WithOwnerReferences (line 175) | func (b *ConsumerApplyConfiguration) WithOwnerReferences(values ...*v1...
method WithFinalizers (line 189) | func (b *ConsumerApplyConfiguration) WithFinalizers(values ...string) ...
method ensureObjectMetaApplyConfigurationExists (line 197) | func (b *ConsumerApplyConfiguration) ensureObjectMetaApplyConfiguratio...
method WithSpec (line 206) | func (b *ConsumerApplyConfiguration) WithSpec(value *ConsumerSpecApply...
method WithStatus (line 214) | func (b *ConsumerApplyConfiguration) WithStatus(value *StatusApplyConf...
method GetKind (line 220) | func (b *ConsumerApplyConfiguration) GetKind() *string {
method GetAPIVersion (line 225) | func (b *ConsumerApplyConfiguration) GetAPIVersion() *string {
method GetName (line 230) | func (b *ConsumerApplyConfiguration) GetName() *string {
method GetNamespace (line 236) | func (b *ConsumerApplyConfiguration) GetNamespace() *string {
function Consumer (line 35) | func Consumer(name, namespace string) *ConsumerApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerlimits.go
type ConsumerLimitsApplyConfiguration (line 20) | type ConsumerLimitsApplyConfiguration struct
method WithInactiveThreshold (line 34) | func (b *ConsumerLimitsApplyConfiguration) WithInactiveThreshold(value...
method WithMaxAckPending (line 42) | func (b *ConsumerLimitsApplyConfiguration) WithMaxAckPending(value int...
function ConsumerLimits (line 27) | func ConsumerLimits() *ConsumerLimitsApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go
type ConsumerSpecApplyConfiguration (line 20) | type ConsumerSpecApplyConfiguration struct
method WithDescription (line 65) | func (b *ConsumerSpecApplyConfiguration) WithDescription(value string)...
method WithAckPolicy (line 73) | func (b *ConsumerSpecApplyConfiguration) WithAckPolicy(value string) *...
method WithAckWait (line 81) | func (b *ConsumerSpecApplyConfiguration) WithAckWait(value string) *Co...
method WithDeliverPolicy (line 89) | func (b *ConsumerSpecApplyConfiguration) WithDeliverPolicy(value strin...
method WithDeliverSubject (line 97) | func (b *ConsumerSpecApplyConfiguration) WithDeliverSubject(value stri...
method WithDeliverGroup (line 105) | func (b *ConsumerSpecApplyConfiguration) WithDeliverGroup(value string...
method WithDurableName (line 113) | func (b *ConsumerSpecApplyConfiguration) WithDurableName(value string)...
method WithFilterSubject (line 121) | func (b *ConsumerSpecApplyConfiguration) WithFilterSubject(value strin...
method WithFilterSubjects (line 129) | func (b *ConsumerSpecApplyConfiguration) WithFilterSubjects(values ......
method WithFlowControl (line 139) | func (b *ConsumerSpecApplyConfiguration) WithFlowControl(value bool) *...
method WithHeartbeatInterval (line 147) | func (b *ConsumerSpecApplyConfiguration) WithHeartbeatInterval(value s...
method WithMaxAckPending (line 155) | func (b *ConsumerSpecApplyConfiguration) WithMaxAckPending(value int) ...
method WithMaxDeliver (line 163) | func (b *ConsumerSpecApplyConfiguration) WithMaxDeliver(value int) *Co...
method WithBackOff (line 171) | func (b *ConsumerSpecApplyConfiguration) WithBackOff(values ...string)...
method WithMaxWaiting (line 181) | func (b *ConsumerSpecApplyConfiguration) WithMaxWaiting(value int) *Co...
method WithOptStartSeq (line 189) | func (b *ConsumerSpecApplyConfiguration) WithOptStartSeq(value int) *C...
method WithOptStartTime (line 197) | func (b *ConsumerSpecApplyConfiguration) WithOptStartTime(value string...
method WithRateLimitBps (line 205) | func (b *ConsumerSpecApplyConfiguration) WithRateLimitBps(value int) *...
method WithReplayPolicy (line 213) | func (b *ConsumerSpecApplyConfiguration) WithReplayPolicy(value string...
method WithSampleFreq (line 221) | func (b *ConsumerSpecApplyConfiguration) WithSampleFreq(value string) ...
method WithHeadersOnly (line 229) | func (b *ConsumerSpecApplyConfiguration) WithHeadersOnly(value bool) *...
method WithMaxRequestBatch (line 237) | func (b *ConsumerSpecApplyConfiguration) WithMaxRequestBatch(value int...
method WithMaxRequestExpires (line 245) | func (b *ConsumerSpecApplyConfiguration) WithMaxRequestExpires(value s...
method WithMaxRequestMaxBytes (line 253) | func (b *ConsumerSpecApplyConfiguration) WithMaxRequestMaxBytes(value ...
method WithInactiveThreshold (line 261) | func (b *ConsumerSpecApplyConfiguration) WithInactiveThreshold(value s...
method WithReplicas (line 269) | func (b *ConsumerSpecApplyConfiguration) WithReplicas(value int) *Cons...
method WithMemStorage (line 277) | func (b *ConsumerSpecApplyConfiguration) WithMemStorage(value bool) *C...
method WithMetadata (line 286) | func (b *ConsumerSpecApplyConfiguration) WithMetadata(entries map[stri...
method WithPauseUntil (line 299) | func (b *ConsumerSpecApplyConfiguration) WithPauseUntil(value string) ...
method WithPriorityPolicy (line 307) | func (b *ConsumerSpecApplyConfiguration) WithPriorityPolicy(value stri...
method WithPinnedTTL (line 315) | func (b *ConsumerSpecApplyConfiguration) WithPinnedTTL(value string) *...
method WithPriorityGroups (line 323) | func (b *ConsumerSpecApplyConfiguration) WithPriorityGroups(values ......
method WithStreamName (line 333) | func (b *ConsumerSpecApplyConfiguration) WithStreamName(value string) ...
function ConsumerSpec (line 58) | func ConsumerSpec() *ConsumerSpecApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/credssecret.go
type CredsSecretApplyConfiguration (line 20) | type CredsSecretApplyConfiguration struct
method WithFile (line 34) | func (b *CredsSecretApplyConfiguration) WithFile(value string) *CredsS...
method WithSecret (line 42) | func (b *CredsSecretApplyConfiguration) WithSecret(value *SecretRefApp...
function CredsSecret (line 27) | func CredsSecret() *CredsSecretApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvalue.go
type KeyValueApplyConfiguration (line 26) | type KeyValueApplyConfiguration struct
method IsApplyConfiguration (line 43) | func (b KeyValueApplyConfiguration) IsApplyConfiguration() {}
method WithKind (line 48) | func (b *KeyValueApplyConfiguration) WithKind(value string) *KeyValueA...
method WithAPIVersion (line 56) | func (b *KeyValueApplyConfiguration) WithAPIVersion(value string) *Key...
method WithName (line 64) | func (b *KeyValueApplyConfiguration) WithName(value string) *KeyValueA...
method WithGenerateName (line 73) | func (b *KeyValueApplyConfiguration) WithGenerateName(value string) *K...
method WithNamespace (line 82) | func (b *KeyValueApplyConfiguration) WithNamespace(value string) *KeyV...
method WithUID (line 91) | func (b *KeyValueApplyConfiguration) WithUID(value types.UID) *KeyValu...
method WithResourceVersion (line 100) | func (b *KeyValueApplyConfiguration) WithResourceVersion(value string)...
method WithGeneration (line 109) | func (b *KeyValueApplyConfiguration) WithGeneration(value int64) *KeyV...
method WithCreationTimestamp (line 118) | func (b *KeyValueApplyConfiguration) WithCreationTimestamp(value metav...
method WithDeletionTimestamp (line 127) | func (b *KeyValueApplyConfiguration) WithDeletionTimestamp(value metav...
method WithDeletionGracePeriodSeconds (line 136) | func (b *KeyValueApplyConfiguration) WithDeletionGracePeriodSeconds(va...
method WithLabels (line 146) | func (b *KeyValueApplyConfiguration) WithLabels(entries map[string]str...
method WithAnnotations (line 161) | func (b *KeyValueApplyConfiguration) WithAnnotations(entries map[strin...
method WithOwnerReferences (line 175) | func (b *KeyValueApplyConfiguration) WithOwnerReferences(values ...*v1...
method WithFinalizers (line 189) | func (b *KeyValueApplyConfiguration) WithFinalizers(values ...string) ...
method ensureObjectMetaApplyConfigurationExists (line 197) | func (b *KeyValueApplyConfiguration) ensureObjectMetaApplyConfiguratio...
method WithSpec (line 206) | func (b *KeyValueApplyConfiguration) WithSpec(value *KeyValueSpecApply...
method WithStatus (line 214) | func (b *KeyValueApplyConfiguration) WithStatus(value *StatusApplyConf...
method GetKind (line 220) | func (b *KeyValueApplyConfiguration) GetKind() *string {
method GetAPIVersion (line 225) | func (b *KeyValueApplyConfiguration) GetAPIVersion() *string {
method GetName (line 230) | func (b *KeyValueApplyConfiguration) GetName() *string {
method GetNamespace (line 236) | func (b *KeyValueApplyConfiguration) GetNamespace() *string {
function KeyValue (line 35) | func KeyValue(name, namespace string) *KeyValueApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvaluespec.go
type KeyValueSpecApplyConfiguration (line 26) | type KeyValueSpecApplyConfiguration struct
method WithBucket (line 52) | func (b *KeyValueSpecApplyConfiguration) WithBucket(value string) *Key...
method WithDescription (line 60) | func (b *KeyValueSpecApplyConfiguration) WithDescription(value string)...
method WithMaxValueSize (line 68) | func (b *KeyValueSpecApplyConfiguration) WithMaxValueSize(value int) *...
method WithHistory (line 76) | func (b *KeyValueSpecApplyConfiguration) WithHistory(value int) *KeyVa...
method WithTTL (line 84) | func (b *KeyValueSpecApplyConfiguration) WithTTL(value string) *KeyVal...
method WithMaxBytes (line 92) | func (b *KeyValueSpecApplyConfiguration) WithMaxBytes(value int) *KeyV...
method WithStorage (line 100) | func (b *KeyValueSpecApplyConfiguration) WithStorage(value string) *Ke...
method WithReplicas (line 108) | func (b *KeyValueSpecApplyConfiguration) WithReplicas(value int) *KeyV...
method WithPlacement (line 116) | func (b *KeyValueSpecApplyConfiguration) WithPlacement(value *StreamPl...
method WithRePublish (line 124) | func (b *KeyValueSpecApplyConfiguration) WithRePublish(value *RePublis...
method WithMirror (line 132) | func (b *KeyValueSpecApplyConfiguration) WithMirror(value *StreamSourc...
method WithSources (line 140) | func (b *KeyValueSpecApplyConfiguration) WithSources(values ...**jetst...
method WithCompression (line 153) | func (b *KeyValueSpecApplyConfiguration) WithCompression(value bool) *...
method WithLimitMarkerTTL (line 161) | func (b *KeyValueSpecApplyConfiguration) WithLimitMarkerTTL(value time...
function KeyValueSpec (line 45) | func KeyValueSpec() *KeyValueSpecApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/nkeysecret.go
type NKeySecretApplyConfiguration (line 20) | type NKeySecretApplyConfiguration struct
method WithSeed (line 34) | func (b *NKeySecretApplyConfiguration) WithSeed(value string) *NKeySec...
method WithSecret (line 42) | func (b *NKeySecretApplyConfiguration) WithSecret(value *SecretRefAppl...
function NKeySecret (line 27) | func NKeySecret() *NKeySecretApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstore.go
type ObjectStoreApplyConfiguration (line 26) | type ObjectStoreApplyConfiguration struct
method IsApplyConfiguration (line 43) | func (b ObjectStoreApplyConfiguration) IsApplyConfiguration() {}
method WithKind (line 48) | func (b *ObjectStoreApplyConfiguration) WithKind(value string) *Object...
method WithAPIVersion (line 56) | func (b *ObjectStoreApplyConfiguration) WithAPIVersion(value string) *...
method WithName (line 64) | func (b *ObjectStoreApplyConfiguration) WithName(value string) *Object...
method WithGenerateName (line 73) | func (b *ObjectStoreApplyConfiguration) WithGenerateName(value string)...
method WithNamespace (line 82) | func (b *ObjectStoreApplyConfiguration) WithNamespace(value string) *O...
method WithUID (line 91) | func (b *ObjectStoreApplyConfiguration) WithUID(value types.UID) *Obje...
method WithResourceVersion (line 100) | func (b *ObjectStoreApplyConfiguration) WithResourceVersion(value stri...
method WithGeneration (line 109) | func (b *ObjectStoreApplyConfiguration) WithGeneration(value int64) *O...
method WithCreationTimestamp (line 118) | func (b *ObjectStoreApplyConfiguration) WithCreationTimestamp(value me...
method WithDeletionTimestamp (line 127) | func (b *ObjectStoreApplyConfiguration) WithDeletionTimestamp(value me...
method WithDeletionGracePeriodSeconds (line 136) | func (b *ObjectStoreApplyConfiguration) WithDeletionGracePeriodSeconds...
method WithLabels (line 146) | func (b *ObjectStoreApplyConfiguration) WithLabels(entries map[string]...
method WithAnnotations (line 161) | func (b *ObjectStoreApplyConfiguration) WithAnnotations(entries map[st...
method WithOwnerReferences (line 175) | func (b *ObjectStoreApplyConfiguration) WithOwnerReferences(values ......
method WithFinalizers (line 189) | func (b *ObjectStoreApplyConfiguration) WithFinalizers(values ...strin...
method ensureObjectMetaApplyConfigurationExists (line 197) | func (b *ObjectStoreApplyConfiguration) ensureObjectMetaApplyConfigura...
method WithSpec (line 206) | func (b *ObjectStoreApplyConfiguration) WithSpec(value *ObjectStoreSpe...
method WithStatus (line 214) | func (b *ObjectStoreApplyConfiguration) WithStatus(value *StatusApplyC...
method GetKind (line 220) | func (b *ObjectStoreApplyConfiguration) GetKind() *string {
method GetAPIVersion (line 225) | func (b *ObjectStoreApplyConfiguration) GetAPIVersion() *string {
method GetName (line 230) | func (b *ObjectStoreApplyConfiguration) GetName() *string {
method GetNamespace (line 236) | func (b *ObjectStoreApplyConfiguration) GetNamespace() *string {
function ObjectStore (line 35) | func ObjectStore(name, namespace string) *ObjectStoreApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstorespec.go
type ObjectStoreSpecApplyConfiguration (line 20) | type ObjectStoreSpecApplyConfiguration struct
method WithBucket (line 41) | func (b *ObjectStoreSpecApplyConfiguration) WithBucket(value string) *...
method WithDescription (line 49) | func (b *ObjectStoreSpecApplyConfiguration) WithDescription(value stri...
method WithTTL (line 57) | func (b *ObjectStoreSpecApplyConfiguration) WithTTL(value string) *Obj...
method WithMaxBytes (line 65) | func (b *ObjectStoreSpecApplyConfiguration) WithMaxBytes(value int) *O...
method WithStorage (line 73) | func (b *ObjectStoreSpecApplyConfiguration) WithStorage(value string) ...
method WithReplicas (line 81) | func (b *ObjectStoreSpecApplyConfiguration) WithReplicas(value int) *O...
method WithPlacement (line 89) | func (b *ObjectStoreSpecApplyConfiguration) WithPlacement(value *Strea...
method WithCompression (line 97) | func (b *ObjectStoreSpecApplyConfiguration) WithCompression(value bool...
method WithMetadata (line 106) | func (b *ObjectStoreSpecApplyConfiguration) WithMetadata(entries map[s...
function ObjectStoreSpec (line 34) | func ObjectStoreSpec() *ObjectStoreSpecApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/republish.go
type RePublishApplyConfiguration (line 20) | type RePublishApplyConfiguration struct
method WithSource (line 35) | func (b *RePublishApplyConfiguration) WithSource(value string) *RePubl...
method WithDestination (line 43) | func (b *RePublishApplyConfiguration) WithDestination(value string) *R...
method WithHeadersOnly (line 51) | func (b *RePublishApplyConfiguration) WithHeadersOnly(value bool) *ReP...
function RePublish (line 28) | func RePublish() *RePublishApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/secretref.go
type SecretRefApplyConfiguration (line 20) | type SecretRefApplyConfiguration struct
method WithName (line 33) | func (b *SecretRefApplyConfiguration) WithName(value string) *SecretRe...
function SecretRef (line 26) | func SecretRef() *SecretRefApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/status.go
type StatusApplyConfiguration (line 20) | type StatusApplyConfiguration struct
method WithObservedGeneration (line 34) | func (b *StatusApplyConfiguration) WithObservedGeneration(value int64)...
method WithConditions (line 42) | func (b *StatusApplyConfiguration) WithConditions(values ...*Condition...
function Status (line 27) | func Status() *StatusApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/stream.go
type StreamApplyConfiguration (line 26) | type StreamApplyConfiguration struct
method IsApplyConfiguration (line 43) | func (b StreamApplyConfiguration) IsApplyConfiguration() {}
method WithKind (line 48) | func (b *StreamApplyConfiguration) WithKind(value string) *StreamApply...
method WithAPIVersion (line 56) | func (b *StreamApplyConfiguration) WithAPIVersion(value string) *Strea...
method WithName (line 64) | func (b *StreamApplyConfiguration) WithName(value string) *StreamApply...
method WithGenerateName (line 73) | func (b *StreamApplyConfiguration) WithGenerateName(value string) *Str...
method WithNamespace (line 82) | func (b *StreamApplyConfiguration) WithNamespace(value string) *Stream...
method WithUID (line 91) | func (b *StreamApplyConfiguration) WithUID(value types.UID) *StreamApp...
method WithResourceVersion (line 100) | func (b *StreamApplyConfiguration) WithResourceVersion(value string) *...
method WithGeneration (line 109) | func (b *StreamApplyConfiguration) WithGeneration(value int64) *Stream...
method WithCreationTimestamp (line 118) | func (b *StreamApplyConfiguration) WithCreationTimestamp(value metav1....
method WithDeletionTimestamp (line 127) | func (b *StreamApplyConfiguration) WithDeletionTimestamp(value metav1....
method WithDeletionGracePeriodSeconds (line 136) | func (b *StreamApplyConfiguration) WithDeletionGracePeriodSeconds(valu...
method WithLabels (line 146) | func (b *StreamApplyConfiguration) WithLabels(entries map[string]strin...
method WithAnnotations (line 161) | func (b *StreamApplyConfiguration) WithAnnotations(entries map[string]...
method WithOwnerReferences (line 175) | func (b *StreamApplyConfiguration) WithOwnerReferences(values ...*v1.O...
method WithFinalizers (line 189) | func (b *StreamApplyConfiguration) WithFinalizers(values ...string) *S...
method ensureObjectMetaApplyConfigurationExists (line 197) | func (b *StreamApplyConfiguration) ensureObjectMetaApplyConfigurationE...
method WithSpec (line 206) | func (b *StreamApplyConfiguration) WithSpec(value *StreamSpecApplyConf...
method WithStatus (line 214) | func (b *StreamApplyConfiguration) WithStatus(value *StatusApplyConfig...
method GetKind (line 220) | func (b *StreamApplyConfiguration) GetKind() *string {
method GetAPIVersion (line 225) | func (b *StreamApplyConfiguration) GetAPIVersion() *string {
method GetName (line 230) | func (b *StreamApplyConfiguration) GetName() *string {
method GetNamespace (line 236) | func (b *StreamApplyConfiguration) GetNamespace() *string {
function Stream (line 35) | func Stream(name, namespace string) *StreamApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamplacement.go
type StreamPlacementApplyConfiguration (line 20) | type StreamPlacementApplyConfiguration struct
method WithCluster (line 34) | func (b *StreamPlacementApplyConfiguration) WithCluster(value string) ...
method WithTags (line 42) | func (b *StreamPlacementApplyConfiguration) WithTags(values ...string)...
function StreamPlacement (line 27) | func StreamPlacement() *StreamPlacementApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamsource.go
type StreamSourceApplyConfiguration (line 24) | type StreamSourceApplyConfiguration struct
method WithName (line 43) | func (b *StreamSourceApplyConfiguration) WithName(value string) *Strea...
method WithOptStartSeq (line 51) | func (b *StreamSourceApplyConfiguration) WithOptStartSeq(value int) *S...
method WithOptStartTime (line 59) | func (b *StreamSourceApplyConfiguration) WithOptStartTime(value string...
method WithFilterSubject (line 67) | func (b *StreamSourceApplyConfiguration) WithFilterSubject(value strin...
method WithExternalAPIPrefix (line 75) | func (b *StreamSourceApplyConfiguration) WithExternalAPIPrefix(value s...
method WithExternalDeliverPrefix (line 83) | func (b *StreamSourceApplyConfiguration) WithExternalDeliverPrefix(val...
method WithSubjectTransforms (line 91) | func (b *StreamSourceApplyConfiguration) WithSubjectTransforms(values ...
function StreamSource (line 36) | func StreamSource() *StreamSourceApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go
type StreamSpecApplyConfiguration (line 24) | type StreamSpecApplyConfiguration struct
method WithName (line 73) | func (b *StreamSpecApplyConfiguration) WithName(value string) *StreamS...
method WithDescription (line 81) | func (b *StreamSpecApplyConfiguration) WithDescription(value string) *...
method WithSubjects (line 89) | func (b *StreamSpecApplyConfiguration) WithSubjects(values ...string) ...
method WithRetention (line 99) | func (b *StreamSpecApplyConfiguration) WithRetention(value string) *St...
method WithMaxConsumers (line 107) | func (b *StreamSpecApplyConfiguration) WithMaxConsumers(value int) *St...
method WithMaxMsgsPerSubject (line 115) | func (b *StreamSpecApplyConfiguration) WithMaxMsgsPerSubject(value int...
method WithMaxMsgs (line 123) | func (b *StreamSpecApplyConfiguration) WithMaxMsgs(value int) *StreamS...
method WithMaxBytes (line 131) | func (b *StreamSpecApplyConfiguration) WithMaxBytes(value int) *Stream...
method WithMaxAge (line 139) | func (b *StreamSpecApplyConfiguration) WithMaxAge(value string) *Strea...
method WithMaxMsgSize (line 147) | func (b *StreamSpecApplyConfiguration) WithMaxMsgSize(value int) *Stre...
method WithStorage (line 155) | func (b *StreamSpecApplyConfiguration) WithStorage(value string) *Stre...
method WithDiscard (line 163) | func (b *StreamSpecApplyConfiguration) WithDiscard(value string) *Stre...
method WithReplicas (line 171) | func (b *StreamSpecApplyConfiguration) WithReplicas(value int) *Stream...
method WithNoAck (line 179) | func (b *StreamSpecApplyConfiguration) WithNoAck(value bool) *StreamSp...
method WithDuplicateWindow (line 187) | func (b *StreamSpecApplyConfiguration) WithDuplicateWindow(value strin...
method WithPlacement (line 195) | func (b *StreamSpecApplyConfiguration) WithPlacement(value *StreamPlac...
method WithMirror (line 203) | func (b *StreamSpecApplyConfiguration) WithMirror(value *StreamSourceA...
method WithSources (line 211) | func (b *StreamSpecApplyConfiguration) WithSources(values ...**jetstre...
method WithCompression (line 224) | func (b *StreamSpecApplyConfiguration) WithCompression(value string) *...
method WithSubjectTransform (line 232) | func (b *StreamSpecApplyConfiguration) WithSubjectTransform(value *Sub...
method WithRePublish (line 240) | func (b *StreamSpecApplyConfiguration) WithRePublish(value *RePublishA...
method WithSealed (line 248) | func (b *StreamSpecApplyConfiguration) WithSealed(value bool) *StreamS...
method WithDenyDelete (line 256) | func (b *StreamSpecApplyConfiguration) WithDenyDelete(value bool) *Str...
method WithDenyPurge (line 264) | func (b *StreamSpecApplyConfiguration) WithDenyPurge(value bool) *Stre...
method WithAllowDirect (line 272) | func (b *StreamSpecApplyConfiguration) WithAllowDirect(value bool) *St...
method WithAllowRollup (line 280) | func (b *StreamSpecApplyConfiguration) WithAllowRollup(value bool) *St...
method WithMirrorDirect (line 288) | func (b *StreamSpecApplyConfiguration) WithMirrorDirect(value bool) *S...
method WithDiscardPerSubject (line 296) | func (b *StreamSpecApplyConfiguration) WithDiscardPerSubject(value boo...
method WithFirstSequence (line 304) | func (b *StreamSpecApplyConfiguration) WithFirstSequence(value uint64)...
method WithMetadata (line 313) | func (b *StreamSpecApplyConfiguration) WithMetadata(entries map[string...
method WithConsumerLimits (line 326) | func (b *StreamSpecApplyConfiguration) WithConsumerLimits(value *Consu...
method WithAllowMsgTTL (line 334) | func (b *StreamSpecApplyConfiguration) WithAllowMsgTTL(value bool) *St...
method WithSubjectDeleteMarkerTTL (line 342) | func (b *StreamSpecApplyConfiguration) WithSubjectDeleteMarkerTTL(valu...
method WithAllowMsgCounter (line 350) | func (b *StreamSpecApplyConfiguration) WithAllowMsgCounter(value bool)...
method WithAllowAtomicPublish (line 358) | func (b *StreamSpecApplyConfiguration) WithAllowAtomicPublish(value bo...
method WithAllowMsgSchedules (line 366) | func (b *StreamSpecApplyConfiguration) WithAllowMsgSchedules(value boo...
method WithPersistMode (line 374) | func (b *StreamSpecApplyConfiguration) WithPersistMode(value string) *...
function StreamSpec (line 66) | func StreamSpec() *StreamSpecApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/subjecttransform.go
type SubjectTransformApplyConfiguration (line 20) | type SubjectTransformApplyConfiguration struct
method WithSource (line 34) | func (b *SubjectTransformApplyConfiguration) WithSource(value string) ...
method WithDest (line 42) | func (b *SubjectTransformApplyConfiguration) WithDest(value string) *S...
function SubjectTransform (line 27) | func SubjectTransform() *SubjectTransformApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tls.go
type TLSApplyConfiguration (line 20) | type TLSApplyConfiguration struct
method WithClientCert (line 35) | func (b *TLSApplyConfiguration) WithClientCert(value string) *TLSApply...
method WithClientKey (line 43) | func (b *TLSApplyConfiguration) WithClientKey(value string) *TLSApplyC...
method WithRootCAs (line 51) | func (b *TLSApplyConfiguration) WithRootCAs(values ...string) *TLSAppl...
function TLS (line 28) | func TLS() *TLSApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tlssecret.go
type TLSSecretApplyConfiguration (line 20) | type TLSSecretApplyConfiguration struct
method WithClientCert (line 36) | func (b *TLSSecretApplyConfiguration) WithClientCert(value string) *TL...
method WithClientKey (line 44) | func (b *TLSSecretApplyConfiguration) WithClientKey(value string) *TLS...
method WithRootCAs (line 52) | func (b *TLSSecretApplyConfiguration) WithRootCAs(value string) *TLSSe...
method WithSecret (line 60) | func (b *TLSSecretApplyConfiguration) WithSecret(value *SecretRefApply...
function TLSSecret (line 29) | func TLSSecret() *TLSSecretApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tokensecret.go
type TokenSecretApplyConfiguration (line 20) | type TokenSecretApplyConfiguration struct
method WithToken (line 34) | func (b *TokenSecretApplyConfiguration) WithToken(value string) *Token...
method WithSecret (line 42) | func (b *TokenSecretApplyConfiguration) WithSecret(value *SecretRefApp...
function TokenSecret (line 27) | func TokenSecret() *TokenSecretApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/user.go
type UserApplyConfiguration (line 20) | type UserApplyConfiguration struct
method WithUser (line 35) | func (b *UserApplyConfiguration) WithUser(value string) *UserApplyConf...
method WithPassword (line 43) | func (b *UserApplyConfiguration) WithPassword(value string) *UserApply...
method WithSecret (line 51) | func (b *UserApplyConfiguration) WithSecret(value *SecretRefApplyConfi...
function User (line 28) | func User() *UserApplyConfiguration {
FILE: pkg/jetstream/generated/applyconfiguration/utils.go
function ForKind (line 29) | func ForKind(kind schema.GroupVersionKind) interface{} {
function NewTypeConverter (line 89) | func NewTypeConverter(scheme *runtime.Scheme) managedfields.TypeConverter {
FILE: pkg/jetstream/generated/clientset/versioned/clientset.go
type Interface (line 28) | type Interface interface
type Clientset (line 34) | type Clientset struct
method JetstreamV1beta2 (line 40) | func (c *Clientset) JetstreamV1beta2() jetstreamv1beta2.JetstreamV1bet...
method Discovery (line 45) | func (c *Clientset) Discovery() discovery.DiscoveryInterface {
function NewForConfig (line 57) | func NewForConfig(c *rest.Config) (*Clientset, error) {
function NewForConfigAndClient (line 77) | func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Cl...
function NewForConfigOrDie (line 102) | func NewForConfigOrDie(c *rest.Config) *Clientset {
function New (line 111) | func New(c rest.Interface) *Clientset {
FILE: pkg/jetstream/generated/clientset/versioned/fake/clientset_generated.go
function NewSimpleClientset (line 39) | func NewSimpleClientset(objects ...runtime.Object) *Clientset {
type Clientset (line 70) | type Clientset struct
method Discovery (line 76) | func (c *Clientset) Discovery() discovery.DiscoveryInterface {
method Tracker (line 80) | func (c *Clientset) Tracker() testing.ObjectTracker {
method JetstreamV1beta2 (line 126) | func (c *Clientset) JetstreamV1beta2() jetstreamv1beta2.JetstreamV1bet...
function NewClientset (line 88) | func NewClientset(objects ...runtime.Object) *Clientset {
FILE: pkg/jetstream/generated/clientset/versioned/fake/register.go
function init (line 50) | func init() {
FILE: pkg/jetstream/generated/clientset/versioned/scheme/register.go
function init (line 50) | func init() {
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/account.go
type AccountsGetter (line 32) | type AccountsGetter interface
type AccountInterface (line 37) | type AccountInterface interface
type accounts (line 55) | type accounts struct
function newAccounts (line 60) | func newAccounts(c *JetstreamV1beta2Client, namespace string) *accounts {
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/consumer.go
type ConsumersGetter (line 32) | type ConsumersGetter interface
type ConsumerInterface (line 37) | type ConsumerInterface interface
type consumers (line 55) | type consumers struct
function newConsumers (line 60) | func newConsumers(c *JetstreamV1beta2Client, namespace string) *consumers {
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_account.go
type fakeAccounts (line 26) | type fakeAccounts struct
function newFakeAccounts (line 31) | func newFakeAccounts(fake *FakeJetstreamV1beta2, namespace string) typed...
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_consumer.go
type fakeConsumers (line 26) | type fakeConsumers struct
function newFakeConsumers (line 31) | func newFakeConsumers(fake *FakeJetstreamV1beta2, namespace string) type...
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go
type FakeJetstreamV1beta2 (line 24) | type FakeJetstreamV1beta2 struct
method Accounts (line 28) | func (c *FakeJetstreamV1beta2) Accounts(namespace string) v1beta2.Acco...
method Consumers (line 32) | func (c *FakeJetstreamV1beta2) Consumers(namespace string) v1beta2.Con...
method KeyValues (line 36) | func (c *FakeJetstreamV1beta2) KeyValues(namespace string) v1beta2.Key...
method ObjectStores (line 40) | func (c *FakeJetstreamV1beta2) ObjectStores(namespace string) v1beta2....
method Streams (line 44) | func (c *FakeJetstreamV1beta2) Streams(namespace string) v1beta2.Strea...
method RESTClient (line 50) | func (c *FakeJetstreamV1beta2) RESTClient() rest.Interface {
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_keyvalue.go
type fakeKeyValues (line 26) | type fakeKeyValues struct
function newFakeKeyValues (line 31) | func newFakeKeyValues(fake *FakeJetstreamV1beta2, namespace string) type...
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_objectstore.go
type fakeObjectStores (line 26) | type fakeObjectStores struct
function newFakeObjectStores (line 31) | func newFakeObjectStores(fake *FakeJetstreamV1beta2, namespace string) t...
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_stream.go
type fakeStreams (line 26) | type fakeStreams struct
function newFakeStreams (line 31) | func newFakeStreams(fake *FakeJetstreamV1beta2, namespace string) typedj...
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go
type AccountExpansion (line 18) | type AccountExpansion interface
type ConsumerExpansion (line 20) | type ConsumerExpansion interface
type KeyValueExpansion (line 22) | type KeyValueExpansion interface
type ObjectStoreExpansion (line 24) | type ObjectStoreExpansion interface
type StreamExpansion (line 26) | type StreamExpansion interface
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go
type JetstreamV1beta2Interface (line 26) | type JetstreamV1beta2Interface interface
type JetstreamV1beta2Client (line 36) | type JetstreamV1beta2Client struct
method Accounts (line 40) | func (c *JetstreamV1beta2Client) Accounts(namespace string) AccountInt...
method Consumers (line 44) | func (c *JetstreamV1beta2Client) Consumers(namespace string) ConsumerI...
method KeyValues (line 48) | func (c *JetstreamV1beta2Client) KeyValues(namespace string) KeyValueI...
method ObjectStores (line 52) | func (c *JetstreamV1beta2Client) ObjectStores(namespace string) Object...
method Streams (line 56) | func (c *JetstreamV1beta2Client) Streams(namespace string) StreamInter...
method RESTClient (line 113) | func (c *JetstreamV1beta2Client) RESTClient() rest.Interface {
function NewForConfig (line 63) | func NewForConfig(c *rest.Config) (*JetstreamV1beta2Client, error) {
function NewForConfigAndClient (line 75) | func NewForConfigAndClient(c *rest.Config, h *http.Client) (*JetstreamV1...
function NewForConfigOrDie (line 87) | func NewForConfigOrDie(c *rest.Config) *JetstreamV1beta2Client {
function New (line 96) | func New(c rest.Interface) *JetstreamV1beta2Client {
function setConfigDefaults (line 100) | func setConfigDefaults(config *rest.Config) {
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/keyvalue.go
type KeyValuesGetter (line 32) | type KeyValuesGetter interface
type KeyValueInterface (line 37) | type KeyValueInterface interface
type keyValues (line 55) | type keyValues struct
function newKeyValues (line 60) | func newKeyValues(c *JetstreamV1beta2Client, namespace string) *keyValues {
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/objectstore.go
type ObjectStoresGetter (line 32) | type ObjectStoresGetter interface
type ObjectStoreInterface (line 37) | type ObjectStoreInterface interface
type objectStores (line 55) | type objectStores struct
function newObjectStores (line 60) | func newObjectStores(c *JetstreamV1beta2Client, namespace string) *objec...
FILE: pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/stream.go
type StreamsGetter (line 32) | type StreamsGetter interface
type StreamInterface (line 37) | type StreamInterface interface
type streams (line 55) | type streams struct
function newStreams (line 60) | func newStreams(c *JetstreamV1beta2Client, namespace string) *streams {
FILE: pkg/jetstream/generated/informers/externalversions/factory.go
type SharedInformerOption (line 33) | type SharedInformerOption
type sharedInformerFactory (line 35) | type sharedInformerFactory struct
method Start (line 121) | func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {
method Shutdown (line 145) | func (f *sharedInformerFactory) Shutdown() {
method WaitForCacheSync (line 154) | func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{...
method InformerFor (line 177) | func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFun...
method Jetstream (line 257) | func (f *sharedInformerFactory) Jetstream() jetstream.Interface {
function WithCustomResyncConfig (line 56) | func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) Sh...
function WithTweakListOptions (line 66) | func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListO...
function WithNamespace (line 74) | func WithNamespace(namespace string) SharedInformerOption {
function WithTransform (line 82) | func WithTransform(transform cache.TransformFunc) SharedInformerOption {
function NewSharedInformerFactory (line 90) | func NewSharedInformerFactory(client versioned.Interface, defaultResync ...
function NewFilteredSharedInformerFactory (line 98) | func NewFilteredSharedInformerFactory(client versioned.Interface, defaul...
function NewSharedInformerFactoryWithOptions (line 103) | func NewSharedInformerFactoryWithOptions(client versioned.Interface, def...
type SharedInformerFactory (line 223) | type SharedInformerFactory interface
FILE: pkg/jetstream/generated/informers/externalversions/generic.go
type GenericInformer (line 28) | type GenericInformer interface
type genericInformer (line 33) | type genericInformer struct
method Informer (line 39) | func (f *genericInformer) Informer() cache.SharedIndexInformer {
method Lister (line 44) | func (f *genericInformer) Lister() cache.GenericLister {
method ForResource (line 50) | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersion...
FILE: pkg/jetstream/generated/informers/externalversions/internalinterfaces/factory_interfaces.go
type NewInformerFunc (line 28) | type NewInformerFunc
type SharedInformerFactory (line 31) | type SharedInformerFactory interface
type TweakListOptionsFunc (line 37) | type TweakListOptionsFunc
FILE: pkg/jetstream/generated/informers/externalversions/jetstream/interface.go
type Interface (line 24) | type Interface interface
type group (line 29) | type group struct
method V1beta2 (line 41) | func (g *group) V1beta2() v1beta2.Interface {
function New (line 36) | func New(f internalinterfaces.SharedInformerFactory, namespace string, t...
FILE: pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/account.go
type AccountInformer (line 34) | type AccountInformer interface
type accountInformer (line 39) | type accountInformer struct
method defaultInformer (line 89) | func (f *accountInformer) defaultInformer(client versioned.Interface, ...
method Informer (line 93) | func (f *accountInformer) Informer() cache.SharedIndexInformer {
method Lister (line 97) | func (f *accountInformer) Lister() jetstreamv1beta2.AccountLister {
function NewAccountInformer (line 48) | func NewAccountInformer(client versioned.Interface, namespace string, re...
function NewFilteredAccountInformer (line 55) | func NewFilteredAccountInformer(client versioned.Interface, namespace st...
FILE: pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/consumer.go
type ConsumerInformer (line 34) | type ConsumerInformer interface
type consumerInformer (line 39) | type consumerInformer struct
method defaultInformer (line 89) | func (f *consumerInformer) defaultInformer(client versioned.Interface,...
method Informer (line 93) | func (f *consumerInformer) Informer() cache.SharedIndexInformer {
method Lister (line 97) | func (f *consumerInformer) Lister() jetstreamv1beta2.ConsumerLister {
function NewConsumerInformer (line 48) | func NewConsumerInformer(client versioned.Interface, namespace string, r...
function NewFilteredConsumerInformer (line 55) | func NewFilteredConsumerInformer(client versioned.Interface, namespace s...
FILE: pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go
type Interface (line 23) | type Interface interface
type version (line 36) | type version struct
method Accounts (line 48) | func (v *version) Accounts() AccountInformer {
method Consumers (line 53) | func (v *version) Consumers() ConsumerInformer {
method KeyValues (line 58) | func (v *version) KeyValues() KeyValueInformer {
method ObjectStores (line 63) | func (v *version) ObjectStores() ObjectStoreInformer {
method Streams (line 68) | func (v *version) Streams() StreamInformer {
function New (line 43) | func New(f internalinterfaces.SharedInformerFactory, namespace string, t...
FILE: pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/keyvalue.go
type KeyValueInformer (line 34) | type KeyValueInformer interface
type keyValueInformer (line 39) | type keyValueInformer struct
method defaultInformer (line 89) | func (f *keyValueInformer) defaultInformer(client versioned.Interface,...
method Informer (line 93) | func (f *keyValueInformer) Informer() cache.SharedIndexInformer {
method Lister (line 97) | func (f *keyValueInformer) Lister() jetstreamv1beta2.KeyValueLister {
function NewKeyValueInformer (line 48) | func NewKeyValueInformer(client versioned.Interface, namespace string, r...
function NewFilteredKeyValueInformer (line 55) | func NewFilteredKeyValueInformer(client versioned.Interface, namespace s...
FILE: pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/objectstore.go
type ObjectStoreInformer (line 34) | type ObjectStoreInformer interface
type objectStoreInformer (line 39) | type objectStoreInformer struct
method defaultInformer (line 89) | func (f *objectStoreInformer) defaultInformer(client versioned.Interfa...
method Informer (line 93) | func (f *objectStoreInformer) Informer() cache.SharedIndexInformer {
method Lister (line 97) | func (f *objectStoreInformer) Lister() jetstreamv1beta2.ObjectStoreLis...
function NewObjectStoreInformer (line 48) | func NewObjectStoreInformer(client versioned.Interface, namespace string...
function NewFilteredObjectStoreInformer (line 55) | func NewFilteredObjectStoreInformer(client versioned.Interface, namespac...
FILE: pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/stream.go
type StreamInformer (line 34) | type StreamInformer interface
type streamInformer (line 39) | type streamInformer struct
method defaultInformer (line 89) | func (f *streamInformer) defaultInformer(client versioned.Interface, r...
method Informer (line 93) | func (f *streamInformer) Informer() cache.SharedIndexInformer {
method Lister (line 97) | func (f *streamInformer) Lister() jetstreamv1beta2.StreamLister {
function NewStreamInformer (line 48) | func NewStreamInformer(client versioned.Interface, namespace string, res...
function NewFilteredStreamInformer (line 55) | func NewFilteredStreamInformer(client versioned.Interface, namespace str...
FILE: pkg/jetstream/generated/listers/jetstream/v1beta2/account.go
type AccountLister (line 27) | type AccountLister interface
type accountLister (line 37) | type accountLister struct
method Accounts (line 47) | func (s *accountLister) Accounts(namespace string) AccountNamespaceLis...
function NewAccountLister (line 42) | func NewAccountLister(indexer cache.Indexer) AccountLister {
type AccountNamespaceLister (line 53) | type AccountNamespaceLister interface
type accountNamespaceLister (line 65) | type accountNamespaceLister struct
FILE: pkg/jetstream/generated/listers/jetstream/v1beta2/consumer.go
type ConsumerLister (line 27) | type ConsumerLister interface
type consumerLister (line 37) | type consumerLister struct
method Consumers (line 47) | func (s *consumerLister) Consumers(namespace string) ConsumerNamespace...
function NewConsumerLister (line 42) | func NewConsumerLister(indexer cache.Indexer) ConsumerLister {
type ConsumerNamespaceLister (line 53) | type ConsumerNamespaceLister interface
type consumerNamespaceLister (line 65) | type consumerNamespaceLister struct
FILE: pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go
type AccountListerExpansion (line 20) | type AccountListerExpansion interface
type AccountNamespaceListerExpansion (line 24) | type AccountNamespaceListerExpansion interface
type ConsumerListerExpansion (line 28) | type ConsumerListerExpansion interface
type ConsumerNamespaceListerExpansion (line 32) | type ConsumerNamespaceListerExpansion interface
type KeyValueListerExpansion (line 36) | type KeyValueListerExpansion interface
type KeyValueNamespaceListerExpansion (line 40) | type KeyValueNamespaceListerExpansion interface
type ObjectStoreListerExpansion (line 44) | type ObjectStoreListerExpansion interface
type ObjectStoreNamespaceListerExpansion (line 48) | type ObjectStoreNamespaceListerExpansion interface
type StreamListerExpansion (line 52) | type StreamListerExpansion interface
type StreamNamespaceListerExpansion (line 56) | type StreamNamespaceListerExpansion interface
FILE: pkg/jetstream/generated/listers/jetstream/v1beta2/keyvalue.go
type KeyValueLister (line 27) | type KeyValueLister interface
type keyValueLister (line 37) | type keyValueLister struct
method KeyValues (line 47) | func (s *keyValueLister) KeyValues(namespace string) KeyValueNamespace...
function NewKeyValueLister (line 42) | func NewKeyValueLister(indexer cache.Indexer) KeyValueLister {
type KeyValueNamespaceLister (line 53) | type KeyValueNamespaceLister interface
type keyValueNamespaceLister (line 65) | type keyValueNamespaceLister struct
FILE: pkg/jetstream/generated/listers/jetstream/v1beta2/objectstore.go
type ObjectStoreLister (line 27) | type ObjectStoreLister interface
type objectStoreLister (line 37) | type objectStoreLister struct
method ObjectStores (line 47) | func (s *objectStoreLister) ObjectStores(namespace string) ObjectStore...
function NewObjectStoreLister (line 42) | func NewObjectStoreLister(indexer cache.Indexer) ObjectStoreLister {
type ObjectStoreNamespaceLister (line 53) | type ObjectStoreNamespaceLister interface
type objectStoreNamespaceLister (line 65) | type objectStoreNamespaceLister struct
FILE: pkg/jetstream/generated/listers/jetstream/v1beta2/stream.go
type StreamLister (line 27) | type StreamLister interface
type streamLister (line 37) | type streamLister struct
method Streams (line 47) | func (s *streamLister) Streams(namespace string) StreamNamespaceLister {
function NewStreamLister (line 42) | func NewStreamLister(indexer cache.Indexer) StreamLister {
type StreamNamespaceLister (line 53) | type StreamNamespaceLister interface
type streamNamespaceLister (line 65) | type streamNamespaceLister struct
FILE: pkg/natsreloader/natsreloader.go
constant errorFmt (line 36) | errorFmt = "Error: %s\n"
function isInotifyExhausted (line 38) | func isInotifyExhausted(err error) bool {
function createInotifyExhaustedError (line 50) | func createInotifyExhaustedError(originalErr error, watchedFileCount int...
type Config (line 76) | type Config struct
type Reloader (line 89) | type Reloader struct
method waitForProcess (line 103) | func (r *Reloader) waitForProcess() error {
method createWatcherWithRetry (line 247) | func (r *Reloader) createWatcherWithRetry() (*fsnotify.Watcher, error) {
method init (line 286) | func (r *Reloader) init() (*fsnotify.Watcher, map[string][]byte, error) {
method reload (line 366) | func (r *Reloader) reload(updatedFiles []string) error {
method Run (line 395) | func (r *Reloader) Run(ctx context.Context) error {
method Stop (line 473) | func (r *Reloader) Stop() error {
method pollForChanges (line 495) | func (r *Reloader) pollForChanges(lastConfigAppliedCache map[string][]...
method runPollingMode (line 529) | func (r *Reloader) runPollingMode(ctx context.Context) error {
function removeDuplicateStrings (line 150) | func removeDuplicateStrings(s []string) []string {
function getFileDigest (line 167) | func getFileDigest(filePath string) ([]byte, error) {
function handleEvent (line 180) | func handleEvent(event fsnotify.Event, lastConfigAppliedCache map[string...
function handleEvents (line 219) | func handleEvents(configWatcher *fsnotify.Watcher, event fsnotify.Event,...
function handleDeletedFiles (line 231) | func handleDeletedFiles(deletedFiles []string, configWatcher *fsnotify.W...
function NewReloader (line 480) | func NewReloader(config *Config) (*Reloader, error) {
function retryJitter (line 488) | func retryJitter(base time.Duration) time.Duration {
function getServerFiles (line 595) | func getServerFiles(configFile string) ([]string, error) {
function getIncludePaths (line 612) | func getIncludePaths(configFile string, checked map[string]interface{}) ...
function getCertPaths (line 664) | func getCertPaths(configPaths []string) ([]string, error) {
FILE: pkg/natsreloader/natsreloader_test.go
constant testConfig_0 (line 33) | testConfig_0 = `
constant testConfig_1 (line 56) | testConfig_1 = `include ./testConfig_2.conf`
constant testConfig_2 (line 58) | testConfig_2 = `
constant includeTest_0 (line 64) | includeTest_0 = `
constant includeTest_1 (line 75) | includeTest_1 = `
function TestReloader (line 98) | func TestReloader(t *testing.T) {
function TestInclude (line 201) | func TestInclude(t *testing.T) {
function TestFileFinder (line 268) | func TestFileFinder(t *testing.T) {
function writeFile (line 363) | func writeFile(content, path string) error {
function TestReloaderInotifyExhaustion (line 386) | func TestReloaderInotifyExhaustion(t *testing.T) {
function TestReloaderPollingMode (line 459) | func TestReloaderPollingMode(t *testing.T) {
function TestReloaderPollingModeFileDeletion (line 549) | func TestReloaderPollingModeFileDeletion(t *testing.T) {
Condensed preview — 177 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,029K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 326,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Discussion\n url: https://github.com/nats-io/nack/discussions\n "
},
{
"path": ".github/ISSUE_TEMPLATE/defect.yml",
"chars": 1316,
"preview": "---\nname: Defect\ndescription: Report a defect, such as a bug or regression.\nlabels:\n - defect\nbody:\n - type: textarea\n"
},
{
"path": ".github/ISSUE_TEMPLATE/proposal.yml",
"chars": 951,
"preview": "---\nname: Proposal\ndescription: Propose an enhancement or new feature.\nlabels:\n - proposal\nbody:\n - type: textarea\n "
},
{
"path": ".github/dependabot.yml",
"chars": 735,
"preview": "version: 2\nupdates:\n # version updates: enabled\n # security updates: enabled\n - package-ecosystem: \"github-actions\"\n "
},
{
"path": ".github/workflows/claude.yml",
"chars": 1051,
"preview": "name: Claude Code\n\n# GITHUB_TOKEN needs contents:read and actions:read — required by\n# claude-code-action for restoring "
},
{
"path": ".github/workflows/deps-release-detect.yaml",
"chars": 3035,
"preview": "name: Deps Release\n\non: 'pull_request'\n\npermissions:\n contents: write\n\njobs:\n detect:\n name: Detect\n runs-on: ub"
},
{
"path": ".github/workflows/deps-release-tag.yaml",
"chars": 2488,
"preview": "name: Deps Release\n\non:\n push:\n branches:\n - main\n\npermissions:\n actions: write\n contents: write\n\njobs:\n tag"
},
{
"path": ".github/workflows/e2e.yaml",
"chars": 887,
"preview": "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"
},
{
"path": ".github/workflows/release.yaml",
"chars": 1686,
"preview": "name: Release\non:\n workflow_dispatch:\n push:\n tags:\n - v[0-9]+.[0-9]+.[0-9]+\njobs:\n release:\n runs-on: ubu"
},
{
"path": ".github/workflows/test.yaml",
"chars": 611,
"preview": "name: Test\non:\n push:\n paths-ignore:\n - '**.md'\n pull_request:\n paths-ignore:\n - '**.md'\n\njobs:\n test"
},
{
"path": ".gitignore",
"chars": 430,
"preview": "# 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# Ou"
},
{
"path": ".goreleaser.yml",
"chars": 1231,
"preview": "version: 2\nproject_name: nack\n\nrelease:\n name_template: 'Release {{.Tag}}'\n draft: true\n skip_upload: true\n github:\n"
},
{
"path": "CLAUDE.md",
"chars": 3847,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "CONTRIBUTING.md",
"chars": 1302,
"preview": "> [!WARNING]\n> This contribution guide is work in progress and is meant to be a location where more developers can contr"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Makefile",
"chars": 7255,
"preview": "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:"
},
{
"path": "README.md",
"chars": 15463,
"preview": "<img width=\"800\" alt=\"nack-large\" src=\"https://user-images.githubusercontent.com/26195/92535603-71ad9a80-f1ec-11ea-8959-"
},
{
"path": "cicd/Dockerfile",
"chars": 1221,
"preview": "#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\n"
},
{
"path": "cicd/Dockerfile_goreleaser",
"chars": 712,
"preview": "#syntax=docker/dockerfile:1.13\nFROM --platform=$BUILDPLATFORM golang:1.25.6-bookworm AS build\n\n\nRUN <<EOT\n set -e\n\n "
},
{
"path": "cicd/assets/entrypoint.sh",
"chars": 33,
"preview": "#!/bin/sh\nexec \"/${GO_APP}\" \"$@\"\n"
},
{
"path": "cicd/tag-deps-version.txt",
"chars": 14,
"preview": "0.21.0\n0.21.1\n"
},
{
"path": "cmd/jetstream-controller/main.go",
"chars": 7825,
"preview": "// Copyright 2020-2023 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may no"
},
{
"path": "cmd/nats-boot-config/main.go",
"chars": 2269,
"preview": "// Copyright 2018 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "cmd/nats-server-config-reloader/main.go",
"chars": 3491,
"preview": "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/n"
},
{
"path": "controllers/jetstream/conn_pool.go",
"chars": 6016,
"preview": "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/n"
},
{
"path": "controllers/jetstream/conn_pool_test.go",
"chars": 1678,
"preview": "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-"
},
{
"path": "controllers/jetstream/consumer.go",
"chars": 12646,
"preview": "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"
},
{
"path": "controllers/jetstream/consumer_test.go",
"chars": 12005,
"preview": "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"
},
{
"path": "controllers/jetstream/controller.go",
"chars": 24391,
"preview": "// Copyright 2020-2022 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may no"
},
{
"path": "controllers/jetstream/controller_test.go",
"chars": 7980,
"preview": "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 \"g"
},
{
"path": "controllers/jetstream/jsmclient.go",
"chars": 1914,
"preview": "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.co"
},
{
"path": "controllers/jetstream/jsmclient_test.go",
"chars": 1796,
"preview": "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.co"
},
{
"path": "controllers/jetstream/stream.go",
"chars": 16622,
"preview": "// Copyright 2020 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "controllers/jetstream/stream_test.go",
"chars": 7187,
"preview": "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 \""
},
{
"path": "dependencies.md",
"chars": 812,
"preview": "# External Dependencies\n\nThis file lists the dependencies used in this repository.\n\n| Dependency "
},
{
"path": "deploy/crds.yml",
"chars": 56350,
"preview": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n name: streams.jetstream.nats.io\nspec:"
},
{
"path": "deploy/examples/consumer_pull.yml",
"chars": 246,
"preview": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Consumer\nmetadata:\n name: my-pull-consumer\nspec:\n streamName: mystream"
},
{
"path": "deploy/examples/consumer_push.yml",
"chars": 342,
"preview": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Consumer\nmetadata:\n name: my-push-consumer\nspec:\n streamName: mystream"
},
{
"path": "deploy/examples/stream.yml",
"chars": 172,
"preview": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n name: mystream\nspec:\n name: mystream\n subjects: [\"o"
},
{
"path": "deploy/examples/stream_mirror.yml",
"chars": 257,
"preview": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n name: mystream-mirror\nspec:\n name: mystream-mirror\n "
},
{
"path": "deploy/examples/stream_placement.yml",
"chars": 177,
"preview": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n name: mystream-placement\nspec:\n name: mystream-place"
},
{
"path": "deploy/examples/stream_servers.yml",
"chars": 332,
"preview": "apiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n name: mystream\nspec:\n name: mystream\n servers:\n - nats"
},
{
"path": "deploy/examples/stream_sources.yml",
"chars": 174,
"preview": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n name: mystream-sources\nspec:\n name: mystream-sources"
},
{
"path": "deploy/rbac.yml",
"chars": 1252,
"preview": "---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n name: jetstream-controller\n namespace: default\n---\napiVersion: rbac"
},
{
"path": "docker-bake.hcl",
"chars": 2352,
"preview": "###################\n### Variables\n###################\n\nvariable REGISTRY {\n default = \"\"\n}\n\n# Comma delimited list of t"
},
{
"path": "docs/api.md",
"chars": 90998,
"preview": "# API Reference\n\nPackages:\n\n- [jetstream.nats.io/v1beta2](#jetstreamnatsiov1beta2)\n- [jetstream.nats.io/v1beta1](#jetstr"
},
{
"path": "examples/secure/client-tls.yaml",
"chars": 326,
"preview": "---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n name: nats-sys-tls\nspec:\n secretName: nats-sys-tls\n d"
},
{
"path": "examples/secure/issuer.yaml",
"chars": 476,
"preview": "---\napiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n name: selfsigning\nspec:\n selfSigned: {}\n---\napiVersi"
},
{
"path": "examples/secure/nack/account-foo.yaml",
"chars": 222,
"preview": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Account\nmetadata:\n name: a\nspec:\n name: a\n servers:\n - nats://nats:4"
},
{
"path": "examples/secure/nack/nats-account-a.yaml",
"chars": 222,
"preview": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Account\nmetadata:\n name: a\nspec:\n name: a\n servers:\n - nats://nats:4"
},
{
"path": "examples/secure/nack/nats-consumer-bar-a.yaml",
"chars": 157,
"preview": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Consumer\nmetadata:\n name: bar\nspec:\n streamName: foo\n durableName: ba"
},
{
"path": "examples/secure/nack/nats-stream-foo-a.yaml",
"chars": 167,
"preview": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n name: foo\nspec:\n name: foo\n subjects: [\"foo\", \"foo."
},
{
"path": "examples/secure/nack/stream-foo.yaml",
"chars": 167,
"preview": "---\napiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n name: foo\nspec:\n name: foo\n subjects: [\"foo\", \"foo."
},
{
"path": "examples/secure/nack-a-client-tls.yaml",
"chars": 315,
"preview": "---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n name: nack-a-tls\nspec:\n secretName: nack-a-tls\n durat"
},
{
"path": "examples/secure/nack-b-client-tls.yaml",
"chars": 315,
"preview": "---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n name: nack-b-tls\nspec:\n secretName: nack-b-tls\n durat"
},
{
"path": "examples/secure/nats-helm.yaml",
"chars": 810,
"preview": "tlsCA:\n enabled: true\n secretName: nats-sys-tls\n key: ca.crt\n\nconfig:\n cluster:\n enabled: true\n jetstream:\n e"
},
{
"path": "examples/secure/server-tls.yaml",
"chars": 427,
"preview": "---\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n name: nats-server-tls\nspec:\n secretName: nats-server-t"
},
{
"path": "go.mod",
"chars": 3847,
"preview": "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."
},
{
"path": "go.sum",
"chars": 23687,
"preview": "github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3"
},
{
"path": "internal/controller/account_controller.go",
"chars": 7663,
"preview": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in "
},
{
"path": "internal/controller/account_controller_test.go",
"chars": 6274,
"preview": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in "
},
{
"path": "internal/controller/client.go",
"chars": 5952,
"preview": "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\"gi"
},
{
"path": "internal/controller/connection_pool.go",
"chars": 1716,
"preview": "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 "
},
{
"path": "internal/controller/connection_pool_test.go",
"chars": 1687,
"preview": "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\"gith"
},
{
"path": "internal/controller/consumer_controller.go",
"chars": 16803,
"preview": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in "
},
{
"path": "internal/controller/consumer_controller_test.go",
"chars": 35258,
"preview": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in "
},
{
"path": "internal/controller/helpers_test.go",
"chars": 1171,
"preview": "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"
},
{
"path": "internal/controller/jetstream_controller.go",
"chars": 12432,
"preview": "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\"str"
},
{
"path": "internal/controller/jetstream_controller_test.go",
"chars": 3542,
"preview": "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\"g"
},
{
"path": "internal/controller/keyvalue_controller.go",
"chars": 13147,
"preview": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in "
},
{
"path": "internal/controller/keyvalue_controller_test.go",
"chars": 28114,
"preview": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in "
},
{
"path": "internal/controller/objectstore_controller.go",
"chars": 12518,
"preview": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in "
},
{
"path": "internal/controller/objectstore_controller_test.go",
"chars": 26293,
"preview": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in "
},
{
"path": "internal/controller/register.go",
"chars": 2110,
"preview": "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"
},
{
"path": "internal/controller/stream_controller.go",
"chars": 18796,
"preview": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in "
},
{
"path": "internal/controller/stream_controller_test.go",
"chars": 35802,
"preview": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in "
},
{
"path": "internal/controller/suite_test.go",
"chars": 3612,
"preview": "/*\nCopyright 2025.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in "
},
{
"path": "internal/controller/types.go",
"chars": 651,
"preview": "package controller\n\nconst (\n\treadyCondType = \"Ready\"\n\taccountFinalizer = \"account.nats.io/finalizer\"\n\tstreamF"
},
{
"path": "kuttl-test.yaml",
"chars": 138,
"preview": "apiVersion: kuttl.dev/v1beta1\nkind: TestSuite\ntestDirs:\n - tests/\ncommands:\n - command: kubectl apply -f ./deploy/crds"
},
{
"path": "pkg/bootconfig/bootconfig.go",
"chars": 3990,
"preview": "// Copyright 2018 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/apis/jetstream/register.go",
"chars": 114,
"preview": "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",
"chars": 1649,
"preview": "package v1beta1\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfac"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta1/doc.go",
"chars": 122,
"preview": "// +k8s:deepcopy-gen=package\n// +groupName=jetstream.nats.io\n\n// Package v1 is the v1 version of the API.\npackage v1beta"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta1/register.go",
"chars": 1319,
"preview": "package v1beta1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/api"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta1/streamtemplatetypes.go",
"chars": 943,
"preview": "package v1beta1\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfac"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta1/streamtypes.go",
"chars": 2399,
"preview": "package v1beta1\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfac"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta1/types.go",
"chars": 614,
"preview": "package v1beta1\n\nimport (\n\tk8sapi \"k8s.io/api/core/v1\"\n)\n\ntype CredentialsSecret struct {\n\tName string `json:\"name\"`\n\tKe"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta1/zz_generated.deepcopy.go",
"chars": 10363,
"preview": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n// Copyright 2025 The NATS Authors\n// Licensed under t"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta2/accounttypes.go",
"chars": 1067,
"preview": "package v1beta2\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfac"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta2/consumertypes.go",
"chars": 3389,
"preview": "package v1beta2\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfac"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta2/doc.go",
"chars": 122,
"preview": "// +k8s:deepcopy-gen=package\n// +groupName=jetstream.nats.io\n\n// Package v1 is the v1 version of the API.\npackage v1beta"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta2/keyvaluetypes.go",
"chars": 1786,
"preview": "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"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta2/objectstoretypes.go",
"chars": 1351,
"preview": "package v1beta2\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfac"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta2/register.go",
"chars": 1379,
"preview": "package v1beta2\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/api"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta2/streamtypes.go",
"chars": 4362,
"preview": "package v1beta2\n\nimport (\n\tk8smeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +genclient\n// +k8s:deepcopy-gen:interfac"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta2/types.go",
"chars": 2128,
"preview": "package v1beta2\n\nimport (\n\tk8sapi \"k8s.io/api/core/v1\"\n)\n\ntype CredentialsSecret struct {\n\tName string `json:\"name\"`\n\tKe"
},
{
"path": "pkg/jetstream/apis/jetstream/v1beta2/zz_generated.deepcopy.go",
"chars": 23684,
"preview": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n// Copyright 2025 The NATS Authors\n// Licensed under t"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/internal/internal.go",
"chars": 1522,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/account.go",
"chars": 11759,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/accountspec.go",
"chars": 3969,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/basestreamconfig.go",
"chars": 2079,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/condition.go",
"chars": 3457,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/connectionopts.go",
"chars": 4409,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumer.go",
"chars": 11811,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerlimits.go",
"chars": 2092,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/consumerspec.go",
"chars": 18029,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/credssecret.go",
"chars": 1980,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvalue.go",
"chars": 11811,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/keyvaluespec.go",
"chars": 8312,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/nkeysecret.go",
"chars": 1968,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstore.go",
"chars": 11967,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/objectstorespec.go",
"chars": 5773,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/republish.go",
"chars": 2418,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/secretref.go",
"chars": 1452,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/status.go",
"chars": 2200,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/stream.go",
"chars": 11707,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamplacement.go",
"chars": 2039,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamsource.go",
"chars": 5071,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/streamspec.go",
"chars": 20670,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/subjecttransform.go",
"chars": 1977,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tls.go",
"chars": 2390,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tlssecret.go",
"chars": 2935,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/tokensecret.go",
"chars": 1986,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/jetstream/v1beta2/user.go",
"chars": 2364,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/applyconfiguration/utils.go",
"chars": 4554,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/clientset.go",
"chars": 3902,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/fake/clientset_generated.go",
"chars": 4627,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/fake/doc.go",
"chars": 711,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/fake/register.go",
"chars": 1881,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/scheme/doc.go",
"chars": 727,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/scheme/register.go",
"chars": 1937,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/account.go",
"chars": 3665,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/consumer.go",
"chars": 3711,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/doc.go",
"chars": 713,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/doc.go",
"chars": 704,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_account.go",
"chars": 2039,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_consumer.go",
"chars": 2062,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_jetstream_client.go",
"chars": 1741,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_keyvalue.go",
"chars": 2062,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_objectstore.go",
"chars": 2131,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/fake/fake_stream.go",
"chars": 2009,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/generated_expansion.go",
"chars": 830,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/jetstream_client.go",
"chars": 3749,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/keyvalue.go",
"chars": 3711,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/objectstore.go",
"chars": 3849,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/clientset/versioned/typed/jetstream/v1beta2/stream.go",
"chars": 3619,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/informers/externalversions/factory.go",
"chars": 9299,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/informers/externalversions/generic.go",
"chars": 2776,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/informers/externalversions/internalinterfaces/factory_interfaces.go",
"chars": 1456,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/informers/externalversions/jetstream/interface.go",
"chars": 1665,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/account.go",
"chars": 4263,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/consumer.go",
"chars": 4287,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/interface.go",
"chars": 2678,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/keyvalue.go",
"chars": 4287,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/objectstore.go",
"chars": 4359,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/informers/externalversions/jetstream/v1beta2/stream.go",
"chars": 4239,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/listers/jetstream/v1beta2/account.go",
"chars": 2666,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/listers/jetstream/v1beta2/consumer.go",
"chars": 2706,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/listers/jetstream/v1beta2/expansion_generated.go",
"chars": 2035,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/listers/jetstream/v1beta2/keyvalue.go",
"chars": 2706,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/listers/jetstream/v1beta2/objectstore.go",
"chars": 2826,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/jetstream/generated/listers/jetstream/v1beta2/stream.go",
"chars": 2626,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/k8scodegen/file-header.txt",
"chars": 586,
"preview": "// Copyright 2025 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
},
{
"path": "pkg/k8scodegen/k8scodegen.go",
"chars": 53,
"preview": "package k8scodegen\n\nimport _ \"k8s.io/code-generator\"\n"
},
{
"path": "pkg/natsreloader/natsreloader.go",
"chars": 18926,
"preview": "// Copyright 2020-2023 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may no"
},
{
"path": "pkg/natsreloader/natsreloader_test.go",
"chars": 15936,
"preview": "// Copyright 2020-2023 The NATS Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may no"
},
{
"path": "tests/Dockerfile",
"chars": 1628,
"preview": "# This Dockerfile enables straight-forward building of the jetstream-controller\n# This is currently not used in the CI/C"
},
{
"path": "tests/nack-control-loop.yaml",
"chars": 199,
"preview": "jetstream:\n enabled: true\n\n image:\n repository: nack\n tag: test\n\n # Enable controller-runtime mode\n additional"
},
{
"path": "tests/nack-legacy.yaml",
"chars": 124,
"preview": "jetstream:\n enabled: true\n\n image:\n repository: nack\n tag: test\n\n nats:\n url: nats://nats:4222\n\nnamespaced: t"
},
{
"path": "tests/nats.yaml",
"chars": 323,
"preview": "---\nglobal:\n labels:\n app: main-jetstream\n\nnatsBox:\n enabled: false\n\nconfig:\n cluster:\n enabled: false\n\n gatew"
},
{
"path": "tests/stream-creation/00-nack.yaml",
"chars": 526,
"preview": "apiVersion: kuttl.dev/v1beta1\nkind: TestStep\nunitTest: false\ncommands:\n - command: helm uninstall --namespace $NAMESPAC"
},
{
"path": "tests/stream-creation/01-stream.yaml",
"chars": 129,
"preview": "apiVersion: kuttl.dev/v1beta1\nkind: TestStep\napply:\n - rides-stream.yaml\nassert:\n - asserted-rides-stream.yaml\nunitTes"
},
{
"path": "tests/stream-creation/02-natscli-stream.yaml",
"chars": 119,
"preview": "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",
"chars": 105,
"preview": "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",
"chars": 563,
"preview": "apiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n name: rides\nspec:\n allowDirect: false\n allowMsgTtl: fal"
},
{
"path": "tests/stream-creation/natscli.yaml",
"chars": 280,
"preview": "apiVersion: v1\nkind: Pod\nmetadata:\n labels:\n run: natscli\n name: natscli\nspec:\n restartPolicy: Never\n containers:"
},
{
"path": "tests/stream-creation/rides-stream.yaml",
"chars": 155,
"preview": "apiVersion: jetstream.nats.io/v1beta2\nkind: Stream\nmetadata:\n name: rides\nspec:\n name: rides\n subjects:\n - \"rides."
}
]
About this extraction
This page contains the full source code of the nats-io/nack GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 177 files (926.2 KB), approximately 251.3k tokens, and a symbol index with 916 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.