├── .dockerignore ├── .github ├── chart-testing.yaml ├── lint.sh ├── test.sh └── workflows │ ├── release-chart.yaml │ ├── release.yml │ ├── test-chart.yaml │ └── test.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── Tiltfile ├── api └── v1alpha1 │ ├── groupversion_info.go │ ├── pipeline_types.go │ └── zz_generated.deepcopy.go ├── chart ├── .helmignore ├── Chart.yaml ├── crds │ └── pipeline.yaml ├── templates │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── rbac.yaml │ └── serviceaccount.yaml └── values.yaml ├── cmd └── main.go ├── config ├── crd │ ├── bases │ │ └── captain.benthos.dev_pipelines.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_benthospipelines.yaml │ │ └── webhook_in_benthospipelines.yaml ├── default │ └── kustomization.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── pipeline_editor_role.yaml │ ├── pipeline_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml └── samples │ ├── captain_v1alpha1_pipeline.yaml │ └── kustomization.yaml ├── docs ├── developer-guide.md └── images │ └── icon.png ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt └── internal ├── controller ├── metrics │ └── metrics.go ├── pipeline_controller.go └── suite_test.go └── pkg └── resource └── resource.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | -------------------------------------------------------------------------------- /.github/chart-testing.yaml: -------------------------------------------------------------------------------- 1 | remote: origin 2 | target-branch: main 3 | debug: true 4 | check-version-increment: true 5 | upgrade: true 6 | validate-chart-schema: true 7 | validate-yaml: true 8 | chart-dirs: 9 | - chart 10 | -------------------------------------------------------------------------------- /.github/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CT_ARGS="" 4 | GIT_SAFE_DIR="false" 5 | 6 | if [ "$GIT_SAFE_DIR" != "true" ]; then 7 | git config --global --add safe.directory /chart 8 | fi 9 | 10 | ct lint --config=./.github/chart-testing.yaml 11 | -------------------------------------------------------------------------------- /.github/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /usr/bin/helm unittest --color ./chart; 4 | -------------------------------------------------------------------------------- /.github/workflows/release-chart.yaml: -------------------------------------------------------------------------------- 1 | name: Release Chart 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Configure Git 18 | run: | 19 | git config user.name blobbot-helm 20 | git config user.email blobbot-helm@github.com 21 | 22 | - name: Get chart.yaml verison 23 | id: chart_version 24 | run: | 25 | echo "CHART_VERSION=$(grep -o 'version: [0-9.]*' Chart.yaml | awk '{print $2}')" >> $GITHUB_OUTPUT 26 | 27 | - name: Check if tag exists 28 | id: tag_exists 29 | run: | 30 | if ! git show-ref --verify --quiet "refs/tags/chart-${{ steps.chart_version.outputs.CHART_VERSION }}"; then 31 | TAG_EXISTS=false 32 | else 33 | TAG_EXISTS=true 34 | fi 35 | 36 | echo "TAG_EXISTS=$TAG_EXISTS" >> $GITHUB_OUTPUT 37 | 38 | - name: Tag release 39 | id: tag_version 40 | uses: mathieudutour/github-tag-action@v6.2 41 | with: 42 | github_token: ${{ secrets.GITHUB_TOKEN }} 43 | custom_tag: ${{ steps.chart_version.outputs.CHART_VERSION }} 44 | tag_prefix: "chart-" 45 | if: steps.tag_exists.outputs.TAG_EXISTS == 'false' 46 | 47 | - name: Create release 48 | if: steps.tag_exists.outputs.TAG_EXISTS == 'false' 49 | uses: ncipollo/release-action@v1 50 | with: 51 | tag: ${{ steps.tag_version.outputs.new_tag }} 52 | name: ${{ steps.tag_version.outputs.new_tag }} 53 | body: ${{ steps.tag_version.outputs.changelog }} 54 | 55 | - name: Publish Helm chart 56 | if: steps.tag_exists.outputs.TAG_EXISTS == 'false' 57 | uses: stefanprodan/helm-gh-pages@89c6698c192e70ed0e495bee7d3d1ca5b477fe82 58 | with: 59 | token: ${{ secrets.CHARTS_REPO_TOKEN }} 60 | owner: benthosdev 61 | repository: charts 62 | branch: main 63 | target_dir: benthos-captain 64 | index_dir: . 65 | charts_dir: . 66 | charts_url: https://benthosdev.github.io/charts 67 | commit_username: blobbot-helm 68 | commit_email: blobbot-helm@github.com 69 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | permissions: 9 | contents: read 10 | packages: write 11 | 12 | env: 13 | GOLANG_VERSION: "1.22.x" 14 | 15 | jobs: 16 | release: 17 | name: Release 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Setup Go 24 | uses: actions/setup-go@v5 25 | with: 26 | go-version: ${{ env.GOLANG_VERSION }} 27 | 28 | - name: golangci-lint 29 | uses: golangci/golangci-lint-action@v3 30 | with: 31 | version: v1.54 32 | args: --timeout=10m 33 | 34 | - name: Test 35 | run: make test 36 | 37 | - name: Log in to registry 38 | run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin 39 | 40 | - name: Release 41 | run: | 42 | make docker-buildx IMG=ghcr.io/benthosdev/benthos-captain:${{github.ref_name}} 43 | make docker-buildx IMG=ghcr.io/benthosdev/benthos-captain:latest 44 | -------------------------------------------------------------------------------- /.github/workflows/test-chart.yaml: -------------------------------------------------------------------------------- 1 | name: Test chart 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_call: 8 | workflow_dispatch: 9 | pull_request: 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Configure Git 21 | run: | 22 | git config user.name blobbot-helm 23 | git config user.email blobbot-helm@github.com 24 | 25 | - name: Test 26 | run: make chart-test 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | GOLANG_VERSION: "1.22.x" 14 | 15 | jobs: 16 | test: 17 | name: Test 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Setup Go 24 | uses: actions/setup-go@v5 25 | with: 26 | go-version: ${{ env.GOLANG_VERSION }} 27 | 28 | - name: golangci-lint 29 | uses: golangci/golangci-lint-action@v3 30 | with: 31 | version: v1.54 32 | args: --timeout=10m 33 | 34 | - name: Test 35 | run: make test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin/* 9 | Dockerfile.cross 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Kubernetes Generated files - skip generated files, except for vendored files 18 | 19 | !vendor/**/zz_generated.* 20 | 21 | # editor and IDE paraphernalia 22 | .idea 23 | .vscode 24 | *.swp 25 | *.swo 26 | *~ 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.22 as builder 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | WORKDIR /workspace 7 | # Copy the Go Modules manifests 8 | COPY go.mod go.mod 9 | COPY go.sum go.sum 10 | # cache deps before building and copying source so that we don't need to re-download as much 11 | # and so that source changes don't invalidate our downloaded layer 12 | RUN go mod download 13 | 14 | # Copy the go source 15 | COPY cmd/main.go cmd/main.go 16 | COPY api/ api/ 17 | COPY internal/ internal/ 18 | 19 | # Build 20 | # the GOARCH has not a default value to allow the binary be built according to the host where the command 21 | # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO 22 | # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, 23 | # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. 24 | RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go 25 | 26 | # Use distroless as minimal base image to package the manager binary 27 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 28 | FROM gcr.io/distroless/static:nonroot 29 | WORKDIR / 30 | COPY --from=builder /workspace/manager . 31 | USER 65532:65532 32 | 33 | ENTRYPOINT ["/manager"] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Benthos Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | chart-test: 3 | docker run ${DOCKER_ARGS} --entrypoint /bin/sh --rm -v $(CURDIR):/charts -w /charts helmunittest/helm-unittest:3.11.1-0.3.0 /charts/.github/test.sh 4 | 5 | chart-lint: 6 | docker run ${DOCKER_ARGS} --env GIT_SAFE_DIR="true" --entrypoint /bin/sh --rm -v $(CURDIR):/charts -w /charts quay.io/helmpack/chart-testing:v3.10.1 /charts/.github/lint.sh 7 | 8 | # Image URL to use all building/pushing image targets 9 | IMG ?= ghcr.io/benthosdev/benthos-captain:latest 10 | # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. 11 | ENVTEST_K8S_VERSION = 1.27.1 12 | 13 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 14 | ifeq (,$(shell go env GOBIN)) 15 | GOBIN=$(shell go env GOPATH)/bin 16 | else 17 | GOBIN=$(shell go env GOBIN) 18 | endif 19 | 20 | # CONTAINER_TOOL defines the container tool to be used for building images. 21 | # Be aware that the target commands are only tested with Docker which is 22 | # scaffolded by default. However, you might want to replace it to use other 23 | # tools. (i.e. podman) 24 | CONTAINER_TOOL ?= docker 25 | 26 | # Setting SHELL to bash allows bash commands to be executed by recipes. 27 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 28 | SHELL = /usr/bin/env bash -o pipefail 29 | .SHELLFLAGS = -ec 30 | 31 | .PHONY: all 32 | all: build 33 | 34 | ##@ General 35 | 36 | # The help target prints out all targets with their descriptions organized 37 | # beneath their categories. The categories are represented by '##@' and the 38 | # target descriptions by '##'. The awk commands is responsible for reading the 39 | # entire set of makefiles included in this invocation, looking for lines of the 40 | # file as xyz: ## something, and then pretty-format the target and help. Then, 41 | # if there's a line with ##@ something, that gets pretty-printed as a category. 42 | # More info on the usage of ANSI control characters for terminal formatting: 43 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 44 | # More info on the awk command: 45 | # http://linuxcommand.org/lc3_adv_awk.php 46 | 47 | .PHONY: help 48 | help: ## Display this help. 49 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 50 | 51 | ##@ Development 52 | 53 | .PHONY: manifests 54 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 55 | $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases 56 | 57 | .PHONY: generate 58 | generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 59 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 60 | 61 | .PHONY: fmt 62 | fmt: ## Run go fmt against code. 63 | go fmt ./... 64 | 65 | .PHONY: lint 66 | lint: 67 | go vet ./... 68 | golangci-lint run ./... 69 | 70 | 71 | .PHONY: test 72 | test: manifests generate fmt lint envtest ## Run tests. 73 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out 74 | 75 | ##@ Build 76 | 77 | .PHONY: build 78 | build: manifests generate fmt lint ## Build manager binary. 79 | go build -o bin/manager cmd/main.go 80 | 81 | .PHONY: run 82 | run: manifests generate fmt lint ## Run a controller from your host. 83 | go run ./cmd/main.go 84 | 85 | # If you wish built the manager image targeting other platforms you can use the --platform flag. 86 | # (i.e. docker build --platform linux/arm64 ). However, you must enable docker buildKit for it. 87 | # More info: https://docs.docker.com/develop/develop-images/build_enhancements/ 88 | .PHONY: docker-build 89 | docker-build: test ## Build docker image with the manager. 90 | $(CONTAINER_TOOL) build -t ${IMG} . 91 | 92 | .PHONY: docker-push 93 | docker-push: ## Push docker image with the manager. 94 | $(CONTAINER_TOOL) push ${IMG} 95 | 96 | # PLATFORMS defines the target platforms for the manager image be build to provide support to multiple 97 | # architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: 98 | # - able to use docker buildx . More info: https://docs.docker.com/build/buildx/ 99 | # - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/ 100 | # - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=> then the export will fail) 101 | # To properly provided solutions that supports more than one platform you should use this option. 102 | PLATFORMS ?= linux/arm64,linux/amd64 103 | .PHONY: docker-buildx 104 | docker-buildx: test ## Build and push docker image for the manager for cross-platform support 105 | # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile 106 | sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross 107 | - $(CONTAINER_TOOL) buildx create --name project-v3-builder 108 | $(CONTAINER_TOOL) buildx use project-v3-builder 109 | - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . 110 | - $(CONTAINER_TOOL) buildx rm project-v3-builder 111 | rm Dockerfile.cross 112 | 113 | ##@ Deployment 114 | 115 | ifndef ignore-not-found 116 | ignore-not-found = false 117 | endif 118 | 119 | .PHONY: install 120 | install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. 121 | $(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f - 122 | 123 | .PHONY: uninstall 124 | uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 125 | $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - 126 | 127 | .PHONY: deploy 128 | deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. 129 | cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} 130 | $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - 131 | 132 | .PHONY: undeploy 133 | undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 134 | $(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - 135 | 136 | ##@ Build Dependencies 137 | 138 | ## Location to install dependencies to 139 | LOCALBIN ?= $(shell pwd)/bin 140 | $(LOCALBIN): 141 | mkdir -p $(LOCALBIN) 142 | 143 | ## Tool Binaries 144 | KUBECTL ?= kubectl 145 | KUSTOMIZE ?= $(LOCALBIN)/kustomize 146 | CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen 147 | ENVTEST ?= $(LOCALBIN)/setup-envtest 148 | 149 | ## Tool Versions 150 | KUSTOMIZE_VERSION ?= v5.0.1 151 | CONTROLLER_TOOLS_VERSION ?= v0.15.0 152 | 153 | .PHONY: kustomize 154 | kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading. 155 | $(KUSTOMIZE): $(LOCALBIN) 156 | @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ 157 | echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ 158 | rm -rf $(LOCALBIN)/kustomize; \ 159 | fi 160 | test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) GO111MODULE=on go install sigs.k8s.io/kustomize/kustomize/v5@$(KUSTOMIZE_VERSION) 161 | 162 | .PHONY: controller-gen 163 | controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten. 164 | $(CONTROLLER_GEN): $(LOCALBIN) 165 | test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ 166 | GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) 167 | 168 | .PHONY: envtest 169 | envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. 170 | $(ENVTEST): $(LOCALBIN) 171 | test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest 172 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: benthos.dev 6 | layout: 7 | - go.kubebuilder.io/v4 8 | projectName: benthos-captain 9 | repo: github.com/benthosdev/benthos-captain 10 | resources: 11 | - api: 12 | crdVersion: v1 13 | namespaced: true 14 | controller: true 15 | domain: benthos.dev 16 | group: captain 17 | kind: Pipeline 18 | path: github.com/benthosdev/benthos-captain/api/v1alpha1 19 | version: v1alpha1 20 | version: "3" 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Benthos Captain](docs/images/icon.png "Benthos Captain") 2 | 3 | [![Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip) 4 | 5 | > ⚠️ **This is a work in progress proof of concept** ⚠️ 6 | 7 | Benthos Captain is a Kubernetes Operator to orchestrate [Benthos](https://www.benthos.dev/) pipelines. 8 | 9 | ## Getting Started 10 | 11 | Currently, there isn't a stable release of the operator. If you want to install the operator for development purposes, you can follow the [developer guide](./docs/developer-guide.md). 12 | 13 | The operator provides a custom resource for managing Benthos pipelines. Once you've got the operator running, you can deploy a `Pipeline` resource to test it out: 14 | 15 | ```yaml 16 | --- 17 | apiVersion: captain.benthos.dev/v1alpha1 18 | kind: Pipeline 19 | metadata: 20 | name: pipeline-sample 21 | spec: 22 | replicas: 1 23 | config: 24 | input: 25 | generate: 26 | mapping: | 27 | let favorite_animal = env("FAVORITE_ANIMAL") 28 | root = match $favorite_animal { 29 | "cat" => file("/config/cat.txt") 30 | "dog" => file("/config/dog.txt") 31 | _ => file("/config/dog.txt") 32 | } 33 | interval: 5s 34 | count: 0 35 | 36 | pipeline: 37 | processors: 38 | - mapping: root = content().uppercase() 39 | 40 | output: 41 | stdout: {} 42 | 43 | configFiles: 44 | cat.txt: | 45 | meow 46 | dog.txt: | 47 | woof 48 | 49 | env: 50 | - name: FAVORITE_ANIMAL 51 | value: cat 52 | ``` 53 | 54 | Once the resource is deployed, you can monitor the state of the resource: 55 | 56 | ```bash 57 | kubectl get pipelines 58 | 59 | NAME READY PHASE REPLICAS AVAILABLE AGE 60 | pipeline-sample true Running 2 2 62s 61 | ``` 62 | -------------------------------------------------------------------------------- /Tiltfile: -------------------------------------------------------------------------------- 1 | # -*- mode: Python -*- 2 | 3 | # build benthos operator 4 | def deploy_benthos_operator(): 5 | docker_build( 6 | "ghcr.io/benthosdev/benthos-captain:latest", 7 | ".", 8 | ignore=[ 9 | ".git", 10 | ".github", 11 | "*.md", 12 | "LICENSE", 13 | ] 14 | ) 15 | 16 | k8s_yaml( 17 | kustomize('./config/default') 18 | ) 19 | 20 | deploy_benthos_operator() 21 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | // Package v1alpha1 contains API Schema definitions for the captain v1alpha1 API group 2 | // +kubebuilder:object:generate=true 3 | // +groupName=captain.benthos.dev 4 | package v1alpha1 5 | 6 | import ( 7 | "k8s.io/apimachinery/pkg/runtime/schema" 8 | "sigs.k8s.io/controller-runtime/pkg/scheme" 9 | ) 10 | 11 | var ( 12 | // GroupVersion is group version used to register these objects 13 | GroupVersion = schema.GroupVersion{Group: "captain.benthos.dev", Version: "v1alpha1"} 14 | 15 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 16 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 17 | 18 | // AddToScheme adds the types in this group-version to the given scheme. 19 | AddToScheme = SchemeBuilder.AddToScheme 20 | ) 21 | -------------------------------------------------------------------------------- /api/v1alpha1/pipeline_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | v1 "k8s.io/api/core/v1" 5 | apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | ) 8 | 9 | const ( 10 | // PipelineFinalizer is the finalizer used by the pipeline controller to cleanup resources when the pipeline is being deleted. 11 | PipelineFinalizer = "pipeline.captain.benthos.dev" 12 | ) 13 | 14 | // PipelineSpec defines the desired state of Pipeline 15 | type PipelineSpec struct { 16 | // Currently this is set just to take a string. Ideally, we should be able to fetch the struct 17 | // as a package but currently `config` is internal. If we decide to fetch from a package, we will need to consider 18 | // Kubernetes API Versioning when it changes. 19 | 20 | // Config defines the Benthos configuration as a string. 21 | Config *apiextensionsv1.JSON `json:"config,omitempty"` 22 | 23 | // Replicas defines the amount of replicas to create for the Benthos deployment. 24 | Replicas int32 `json:"replicas,omitempty"` 25 | 26 | // Image defines the image and tag to use for the Benthos deployment. 27 | // +optional 28 | Image string `json:"image,omitempty"` 29 | 30 | // ImagePullSecret is an optional reference to a secret in the same namespace to use for pulling the image used by 31 | // the benthos Pod. Similar to ImagePullSecrets, see v1.PodSpec#ImagePullSecrets. 32 | ImagePullSecret string `json:"imagePullSecret,omitempty"` 33 | 34 | // ConfigFiles Additional configuration, as Key/Value pairs, that will be mounted as files with the /config 35 | // directory on the pod. The key should be the file name and the value should be its content. 36 | ConfigFiles map[string]string `json:"configFiles,omitempty"` 37 | 38 | // Env Environment Variables to set in the benthos pipeline pod. 39 | Env []v1.EnvVar `json:"env,omitempty"` 40 | } 41 | 42 | // PipelineStatus defines the observed state of Pipeline 43 | type PipelineStatus struct { 44 | Ready bool `json:"ready,omitempty"` 45 | Phase string `json:"phase,omitempty"` 46 | // AvailableReplicas is the amount of pods available from the deployment. 47 | AvailableReplicas int32 `json:"availableReplicas,omitempty"` 48 | } 49 | 50 | // +kubebuilder:object:root=true 51 | // +kubebuilder:resource:path=pipelines,scope=Namespaced 52 | // +kubebuilder:subresource:status 53 | // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.ready",description="The current state the Benthos Pipeline." 54 | // +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",description="The current phase of the Benthos Pipeline." 55 | // +kubebuilder:printcolumn:name="Replicas",type="integer",JSONPath=".spec.replicas",description="The desired amount of running Benthos replicas." 56 | // +kubebuilder:printcolumn:name="Available",type="integer",JSONPath=".status.availableReplicas",description="The amount of available Benthos replicas." 57 | // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of this resource" 58 | 59 | // Pipeline is the Schema for the pipelines API 60 | type Pipeline struct { 61 | metav1.TypeMeta `json:",inline"` 62 | metav1.ObjectMeta `json:"metadata,omitempty"` 63 | 64 | Spec PipelineSpec `json:"spec,omitempty"` 65 | Status PipelineStatus `json:"status,omitempty"` 66 | } 67 | 68 | //+kubebuilder:object:root=true 69 | 70 | // PipelineList contains a list of Pipeline 71 | type PipelineList struct { 72 | metav1.TypeMeta `json:",inline"` 73 | metav1.ListMeta `json:"metadata,omitempty"` 74 | Items []Pipeline `json:"items"` 75 | } 76 | 77 | func init() { 78 | SchemeBuilder.Register(&Pipeline{}, &PipelineList{}) 79 | } 80 | 81 | // Currently these have been pulled directly from the Benthos repo. Ideally, we should be able to fetch these 82 | // as a package but currently `config` is internal. If we decide to fetch from a package, we will need to consider 83 | // Kubernetes API Versioning when it changes. 84 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | // Code generated by controller-gen. DO NOT EDIT. 4 | 5 | package v1alpha1 6 | 7 | import ( 8 | corev1 "k8s.io/api/core/v1" 9 | "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 10 | runtime "k8s.io/apimachinery/pkg/runtime" 11 | ) 12 | 13 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 14 | func (in *Pipeline) DeepCopyInto(out *Pipeline) { 15 | *out = *in 16 | out.TypeMeta = in.TypeMeta 17 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 18 | in.Spec.DeepCopyInto(&out.Spec) 19 | out.Status = in.Status 20 | } 21 | 22 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Pipeline. 23 | func (in *Pipeline) DeepCopy() *Pipeline { 24 | if in == nil { 25 | return nil 26 | } 27 | out := new(Pipeline) 28 | in.DeepCopyInto(out) 29 | return out 30 | } 31 | 32 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 33 | func (in *Pipeline) DeepCopyObject() runtime.Object { 34 | if c := in.DeepCopy(); c != nil { 35 | return c 36 | } 37 | return nil 38 | } 39 | 40 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 41 | func (in *PipelineList) DeepCopyInto(out *PipelineList) { 42 | *out = *in 43 | out.TypeMeta = in.TypeMeta 44 | in.ListMeta.DeepCopyInto(&out.ListMeta) 45 | if in.Items != nil { 46 | in, out := &in.Items, &out.Items 47 | *out = make([]Pipeline, len(*in)) 48 | for i := range *in { 49 | (*in)[i].DeepCopyInto(&(*out)[i]) 50 | } 51 | } 52 | } 53 | 54 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineList. 55 | func (in *PipelineList) DeepCopy() *PipelineList { 56 | if in == nil { 57 | return nil 58 | } 59 | out := new(PipelineList) 60 | in.DeepCopyInto(out) 61 | return out 62 | } 63 | 64 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 65 | func (in *PipelineList) DeepCopyObject() runtime.Object { 66 | if c := in.DeepCopy(); c != nil { 67 | return c 68 | } 69 | return nil 70 | } 71 | 72 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 73 | func (in *PipelineSpec) DeepCopyInto(out *PipelineSpec) { 74 | *out = *in 75 | if in.Config != nil { 76 | in, out := &in.Config, &out.Config 77 | *out = new(v1.JSON) 78 | (*in).DeepCopyInto(*out) 79 | } 80 | if in.ConfigFiles != nil { 81 | in, out := &in.ConfigFiles, &out.ConfigFiles 82 | *out = make(map[string]string, len(*in)) 83 | for key, val := range *in { 84 | (*out)[key] = val 85 | } 86 | } 87 | if in.Env != nil { 88 | in, out := &in.Env, &out.Env 89 | *out = make([]corev1.EnvVar, len(*in)) 90 | for i := range *in { 91 | (*in)[i].DeepCopyInto(&(*out)[i]) 92 | } 93 | } 94 | } 95 | 96 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineSpec. 97 | func (in *PipelineSpec) DeepCopy() *PipelineSpec { 98 | if in == nil { 99 | return nil 100 | } 101 | out := new(PipelineSpec) 102 | in.DeepCopyInto(out) 103 | return out 104 | } 105 | 106 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 107 | func (in *PipelineStatus) DeepCopyInto(out *PipelineStatus) { 108 | *out = *in 109 | } 110 | 111 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineStatus. 112 | func (in *PipelineStatus) DeepCopy() *PipelineStatus { 113 | if in == nil { 114 | return nil 115 | } 116 | out := new(PipelineStatus) 117 | in.DeepCopyInto(out) 118 | return out 119 | } 120 | -------------------------------------------------------------------------------- /chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: benthos-captain 3 | description: A Kubernetes Operator to orchestrate Benthos pipelines. 4 | # A chart can be either an 'application' or a 'library' chart. 5 | # 6 | # Application charts are a collection of templates that can be packaged into versioned archives 7 | # to be deployed. 8 | # 9 | # Library charts provide useful utilities or functions for the chart developer. They're included as 10 | # a dependency of application charts to inject those utilities and functions into the rendering 11 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 12 | type: application 13 | # This is the chart version. This version number should be incremented each time you make changes 14 | # to the chart and its templates, including the app version. 15 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 16 | version: 0.1.0 17 | # This is the version number of the application being deployed. This version number should be 18 | # incremented each time you make changes to the application. Versions are not expected to 19 | # follow Semantic Versioning. They should reflect the version the application is using. 20 | # It is recommended to use it with quotes. 21 | appVersion: "v0.1.0" 22 | -------------------------------------------------------------------------------- /chart/crds/pipeline.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: pipelines.captain.benthos.dev 5 | labels: 6 | app: benthos-captain 7 | spec: 8 | group: captain.benthos.dev 9 | names: 10 | kind: Pipeline 11 | listKind: PipelineList 12 | plural: pipelines 13 | singular: pipeline 14 | scope: Namespaced 15 | versions: 16 | - additionalPrinterColumns: 17 | - description: The current state the Benthos Pipeline. 18 | jsonPath: .status.ready 19 | name: Ready 20 | type: string 21 | - description: The current phase of the Benthos Pipeline. 22 | jsonPath: .status.phase 23 | name: Phase 24 | type: string 25 | - description: The desired amount of running Benthos replicas. 26 | jsonPath: .spec.replicas 27 | name: Replicas 28 | type: integer 29 | - description: The amount of available Benthos replicas. 30 | jsonPath: .status.availableReplicas 31 | name: Available 32 | type: integer 33 | - description: The age of this resource 34 | jsonPath: .metadata.creationTimestamp 35 | name: Age 36 | type: date 37 | name: v1alpha1 38 | schema: 39 | openAPIV3Schema: 40 | description: Pipeline is the Schema for the pipelines API 41 | properties: 42 | apiVersion: 43 | description: 'APIVersion defines the versioned schema of this representation 44 | of an object. Servers should convert recognized schemas to the latest 45 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 46 | type: string 47 | kind: 48 | description: 'Kind is a string value representing the REST resource this 49 | object represents. Servers may infer this from the endpoint the client 50 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 51 | type: string 52 | metadata: 53 | type: object 54 | spec: 55 | description: PipelineSpec defines the desired state of Pipeline 56 | properties: 57 | config: 58 | description: Config defines the Benthos configuration as a string. 59 | x-kubernetes-preserve-unknown-fields: true 60 | image: 61 | description: Image defines the image and tag to use for the Benthos 62 | deployment. 63 | type: string 64 | replicas: 65 | description: Replicas defines the amount of replicas to create for the 66 | Benthos deployment. 67 | format: int32 68 | type: integer 69 | type: object 70 | status: 71 | description: PipelineStatus defines the observed state of Pipeline 72 | properties: 73 | availableReplicas: 74 | description: AvailableReplicas is the amount of pods available from 75 | the deployment. 76 | format: int32 77 | type: integer 78 | phase: 79 | type: string 80 | ready: 81 | type: boolean 82 | type: object 83 | type: object 84 | served: true 85 | storage: true 86 | subresources: 87 | status: {} 88 | status: 89 | acceptedNames: 90 | kind: "" 91 | plural: "" 92 | conditions: [] 93 | storedVersions: [] 94 | -------------------------------------------------------------------------------- /chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "benthos-captain.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "benthos-captain.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "benthos-captain.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "benthos-captain.labels" -}} 37 | helm.sh/chart: {{ include "benthos-captain.chart" . }} 38 | {{ include "benthos-captain.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "benthos-captain.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "benthos-captain.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "benthos-captain.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "benthos-captain.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /chart/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "benthos-captain.fullname" . }} 5 | labels: 6 | app.kubernetes.io/component: manager 7 | app.kubernetes.io/created-by: benthos-captain 8 | app.kubernetes.io/part-of: benthos-captain 9 | control-plane: controller-manager 10 | {{- include "benthos-captain.labels" . | nindent 4 }} 11 | spec: 12 | replicas: {{ .Values.deployment.replicas }} 13 | selector: 14 | matchLabels: 15 | control-plane: controller-manager 16 | {{- include "benthos-captain.selectorLabels" . | nindent 6 }} 17 | template: 18 | metadata: 19 | labels: 20 | control-plane: controller-manager 21 | {{- include "benthos-captain.selectorLabels" . | nindent 8 }} 22 | annotations: 23 | kubectl.kubernetes.io/default-container: manager 24 | spec: 25 | affinity: 26 | nodeAffinity: 27 | requiredDuringSchedulingIgnoredDuringExecution: 28 | nodeSelectorTerms: 29 | - matchExpressions: 30 | - key: kubernetes.io/arch 31 | operator: In 32 | values: 33 | - amd64 34 | - arm64 35 | - key: kubernetes.io/os 36 | operator: In 37 | values: 38 | - linux 39 | containers: 40 | - args: {{- toYaml .Values.deployment.manager.args | nindent 8 }} 41 | command: 42 | - /manager 43 | image: {{ .Values.deployment.manager.image.repository }}:{{ .Values.deployment.manager.image.tag 44 | | default .Chart.AppVersion }} 45 | livenessProbe: 46 | httpGet: 47 | path: /healthz 48 | port: 8081 49 | initialDelaySeconds: 15 50 | periodSeconds: 20 51 | name: manager 52 | readinessProbe: 53 | httpGet: 54 | path: /readyz 55 | port: 8081 56 | initialDelaySeconds: 5 57 | periodSeconds: 10 58 | resources: {{- toYaml .Values.deployment.manager.resources | nindent 10 59 | }} 60 | securityContext: {{- toYaml .Values.deployment.manager.containerSecurityContext 61 | | nindent 10 }} 62 | securityContext: 63 | runAsNonRoot: true 64 | serviceAccountName: {{ include "benthos-captain.fullname" . }} 65 | terminationGracePeriodSeconds: 10 66 | -------------------------------------------------------------------------------- /chart/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: {{ include "benthos-captain.fullname" . }} 5 | labels: 6 | app.kubernetes.io/component: rbac 7 | app.kubernetes.io/created-by: benthos-captain 8 | app.kubernetes.io/part-of: benthos-captain 9 | {{- include "benthos-captain.labels" . | nindent 4 }} 10 | rules: 11 | - apiGroups: 12 | - "" 13 | resources: 14 | - configmaps 15 | verbs: 16 | - create 17 | - delete 18 | - get 19 | - list 20 | - patch 21 | - update 22 | - watch 23 | - apiGroups: 24 | - apps 25 | resources: 26 | - deployments 27 | verbs: 28 | - create 29 | - delete 30 | - get 31 | - list 32 | - patch 33 | - update 34 | - watch 35 | - apiGroups: 36 | - apps 37 | resources: 38 | - deployments/status 39 | verbs: 40 | - get 41 | - apiGroups: 42 | - captain.benthos.dev 43 | resources: 44 | - pipelines 45 | verbs: 46 | - create 47 | - delete 48 | - get 49 | - list 50 | - patch 51 | - update 52 | - watch 53 | - apiGroups: 54 | - captain.benthos.dev 55 | resources: 56 | - pipelines/finalizers 57 | verbs: 58 | - update 59 | - apiGroups: 60 | - captain.benthos.dev 61 | resources: 62 | - pipelines/status 63 | verbs: 64 | - get 65 | - patch 66 | - update 67 | - apiGroups: 68 | - coordination.k8s.io 69 | resources: 70 | - leases 71 | verbs: 72 | - get 73 | - list 74 | - watch 75 | - create 76 | - update 77 | - patch 78 | - delete 79 | - apiGroups: 80 | - "" 81 | resources: 82 | - events 83 | verbs: 84 | - create 85 | - patch 86 | --- 87 | apiVersion: rbac.authorization.k8s.io/v1 88 | kind: ClusterRoleBinding 89 | metadata: 90 | name: {{ include "benthos-captain.fullname" . }} 91 | labels: 92 | app.kubernetes.io/component: rbac 93 | app.kubernetes.io/created-by: benthos-captain 94 | app.kubernetes.io/part-of: benthos-captain 95 | {{- include "benthos-captain.labels" . | nindent 4 }} 96 | roleRef: 97 | apiGroup: rbac.authorization.k8s.io 98 | kind: ClusterRole 99 | name: '{{ include "benthos-captain.fullname" . }}' 100 | subjects: 101 | - kind: ServiceAccount 102 | name: '{{ include "benthos-captain.fullname" . }}' 103 | namespace: '{{ .Release.Namespace }}' 104 | -------------------------------------------------------------------------------- /chart/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: {{ include "benthos-captain.fullname" . }} 5 | labels: 6 | app.kubernetes.io/component: rbac 7 | app.kubernetes.io/created-by: benthos-captain 8 | app.kubernetes.io/part-of: benthos-captain 9 | {{- include "benthos-captain.labels" . | nindent 4 }} 10 | annotations: 11 | {{- toYaml .Values.deployment.serviceAccount.annotations | nindent 4 }} 12 | -------------------------------------------------------------------------------- /chart/values.yaml: -------------------------------------------------------------------------------- 1 | deployment: 2 | manager: 3 | args: 4 | - --leader-elect 5 | - -zap-devel 6 | containerSecurityContext: 7 | allowPrivilegeEscalation: false 8 | capabilities: 9 | drop: 10 | - ALL 11 | image: 12 | repository: ghcr.io/benthosdev/benthos-captain 13 | # resources: 14 | # limits: 15 | # cpu: 500m 16 | # memory: 128Mi 17 | # requests: 18 | # cpu: 10m 19 | # memory: 64Mi 20 | replicas: 1 21 | serviceAccount: 22 | annotations: {} 23 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 7 | "sigs.k8s.io/controller-runtime/pkg/webhook" 8 | 9 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 10 | // to ensure that exec-entrypoint and run can make use of them. 11 | _ "k8s.io/client-go/plugin/pkg/client/auth" 12 | 13 | "k8s.io/apimachinery/pkg/runtime" 14 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 15 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 16 | ctrl "sigs.k8s.io/controller-runtime" 17 | "sigs.k8s.io/controller-runtime/pkg/healthz" 18 | "sigs.k8s.io/controller-runtime/pkg/log" 19 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 20 | 21 | captainv1alpha1 "github.com/benthosdev/benthos-captain/api/v1alpha1" 22 | "github.com/benthosdev/benthos-captain/internal/controller" 23 | //+kubebuilder:scaffold:imports 24 | ) 25 | 26 | var ( 27 | scheme = runtime.NewScheme() 28 | setupLog = ctrl.Log.WithName("setup") 29 | ) 30 | 31 | func init() { 32 | log.SetLogger(zap.New()) 33 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 34 | 35 | utilruntime.Must(captainv1alpha1.AddToScheme(scheme)) 36 | //+kubebuilder:scaffold:scheme 37 | } 38 | 39 | func main() { 40 | var metricsAddr string 41 | var enableLeaderElection bool 42 | var probeAddr string 43 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 44 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 45 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 46 | "Enable leader election for controller manager. "+ 47 | "Enabling this will ensure there is only one active controller manager.") 48 | opts := zap.Options{ 49 | Development: true, 50 | } 51 | opts.BindFlags(flag.CommandLine) 52 | flag.Parse() 53 | 54 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 55 | 56 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 57 | Scheme: scheme, 58 | Metrics: metricsserver.Options{BindAddress: metricsAddr}, 59 | WebhookServer: webhook.NewServer(webhook.Options{Port: 9443}), 60 | HealthProbeBindAddress: probeAddr, 61 | LeaderElection: enableLeaderElection, 62 | LeaderElectionID: "3c552ee9.benthos.dev", 63 | // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily 64 | // when the Manager ends. This requires the binary to immediately end when the 65 | // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly 66 | // speeds up voluntary leader transitions as the new leader don't have to wait 67 | // LeaseDuration time first. 68 | // 69 | // In the default scaffold provided, the program ends immediately after 70 | // the manager stops, so would be fine to enable this option. However, 71 | // if you are doing or is intended to do any operation such as perform cleanups 72 | // after the manager stops then its usage might be unsafe. 73 | // LeaderElectionReleaseOnCancel: true, 74 | }) 75 | if err != nil { 76 | setupLog.Error(err, "unable to start manager") 77 | os.Exit(1) 78 | } 79 | 80 | if err = (&controller.PipelineReconciler{ 81 | Client: mgr.GetClient(), 82 | Scheme: mgr.GetScheme(), 83 | }).SetupWithManager(mgr); err != nil { 84 | setupLog.Error(err, "unable to create controller", "controller", "Pipeline") 85 | os.Exit(1) 86 | } 87 | //+kubebuilder:scaffold:builder 88 | 89 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 90 | setupLog.Error(err, "unable to set up health check") 91 | os.Exit(1) 92 | } 93 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 94 | setupLog.Error(err, "unable to set up ready check") 95 | os.Exit(1) 96 | } 97 | 98 | setupLog.Info("starting manager") 99 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 100 | setupLog.Error(err, "problem running manager") 101 | os.Exit(1) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /config/crd/bases/captain.benthos.dev_pipelines.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.15.0 7 | name: pipelines.captain.benthos.dev 8 | spec: 9 | group: captain.benthos.dev 10 | names: 11 | kind: Pipeline 12 | listKind: PipelineList 13 | plural: pipelines 14 | singular: pipeline 15 | scope: Namespaced 16 | versions: 17 | - additionalPrinterColumns: 18 | - description: The current state the Benthos Pipeline. 19 | jsonPath: .status.ready 20 | name: Ready 21 | type: string 22 | - description: The current phase of the Benthos Pipeline. 23 | jsonPath: .status.phase 24 | name: Phase 25 | type: string 26 | - description: The desired amount of running Benthos replicas. 27 | jsonPath: .spec.replicas 28 | name: Replicas 29 | type: integer 30 | - description: The amount of available Benthos replicas. 31 | jsonPath: .status.availableReplicas 32 | name: Available 33 | type: integer 34 | - description: The age of this resource 35 | jsonPath: .metadata.creationTimestamp 36 | name: Age 37 | type: date 38 | name: v1alpha1 39 | schema: 40 | openAPIV3Schema: 41 | description: Pipeline is the Schema for the pipelines API 42 | properties: 43 | apiVersion: 44 | description: |- 45 | APIVersion defines the versioned schema of this representation of an object. 46 | Servers should convert recognized schemas to the latest internal value, and 47 | may reject unrecognized values. 48 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 49 | type: string 50 | kind: 51 | description: |- 52 | Kind is a string value representing the REST resource this object represents. 53 | Servers may infer this from the endpoint the client submits requests to. 54 | Cannot be updated. 55 | In CamelCase. 56 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 57 | type: string 58 | metadata: 59 | type: object 60 | spec: 61 | description: PipelineSpec defines the desired state of Pipeline 62 | properties: 63 | config: 64 | description: Config defines the Benthos configuration as a string. 65 | x-kubernetes-preserve-unknown-fields: true 66 | configFiles: 67 | additionalProperties: 68 | type: string 69 | description: |- 70 | ConfigFiles Additional configuration, as Key/Value pairs, that will be mounted as files with the /config 71 | directory on the pod. The key should be the file name and the value should be its content. 72 | type: object 73 | env: 74 | description: Env Environment Variables to set in the benthos pipeline 75 | pod. 76 | items: 77 | description: EnvVar represents an environment variable present in 78 | a Container. 79 | properties: 80 | name: 81 | description: Name of the environment variable. Must be a C_IDENTIFIER. 82 | type: string 83 | value: 84 | description: |- 85 | Variable references $(VAR_NAME) are expanded 86 | using the previously defined environment variables in the container and 87 | any service environment variables. If a variable cannot be resolved, 88 | the reference in the input string will be unchanged. Double $$ are reduced 89 | to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. 90 | "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". 91 | Escaped references will never be expanded, regardless of whether the variable 92 | exists or not. 93 | Defaults to "". 94 | type: string 95 | valueFrom: 96 | description: Source for the environment variable's value. Cannot 97 | be used if value is not empty. 98 | properties: 99 | configMapKeyRef: 100 | description: Selects a key of a ConfigMap. 101 | properties: 102 | key: 103 | description: The key to select. 104 | type: string 105 | name: 106 | description: |- 107 | Name of the referent. 108 | More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names 109 | TODO: Add other useful fields. apiVersion, kind, uid? 110 | type: string 111 | optional: 112 | description: Specify whether the ConfigMap or its key 113 | must be defined 114 | type: boolean 115 | required: 116 | - key 117 | type: object 118 | x-kubernetes-map-type: atomic 119 | fieldRef: 120 | description: |- 121 | Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, 122 | spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. 123 | properties: 124 | apiVersion: 125 | description: Version of the schema the FieldPath is 126 | written in terms of, defaults to "v1". 127 | type: string 128 | fieldPath: 129 | description: Path of the field to select in the specified 130 | API version. 131 | type: string 132 | required: 133 | - fieldPath 134 | type: object 135 | x-kubernetes-map-type: atomic 136 | resourceFieldRef: 137 | description: |- 138 | Selects a resource of the container: only resources limits and requests 139 | (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. 140 | properties: 141 | containerName: 142 | description: 'Container name: required for volumes, 143 | optional for env vars' 144 | type: string 145 | divisor: 146 | anyOf: 147 | - type: integer 148 | - type: string 149 | description: Specifies the output format of the exposed 150 | resources, defaults to "1" 151 | pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ 152 | x-kubernetes-int-or-string: true 153 | resource: 154 | description: 'Required: resource to select' 155 | type: string 156 | required: 157 | - resource 158 | type: object 159 | x-kubernetes-map-type: atomic 160 | secretKeyRef: 161 | description: Selects a key of a secret in the pod's namespace 162 | properties: 163 | key: 164 | description: The key of the secret to select from. Must 165 | be a valid secret key. 166 | type: string 167 | name: 168 | description: |- 169 | Name of the referent. 170 | More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names 171 | TODO: Add other useful fields. apiVersion, kind, uid? 172 | type: string 173 | optional: 174 | description: Specify whether the Secret or its key must 175 | be defined 176 | type: boolean 177 | required: 178 | - key 179 | type: object 180 | x-kubernetes-map-type: atomic 181 | type: object 182 | required: 183 | - name 184 | type: object 185 | type: array 186 | image: 187 | description: Image defines the image and tag to use for the Benthos 188 | deployment. 189 | type: string 190 | imagePullSecret: 191 | description: |- 192 | ImagePullSecret is an optional reference to a secret in the same namespace to use for pulling the image used by 193 | the benthos Pod. Similar to ImagePullSecrets, see v1.PodSpec#ImagePullSecrets. 194 | type: string 195 | replicas: 196 | description: Replicas defines the amount of replicas to create for 197 | the Benthos deployment. 198 | format: int32 199 | type: integer 200 | type: object 201 | status: 202 | description: PipelineStatus defines the observed state of Pipeline 203 | properties: 204 | availableReplicas: 205 | description: AvailableReplicas is the amount of pods available from 206 | the deployment. 207 | format: int32 208 | type: integer 209 | phase: 210 | type: string 211 | ready: 212 | type: boolean 213 | type: object 214 | type: object 215 | served: true 216 | storage: true 217 | subresources: 218 | status: {} 219 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/captain.benthos.dev_pipelines.yaml 6 | #+kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patches: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | #- path: patches/webhook_in_pipelines.yaml 12 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | #- path: patches/cainjection_in_pipelines.yaml 17 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 18 | 19 | # the following config is for teaching kustomize how to do kustomization for CRDs. 20 | configurations: 21 | - kustomizeconfig.yaml 22 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_benthospipelines.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME 7 | name: pipelines.captain.benthos.dev 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_benthospipelines.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: pipelines.captain.benthos.dev 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: benthos-captain-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: benthos-captain- 10 | 11 | resources: 12 | - ../crd 13 | - ../rbac 14 | - ../manager 15 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: namespace 7 | app.kubernetes.io/instance: system 8 | app.kubernetes.io/component: manager 9 | app.kubernetes.io/created-by: benthos-captain 10 | app.kubernetes.io/part-of: benthos-captain 11 | app.kubernetes.io/managed-by: kustomize 12 | name: system 13 | --- 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: controller-manager 18 | namespace: system 19 | labels: 20 | control-plane: controller-manager 21 | app.kubernetes.io/name: deployment 22 | app.kubernetes.io/instance: controller-manager 23 | app.kubernetes.io/component: manager 24 | app.kubernetes.io/created-by: benthos-captain 25 | app.kubernetes.io/part-of: benthos-captain 26 | app.kubernetes.io/managed-by: kustomize 27 | spec: 28 | selector: 29 | matchLabels: 30 | control-plane: controller-manager 31 | replicas: 1 32 | template: 33 | metadata: 34 | annotations: 35 | kubectl.kubernetes.io/default-container: manager 36 | labels: 37 | control-plane: controller-manager 38 | spec: 39 | affinity: 40 | nodeAffinity: 41 | requiredDuringSchedulingIgnoredDuringExecution: 42 | nodeSelectorTerms: 43 | - matchExpressions: 44 | - key: kubernetes.io/arch 45 | operator: In 46 | values: 47 | - amd64 48 | - arm64 49 | - key: kubernetes.io/os 50 | operator: In 51 | values: 52 | - linux 53 | securityContext: 54 | runAsNonRoot: true 55 | # TODO(user): For common cases that do not require escalating privileges 56 | # it is recommended to ensure that all your Pods/Containers are restrictive. 57 | # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted 58 | # Please uncomment the following code if your project does NOT have to work on old Kubernetes 59 | # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). 60 | # seccompProfile: 61 | # type: RuntimeDefault 62 | containers: 63 | - command: 64 | - /manager 65 | args: 66 | - --leader-elect 67 | - -zap-devel 68 | image: ghcr.io/benthosdev/benthos-captain:latest 69 | name: manager 70 | ports: 71 | - containerPort: 8080 72 | name: metrics 73 | protocol: TCP 74 | securityContext: 75 | allowPrivilegeEscalation: false 76 | capabilities: 77 | drop: 78 | - "ALL" 79 | livenessProbe: 80 | httpGet: 81 | path: /healthz 82 | port: 8081 83 | initialDelaySeconds: 15 84 | periodSeconds: 20 85 | readinessProbe: 86 | httpGet: 87 | path: /readyz 88 | port: 8081 89 | initialDelaySeconds: 5 90 | periodSeconds: 10 91 | # TODO(user): Configure the resources accordingly based on the project requirements. 92 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 93 | resources: 94 | limits: 95 | cpu: 500m 96 | memory: 128Mi 97 | requests: 98 | cpu: 10m 99 | memory: 64Mi 100 | serviceAccountName: controller-manager 101 | terminationGracePeriodSeconds: 10 102 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | app.kubernetes.io/name: servicemonitor 9 | app.kubernetes.io/instance: controller-manager-metrics-monitor 10 | app.kubernetes.io/component: metrics 11 | app.kubernetes.io/created-by: benthos-captain 12 | app.kubernetes.io/part-of: benthos-captain 13 | app.kubernetes.io/managed-by: kustomize 14 | name: controller-manager-metrics-monitor 15 | namespace: system 16 | spec: 17 | endpoints: 18 | - path: /metrics 19 | port: https 20 | scheme: https 21 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 22 | tlsConfig: 23 | insecureSkipVerify: true 24 | selector: 25 | matchLabels: 26 | control-plane: controller-manager 27 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: metrics-reader 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: benthos-captain 9 | app.kubernetes.io/part-of: benthos-captain 10 | app.kubernetes.io/managed-by: kustomize 11 | name: metrics-reader 12 | rules: 13 | - nonResourceURLs: 14 | - "/metrics" 15 | verbs: 16 | - get 17 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: proxy-role 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: benthos-captain 9 | app.kubernetes.io/part-of: benthos-captain 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-role 12 | rules: 13 | - apiGroups: 14 | - authentication.k8s.io 15 | resources: 16 | - tokenreviews 17 | verbs: 18 | - create 19 | - apiGroups: 20 | - authorization.k8s.io 21 | resources: 22 | - subjectaccessreviews 23 | verbs: 24 | - create 25 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: proxy-rolebinding 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: benthos-captain 9 | app.kubernetes.io/part-of: benthos-captain 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: proxy-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: service 7 | app.kubernetes.io/instance: controller-manager-metrics-service 8 | app.kubernetes.io/component: kube-rbac-proxy 9 | app.kubernetes.io/created-by: benthos-captain 10 | app.kubernetes.io/part-of: benthos-captain 11 | app.kubernetes.io/managed-by: kustomize 12 | name: controller-manager-metrics-service 13 | namespace: system 14 | spec: 15 | ports: 16 | - name: https 17 | port: 8443 18 | protocol: TCP 19 | targetPort: https 20 | selector: 21 | control-plane: controller-manager 22 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: role 7 | app.kubernetes.io/instance: leader-election-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: benthos-captain 10 | app.kubernetes.io/part-of: benthos-captain 11 | app.kubernetes.io/managed-by: kustomize 12 | name: leader-election-role 13 | rules: 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - configmaps 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - create 23 | - update 24 | - patch 25 | - delete 26 | - apiGroups: 27 | - coordination.k8s.io 28 | resources: 29 | - leases 30 | verbs: 31 | - get 32 | - list 33 | - watch 34 | - create 35 | - update 36 | - patch 37 | - delete 38 | - apiGroups: 39 | - "" 40 | resources: 41 | - events 42 | verbs: 43 | - create 44 | - patch 45 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: rolebinding 6 | app.kubernetes.io/instance: leader-election-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: benthos-captain 9 | app.kubernetes.io/part-of: benthos-captain 10 | app.kubernetes.io/managed-by: kustomize 11 | name: leader-election-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: leader-election-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/pipeline_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit pipelines. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: pipeline-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: benthos-captain 10 | app.kubernetes.io/part-of: benthos-captain 11 | app.kubernetes.io/managed-by: kustomize 12 | name: pipeline-editor-role 13 | rules: 14 | - apiGroups: 15 | - captain.benthos.dev 16 | resources: 17 | - pipelines 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - captain.benthos.dev 28 | resources: 29 | - pipelines/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/pipeline_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view pipelines. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: pipeline-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: benthos-captain 10 | app.kubernetes.io/part-of: benthos-captain 11 | app.kubernetes.io/managed-by: kustomize 12 | name: pipeline-viewer-role 13 | rules: 14 | - apiGroups: 15 | - captain.benthos.dev 16 | resources: 17 | - pipelines 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - captain.benthos.dev 24 | resources: 25 | - pipelines/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - apps 21 | resources: 22 | - deployments 23 | verbs: 24 | - create 25 | - delete 26 | - get 27 | - list 28 | - patch 29 | - update 30 | - watch 31 | - apiGroups: 32 | - apps 33 | resources: 34 | - deployments/status 35 | verbs: 36 | - get 37 | - apiGroups: 38 | - captain.benthos.dev 39 | resources: 40 | - pipelines 41 | verbs: 42 | - create 43 | - delete 44 | - get 45 | - list 46 | - patch 47 | - update 48 | - watch 49 | - apiGroups: 50 | - captain.benthos.dev 51 | resources: 52 | - pipelines/finalizers 53 | verbs: 54 | - update 55 | - apiGroups: 56 | - captain.benthos.dev 57 | resources: 58 | - pipelines/status 59 | verbs: 60 | - get 61 | - patch 62 | - update 63 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: manager-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: benthos-captain 9 | app.kubernetes.io/part-of: benthos-captain 10 | app.kubernetes.io/managed-by: kustomize 11 | name: manager-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: manager-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: serviceaccount 6 | app.kubernetes.io/instance: controller-manager-sa 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: benthos-captain 9 | app.kubernetes.io/part-of: benthos-captain 10 | app.kubernetes.io/managed-by: kustomize 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/samples/captain_v1alpha1_pipeline.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: captain.benthos.dev/v1alpha1 2 | kind: Pipeline 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: pipeline 6 | app.kubernetes.io/instance: pipeline-sample 7 | app.kubernetes.io/part-of: benthos-captain 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: benthos-captain 10 | name: pipeline-sample 11 | spec: 12 | replicas: 1 13 | config: 14 | input: 15 | generate: 16 | mapping: | 17 | let favorite_animal = env("FAVORITE_ANIMAL") 18 | root = match $favorite_animal { 19 | "cat" => file("/config/cat.txt") 20 | "dog" => file("/config/dog.txt") 21 | _ => file("/config/dog.txt") 22 | } 23 | interval: 5s 24 | count: 0 25 | 26 | pipeline: 27 | processors: 28 | - mapping: root = content().uppercase() 29 | 30 | output: 31 | stdout: {} 32 | 33 | configFiles: 34 | cat.txt: | 35 | meow 36 | dog.txt: | 37 | woof 38 | 39 | env: 40 | - name: FAVORITE_ANIMAL 41 | value: cat 42 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples of your project ## 2 | resources: 3 | - captain_v1alpha1_pipeline.yaml 4 | #+kubebuilder:scaffold:manifestskustomizesamples 5 | -------------------------------------------------------------------------------- /docs/developer-guide.md: -------------------------------------------------------------------------------- 1 | # Developer Guide 2 | 3 | Benthos Operator is a Kubernetes Operator built with [kubebuilder](https://github.com/kubernetes-sigs/kubebuilder). There are ways to get a local development environment up and running. The easiest is using [Tilt](https://tilt.dev/). You can still work locally using a makefile and manually build the image if you'd prefer. 4 | 5 | ## Provision a Cluster 6 | 7 | Before we do anything, we'll need a local cluster to work on. The easiest way to create one is to use [kind](https://kind.sigs.k8s.io/docs/user/quick-start/). 8 | 9 | ```bash 10 | kind create cluster 11 | ``` 12 | 13 | **Note: if you already manage Kubernetes clusters you might want to check your context before proceeding.** 14 | 15 | ## Using Tilt 16 | 17 | Once you've installed Tilt, you'll simply be able to run `tilt up` and it will do everything for you, it will also reload the deployment and build the docker image when you make any code changes. 18 | 19 | ```bash 20 | tilt up 21 | ``` 22 | 23 | ## Manually 24 | 25 | If you don't want to use Tilt, you can use the makefile targets generated by kubebuilder. 26 | 27 | ### Running on the cluster 28 | 29 | 1. Install Instances of Custom Resources: 30 | 31 | ```sh 32 | kubectl apply -f config/samples/ 33 | ``` 34 | 35 | 2. Build and push your image to the location specified by `IMG`: 36 | 37 | ```sh 38 | make docker-build docker-push IMG=/benthos-captain:tag 39 | ``` 40 | 41 | 3. Deploy the controller to the cluster with the image specified by `IMG`: 42 | 43 | ```sh 44 | make deploy IMG=/benthos-captain:tag 45 | ``` 46 | 47 | ### Uninstall CRDs 48 | 49 | To delete the CRDs from the cluster: 50 | 51 | ```sh 52 | make uninstall 53 | ``` 54 | 55 | ### Undeploy controller 56 | 57 | UnDeploy the controller from the cluster: 58 | 59 | ```sh 60 | make undeploy 61 | ``` 62 | 63 | ## Contributing 64 | 65 | // TODO(user): Add detailed information on how you would like others to contribute to this project 66 | 67 | ### How it works 68 | 69 | This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/). 70 | 71 | It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/), 72 | which provide a reconcile function responsible for synchronizing resources until the desired state is reached on the cluster. 73 | 74 | ### Test It Out 75 | 76 | 1. Install the CRDs into the cluster: 77 | 78 | ```sh 79 | make install 80 | ``` 81 | 82 | 2. Run your controller (this will run in the foreground, so switch to a new terminal if you want to leave it running): 83 | 84 | ```sh 85 | make run 86 | ``` 87 | 88 | **NOTE:** You can also run this in one step by running: `make install run` 89 | 90 | ### Modifying the API definitions 91 | 92 | If you are editing the API definitions, generate the manifests such as CRs or CRDs using: 93 | 94 | ```sh 95 | make manifests 96 | ``` 97 | 98 | **NOTE:** Run `make --help` for more information on all potential `make` targets 99 | 100 | More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html) 101 | -------------------------------------------------------------------------------- /docs/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benthosdev/benthos-captain/c884e63ddbf92f4976b3cbfac1a8da8bc9c5815e/docs/images/icon.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/benthosdev/benthos-captain 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/go-logr/logr v1.4.1 7 | github.com/onsi/ginkgo/v2 v2.14.0 8 | github.com/onsi/gomega v1.30.0 9 | github.com/pkg/errors v0.9.1 10 | k8s.io/api v0.29.4 11 | k8s.io/apiextensions-apiserver v0.29.4 12 | k8s.io/apimachinery v0.29.4 13 | k8s.io/client-go v0.29.4 14 | sigs.k8s.io/controller-runtime v0.17.3 15 | ) 16 | 17 | require ( 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/emicklei/go-restful/v3 v3.12.0 // indirect 22 | github.com/evanphx/json-patch/v5 v5.9.0 // indirect 23 | github.com/fsnotify/fsnotify v1.7.0 // indirect 24 | github.com/go-logr/zapr v1.3.0 // indirect 25 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 26 | github.com/go-openapi/jsonreference v0.21.0 // indirect 27 | github.com/go-openapi/swag v0.23.0 // indirect 28 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 29 | github.com/gogo/protobuf v1.3.2 // indirect 30 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 31 | github.com/golang/protobuf v1.5.4 // indirect 32 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect 33 | github.com/google/go-cmp v0.6.0 // indirect 34 | github.com/google/gofuzz v1.2.0 // indirect 35 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect 36 | github.com/google/uuid v1.6.0 // indirect 37 | github.com/imdario/mergo v0.3.16 // indirect 38 | github.com/josharian/intern v1.0.0 // indirect 39 | github.com/json-iterator/go v1.1.12 // indirect 40 | github.com/mailru/easyjson v0.7.7 // indirect 41 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 42 | github.com/modern-go/reflect2 v1.0.2 // indirect 43 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 44 | github.com/prometheus/client_golang v1.19.0 // indirect 45 | github.com/prometheus/client_model v0.6.1 // indirect 46 | github.com/prometheus/common v0.52.3 // indirect 47 | github.com/prometheus/procfs v0.13.0 // indirect 48 | github.com/spf13/pflag v1.0.5 // indirect 49 | go.uber.org/multierr v1.11.0 // indirect 50 | go.uber.org/zap v1.27.0 // indirect 51 | golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect 52 | golang.org/x/net v0.24.0 // indirect 53 | golang.org/x/oauth2 v0.19.0 // indirect 54 | golang.org/x/sys v0.19.0 // indirect 55 | golang.org/x/term v0.19.0 // indirect 56 | golang.org/x/text v0.14.0 // indirect 57 | golang.org/x/time v0.5.0 // indirect 58 | golang.org/x/tools v0.20.0 // indirect 59 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 60 | google.golang.org/protobuf v1.33.0 // indirect 61 | gopkg.in/inf.v0 v0.9.1 // indirect 62 | gopkg.in/yaml.v2 v2.4.0 // indirect 63 | gopkg.in/yaml.v3 v3.0.1 // indirect 64 | k8s.io/component-base v0.29.4 // indirect 65 | k8s.io/klog/v2 v2.120.1 // indirect 66 | k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 // indirect 67 | k8s.io/utils v0.0.0-20240310230437-4693a0247e57 // indirect 68 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 69 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 70 | sigs.k8s.io/yaml v1.4.0 // indirect 71 | ) 72 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 4 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 6 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 7 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= 12 | github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 13 | github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= 14 | github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 15 | github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= 16 | github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= 17 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 18 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 19 | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 20 | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 21 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 22 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 23 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 24 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 25 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 26 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 27 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 28 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 29 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 30 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 31 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 32 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 33 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 34 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 35 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 36 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 37 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= 38 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= 39 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 40 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 41 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 42 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 43 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 44 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 45 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= 46 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 47 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 48 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 49 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 50 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= 51 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 52 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 53 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 54 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 55 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 56 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 57 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 58 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 59 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 60 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 61 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 62 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 63 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 64 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 65 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 66 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 67 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 68 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 69 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 70 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 71 | github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= 72 | github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= 73 | github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= 74 | github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= 75 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 76 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 77 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 78 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 79 | github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= 80 | github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= 81 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 82 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 83 | github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA= 84 | github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= 85 | github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= 86 | github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= 87 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 88 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 89 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 90 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 91 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 92 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 93 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 94 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 95 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 96 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 97 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 98 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 99 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 100 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 101 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 102 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 103 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 104 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 105 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 106 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 107 | golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= 108 | golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= 109 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 110 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 111 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 112 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 113 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 114 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 115 | golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= 116 | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 117 | golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= 118 | golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= 119 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 120 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 121 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 122 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 123 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 124 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 125 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 126 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 127 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 128 | golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= 129 | golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= 130 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 131 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 132 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 133 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 134 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 135 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 136 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 137 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 138 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 139 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 140 | golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= 141 | golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= 142 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 143 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 144 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 145 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 146 | gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= 147 | gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 148 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 149 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 150 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 151 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 152 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 153 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 154 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 155 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 156 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 157 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 158 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 159 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 160 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 161 | k8s.io/api v0.29.4 h1:WEnF/XdxuCxdG3ayHNRR8yH3cI1B/llkWBma6bq4R3w= 162 | k8s.io/api v0.29.4/go.mod h1:DetSv0t4FBTcEpfA84NJV3g9a7+rSzlUHk5ADAYHUv0= 163 | k8s.io/apiextensions-apiserver v0.29.4 h1:M7hbuHU/ckbibR7yPbe6DyNWgTFKNmZDbdZKD8q1Smk= 164 | k8s.io/apiextensions-apiserver v0.29.4/go.mod h1:TTDC9fB+0kHY2rogf5hgBR03KBKCwED+GHUsXGpR7SM= 165 | k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q= 166 | k8s.io/apimachinery v0.29.4/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= 167 | k8s.io/client-go v0.29.4 h1:79ytIedxVfyXV8rpH3jCBW0u+un0fxHDwX5F9K8dPR8= 168 | k8s.io/client-go v0.29.4/go.mod h1:kC1thZQ4zQWYwldsfI088BbK6RkxK+aF5ebV8y9Q4tk= 169 | k8s.io/component-base v0.29.4 h1:xeKzuuHI/1tjleu5jycDAcYbhAxeGHCQBZUY2eRIkOo= 170 | k8s.io/component-base v0.29.4/go.mod h1:pYjt+oEZP9gtmwSikwAJgfSBikqKX2gOqRat0QjmQt0= 171 | k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= 172 | k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 173 | k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 h1:SbdLaI6mM6ffDSJCadEaD4IkuPzepLDGlkd2xV0t1uA= 174 | k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= 175 | k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY= 176 | k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 177 | sigs.k8s.io/controller-runtime v0.17.3 h1:65QmN7r3FWgTxDMz9fvGnO1kbf2nu+acg9p2R9oYYYk= 178 | sigs.k8s.io/controller-runtime v0.17.3/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY= 179 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 180 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 181 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 182 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 183 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 184 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 185 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benthosdev/benthos-captain/c884e63ddbf92f4976b3cbfac1a8da8bc9c5815e/hack/boilerplate.go.txt -------------------------------------------------------------------------------- /internal/controller/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | "sigs.k8s.io/controller-runtime/pkg/metrics" 6 | ) 7 | 8 | var ( 9 | PipelineReconciles = prometheus.NewCounterVec(prometheus.CounterOpts{ 10 | Namespace: "benthos_captain", 11 | Name: "reconciles", 12 | Help: "reconciles per Benthos pipeline", 13 | }, []string{"pipeline"}) 14 | 15 | PipelineFailedReconciles = prometheus.NewCounterVec(prometheus.CounterOpts{ 16 | Namespace: "benthos_captain", 17 | Name: "failed_reconciles", 18 | Help: "failed reconciles per Benthos pipeline", 19 | }, []string{"pipeline"}) 20 | ) 21 | 22 | func init() { 23 | metrics.Registry.MustRegister(PipelineReconciles, PipelineFailedReconciles) 24 | } 25 | -------------------------------------------------------------------------------- /internal/controller/pipeline_controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/go-logr/logr" 9 | "github.com/pkg/errors" 10 | appsv1 "k8s.io/api/apps/v1" 11 | corev1 "k8s.io/api/core/v1" 12 | apierrors "k8s.io/apimachinery/pkg/api/errors" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/apimachinery/pkg/runtime" 15 | "k8s.io/apimachinery/pkg/types" 16 | ctrl "sigs.k8s.io/controller-runtime" 17 | "sigs.k8s.io/controller-runtime/pkg/client" 18 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 19 | "sigs.k8s.io/controller-runtime/pkg/log" 20 | 21 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 22 | 23 | captainv1 "github.com/benthosdev/benthos-captain/api/v1alpha1" 24 | "github.com/benthosdev/benthos-captain/internal/controller/metrics" 25 | "github.com/benthosdev/benthos-captain/internal/pkg/resource" 26 | ) 27 | 28 | // PipelineReconciler reconciles a Pipeline object 29 | type PipelineReconciler struct { 30 | client.Client 31 | Scheme *runtime.Scheme 32 | } 33 | 34 | type PipelineScope struct { 35 | Log *logr.Logger 36 | Ctx context.Context 37 | Client client.Client 38 | Pipeline *captainv1.Pipeline 39 | } 40 | 41 | const ( 42 | StatusRunning = "Running" 43 | StatusProvisioning = "Provisioning" 44 | StatusUpdating = "Updating" 45 | StatusScalingUp = "Scaling up" 46 | StatusScalingDown = "Scaling down" 47 | StatusFailed = "Deployment failed" 48 | ) 49 | 50 | // +kubebuilder:rbac:groups=captain.benthos.dev,resources=pipelines,verbs=get;list;watch;create;update;patch;delete 51 | // +kubebuilder:rbac:groups=captain.benthos.dev,resources=pipelines/status,verbs=get;update;patch 52 | // +kubebuilder:rbac:groups=captain.benthos.dev,resources=pipelines/finalizers,verbs=update 53 | // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete 54 | // +kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get 55 | // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete 56 | 57 | // Reconcile is the main reconcile loop for the Benthos Pipeline 58 | func (r *PipelineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 59 | log := log.Log.WithName("pipeline") 60 | 61 | // fetch Pipeline 62 | pipeline := &captainv1.Pipeline{} 63 | err := r.Get(ctx, req.NamespacedName, pipeline) 64 | if err != nil { 65 | if apierrors.IsNotFound(err) { 66 | // Request object not found, could have been deleted after reconcile request. 67 | // Return and don't requeue 68 | log.Info("pipeline resource not found") 69 | return reconcile.Result{}, nil 70 | } 71 | // Error reading the object - requeue the request. 72 | log.Error(err, "failed to get pipeline") 73 | return reconcile.Result{}, err 74 | } 75 | 76 | metrics.PipelineReconciles.WithLabelValues(pipeline.Name).Inc() 77 | 78 | scope := &PipelineScope{ 79 | Log: &log, 80 | Ctx: ctx, 81 | Client: r.Client, 82 | Pipeline: pipeline, 83 | } 84 | 85 | // handle pipeline deletion 86 | if !pipeline.ObjectMeta.DeletionTimestamp.IsZero() { 87 | return r.reconcileDelete(scope) 88 | } 89 | 90 | if !controllerutil.ContainsFinalizer(pipeline, captainv1.PipelineFinalizer) { 91 | log.Info("adding Finalizer for pipeline") 92 | if ok := controllerutil.AddFinalizer(pipeline, captainv1.PipelineFinalizer); !ok { 93 | log.Error(nil, "failed to add finalizer to pipeline, requeing") 94 | return ctrl.Result{Requeue: true}, nil 95 | } 96 | 97 | // add finalizer and update 98 | // the update will trigger a new reconciliation 99 | return ctrl.Result{}, r.Update(ctx, pipeline) 100 | } 101 | // handle pipeline reconcile 102 | return r.reconcileNormal(scope) 103 | } 104 | 105 | // reconcileNormal handles normal reconciles 106 | func (r *PipelineReconciler) reconcileNormal(scope *PipelineScope) (ctrl.Result, error) { 107 | // check if the Pipeline has already created a deployment 108 | _, err := r.createOrUpdatePipeline(scope) 109 | if err != nil { 110 | return reconcile.Result{}, err 111 | } 112 | 113 | return reconcile.Result{}, nil 114 | } 115 | 116 | // reconcileNormal handles a deletion during the reconcile 117 | func (r *PipelineReconciler) reconcileDelete(scope *PipelineScope) (ctrl.Result, error) { 118 | // remove finalizer to allow the resource to be deleted 119 | if controllerutil.RemoveFinalizer(scope.Pipeline, captainv1.PipelineFinalizer) { 120 | return reconcile.Result{}, r.Update(scope.Ctx, scope.Pipeline) 121 | } 122 | return reconcile.Result{}, nil 123 | } 124 | 125 | // SetupWithManager sets up the controller with the Manager. 126 | func (r *PipelineReconciler) SetupWithManager(mgr ctrl.Manager) error { 127 | return ctrl.NewControllerManagedBy(mgr). 128 | For(&captainv1.Pipeline{}). 129 | Owns(&appsv1.Deployment{}). 130 | Owns(&corev1.ConfigMap{}). 131 | Complete(r) 132 | } 133 | 134 | // createOrUpdatePipeline orchestrates the updating and creation of the Benthos deployment 135 | func (r *PipelineReconciler) createOrUpdatePipeline(scope *PipelineScope) (ctrl.Result, error) { 136 | pipeline := scope.Pipeline 137 | 138 | // create Benthos ConfigMap 139 | _, err := r.createOrPatchConfigMap(scope) 140 | if err != nil { 141 | return reconcile.Result{}, err 142 | } 143 | 144 | // create Benthos Deployment 145 | _, err = r.createOrPatchDeployment(scope) 146 | if err != nil { 147 | return reconcile.Result{}, err 148 | } 149 | 150 | // Fetch deployment to get replicas 151 | deployment := &appsv1.Deployment{} 152 | err = r.Get(context.TODO(), types.NamespacedName{Name: pipeline.Name, Namespace: pipeline.Namespace}, deployment) 153 | if err != nil { 154 | return reconcile.Result{}, errors.Wrapf(err, "failed to get deployment %s", pipeline.Name) 155 | } 156 | 157 | // set pipeline phase if not set 158 | if pipeline.Status.Phase == "" { 159 | scope.status(false, StatusProvisioning) 160 | } 161 | 162 | // Update available replicas on the status 163 | scope.Log.Info("Found deployment", "status", deployment.Status) 164 | return r.setPipelineStatus(scope, deployment) 165 | } 166 | 167 | // setPipelineStatus sets the latest status of the deployment 168 | func (r *PipelineReconciler) setPipelineStatus(scope *PipelineScope, deployment *appsv1.Deployment) (res ctrl.Result, resErr error) { 169 | // defer applying the status change 170 | defer func() { 171 | scope.Log.Info("Setting Pipeline status.", "ready", scope.Pipeline.Status.Ready, "phase", scope.Pipeline.Status.Phase) 172 | 173 | err := r.Status().Update(context.Background(), scope.Pipeline) 174 | if err != nil { 175 | resErr = err 176 | } 177 | }() 178 | 179 | replicas := scope.Pipeline.Spec.Replicas 180 | status := deployment.Status 181 | 182 | // set available replicas on the pipeline 183 | scope.Pipeline.Status.AvailableReplicas = deployment.Status.AvailableReplicas 184 | 185 | available := getConditionStatus(status, appsv1.DeploymentAvailable) == corev1.ConditionTrue 186 | progressing := getConditionStatus(status, appsv1.DeploymentProgressing) == corev1.ConditionTrue 187 | 188 | // how long since deployment creation before reporting an error. 189 | failedDelay := time.Second * 30 190 | if !available && deployment.Status.UnavailableReplicas == replicas && time.Until(deployment.CreationTimestamp.Add(failedDelay)) > 0 { 191 | scope.status(false, StatusFailed) 192 | return reconcile.Result{}, errors.New("One or more pods failed to start. Check the logs of the pods.") 193 | } 194 | 195 | if progressing { 196 | if deployment.Status.UpdatedReplicas != replicas { 197 | scope.status(true, StatusUpdating) 198 | return reconcile.Result{}, nil 199 | } 200 | if deployment.Status.ReadyReplicas > replicas { 201 | scope.status(true, StatusScalingDown) 202 | return reconcile.Result{}, nil 203 | } 204 | if deployment.Status.ReadyReplicas < replicas { 205 | scope.status(true, StatusScalingUp) 206 | return reconcile.Result{}, nil 207 | } 208 | } 209 | 210 | if available { 211 | scope.status(true, StatusRunning) 212 | } 213 | return reconcile.Result{}, nil 214 | } 215 | 216 | func getConditionStatus(ds appsv1.DeploymentStatus, condition appsv1.DeploymentConditionType) corev1.ConditionStatus { 217 | for i := range ds.Conditions { 218 | c := ds.Conditions[i] 219 | if c.Type == condition { 220 | return c.Status 221 | } 222 | } 223 | return corev1.ConditionUnknown 224 | } 225 | 226 | // status is a wrapper for settings the pipeline status options 227 | func (ps *PipelineScope) status(ready bool, phase string) { 228 | ps.Pipeline.Status.Ready = ready 229 | ps.Pipeline.Status.Phase = phase 230 | } 231 | 232 | // createOrPatchDeployment updates a benthos deployment or creates it if it doesn't exist 233 | func (r *PipelineReconciler) createOrPatchDeployment(scope *PipelineScope) (ctrl.Result, error) { 234 | name := scope.Pipeline.Name 235 | namespace := scope.Pipeline.Namespace 236 | spec := scope.Pipeline.Spec 237 | 238 | scope.Log.Info("Creating deployment", "name", name) 239 | 240 | dp := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}} 241 | op, err := controllerutil.CreateOrPatch(scope.Ctx, scope.Client, dp, func() error { 242 | template := resource.NewDeployment(name, namespace, spec) 243 | 244 | // Deployment selector is immutable we only change this value if we're creating a new resource. 245 | if dp.CreationTimestamp.IsZero() { 246 | dp.Spec.Selector = &metav1.LabelSelector{ 247 | MatchLabels: template.Spec.Selector.MatchLabels, 248 | } 249 | dp.Spec.Template.ObjectMeta = template.Spec.Template.ObjectMeta 250 | } 251 | 252 | // Update the template, ignore metadata 253 | dp.Spec.Template.Spec = template.Spec.Template.Spec 254 | dp.Spec.Replicas = template.Spec.Replicas 255 | 256 | err := controllerutil.SetControllerReference(scope.Pipeline, dp, r.Scheme) 257 | if err != nil { 258 | return errors.Wrapf(err, "Failed to set controller reference on deployment %s", name) 259 | } 260 | 261 | return nil 262 | }) 263 | if err != nil { 264 | metrics.PipelineFailedReconciles.WithLabelValues(name).Inc() 265 | return reconcile.Result{}, errors.Wrapf(err, "Failed to reconcile deployment %s", name) 266 | } 267 | scope.Log.Info("Succesfully reconciled deployment", "name", name, "operation", op) 268 | return reconcile.Result{}, nil 269 | } 270 | 271 | // createOrPatchConfigMap updates a config map or creates it if it doesn't exist 272 | func (r *PipelineReconciler) createOrPatchConfigMap(scope *PipelineScope) (ctrl.Result, error) { 273 | name := scope.Pipeline.Name 274 | 275 | scope.Log.Info("Creating config map", "name", name) 276 | 277 | sc := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "benthos-" + name, Namespace: scope.Pipeline.Namespace}} 278 | op, err := controllerutil.CreateOrPatch(scope.Ctx, scope.Client, sc, func() error { 279 | sc.Data = map[string]string{ 280 | "benthos.yaml": string(scope.Pipeline.Spec.Config.Raw), 281 | } 282 | for file, config := range scope.Pipeline.Spec.ConfigFiles { 283 | sc.Data[file] = config 284 | } 285 | err := controllerutil.SetControllerReference(scope.Pipeline, sc, r.Scheme) 286 | if err != nil { 287 | return errors.Wrapf(err, "Failed to set controller reference on configmap %s", name) 288 | } 289 | return nil 290 | }) 291 | if err != nil { 292 | return reconcile.Result{}, errors.Wrapf(err, "Failed to reconcile config map %s", name) 293 | } 294 | 295 | scope.Log.Info("Successfully reconciled config map", "name", name, "operation", op) 296 | 297 | // rollout the deployment if the configmap changes 298 | if op == controllerutil.OperationResultUpdated { 299 | return r.rolloutDeployment(scope) 300 | } 301 | 302 | return reconcile.Result{}, nil 303 | } 304 | 305 | // rolloutDeployment rolls out a new Benthos deployment 306 | func (r *PipelineReconciler) rolloutDeployment(scope *PipelineScope) (ctrl.Result, error) { 307 | name := scope.Pipeline.Name 308 | namespace := scope.Pipeline.Namespace 309 | 310 | scope.Log.Info("Rolling out deployment.", "name", name) 311 | 312 | dp := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}} 313 | body := fmt.Sprintf(`{"spec":{"template":{"metadata":{"annotations":{"%s":"%s"}}}}}`, "captain.benthos.dev/restartedAt", time.Now().Format(time.RFC3339)) 314 | err := r.Patch(scope.Ctx, dp, client.RawPatch(types.StrategicMergePatchType, []byte(body))) 315 | if err != nil { 316 | return reconcile.Result{}, errors.Wrapf(err, "Failed to rollout deployment %s", name) 317 | } 318 | 319 | scope.Log.Info("Deployment rollout success", "name", name) 320 | return reconcile.Result{}, nil 321 | } 322 | -------------------------------------------------------------------------------- /internal/controller/suite_test.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | 10 | "k8s.io/client-go/kubernetes/scheme" 11 | "k8s.io/client-go/rest" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | "sigs.k8s.io/controller-runtime/pkg/envtest" 14 | logf "sigs.k8s.io/controller-runtime/pkg/log" 15 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 16 | 17 | captainv1alpha1 "github.com/benthosdev/benthos-captain/api/v1alpha1" 18 | //+kubebuilder:scaffold:imports 19 | ) 20 | 21 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 22 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 23 | 24 | var cfg *rest.Config 25 | var k8sClient client.Client 26 | var testEnv *envtest.Environment 27 | 28 | func TestControllers(t *testing.T) { 29 | RegisterFailHandler(Fail) 30 | 31 | RunSpecs(t, "Controller Suite") 32 | } 33 | 34 | var _ = BeforeSuite(func() { 35 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 36 | 37 | By("bootstrapping test environment") 38 | testEnv = &envtest.Environment{ 39 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, 40 | ErrorIfCRDPathMissing: true, 41 | } 42 | 43 | var err error 44 | // cfg is defined in this file globally. 45 | cfg, err = testEnv.Start() 46 | Expect(err).NotTo(HaveOccurred()) 47 | Expect(cfg).NotTo(BeNil()) 48 | 49 | err = captainv1alpha1.AddToScheme(scheme.Scheme) 50 | Expect(err).NotTo(HaveOccurred()) 51 | 52 | //+kubebuilder:scaffold:scheme 53 | 54 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 55 | Expect(err).NotTo(HaveOccurred()) 56 | Expect(k8sClient).NotTo(BeNil()) 57 | 58 | }) 59 | 60 | var _ = AfterSuite(func() { 61 | By("tearing down the test environment") 62 | err := testEnv.Stop() 63 | Expect(err).NotTo(HaveOccurred()) 64 | }) 65 | -------------------------------------------------------------------------------- /internal/pkg/resource/resource.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | captainv1 "github.com/benthosdev/benthos-captain/api/v1alpha1" 5 | appsv1 "k8s.io/api/apps/v1" 6 | corev1 "k8s.io/api/core/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | ) 9 | 10 | const DefaultImage = "jeffail/benthos:4.27" 11 | 12 | func NewDeployment(name string, namespace string, scope captainv1.PipelineSpec) *appsv1.Deployment { 13 | labels := map[string]string{ 14 | "app.kubernetes.io/name": "benthos", 15 | "app.kubernetes.io/instance": name, 16 | } 17 | 18 | image := DefaultImage 19 | if scope.Image != "" { 20 | image = scope.Image 21 | } 22 | 23 | podSpec := corev1.PodSpec{ 24 | Containers: []corev1.Container{{ 25 | Image: image, 26 | ImagePullPolicy: corev1.PullAlways, 27 | Name: "benthos", 28 | Ports: []corev1.ContainerPort{{ 29 | ContainerPort: 4195, 30 | Name: "http", 31 | }}, 32 | Args: []string{ 33 | "-c", 34 | "/config/benthos.yaml", 35 | }, 36 | VolumeMounts: []corev1.VolumeMount{ 37 | { 38 | Name: "config", 39 | MountPath: "/config", 40 | ReadOnly: true, 41 | }, 42 | }, 43 | Env: scope.Env, 44 | }}, 45 | Volumes: []corev1.Volume{ 46 | { 47 | Name: "config", 48 | VolumeSource: corev1.VolumeSource{ 49 | ConfigMap: &corev1.ConfigMapVolumeSource{ 50 | LocalObjectReference: corev1.LocalObjectReference{ 51 | Name: "benthos-" + name, 52 | }, 53 | }, 54 | }, 55 | }, 56 | }, 57 | } 58 | 59 | if scope.ImagePullSecret != "" { 60 | podSpec.ImagePullSecrets = []corev1.LocalObjectReference{ 61 | {Name: scope.ImagePullSecret}, 62 | } 63 | } 64 | 65 | return &appsv1.Deployment{ 66 | ObjectMeta: metav1.ObjectMeta{ 67 | Name: name, 68 | Namespace: namespace, 69 | }, 70 | Spec: appsv1.DeploymentSpec{ 71 | Replicas: &scope.Replicas, 72 | Selector: &metav1.LabelSelector{ 73 | MatchLabels: labels, 74 | }, 75 | Template: corev1.PodTemplateSpec{ 76 | ObjectMeta: metav1.ObjectMeta{ 77 | Labels: labels, 78 | }, 79 | Spec: podSpec, 80 | }, 81 | }, 82 | } 83 | } 84 | --------------------------------------------------------------------------------