Add initial version with help and admission controller (#1)

- It adds a helm chart that will be hosted in the Github pages URL of this repository
- an admission controller operator 
- Basic documentation for installing the operator, configuring CRDs and example pod configuration
This commit is contained in:
M. Essam
2025-01-29 22:44:09 +01:00
committed by GitHub
parent 50aa9ada37
commit 64091a6439
45 changed files with 4638 additions and 9 deletions
+3
View File
@@ -0,0 +1,3 @@
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
# Ignore build and test binaries.
bin/
+69
View File
@@ -0,0 +1,69 @@
name: Docker
on:
push:
tags:
- "v*"
branches:
- main
pull_request:
env:
GORELEASER_VER: "v2.3.2"
jobs:
release:
env:
flags: ""
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Parse semver string
id: semver_parser
uses: booxmedialtd/ws-action-parse-semver@v1
with:
input_string: ${{ (startsWith(github.ref, 'refs/tags/v') && github.ref) || 'refs/tags/v0.0.0' }}
version_extractor_regex: '\/v(.*)$'
- if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
run: echo "flags=--snapshot" >> $GITHUB_ENV
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # It is required for GoReleaser to work properly
# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Login to Docker hub
if: github.repository == github.event.pull_request.head.repo.full_name || !github.head_ref
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Install OS build dependencies
run: sudo apt update && sudo apt install -y -q gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu
- name: Install goversioninfo
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
version: ${{ env.GORELEASER_VER }}
args: release --clean ${{ env.flags }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+36
View File
@@ -0,0 +1,36 @@
name: Release Helm Chart and Carvel package
on:
push:
paths:
# update this file to trigger helm chart release
- 'helm/netbird-operator/Chart.yaml'
branches:
- main
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3.1.0
with:
fetch-depth: 0
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@v3.4
with:
version: v3.4.2
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.4.1
with:
charts_dir: helm
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
CR_RELEASE_NAME_TEMPLATE: "helm-v{{ .Version }}"
+23
View File
@@ -0,0 +1,23 @@
name: Lint
on:
push:
pull_request:
jobs:
lint:
name: Run on Ubuntu
runs-on: ubuntu-latest
steps:
- name: Clone the code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Run linter
uses: golangci/golangci-lint-action@v6
with:
version: v1.63.4
+68
View File
@@ -0,0 +1,68 @@
name: Test Chart
on:
push:
pull_request:
jobs:
test-e2e:
name: Run on Ubuntu
runs-on: ubuntu-latest
steps:
- name: Clone the code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Install the latest version of kind
run: |
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
- name: Verify kind installation
run: kind version
- name: Create kind cluster
run: kind create cluster
- name: Prepare operator
run: |
go mod tidy
make docker-build IMG=netbirdio/kubernetes-operator:debug
kind load docker-image netbirdio/kubernetes-operator:debug
- name: Install Helm
run: |
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
- name: Verify Helm installation
run: helm version
- name: Lint Helm Chart
run: |
helm lint ./helm/netbird-operator
- name: Install cert-manager via Helm
run: |
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true
- name: Wait for cert-manager to be ready
run: |
kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager
kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-cainjector
kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-webhook
- name: Install Helm chart for project
run: |
helm install test-chart --create-namespace --namespace netbird --set 'operator.image.tag=debug' ./helm/netbird-operator
- name: Check Helm release status
run: |
helm status test-chart --namespace netbird
+35
View File
@@ -0,0 +1,35 @@
name: E2E Tests
on:
push:
pull_request:
jobs:
test-e2e:
name: Run on Ubuntu
runs-on: ubuntu-latest
steps:
- name: Clone the code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Install the latest version of kind
run: |
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
- name: Verify kind installation
run: kind version
- name: Create kind cluster
run: kind create cluster
- name: Running Test e2e
run: |
go mod tidy
make test-e2e
+23
View File
@@ -0,0 +1,23 @@
name: Tests
on:
push:
pull_request:
jobs:
test:
name: Run on Ubuntu
runs-on: ubuntu-latest
steps:
- name: Clone the code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Running Tests
run: |
go mod tidy
make test
+11 -9
View File
@@ -1,12 +1,11 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins # Binaries for programs and plugins
*.exe *.exe
*.exe~ *.exe~
*.dll *.dll
*.so *.so
*.dylib *.dylib
bin/*
Dockerfile.cross
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test
@@ -14,12 +13,15 @@
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file # Go workspace file
go.work go.work
go.work.sum
# env file # Kubernetes Generated files - skip generated files, except for vendored files
.env !vendor/**/zz_generated.*
# editor and IDE paraphernalia
.idea
.vscode
*.swp
*.swo
*~
+47
View File
@@ -0,0 +1,47 @@
run:
timeout: 5m
allow-parallel-runners: true
issues:
# don't skip warning about doc comments
# don't exclude the default set of lint
exclude-use-default: false
# restore some of the defaults
# (fill in the rest as needed)
exclude-rules:
- path: "api/*"
linters:
- lll
- path: "internal/*"
linters:
- dupl
- lll
linters:
disable-all: true
enable:
- dupl
- errcheck
- copyloopvar
- ginkgolinter
- goconst
- gocyclo
- gofmt
- goimports
- gosimple
- govet
- ineffassign
- lll
- misspell
- nakedret
- prealloc
- revive
- staticcheck
- typecheck
- unconvert
- unparam
- unused
linters-settings:
revive:
rules:
- name: comment-spacings
+83
View File
@@ -0,0 +1,83 @@
version: 2
project_name: kubernetes-operator
builds:
- id: manager
dir: cmd
binary: manager
env: [CGO_ENABLED=0]
goos:
- linux
goarch:
- arm
- amd64
- arm64
ldflags:
- -s -w
mod_timestamp: "{{ .CommitTimestamp }}"
dockers:
- image_templates:
- netbirdio/kubernetes-operator:{{ .Version }}-amd64
ids:
- manager
goarch: amd64
dockerfile: Dockerfile.release
use: buildx
skip_push: false
build_flag_templates:
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=maintainer=dev@netbird.io"
- image_templates:
- netbirdio/kubernetes-operator:{{ .Version }}-arm64v8
ids:
- manager
goarch: arm64
dockerfile: Dockerfile.release
use: buildx
skip_push: false
build_flag_templates:
- "--platform=linux/arm64"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=maintainer=dev@netbird.io"
- image_templates:
- netbirdio/kubernetes-operator:{{ .Version }}-arm
ids:
- manager
goarch: arm
goarm: 6
dockerfile: Dockerfile.release
use: buildx
skip_push: false
build_flag_templates:
- "--platform=linux/arm"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=maintainer=dev@netbird.io"
docker_manifests:
- name_template: netbirdio/kubernetes-operator:{{ .Version }}
skip_push: false
image_templates:
- netbirdio/kubernetes-operator:{{ .Version }}-arm64v8
- netbirdio/kubernetes-operator:{{ .Version }}-arm
- netbirdio/kubernetes-operator:{{ .Version }}-amd64
- name_template: netbirdio/kubernetes-operator:latest
image_templates:
- netbirdio/kubernetes-operator:{{ .Version }}-arm64v8
- netbirdio/kubernetes-operator:{{ .Version }}-arm
- netbirdio/kubernetes-operator:{{ .Version }}-amd64
+35
View File
@@ -0,0 +1,35 @@
# This dockerfile is used for tests and local builds
# Build the manager binary
FROM docker.io/golang:1.23 AS builder
ARG TARGETOS
ARG TARGETARCH
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download
# Copy the go source
COPY cmd/main.go cmd/main.go
COPY api/ api/
COPY internal/ internal/
# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -ldflags="-w -s" -a -o manager cmd/main.go
# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532
ENTRYPOINT ["/manager"]
+8
View File
@@ -0,0 +1,8 @@
# This dockerfile is used for goreleaser
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY manager .
USER 65532:65532
ENTRYPOINT ["/manager"]
+212
View File
@@ -0,0 +1,212 @@
# Image URL to use all building/pushing image targets
IMG ?= docker.io/netbirdio/kubernetes-operator:latest
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif
# CONTAINER_TOOL defines the container tool to be used for building images.
# Be aware that the target commands are only tested with Docker which is
# scaffolded by default. However, you might want to replace it to use other
# tools. (i.e. podman)
CONTAINER_TOOL ?= docker
# Setting SHELL to bash allows bash commands to be executed by recipes.
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
SHELL = /usr/bin/env bash -o pipefail
.SHELLFLAGS = -ec
.PHONY: all
all: build
##@ General
# The help target prints out all targets with their descriptions organized
# beneath their categories. The categories are represented by '##@' and the
# target descriptions by '##'. The awk command is responsible for reading the
# entire set of makefiles included in this invocation, looking for lines of the
# file as xyz: ## something, and then pretty-format the target and help. Then,
# if there's a line with ##@ something, that gets pretty-printed as a category.
# More info on the usage of ANSI control characters for terminal formatting:
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
# More info on the awk command:
# http://linuxcommand.org/lc3_adv_awk.php
.PHONY: help
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
##@ Development
.PHONY: manifests
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
$(CONTROLLER_GEN) crd paths="./..." output:crd:artifacts:config=helm/netbird-operator/crds
.PHONY: generate
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
.PHONY: fmt
fmt: ## Run go fmt against code.
go fmt ./...
.PHONY: vet
vet: ## Run go vet against code.
go vet ./...
.PHONY: test
test: manifests fmt vet setup-envtest ## Run tests.
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test -v $$(go list ./... | grep -v /e2e) -coverprofile cover.out
.PHONY: test-e2e
test-e2e: manifests fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.
@command -v kind >/dev/null 2>&1 || { \
echo "Kind is not installed. Please install Kind manually."; \
exit 1; \
}
@kind get clusters | grep -q 'kind' || { \
echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \
exit 1; \
}
go test ./test/e2e/ -v -ginkgo.v
.PHONY: lint
lint: golangci-lint ## Run golangci-lint linter
$(GOLANGCI_LINT) run
.PHONY: lint-fix
lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
$(GOLANGCI_LINT) run --fix
.PHONY: lint-config
lint-config: golangci-lint ## Verify golangci-lint linter configuration
$(GOLANGCI_LINT) config verify
##@ Build
.PHONY: build
build: manifests fmt vet ## Build manager binary.
go build -o bin/manager cmd/main.go
.PHONY: run
run: manifests fmt vet ## Run a controller from your host.
go run ./cmd/main.go
# If you wish to build the manager image targeting other platforms you can use the --platform flag.
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
.PHONY: docker-build
docker-build: ## Build docker image with the manager.
$(CONTAINER_TOOL) build -t ${IMG} .
.PHONY: docker-push
docker-push: ## Push docker image with the manager.
$(CONTAINER_TOOL) push ${IMG}
# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/
# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/
# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)
# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.
PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
.PHONY: docker-buildx
docker-buildx: ## Build and push docker image for the manager for cross-platform support
# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
- $(CONTAINER_TOOL) buildx create --name operator-builder
$(CONTAINER_TOOL) buildx use operator-builder
- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .
- $(CONTAINER_TOOL) buildx rm operator-builder
rm Dockerfile.cross
.PHONY: build-installer
build-installer: manifests ## Generate a consolidated YAML with CRDs and deployment.
mkdir -p dist
$(HELM) template --include-crds netbird-operator helm/netbird-operator > dist/install.yaml
##@ Deployment
ifndef ignore-not-found
ignore-not-found = false
endif
.PHONY: install
install: manifests ## Install CRDs into the K8s cluster specified in ~/.kube/config.
$(KUBECTL) apply -f helm/netbird-operator/crds
.PHONY: uninstall
uninstall: manifests ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
$(KUBECTL) delete -f helm/netbird-operator/crds
.PHONY: deploy
deploy: manifests ## Deploy controller to the K8s cluster specified in ~/.kube/config.
$(HELM) install -n netbird --create-namespace netbird-operator --set operator.image.tag=$(word 2,$(subst :, ,${IMG})) helm/netbird-operator
.PHONY: undeploy
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.
$(HELM) uninstall -n netbird netbird-operator
##@ Dependencies
## Location to install dependencies to
LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)
## Tool Binaries
KUBECTL ?= kubectl
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
ENVTEST ?= $(LOCALBIN)/setup-envtest
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
HELM ?= helm
## Tool Versions
CONTROLLER_TOOLS_VERSION ?= v0.17.1
#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)
ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}')
#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}')
GOLANGCI_LINT_VERSION ?= v1.63.4
.PHONY: controller-gen
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
$(CONTROLLER_GEN): $(LOCALBIN)
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))
.PHONY: setup-envtest
setup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.
@echo "Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)..."
@$(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path || { \
echo "Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION)."; \
exit 1; \
}
.PHONY: envtest
envtest: $(ENVTEST) ## Download setup-envtest locally if necessary.
$(ENVTEST): $(LOCALBIN)
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))
.PHONY: golangci-lint
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary
# $2 - package url which can be installed
# $3 - specific version of package
define go-install-tool
@[ -f "$(1)-$(3)" ] || { \
set -e; \
package=$(2)@$(3) ;\
echo "Downloading $${package}" ;\
rm -f $(1) || true ;\
GOBIN=$(LOCALBIN) go install $${package} ;\
mv $(1) $(1)-$(3) ;\
} ;\
ln -sf $(1)-$(3) $(1)
endef
+32
View File
@@ -0,0 +1,32 @@
# Code generated by tool. DO NOT EDIT.
# This file is used to track the info used to scaffold your project
# and allow the plugins properly work.
# More info: https://book.kubebuilder.io/reference/project-config.html
domain: netbird.io
layout:
- helm.kubebuilder.io/v1-alpha
- go.kubebuilder.io/v4
plugins:
helm.kubebuilder.io/v1-alpha: {}
projectName: kubernetes-operator
repo: github.com/netbirdio/kubernetes-operator
resources:
- api:
crdVersion: v1
namespaced: true
controller: true
domain: netbird.io
kind: NBSetupKey
path: github.com/netbirdio/kubernetes-operator/api/v1
version: v1
webhooks:
validation: true
webhookVersion: v1
- external: true
kind: Pod
path: k8s.io/api/core/v1
version: v1
webhooks:
defaulting: true
webhookVersion: v1
version: "3"
+124
View File
@@ -0,0 +1,124 @@
# NetBird Kubernetes Operator
For easily provisioning access to Kubernetes resources using NetBird.
## Description
This operator enables easily provisioning NetBird access on kubernetes clusters, allowing users to access internal resources directly.
## Getting Started
### Prerequisites
- helm version 3+
- kubectl version v1.11.3+.
- Access to a Kubernetes v1.11.3+ cluster.
- (Optional for Helm chart installation) Cert Manager.
### To Deploy on the cluster
**Using the install.yaml**
```sh
kubectl create namespace netbird
kubectl apply -n netbird -f https://github.com/netbirdio/kubernetes-operator/releases/latest/dist/install.yaml
```
**Using the Helm Chart**
```sh
helm repo add netbirdio https://netbirdio.github.io/kubernetes-operator
helm install -n netbird netbird-operator netbirdio/netbird-operator
```
For more options, check the default values by running
```sh
helm show values netbirdio/netbird-operator
```
### To Uninstall
**Using install.yaml**
```sh
kubectl delete -n netbird -f https://github.com/netbirdio/kubernetes-operator/releases/latest/dist/install.yaml
kubectl delete namespace netbird
```
**Using helm**
```sh
helm uninstall -n netbird netbird-operator
```
### Provision pods with NetBird access
1. Create a Setup Key in your [NetBird console](https://docs.netbird.io/how-to/register-machines-using-setup-keys#using-setup-keys).
1. Create a Secret object in the namespace where you need to provision NetBird access (secret name and field can be anything).
```yaml
apiVersion: v1
stringData:
setupkey: EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE
kind: Secret
metadata:
name: test
```
1. Create an NBSetupKey object referring to your secret.
```yaml
apiVersion: netbird.io/v1
kind: NBSetupKey
metadata:
name: test
spec:
# Optional, overrides management URL for this setupkey only
# defaults to https://api.netbird.io
managementURL: https://netbird.example.com
secretKeyRef:
name: test # Required
key: setupkey # Required
```
1. Annotate the pods you need to inject NetBird into with `netbird.io/setup-key`.
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment
spec:
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
annotations:
netbird.io/setup-key: test # Must match the name of an NBSetupKey object in the same namespace
spec:
containers:
- image: yourimage
name: container
```
## Contributing
### Prerequisites
To be able to develop on this project, you need to have the following tools installed:
- [Git](https://git-scm.com/).
- [Make](https://www.gnu.org/software/make/).
- [Go programming language](https://golang.org/dl/).
- [Docker CE](https://www.docker.com/community-edition).
- [Kubernetes cluster (v1.16+)](https://kubernetes.io/docs/setup/). [KIND](https://github.com/kubernetes-sigs/kind) is recommended.
- [Kubebuilder](https://book.kubebuilder.io/).
### Running tests
**Running unit tests**
```sh
make test
```
**Running E2E tests**
```sh
kind create cluster # If not already created, you can check with `kind get clusters`
make test-e2e
```
+36
View File
@@ -0,0 +1,36 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package v1 contains API Schema definitions for the v1 API group.
// +kubebuilder:object:generate=true
// +groupName=netbird.io
package v1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is group version used to register these objects.
GroupVersion = schema.GroupVersion{Group: "netbird.io", Version: "v1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
+90
View File
@@ -0,0 +1,90 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// NBSetupKeyConditionType is a valid value for PodCondition.Type
type NBSetupKeyConditionType string
// These are built-in conditions of pod. An application may use a custom condition not listed here.
const (
// Ready indicates whether NBSetupKey is valid and ready to use.
Ready NBSetupKeyConditionType = "Ready"
)
// NBSetupKeySpec defines the desired state of NBSetupKey.
type NBSetupKeySpec struct {
// SecretKeyRef is a reference to the secret containing the setup key
SecretKeyRef corev1.SecretKeySelector `json:"secretKeyRef"`
// ManagementURL optional, override operator management URL
ManagementURL string `json:"managementURL,omitempty"`
}
// NBSetupKeyStatus defines the observed state of NBSetupKey.
type NBSetupKeyStatus struct {
Conditions []NBSetupKeyCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,2,rep,name=conditions"`
}
// NBSetupKeyCondition defines a condition in NBSetupKey status.
type NBSetupKeyCondition struct {
// Type is the type of the condition.
Type NBSetupKeyConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=NBSetupKeyConditionType"`
// Status is the status of the condition.
// Can be True, False, Unknown.
Status corev1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=ConditionStatus"`
// Last time we probed the condition.
// +optional
LastProbeTime metav1.Time `json:"lastProbeTime,omitempty" protobuf:"bytes,3,opt,name=lastProbeTime"`
// Last time the condition transitioned from one status to another.
// +optional
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastTransitionTime"`
// Unique, one-word, CamelCase reason for the condition's last transition.
// +optional
Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"`
// Human-readable message indicating details about last transition.
// +optional
Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// NBSetupKey is the Schema for the nbsetupkeys API.
type NBSetupKey struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec NBSetupKeySpec `json:"spec,omitempty"`
Status NBSetupKeyStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// NBSetupKeyList contains a list of NBSetupKey.
type NBSetupKeyList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []NBSetupKey `json:"items"`
}
func init() {
SchemeBuilder.Register(&NBSetupKey{}, &NBSetupKeyList{})
}
+123
View File
@@ -0,0 +1,123 @@
//go:build !ignore_autogenerated
// Code generated by controller-gen. DO NOT EDIT.
package v1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NBSetupKey) DeepCopyInto(out *NBSetupKey) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBSetupKey.
func (in *NBSetupKey) DeepCopy() *NBSetupKey {
if in == nil {
return nil
}
out := new(NBSetupKey)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *NBSetupKey) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NBSetupKeyCondition) DeepCopyInto(out *NBSetupKeyCondition) {
*out = *in
in.LastProbeTime.DeepCopyInto(&out.LastProbeTime)
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBSetupKeyCondition.
func (in *NBSetupKeyCondition) DeepCopy() *NBSetupKeyCondition {
if in == nil {
return nil
}
out := new(NBSetupKeyCondition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NBSetupKeyList) DeepCopyInto(out *NBSetupKeyList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]NBSetupKey, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBSetupKeyList.
func (in *NBSetupKeyList) DeepCopy() *NBSetupKeyList {
if in == nil {
return nil
}
out := new(NBSetupKeyList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *NBSetupKeyList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NBSetupKeySpec) DeepCopyInto(out *NBSetupKeySpec) {
*out = *in
in.SecretKeyRef.DeepCopyInto(&out.SecretKeyRef)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBSetupKeySpec.
func (in *NBSetupKeySpec) DeepCopy() *NBSetupKeySpec {
if in == nil {
return nil
}
out := new(NBSetupKeySpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NBSetupKeyStatus) DeepCopyInto(out *NBSetupKeyStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]NBSetupKeyCondition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NBSetupKeyStatus.
func (in *NBSetupKeyStatus) DeepCopy() *NBSetupKeyStatus {
if in == nil {
return nil
}
out := new(NBSetupKeyStatus)
in.DeepCopyInto(out)
return out
}
+204
View File
@@ -0,0 +1,204 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"crypto/tls"
"flag"
"os"
"path/filepath"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
corev1 "k8s.io/api/core/v1"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
"github.com/netbirdio/kubernetes-operator/internal/controller"
webhookk8siov1 "github.com/netbirdio/kubernetes-operator/internal/webhook/v1"
webhooknetbirdiov1 "github.com/netbirdio/kubernetes-operator/internal/webhook/v1"
// +kubebuilder:scaffold:imports
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(netbirdiov1.AddToScheme(scheme))
utilruntime.Must(corev1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}
// nolint:gocyclo
func main() {
// NB Specific flags
var (
managementURL string
clientImage string
)
flag.StringVar(&managementURL, "netbird-management-url", "https://api.netbird.io", "Management service URL")
flag.StringVar(&clientImage, "netbird-client-image", "netbirdio/netbird:latest", "Image for netbird client container")
// Controller generic flags
var (
metricsAddr string
webhookCertPath string
webhookCertName string
webhookCertKey string
enableLeaderElection bool
probeAddr string
enableHTTP2 bool
enableWebhooks bool
tlsOpts []func(*tls.Config)
)
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.")
flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.")
flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.BoolVar(&enableWebhooks, "enable-webhooks", true, "If set, enable Mutating and Validating webhooks.")
opts := zap.Options{
Development: true,
}
opts.BindFlags(flag.CommandLine)
flag.Parse()
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
disableHTTP2 := func(c *tls.Config) {
setupLog.Info("disabling http/2")
c.NextProtos = []string{"http/1.1"}
}
if !enableHTTP2 {
tlsOpts = append(tlsOpts, disableHTTP2)
}
// Create watcher for webhooks certificates
var webhookCertWatcher *certwatcher.CertWatcher
// Initial webhook TLS options
webhookTLSOpts := tlsOpts
if len(webhookCertPath) > 0 {
setupLog.Info("Initializing webhook certificate watcher using provided certificates",
"webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey)
var err error
webhookCertWatcher, err = certwatcher.New(
filepath.Join(webhookCertPath, webhookCertName),
filepath.Join(webhookCertPath, webhookCertKey),
)
if err != nil {
setupLog.Error(err, "Failed to initialize webhook certificate watcher")
os.Exit(1)
}
webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) {
config.GetCertificate = webhookCertWatcher.GetCertificate
})
}
webhookServer := webhook.NewServer(webhook.Options{
TLSOpts: webhookTLSOpts,
})
metricsServerOptions := metricsserver.Options{
BindAddress: metricsAddr,
TLSOpts: tlsOpts,
}
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: metricsServerOptions,
WebhookServer: webhookServer,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "operator.netbird.io",
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
nbSetupKeyController := &controller.NBSetupKeyReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}
if err = nbSetupKeyController.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "NBSetupKey")
os.Exit(1)
}
if enableWebhooks {
if err = webhookk8siov1.SetupPodWebhookWithManager(mgr, managementURL, clientImage); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "Pod")
os.Exit(1)
}
if err = webhooknetbirdiov1.SetupNBSetupKeyWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "NBSetupKey")
os.Exit(1)
}
}
// +kubebuilder:scaffold:builder
if webhookCertWatcher != nil {
setupLog.Info("Adding webhook certificate watcher to manager")
if err := mgr.Add(webhookCertWatcher); err != nil {
setupLog.Error(err, "unable to add webhook certificate watcher to manager")
os.Exit(1)
}
}
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
+466
View File
@@ -0,0 +1,466 @@
---
# Source: netbird-operator/crds/netbird.io_nbsetupkeys.yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.1
name: nbsetupkeys.netbird.io
spec:
group: netbird.io
names:
kind: NBSetupKey
listKind: NBSetupKeyList
plural: nbsetupkeys
singular: nbsetupkey
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
description: NBSetupKey is the Schema for the nbsetupkeys API.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: NBSetupKeySpec defines the desired state of NBSetupKey.
properties:
managementURL:
description: ManagementURL optional, override operator management
URL
type: string
secretKeyRef:
description: SecretKeyRef is a reference to the secret containing
the setup key
properties:
key:
description: The key of the secret to select from. Must be a
valid secret key.
type: string
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
optional:
description: Specify whether the Secret or its key must be defined
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
required:
- secretKeyRef
type: object
status:
description: NBSetupKeyStatus defines the observed state of NBSetupKey.
properties:
ready:
type: boolean
type: object
type: object
served: true
storage: true
subresources:
status: {}
---
# Source: netbird-operator/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: netbird-operator
labels:
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
automountServiceAccountToken: true
---
# Source: netbird-operator/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: netbird-operator
labels:
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
rules:
- apiGroups:
- netbird.io
resources:
- nbsetupkeys
verbs:
- get
- list
- watch
- apiGroups:
- netbird.io
resources:
- nbsetupkeys/finalizers
verbs:
- update
- apiGroups:
- netbird.io
resources:
- nbsetupkeys/status
verbs:
- get
- patch
- update
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
---
# Source: netbird-operator/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: netbird-operator
labels:
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: netbird-operator
subjects:
- kind: ServiceAccount
name: netbird-operator
namespace: default
---
# Source: netbird-operator/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: netbird-operator
labels:
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
---
# Source: netbird-operator/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: netbird-operator
labels:
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: netbird-operator
subjects:
- kind: ServiceAccount
name: netbird-operator
namespace: default
---
# Source: netbird-operator/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: netbird-operator-metrics
labels:
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
type: ClusterIP
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 8080
selector:
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
---
# Source: netbird-operator/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: netbird-operator-webhook-service
labels:
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
type: ClusterIP
ports:
- name: https
port: 443
protocol: TCP
targetPort: 9443
selector:
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
---
# Source: netbird-operator/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: netbird-operator
labels:
app.kubernetes.io/component: operator
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
template:
metadata:
labels:
app.kubernetes.io/component: operator
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
serviceAccountName: netbird-operator
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: netbird-operator
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
image: "docker.io/netbirdio/kubernetes-operator:v0.1.0"
imagePullPolicy: IfNotPresent
command:
- /manager
args:
- --metrics-bind-address=:8080
- --leader-elect
- --health-probe-bind-address=:8081
- --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs
ports:
- name: webhook-server
containerPort: 443
protocol: TCP
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 8081
scheme: HTTP
initialDelaySeconds: 15
periodSeconds: 20
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /readyz
port: 8081
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources:
{}
volumeMounts:
- mountPath: /tmp/k8s-webhook-server/serving-certs
name: webhook-certs
readOnly: true
volumes:
- name: webhook-certs
secret:
defaultMode: 420
secretName: netbird-operator-tls
---
# Source: netbird-operator/templates/webhook.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: netbird-operator-serving-cert
namespace: default
labels:
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
dnsNames:
- netbird-operator-webhook-service.default.svc
- netbird-operator-webhook-service.default.svc.cluster.local
issuerRef:
kind: Issuer
name: netbird-operator-selfsigned-issuer
secretName: netbird-operator-tls
---
# Source: netbird-operator/templates/webhook.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: netbird-operator-selfsigned-issuer
namespace: default
labels:
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
selfSigned: {}
---
# Source: netbird-operator/templates/webhook.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
annotations:
cert-manager.io/inject-ca-from: default/netbird-operator-serving-cert
name: netbird-operator-mpod-webhook
labels:
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
webhooks:
- clientConfig:
service:
name: netbird-operator-webhook-service
namespace: default
path: /mutate--v1-pod
failurePolicy: Fail
name: mpod-v1.netbird.io
admissionReviewVersions:
- v1
objectSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: NotIn
values:
- netbird-operator
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
sideEffects: None
---
# Source: netbird-operator/templates/webhook.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
annotations:
cert-manager.io/inject-ca-from: default/netbird-operator-serving-cert
name: netbird-operator-vnbsetupkey-webhook
labels:
helm.sh/chart: netbird-operator-0.1.0
app.kubernetes.io/name: netbird-operator
app.kubernetes.io/instance: netbird-operator
app.kubernetes.io/version: "v0.1.0"
app.kubernetes.io/managed-by: Helm
webhooks:
- clientConfig:
service:
name: netbird-operator-webhook-service
namespace: default
path: /validate-netbird-io-v1-nbsetupkey
failurePolicy: Fail
name: vnbsetupkey-v1.netbird.io
admissionReviewVersions:
- v1
rules:
- apiGroups:
- netbird.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- "*/*"
sideEffects: None
+73
View File
@@ -0,0 +1,73 @@
module github.com/netbirdio/kubernetes-operator
go 1.23.0
godebug default=go1.23
require (
github.com/google/uuid v1.6.0
github.com/onsi/ginkgo/v2 v2.21.0
github.com/onsi/gomega v1.35.1
k8s.io/api v0.32.0
k8s.io/apimachinery v0.32.0
k8s.io/client-go v0.32.0
sigs.k8s.io/controller-runtime v0.20.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.26.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.32.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
+190
View File
@@ -0,0 +1,190 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE=
k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0=
k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0=
k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw=
k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg=
k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8=
k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.20.0 h1:jjkMo29xEXH+02Md9qaVXfEIaMESSpy3TBWPrsfQkQs=
sigs.k8s.io/controller-runtime v0.20.0/go.mod h1:BrP3w158MwvB3ZbNpaAcIKkHQ7YGpYnzpoSTZ8E14WU=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
View File
+23
View File
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v2
name: netbird-operator
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "v0.1.0"
@@ -0,0 +1,116 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.1
name: nbsetupkeys.netbird.io
spec:
group: netbird.io
names:
kind: NBSetupKey
listKind: NBSetupKeyList
plural: nbsetupkeys
singular: nbsetupkey
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
description: NBSetupKey is the Schema for the nbsetupkeys API.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: NBSetupKeySpec defines the desired state of NBSetupKey.
properties:
managementURL:
description: ManagementURL optional, override operator management
URL
type: string
secretKeyRef:
description: SecretKeyRef is a reference to the secret containing
the setup key
properties:
key:
description: The key of the secret to select from. Must be a
valid secret key.
type: string
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
optional:
description: Specify whether the Secret or its key must be defined
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
required:
- secretKeyRef
type: object
status:
description: NBSetupKeyStatus defines the observed state of NBSetupKey.
properties:
conditions:
items:
description: NBSetupKeyCondition defines a condition in NBSetupKey
status.
properties:
lastProbeTime:
description: Last time we probed the condition.
format: date-time
type: string
lastTransitionTime:
description: Last time the condition transitioned from one status
to another.
format: date-time
type: string
message:
description: Human-readable message indicating details about
last transition.
type: string
reason:
description: Unique, one-word, CamelCase reason for the condition's
last transition.
type: string
status:
description: |-
Status is the status of the condition.
Can be True, False, Unknown.
type: string
type:
description: Type is the type of the condition.
type: string
required:
- status
- type
type: object
type: array
type: object
type: object
served: true
storage: true
subresources:
status: {}
@@ -0,0 +1,102 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "netbird-operator.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "netbird-operator.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "netbird-operator.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "netbird-operator.labels" -}}
helm.sh/chart: {{ include "netbird-operator.chart" . }}
{{ include "netbird-operator.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "netbird-operator.selectorLabels" -}}
app.kubernetes.io/name: {{ include "netbird-operator.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "netbird-operator.serviceAccountName" -}}
{{- if .Values.operator.serviceAccount.create }}
{{- default (include "netbird-operator.fullname" .) .Values.operator.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.operator.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Create the name of the webhook service
*/}}
{{- define "netbird-operator.webhookService" -}}
{{- printf "%s-webhook-service" (include "netbird-operator.fullname" .) -}}
{{- end -}}
{{/*
Create the name of the webhook cert secret
*/}}
{{- define "netbird-operator.webhookCertSecret" -}}
{{- printf "%s-tls" (include "netbird-operator.fullname" .) -}}
{{- end -}}
{{/*
Generate certificates for webhook
*/}}
{{- define "netbird-operator.webhookCerts" -}}
{{- $serviceName := (include "netbird-operator.webhookService" .) -}}
{{- $secretName := (include "netbird-operator.webhookCertSecret" .) -}}
{{- $secret := lookup "v1" "Secret" .Release.Namespace $secretName -}}
{{- if (and .Values.webhook.tls.caCert .Values.webhook.tls.cert .Values.webhook.tls.key) -}}
caCert: {{ .Values.webhook.tls.caCert | b64enc }}
clientCert: {{ .Values.webhook.tls.cert | b64enc }}
clientKey: {{ .Values.webhook.tls.key | b64enc }}
{{- else if and .Values.keepTLSSecret $secret -}}
caCert: {{ index $secret.data "ca.crt" }}
clientCert: {{ index $secret.data "tls.crt" }}
clientKey: {{ index $secret.data "tls.key" }}
{{- else -}}
{{- $altNames := list (printf "%s.%s" $serviceName .Release.Namespace) (printf "%s.%s.svc" $serviceName .Release.Namespace) (printf "%s.%s.svc.%s" $serviceName .Release.Namespace .Values.webhook.cluster.dnsDomain) -}}
{{- $ca := genCA "netbird-operator-ca" 3650 -}}
{{- $cert := genSignedCert (include "netbird-operator.fullname" .) nil $altNames 3650 $ca -}}
caCert: {{ $ca.Cert | b64enc }}
clientCert: {{ $cert.Cert | b64enc }}
clientKey: {{ $cert.Key | b64enc }}
{{- end -}}
{{- end -}}
@@ -0,0 +1,103 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "netbird-operator.fullname" . }}
labels:
app.kubernetes.io/component: operator
{{- include "netbird-operator.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.operator.replicaCount }}
selector:
matchLabels:
{{- include "netbird-operator.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.operator.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
app.kubernetes.io/component: operator
{{- include "netbird-operator.labels" . | nindent 8 }}
{{- with .Values.operator.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.operator.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "netbird-operator.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.operator.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.operator.securityContext | nindent 12 }}
image: "{{ .Values.operator.image.registry }}/{{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.operator.image.pullPolicy }}
command:
- /manager
args:
{{- if .Values.operator.metrics.enabled }}
- --metrics-bind-address=:{{ .Values.operator.metrics.port}}
{{- end }}
- --leader-elect
- --health-probe-bind-address=:{{ .Values.operator.livenessProbe.port }}
- --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs
{{- if .Values.managementURL }}
- --netbird-management-url={{.Values.managementURL}}
{{- end }}
ports:
- name: webhook-server
containerPort: {{ .Values.webhook.service.port }}
protocol: TCP
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: {{ .Values.operator.livenessProbe.port }}
scheme: HTTP
initialDelaySeconds: {{ .Values.operator.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.operator.livenessProbe.periodSeconds }}
successThreshold: {{ .Values.operator.livenessProbe.successThreshold }}
timeoutSeconds: {{ .Values.operator.livenessProbe.timeoutSeconds }}
readinessProbe:
failureThreshold: 3
httpGet:
path: /readyz
port: {{ .Values.operator.readinessProbe.port }}
scheme: HTTP
initialDelaySeconds: {{ .Values.operator.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.operator.readinessProbe.periodSeconds }}
successThreshold: {{ .Values.operator.readinessProbe.successThreshold }}
timeoutSeconds: {{ .Values.operator.readinessProbe.timeoutSeconds }}
resources:
{{- toYaml .Values.operator.resources | nindent 12 }}
volumeMounts:
- mountPath: /tmp/k8s-webhook-server/serving-certs
name: webhook-certs
readOnly: true
{{- with .Values.operator.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumes:
- name: webhook-certs
secret:
defaultMode: 420
secretName: {{ template "netbird-operator.webhookCertSecret" . }}
{{- with .Values.operator.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.operator.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.operator.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.operator.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
+116
View File
@@ -0,0 +1,116 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "netbird-operator.fullname" . }}
labels:
{{- include "netbird-operator.labels" . | nindent 4 }}
rules:
- apiGroups:
- netbird.io
resources:
- nbsetupkeys
verbs:
- get
- list
- watch
- apiGroups:
- netbird.io
resources:
- nbsetupkeys/finalizers
verbs:
- update
- apiGroups:
- netbird.io
resources:
- nbsetupkeys/status
verbs:
- get
- patch
- update
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
{{- if .Values.clusterSecretsPermissions.allowAllSecrets }}
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- watch
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "netbird-operator.fullname" . }}
labels:
{{- include "netbird-operator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "netbird-operator.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "netbird-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "netbird-operator.fullname" . }}
labels:
{{- include "netbird-operator.labels" . | nindent 4 }}
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "netbird-operator.fullname" . }}
labels:
{{- include "netbird-operator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "netbird-operator.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "netbird-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
@@ -0,0 +1,33 @@
{{- if .Values.operator.metrics.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "netbird-operator.fullname" . }}-metrics
labels:
{{- include "netbird-operator.labels" . | nindent 4 }}
spec:
type: {{ .Values.operator.metrics.type }}
ports:
- name: http
port: {{ .Values.operator.metrics.port }}
protocol: TCP
targetPort: {{ .Values.operator.metrics.port }}
selector:
{{- include "netbird-operator.selectorLabels" . | nindent 4 }}
{{- end }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "netbird-operator.webhookService" . }}
labels:
{{- include "netbird-operator.labels" . | nindent 4 }}
spec:
type: {{ .Values.webhook.service.type }}
ports:
- name: https
port: {{ .Values.webhook.service.port }}
protocol: TCP
targetPort: {{ .Values.webhook.service.targetPort }}
selector:
{{- include "netbird-operator.selectorLabels" . | nindent 4 }}
@@ -0,0 +1,13 @@
{{- if .Values.operator.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "netbird-operator.serviceAccountName" . }}
labels:
{{- include "netbird-operator.labels" . | nindent 4 }}
{{- with .Values.operator.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.operator.serviceAccount.automount }}
{{- end }}
@@ -0,0 +1,134 @@
{{ $tls := fromYaml ( include "netbird-operator.webhookCerts" . ) }}
---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
{{- if $.Values.webhook.enableCertManager }}
annotations:
cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ template "netbird-operator.fullname" . }}-serving-cert
{{- end }}
name: {{ include "netbird-operator.fullname" . }}-mpod-webhook
labels:
{{- include "netbird-operator.labels" . | nindent 4 }}
webhooks:
- clientConfig:
{{- if not $.Values.webhook.enableCertManager -}}
caBundle: {{ $tls.caCert }}
{{ end }}
service:
name: {{ template "netbird-operator.webhookService" . }}
namespace: {{ $.Release.Namespace }}
path: /mutate--v1-pod
failurePolicy: Fail
name: mpod-v1.netbird.io
admissionReviewVersions:
- v1
{{- if .Values.webhook.namespaceSelectors }}
namespaceSelector:
matchExpressions:
{{ toYaml .Values.webhook.namespaceSelectors | nindent 4 }}
{{ end }}
objectSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: NotIn
values:
- {{ include "netbird-operator.name" . }}
{{- if .Values.webhook.objectSelector.matchExpressions }}
{{- toYaml .Values.webhook.objectSelector.matchExpressions | nindent 4 }}
{{- end }}
{{- if .Values.webhook.objectSelector.matchLabels }}
matchLabels:
{{- toYaml .Values.webhook.objectSelector.matchLabels | nindent 6 }}
{{- end }}
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
sideEffects: None
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
{{- if $.Values.webhook.enableCertManager }}
annotations:
cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ template "netbird-operator.fullname" . }}-serving-cert
{{- end }}
name: {{ include "netbird-operator.fullname" . }}-vnbsetupkey-webhook
labels:
{{- include "netbird-operator.labels" . | nindent 4 }}
webhooks:
- clientConfig:
{{- if not $.Values.webhook.enableCertManager -}}
caBundle: {{ $tls.caCert }}
{{ end }}
service:
name: {{ template "netbird-operator.webhookService" . }}
namespace: {{ $.Release.Namespace }}
path: /validate-netbird-io-v1-nbsetupkey
failurePolicy: Fail
name: vnbsetupkey-v1.netbird.io
admissionReviewVersions:
- v1
{{- if .Values.webhook.namespaceSelectors }}
namespaceSelector:
matchExpressions:
{{ toYaml .Values.webhook.namespaceSelectors | nindent 4 }}
{{ end }}
rules:
- apiGroups:
- netbird.io
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- "*/*"
sideEffects: None
---
{{- if not $.Values.webhook.enableCertManager }}
apiVersion: v1
kind: Secret
metadata:
name: {{ template "netbird-operator.webhookCertSecret" . }}
namespace: {{ .Release.Namespace }}
labels:
{{ include "netbird-operator.labels" . | indent 4 }}
type: kubernetes.io/tls
data:
ca.crt: {{ $tls.caCert }}
tls.crt: {{ $tls.clientCert }}
tls.key: {{ $tls.clientKey }}
{{- else }}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ template "netbird-operator.fullname" . }}-serving-cert
namespace: {{ .Release.Namespace }}
labels:
{{ include "netbird-operator.labels" . | indent 4 }}
spec:
dnsNames:
- {{ template "netbird-operator.webhookService" . }}.{{ .Release.Namespace }}.svc
- {{ template "netbird-operator.webhookService" . }}.{{ .Release.Namespace }}.svc.{{ .Values.webhook.cluster.dnsDomain }}
issuerRef:
kind: Issuer
name: {{ template "netbird-operator.fullname" . }}-selfsigned-issuer
secretName: {{ template "netbird-operator.webhookCertSecret" . }}
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: {{ template "netbird-operator.fullname" . }}-selfsigned-issuer
namespace: {{ .Release.Namespace }}
labels:
{{ include "netbird-operator.labels" . | indent 4 }}
spec:
selfSigned: {}
{{- end }}
+130
View File
@@ -0,0 +1,130 @@
clusterSecretsPermissions:
allowAllSecrets: true
webhook:
service:
type: ClusterIP
port: 443
targetPort: 9443
cluster:
# Cluster DNS domain (required for requesting TLS certificates)
dnsDomain: cluster.local
# TLS configuration for webhook
tls: {}
# Use cert-manager to provision webhook certificates
enableCertManager: true
namespaceSelectors: []
# - key: foo
# operator: In
# values:
# - bar
objectSelector:
matchExpressions: []
# - key: app.kubernetes.io/name
# operator: NotIn
# values:
# - foo
operator:
# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
replicaCount: 1
# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
image:
# Set operator image registry
registry: docker.io
# Set operator image repository
repository: netbirdio/kubernetes-operator
# This sets the pull policy for images.
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
metrics:
enabled: true
type: ClusterIP
port: 8080
# This is for the secretes for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets: []
# This is to override the chart name.
nameOverride: ""
fullnameOverride: ""
#This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
serviceAccount:
# Specifies whether a service account should be created
create: true
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# This is for setting Kubernetes Annotations to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
podAnnotations: {}
# This is for setting Kubernetes Labels to a Pod.
# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
podLabels: {}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/
service:
# This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
type: ClusterIP
# This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
port: 9443
resources: {}
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
livenessProbe:
port: 8081
initialDelaySeconds: 15
periodSeconds: 20
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
# Additional volumes on the output Deployment definition.
volumes: []
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
nodeSelector: {}
tolerations: []
affinity: {}
@@ -0,0 +1,156 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
"fmt"
"github.com/google/uuid"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
)
// NBSetupKeyReconciler reconciles a NBSetupKey object
type NBSetupKeyReconciler struct {
client.Client
Scheme *runtime.Scheme
ReferencedSecrets map[string]types.NamespacedName
}
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
func (r *NBSetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = log.FromContext(ctx)
nbSetupKey := netbirdiov1.NBSetupKey{}
err := r.Get(ctx, req.NamespacedName, &nbSetupKey)
if err != nil {
ctrl.Log.Error(fmt.Errorf("internalError"), "error getting NBSetupKey", "err", err, "namespace", req.Namespace, "name", req.Name)
return ctrl.Result{}, nil
}
if nbSetupKey.Spec.SecretKeyRef.Name == "" || nbSetupKey.Spec.SecretKeyRef.Key == "" {
ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "secretKeyRef must contain both secret name and secret key", "namespace", req.Namespace, "name", req.Name)
return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{
Conditions: []netbirdiov1.NBSetupKeyCondition{
{
Type: netbirdiov1.Ready,
Status: corev1.ConditionFalse,
LastProbeTime: v1.Now(),
Reason: "InvalidConfig",
Message: "secretKeyRef must contain both secret name and secret key.",
},
},
})
}
// Handle updated secret name
for k, v := range r.ReferencedSecrets {
if v == req.NamespacedName {
delete(r.ReferencedSecrets, k)
break
}
}
r.ReferencedSecrets[fmt.Sprintf("%s/%s", nbSetupKey.Namespace, nbSetupKey.Spec.SecretKeyRef.Name)] = req.NamespacedName
secret := corev1.Secret{}
err = r.Get(ctx, types.NamespacedName{Namespace: nbSetupKey.Namespace, Name: nbSetupKey.Spec.SecretKeyRef.Name}, &secret)
if err != nil {
if !errors.IsNotFound(err) {
ctrl.Log.Error(fmt.Errorf("internalError"), "error getting secret", "err", err, "namespace", req.Namespace, "name", req.Name)
return ctrl.Result{}, err
}
ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "secret referenced not found", "err", err, "namespace", req.Namespace, "name", req.Name)
return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{
Type: netbirdiov1.Ready,
Status: corev1.ConditionFalse,
LastProbeTime: v1.Now(),
Reason: "SecretNotExists",
Message: "Referenced secret does not exist",
}}})
}
uuidBytes, ok := secret.Data[nbSetupKey.Spec.SecretKeyRef.Key]
if !ok {
ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "secret key referenced not found", "namespace", req.Namespace, "name", req.Name)
return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{
Type: netbirdiov1.Ready,
Status: corev1.ConditionFalse,
LastProbeTime: v1.Now(),
Reason: "SecretKeyNotExists",
Message: "Referenced secret key does not exist",
}}})
}
_, err = uuid.Parse(string(uuidBytes))
if err != nil {
ctrl.Log.Error(fmt.Errorf("invalid NBSetupKey"), "setupKey is not a valid UUID", "err", err, "namespace", req.Namespace, "name", req.Name)
return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{
Type: netbirdiov1.Ready,
Status: corev1.ConditionFalse,
LastProbeTime: v1.Now(),
Reason: "InvalidSetupKey",
Message: "Referenced secret is not a valid SetupKey",
}}})
}
return ctrl.Result{}, r.setStatus(ctx, &nbSetupKey, netbirdiov1.NBSetupKeyStatus{Conditions: []netbirdiov1.NBSetupKeyCondition{{
Type: netbirdiov1.Ready,
Status: corev1.ConditionTrue,
LastProbeTime: v1.Now(),
}}})
}
func (r *NBSetupKeyReconciler) setStatus(ctx context.Context, nbsetupkey *netbirdiov1.NBSetupKey, status netbirdiov1.NBSetupKeyStatus) error {
nbsetupkey.Status = status
err := r.Status().Update(ctx, nbsetupkey)
return err
}
// SetupWithManager sets up the controller with the Manager.
func (r *NBSetupKeyReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.ReferencedSecrets = make(map[string]types.NamespacedName)
return ctrl.NewControllerManagedBy(mgr).
For(&netbirdiov1.NBSetupKey{}).
Named("nbsetupkey").
Watches(
&corev1.Secret{},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
if v, ok := r.ReferencedSecrets[fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName())]; ok {
return []reconcile.Request{
{
NamespacedName: v,
},
}
}
return nil
}),
). // Trigger reconciliation when the labeled Busybox resource changes
Complete(r)
}
@@ -0,0 +1,200 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
)
var _ = Describe("NBSetupKey Controller", func() {
Context("When reconciling a resource", func() {
const resourceName = "test-resource"
ctx := context.Background()
typeNamespacedName := types.NamespacedName{
Name: resourceName,
Namespace: "default",
}
nbsetupkey := &netbirdiov1.NBSetupKey{}
secret := &v1.Secret{}
BeforeEach(func() {
By("creating the custom resource for the Kind NBSetupKey")
err := k8sClient.Get(ctx, typeNamespacedName, nbsetupkey)
resource := &netbirdiov1.NBSetupKey{
ObjectMeta: metav1.ObjectMeta{
Name: resourceName,
Namespace: "default",
},
Spec: netbirdiov1.NBSetupKeySpec{
SecretKeyRef: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: resourceName,
},
Key: "setupkey",
},
},
}
if err == nil {
Expect(k8sClient.Delete(ctx, nbsetupkey)).To(Succeed())
}
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
})
AfterEach(func() {
resource := &netbirdiov1.NBSetupKey{}
err := k8sClient.Get(ctx, typeNamespacedName, resource)
Expect(err).NotTo(HaveOccurred())
By("Cleanup the specific resource instance NBSetupKey")
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
})
When("No secret present", func() {
It("should set status to not ready", func() {
controllerReconciler := &NBSetupKeyReconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
ReferencedSecrets: make(map[string]types.NamespacedName),
}
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
err = k8sClient.Get(ctx, typeNamespacedName, nbsetupkey)
Expect(err).NotTo(HaveOccurred())
Expect(nbsetupkey.Status.Conditions).NotTo(BeNil())
Expect(nbsetupkey.Status.Conditions).To(HaveLen(1))
Expect(nbsetupkey.Status.Conditions[0].Status).To(Equal(v1.ConditionFalse))
Expect(nbsetupkey.Status.Conditions[0].Reason).To(Equal("SecretNotExists"))
Expect(controllerReconciler.ReferencedSecrets).To(HaveKey("default/test-resource"))
})
})
When("Secret present", Ordered, func() {
createSecret := func(secretkey, setupkey string) {
resource := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: resourceName,
},
Data: map[string][]byte{
secretkey: []byte(setupkey),
},
}
secret = &v1.Secret{}
err := k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: resourceName}, secret)
if err == nil {
Expect(k8sClient.Delete(ctx, secret)).To(Succeed())
}
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
}
When("secret is invalid", func() {
It("should set status to not ready", func() {
createSecret("setupkey", "invalid-key")
controllerReconciler := &NBSetupKeyReconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
ReferencedSecrets: make(map[string]types.NamespacedName),
}
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
err = k8sClient.Get(ctx, typeNamespacedName, nbsetupkey)
Expect(err).NotTo(HaveOccurred())
Expect(nbsetupkey.Status.Conditions).NotTo(BeNil())
Expect(nbsetupkey.Status.Conditions).To(HaveLen(1))
Expect(nbsetupkey.Status.Conditions[0].Status).To(Equal(v1.ConditionFalse))
Expect(nbsetupkey.Status.Conditions[0].Reason).To(Equal("InvalidSetupKey"))
Expect(controllerReconciler.ReferencedSecrets).To(HaveKey("default/test-resource"))
})
})
When("secret key is missing", func() {
It("should set status to not ready", func() {
createSecret("key", "EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE")
controllerReconciler := &NBSetupKeyReconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
ReferencedSecrets: make(map[string]types.NamespacedName),
}
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
err = k8sClient.Get(ctx, typeNamespacedName, nbsetupkey)
Expect(err).NotTo(HaveOccurred())
Expect(nbsetupkey.Status.Conditions).NotTo(BeNil())
Expect(nbsetupkey.Status.Conditions).To(HaveLen(1))
Expect(nbsetupkey.Status.Conditions[0].Status).To(Equal(v1.ConditionFalse))
Expect(nbsetupkey.Status.Conditions[0].Reason).To(Equal("SecretKeyNotExists"))
Expect(controllerReconciler.ReferencedSecrets).To(HaveKey("default/test-resource"))
})
})
When("secret is valid", func() {
It("should set status to ready", func() {
createSecret("setupkey", "EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE")
controllerReconciler := &NBSetupKeyReconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
ReferencedSecrets: make(map[string]types.NamespacedName),
}
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
err = k8sClient.Get(ctx, typeNamespacedName, nbsetupkey)
Expect(err).NotTo(HaveOccurred())
Expect(nbsetupkey.Status.Conditions).NotTo(BeNil())
Expect(nbsetupkey.Status.Conditions).To(HaveLen(1))
Expect(nbsetupkey.Status.Conditions[0].Status).To(Equal(v1.ConditionTrue))
Expect(controllerReconciler.ReferencedSecrets).To(HaveKey("default/test-resource"))
})
})
})
})
})
+116
View File
@@ -0,0 +1,116 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
"os"
"path/filepath"
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
// +kubebuilder:scaffold:imports
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var (
ctx context.Context
cancel context.CancelFunc
testEnv *envtest.Environment
cfg *rest.Config
k8sClient client.Client
)
func TestControllers(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Controller Suite")
}
var _ = BeforeSuite(func() {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
ctx, cancel = context.WithCancel(context.TODO())
var err error
err = netbirdiov1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "helm", "netbird-operator", "crds")},
ErrorIfCRDPathMissing: true,
}
// Retrieve the first found binary directory to allow running tests from IDEs
if getFirstFoundEnvTestBinaryDir() != "" {
testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()
}
// cfg is defined in this file globally.
cfg, err = testEnv.Start()
Expect(err).NotTo(HaveOccurred())
Expect(cfg).NotTo(BeNil())
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil())
})
var _ = AfterSuite(func() {
By("tearing down the test environment")
cancel()
err := testEnv.Stop()
Expect(err).NotTo(HaveOccurred())
})
// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.
// ENVTEST-based tests depend on specific binaries, usually located in paths set by
// controller-runtime. When running tests directly (e.g., via an IDE) without using
// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.
//
// This function streamlines the process by finding the required binaries, similar to
// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are
// properly set up, run 'make setup-envtest' beforehand.
func getFirstFoundEnvTestBinaryDir() string {
basePath := filepath.Join("..", "..", "bin", "k8s")
entries, err := os.ReadDir(basePath)
if err != nil {
logf.Log.Error(err, "Failed to read directory", "path", basePath)
return ""
}
for _, entry := range entries {
if entry.IsDir() {
return filepath.Join(basePath, entry.Name())
}
}
return ""
}
+113
View File
@@ -0,0 +1,113 @@
package v1
import (
"context"
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"github.com/google/uuid"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
)
// nolint:unused
// log is for logging in this package.
var nbsetupkeylog = logf.Log.WithName("nbsetupkey-resource")
// SetupNBSetupKeyWebhookWithManager registers the webhook for NBSetupKey in the manager.
func SetupNBSetupKeyWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).For(&netbirdiov1.NBSetupKey{}).
WithValidator(&NBSetupKeyCustomValidator{client: mgr.GetClient()}).
Complete()
}
// NBSetupKeyCustomValidator struct is responsible for validating the NBSetupKey resource
// when it is created, updated, or deleted.
type NBSetupKeyCustomValidator struct {
client client.Client
}
var _ webhook.CustomValidator = &NBSetupKeyCustomValidator{}
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type NBSetupKey.
func (v *NBSetupKeyCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
nbSetupKey, ok := obj.(*netbirdiov1.NBSetupKey)
if !ok {
return nil, fmt.Errorf("expected a NBSetupKey object but got %T", obj)
}
nbsetupkeylog.Info("Validating NBSetupKey", "namespace", nbSetupKey.Namespace, "name", nbSetupKey.Name)
if nbSetupKey.Spec.SecretKeyRef.Name == "" {
return nil, fmt.Errorf("spec.secretKeyRef.name is required")
}
if nbSetupKey.Spec.SecretKeyRef.Key == "" {
return nil, fmt.Errorf("spec.secretKeyRef.key is required")
}
var secret corev1.Secret
err := v.client.Get(ctx, types.NamespacedName{Namespace: nbSetupKey.Namespace, Name: nbSetupKey.Spec.SecretKeyRef.Name}, &secret)
if err != nil {
if errors.IsNotFound(err) {
return admission.Warnings{fmt.Sprintf("secret %s/%s not found", nbSetupKey.Namespace, nbSetupKey.Spec.SecretKeyRef.Name)}, nil
}
return nil, err
}
uuidBytes, ok := secret.Data[nbSetupKey.Spec.SecretKeyRef.Key]
if !ok {
return admission.Warnings{fmt.Sprintf("key %s in secret %s/%s not found", nbSetupKey.Spec.SecretKeyRef.Key, nbSetupKey.Namespace, nbSetupKey.Spec.SecretKeyRef.Name)}, nil
}
_, err = uuid.Parse(string(uuidBytes))
if err != nil {
return admission.Warnings{fmt.Sprintf("setupkey %s in secret %s/%s is not a valid setup key", nbSetupKey.Spec.SecretKeyRef.Key, nbSetupKey.Namespace, nbSetupKey.Spec.SecretKeyRef.Name)}, nil
}
return nil, nil
}
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type NBSetupKey.
func (v *NBSetupKeyCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
return v.ValidateCreate(ctx, newObj)
}
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type NBSetupKey.
func (v *NBSetupKeyCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
nbSetupKey, ok := obj.(*netbirdiov1.NBSetupKey)
if !ok {
return nil, fmt.Errorf("expected a NBSetupKey object but got %T", obj)
}
nbsetupkeylog.Info("Validating NBSetupKey deletion", "namespace", nbSetupKey.Namespace, "name", nbSetupKey.Name)
var pods corev1.PodList
err := v.client.List(ctx, &pods, client.InNamespace(nbSetupKey.Namespace))
if err != nil {
return nil, err
}
//nolint:prealloc
var invalidPods []string
for _, p := range pods.Items {
// If annotation doesn't exist, or doesn't match NBSetupKey being deleted, ignore
if v, ok := p.Annotations[setupKeyAnnotation]; !ok || v != nbSetupKey.Name {
continue
}
invalidPods = append(invalidPods, p.Name)
}
if len(invalidPods) > 0 {
return nil, fmt.Errorf("NBSetupKey is in-use by %d pods: %s", len(invalidPods), strings.Join(invalidPods, ","))
}
return nil, nil
}
@@ -0,0 +1,124 @@
package v1
import (
"context"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
)
var _ = Describe("NBSetupKey Webhook", func() {
var (
obj *netbirdiov1.NBSetupKey
validator NBSetupKeyCustomValidator
resourceName = "test"
secret *corev1.Secret
)
BeforeEach(func() {
obj = &netbirdiov1.NBSetupKey{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
}
validator = NBSetupKeyCustomValidator{
client: k8sClient,
}
Expect(validator).NotTo(BeNil(), "Expected validator to be initialized")
Expect(obj).NotTo(BeNil(), "Expected obj to be initialized")
})
AfterEach(func() {
})
Context("When creating or updating NBSetupKey under Validating Webhook", func() {
When("secretKeyRef is empty", func() {
It("Should fail", func() {
obj.Spec = netbirdiov1.NBSetupKeySpec{}
warnings, err := validator.ValidateCreate(context.Background(), obj)
Expect(err).To(HaveOccurred())
Expect(warnings).To(BeEmpty())
})
})
When("secret doesn't exist", func() {
It("Should fail", func() {
obj.Spec = netbirdiov1.NBSetupKeySpec{
SecretKeyRef: corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resourceName,
},
Key: "setupkey",
},
}
warnings, err := validator.ValidateCreate(context.Background(), obj)
Expect(err).NotTo(HaveOccurred())
Expect(warnings).NotTo(BeEmpty())
})
})
Context("secret exists", Ordered, func() {
createSecret := func(secretkey, setupkey string) {
resource := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: resourceName,
},
Data: map[string][]byte{
secretkey: []byte(setupkey),
},
}
secret = &corev1.Secret{}
err := k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: resourceName}, secret)
if err == nil {
Expect(k8sClient.Delete(ctx, secret)).To(Succeed())
}
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
}
BeforeEach(func() {
obj.Spec = netbirdiov1.NBSetupKeySpec{
SecretKeyRef: corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: resourceName,
},
Key: "setupkey",
},
}
})
When("secret key doesn't exist", func() {
It("Should fail", func() {
createSecret("wrongkey", "EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE")
warnings, err := validator.ValidateCreate(context.Background(), obj)
Expect(err).NotTo(HaveOccurred())
Expect(warnings).NotTo(BeEmpty())
})
})
When("setup key is invalid", func() {
It("Should fail", func() {
createSecret("setupkey", "EEEEEEEE")
warnings, err := validator.ValidateCreate(context.Background(), obj)
Expect(err).NotTo(HaveOccurred())
Expect(warnings).NotTo(BeEmpty())
})
})
When("setup key is valid", func() {
It("Should allow creation", func() {
createSecret("setupkey", "EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE")
warnings, err := validator.ValidateCreate(context.Background(), obj)
Expect(err).NotTo(HaveOccurred())
Expect(warnings).To(BeEmpty())
})
})
})
})
})
+127
View File
@@ -0,0 +1,127 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"context"
"fmt"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)
const (
setupKeyAnnotation = "netbird.io/setup-key"
)
// nolint:unused
// log is for logging in this package.
var podlog = logf.Log.WithName("pod-resource")
// SetupPodWebhookWithManager registers the webhook for Pod in the manager.
func SetupPodWebhookWithManager(mgr ctrl.Manager, managementURL, clientImage string) error {
return ctrl.NewWebhookManagedBy(mgr).For(&corev1.Pod{}).
WithDefaulter(&PodNetbirdInjector{
client: mgr.GetClient(),
managementURL: managementURL,
clientImage: clientImage,
}).
Complete()
}
// PodNetbirdInjector struct is responsible for setting default values on the custom resource of the
// Kind Pod when those are created or updated.
type PodNetbirdInjector struct {
client client.Client
managementURL string
clientImage string
}
var _ webhook.CustomDefaulter = &PodNetbirdInjector{}
// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind Pod.
func (d *PodNetbirdInjector) Default(ctx context.Context, obj runtime.Object) error {
pod, ok := obj.(*corev1.Pod)
if !ok {
return fmt.Errorf("expected an Pod object but got %T", obj)
}
podlog.Info("Defaulting for Pod", "name", pod.GetName())
if pod.Annotations == nil || pod.Annotations[setupKeyAnnotation] == "" {
return nil
}
var nbSetupKey netbirdiov1.NBSetupKey
err := d.client.Get(ctx, types.NamespacedName{Namespace: pod.Namespace, Name: pod.Annotations[setupKeyAnnotation]}, &nbSetupKey)
if err != nil {
return err
}
ready := false
for _, c := range nbSetupKey.Status.Conditions {
if c.Type == netbirdiov1.Ready {
ready = c.Status == corev1.ConditionTrue
}
}
if !ready {
return fmt.Errorf("NBSetupKey is not ready")
}
managementURL := d.managementURL
if nbSetupKey.Spec.ManagementURL != "" {
managementURL = nbSetupKey.Spec.ManagementURL
}
pod.Spec.Containers = append(pod.Spec.Containers, corev1.Container{
Name: "netbird",
Image: d.clientImage,
Args: []string{
"--setup-key-file",
"/etc/nbkey",
"-m",
managementURL,
},
Env: []corev1.EnvVar{
{
Name: "NB_SETUP_KEY",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &nbSetupKey.Spec.SecretKeyRef,
},
},
{
Name: "NB_MANAGEMENT_URL",
Value: managementURL,
},
},
SecurityContext: &corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
Add: []corev1.Capability{
"NET_ADMIN",
},
},
},
})
return nil
}
+130
View File
@@ -0,0 +1,130 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"context"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var _ = Describe("Pod Webhook", func() {
var (
obj *corev1.Pod
defaulter PodNetbirdInjector
)
BeforeEach(func() {
obj = &corev1.Pod{
ObjectMeta: v1.ObjectMeta{
Name: "test",
Namespace: "test",
Annotations: make(map[string]string),
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test",
},
},
},
}
defaulter = PodNetbirdInjector{
client: k8sClient,
managementURL: "https://api.netbird.io",
clientImage: "netbirdio/netbird:latest",
}
Expect(defaulter).NotTo(BeNil(), "Expected defaulter to be initialized")
Expect(obj).NotTo(BeNil(), "Expected obj to be initialized")
})
AfterEach(func() {
})
Context("When creating Pod without annotation", func() {
It("Should not modify anything", func() {
err := defaulter.Default(context.Background(), obj)
Expect(err).NotTo(HaveOccurred())
Expect(obj.Spec.Containers).To(HaveLen(1))
})
})
Context("When creating Pod with annotation", func() {
BeforeEach(func() {
obj.Annotations[setupKeyAnnotation] = "test"
})
When("NBSetupKey doesn't exist", func() {
It("Should fail", func() {
Expect(defaulter.Default(context.Background(), obj)).To(HaveOccurred())
Expect(obj.Spec.Containers).To(HaveLen(1))
})
})
When("NBSetupKey exists", Ordered, func() {
BeforeAll(func() {
sk := netbirdiov1.NBSetupKey{
ObjectMeta: v1.ObjectMeta{
Name: "test",
Namespace: "test",
},
Spec: netbirdiov1.NBSetupKeySpec{
SecretKeyRef: corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "test",
},
Key: "test",
},
},
}
err := k8sClient.Create(context.Background(), &corev1.Namespace{
ObjectMeta: v1.ObjectMeta{
Name: "test",
},
})
Expect(err).NotTo(HaveOccurred())
err = k8sClient.Create(context.Background(), &sk)
Expect(err).NotTo(HaveOccurred())
sk.Status = netbirdiov1.NBSetupKeyStatus{
Conditions: []netbirdiov1.NBSetupKeyCondition{
{
Type: netbirdiov1.Ready,
Status: corev1.ConditionTrue,
},
},
}
err = k8sClient.Status().Update(context.Background(), &sk)
Expect(err).NotTo(HaveOccurred())
})
It("Should inject NB container", func() {
Expect(defaulter.Default(context.Background(), obj)).NotTo(HaveOccurred())
Expect(obj.Spec.Containers).To(HaveLen(2))
Expect(obj.Spec.Containers[1].Name).To(Equal("netbird"))
})
})
})
})
+176
View File
@@ -0,0 +1,176 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"context"
"crypto/tls"
"fmt"
"net"
"os"
"path/filepath"
"testing"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
admissionv1 "k8s.io/api/admission/v1"
k8siov1 "k8s.io/api/core/v1"
apimachineryruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
// +kubebuilder:scaffold:imports
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var (
ctx context.Context
cancel context.CancelFunc
k8sClient client.Client
cfg *rest.Config
testEnv *envtest.Environment
)
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Webhook Suite")
}
var _ = BeforeSuite(func() {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
ctx, cancel = context.WithCancel(context.TODO())
var err error
scheme := apimachineryruntime.NewScheme()
err = k8siov1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())
err = admissionv1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())
err = netbirdiov1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "helm", "netbird-operator", "crds")},
ErrorIfCRDPathMissing: false,
// WebhookInstallOptions: envtest.WebhookInstallOptions{
// Paths: []string{filepath.Join("..", "..", "..", "config", "webhook")},
// },
}
// Retrieve the first found binary directory to allow running tests from IDEs
if getFirstFoundEnvTestBinaryDir() != "" {
testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()
}
// cfg is defined in this file globally.
cfg, err = testEnv.Start()
Expect(err).NotTo(HaveOccurred())
Expect(cfg).NotTo(BeNil())
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil())
// start webhook server using Manager.
webhookInstallOptions := &testEnv.WebhookInstallOptions
mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme,
WebhookServer: webhook.NewServer(webhook.Options{
Host: webhookInstallOptions.LocalServingHost,
Port: webhookInstallOptions.LocalServingPort,
CertDir: webhookInstallOptions.LocalServingCertDir,
}),
LeaderElection: false,
Metrics: metricsserver.Options{BindAddress: "0"},
})
Expect(err).NotTo(HaveOccurred())
err = SetupPodWebhookWithManager(mgr, "", "")
Expect(err).NotTo(HaveOccurred())
err = SetupNBSetupKeyWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:webhook
go func() {
defer GinkgoRecover()
err = mgr.Start(ctx)
Expect(err).NotTo(HaveOccurred())
}()
// wait for the webhook server to get ready.
dialer := &net.Dialer{Timeout: time.Second}
addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)
Eventually(func() error {
conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true})
if err != nil {
return err
}
return conn.Close()
}).Should(Succeed())
})
var _ = AfterSuite(func() {
By("tearing down the test environment")
cancel()
err := testEnv.Stop()
Expect(err).NotTo(HaveOccurred())
})
// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.
// ENVTEST-based tests depend on specific binaries, usually located in paths set by
// controller-runtime. When running tests directly (e.g., via an IDE) without using
// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.
//
// This function streamlines the process by finding the required binaries, similar to
// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are
// properly set up, run 'make setup-envtest' beforehand.
func getFirstFoundEnvTestBinaryDir() string {
basePath := filepath.Join("..", "..", "..", "bin", "k8s")
entries, err := os.ReadDir(basePath)
if err != nil {
logf.Log.Error(err, "Failed to read directory", "path", basePath)
return ""
}
for _, entry := range entries {
if entry.IsDir() {
return filepath.Join(basePath, entry.Name())
}
}
return ""
}
+83
View File
@@ -0,0 +1,83 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package e2e
import (
"fmt"
"os"
"os/exec"
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/netbirdio/kubernetes-operator/test/utils"
)
var (
// Optional Environment Variables:
// - CERT_MANAGER_INSTALL_SKIP=true: Skips CertManager installation during test setup.
// This variable is useful if CertManager is already installed, avoiding
// re-installation and conflicts.
skipCertManagerInstall = os.Getenv("CERT_MANAGER_INSTALL_SKIP") == "true"
// isCertManagerAlreadyInstalled will be set true when CertManager CRDs be found on the cluster
isCertManagerAlreadyInstalled = false
// projectImage is the name of the image which will be build and loaded
// with the code source changes to be tested.
projectImage = "docker.io/netbirdio/kubernetes-operator:e2e"
)
// TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated,
// temporary environment to validate project changes with the the purposed to be used in CI jobs.
// The default setup requires Kind, builds/loads the Manager Docker image locally, and installs
// CertManager.
func TestE2E(t *testing.T) {
RegisterFailHandler(Fail)
_, _ = fmt.Fprintf(GinkgoWriter, "Starting operator integration test suite\n")
RunSpecs(t, "e2e suite")
}
var _ = BeforeSuite(func() {
By("building the manager(Operator) image")
cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectImage))
_, err := utils.Run(cmd)
ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the manager(Operator) image")
By("loading the manager(Operator) image on Kind")
err = utils.LoadImageToKindClusterWithName(projectImage)
ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the manager(Operator) image into Kind")
if !skipCertManagerInstall {
By("checking if cert manager is installed already")
isCertManagerAlreadyInstalled = utils.IsCertManagerCRDsInstalled()
if !isCertManagerAlreadyInstalled {
_, _ = fmt.Fprintf(GinkgoWriter, "Installing CertManager...\n")
Expect(utils.InstallCertManager()).To(Succeed(), "Failed to install CertManager")
} else {
_, _ = fmt.Fprintf(GinkgoWriter, "WARNING: CertManager is already installed. Skipping installation...\n")
}
}
})
var _ = AfterSuite(func() {
// Teardown CertManager after the suite if not skipped and if it was not already installed
if !skipCertManagerInstall && !isCertManagerAlreadyInstalled {
_, _ = fmt.Fprintf(GinkgoWriter, "Uninstalling CertManager...\n")
utils.UninstallCertManager()
}
})
+472
View File
@@ -0,0 +1,472 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package e2e
import (
"fmt"
"os/exec"
"strings"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/netbirdio/kubernetes-operator/test/utils"
)
// namespace where the project is deployed in
const namespace = "netbird"
// metricsServiceName is the name of the metrics service of the project
const metricsServiceName = "netbird-operator-metrics"
var _ = Describe("Manager", Ordered, func() {
var controllerPodName string
// Before running the tests, set up the environment by creating the namespace,
// enforce the restricted security policy to the namespace, installing CRDs,
// and deploying the controller.
BeforeAll(func() {
By("creating manager namespace")
cmd := exec.Command("kubectl", "create", "ns", namespace)
_, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred(), "Failed to create namespace")
By("labeling the namespace to enforce the restricted security policy")
cmd = exec.Command("kubectl", "label", "--overwrite", "ns", namespace,
"pod-security.kubernetes.io/enforce=restricted")
_, err = utils.Run(cmd)
Expect(err).NotTo(HaveOccurred(), "Failed to label namespace with restricted policy")
By("installing CRDs")
cmd = exec.Command("make", "install", fmt.Sprintf("IMG=%s", projectImage))
_, err = utils.Run(cmd)
Expect(err).NotTo(HaveOccurred(), "Failed to install CRDs")
By("deploying the netbird-operator")
cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectImage))
out, err := utils.Run(cmd)
if err != nil {
fmt.Println(out)
}
Expect(err).NotTo(HaveOccurred(), "Failed to deploy the netbird-operator")
})
// After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs,
// and deleting the namespace.
AfterAll(func() {
By("cleaning up the curl pod for metrics")
cmd := exec.Command("kubectl", "delete", "pod", "curl-metrics", "-n", namespace)
_, _ = utils.Run(cmd)
By("undeploying the netbird-operator")
cmd = exec.Command("make", "undeploy")
_, _ = utils.Run(cmd)
By("uninstalling CRDs")
cmd = exec.Command("make", "uninstall")
_, _ = utils.Run(cmd)
By("removing manager namespace")
cmd = exec.Command("kubectl", "delete", "ns", namespace)
_, _ = utils.Run(cmd)
})
// After each test, check for failures and collect logs, events,
// and pod descriptions for debugging.
AfterEach(func() {
specReport := CurrentSpecReport()
if specReport.Failed() {
By("Fetching controller manager pod logs")
cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace)
controllerLogs, err := utils.Run(cmd)
if err == nil {
_, _ = fmt.Fprintf(GinkgoWriter, "Controller logs:\n %s", controllerLogs)
} else {
_, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Controller logs: %s", err)
}
By("Fetching Kubernetes events")
cmd = exec.Command("kubectl", "get", "events", "-n", namespace, "--sort-by=.lastTimestamp")
eventsOutput, err := utils.Run(cmd)
if err == nil {
_, _ = fmt.Fprintf(GinkgoWriter, "Kubernetes events:\n%s", eventsOutput)
} else {
_, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Kubernetes events: %s", err)
}
By("Fetching curl-metrics logs")
cmd = exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace)
metricsOutput, err := utils.Run(cmd)
if err == nil {
_, _ = fmt.Fprintf(GinkgoWriter, "Metrics logs:\n %s", metricsOutput)
} else {
_, _ = fmt.Fprintf(GinkgoWriter, "Failed to get curl-metrics logs: %s", err)
}
By("Fetching controller manager pod description")
cmd = exec.Command("kubectl", "describe", "pod", controllerPodName, "-n", namespace)
podDescription, err := utils.Run(cmd)
if err == nil {
fmt.Println("Pod description:\n", podDescription)
} else {
fmt.Println("Failed to describe controller pod")
}
}
})
SetDefaultEventuallyTimeout(2 * time.Minute)
SetDefaultEventuallyPollingInterval(time.Second)
Context("Manager", func() {
It("should run successfully", func() {
By("validating that the netbird-operator pod is running as expected")
verifyControllerUp := func(g Gomega) {
// Get the name of the netbird-operator pod
cmd := exec.Command("kubectl", "get",
"pods", "-l", "app.kubernetes.io/component=operator,app.kubernetes.io/name=netbird-operator",
"-o", "go-template={{ range .items }}"+
"{{ if not .metadata.deletionTimestamp }}"+
"{{ .metadata.name }}"+
"{{ \"\\n\" }}{{ end }}{{ end }}",
"-n", namespace,
)
podOutput, err := utils.Run(cmd)
g.Expect(err).NotTo(HaveOccurred(), "Failed to retrieve netbird-operator pod information")
podNames := utils.GetNonEmptyLines(podOutput)
g.Expect(podNames).To(HaveLen(1), "expected 1 controller pod running")
controllerPodName = podNames[0]
g.Expect(controllerPodName).To(ContainSubstring("netbird-operator"))
// Validate the pod's status
cmd = exec.Command("kubectl", "get",
"pods", controllerPodName, "-o", "jsonpath={.status.phase}",
"-n", namespace,
)
output, err := utils.Run(cmd)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(output).To(Equal("Running"), "Incorrect netbird-operator pod status")
}
Eventually(verifyControllerUp).Should(Succeed())
})
It("should ensure the metrics endpoint is serving metrics", func() {
By("validating that the metrics service is available")
cmd := exec.Command("kubectl", "get", "service", metricsServiceName, "-n", namespace)
_, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred(), "Metrics service should exist")
By("waiting for the metrics endpoint to be ready")
verifyMetricsEndpointReady := func(g Gomega) {
cmd := exec.Command("kubectl", "get", "endpoints", metricsServiceName, "-n", namespace)
output, err := utils.Run(cmd)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(output).To(ContainSubstring("8080"), "Metrics endpoint is not ready")
}
Eventually(verifyMetricsEndpointReady).Should(Succeed())
By("verifying that the controller manager is serving the metrics server")
verifyMetricsServerStarted := func(g Gomega) {
cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace)
output, err := utils.Run(cmd)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(output).To(ContainSubstring("controller-runtime.metrics\tServing metrics server"),
"Metrics server not yet started")
}
Eventually(verifyMetricsServerStarted).Should(Succeed())
By("creating the curl-metrics pod to access the metrics endpoint")
cmd = exec.Command("kubectl", "run", "curl-metrics", "--restart=Never",
"--namespace", namespace,
"--image=curlimages/curl:latest",
"--overrides",
fmt.Sprintf(`{
"spec": {
"containers": [{
"name": "curl",
"image": "curlimages/curl:latest",
"command": ["/bin/sh", "-c"],
"args": ["curl -v http://%s.%s.svc.cluster.local:8080/metrics"],
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": ["ALL"]
},
"runAsNonRoot": true,
"runAsUser": 1000,
"seccompProfile": {
"type": "RuntimeDefault"
}
}
}]
}
}`, metricsServiceName, namespace))
_, err = utils.Run(cmd)
Expect(err).NotTo(HaveOccurred(), "Failed to create curl-metrics pod")
By("waiting for the curl-metrics pod to complete.")
verifyCurlUp := func(g Gomega) {
cmd := exec.Command("kubectl", "get", "pods", "curl-metrics",
"-o", "jsonpath={.status.phase}",
"-n", namespace)
output, err := utils.Run(cmd)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(output).To(Equal("Succeeded"), "curl pod in wrong status")
}
Eventually(verifyCurlUp, 5*time.Minute).Should(Succeed())
By("getting the metrics by checking curl-metrics logs")
metricsOutput := getMetricsOutput()
Expect(metricsOutput).To(ContainSubstring(
"controller_runtime_reconcile_total",
))
})
It("should provisioned cert-manager", func() {
By("validating that cert-manager has the certificate Secret")
verifyCertManager := func(g Gomega) {
cmd := exec.Command("kubectl", "get", "secrets", "netbird-operator-tls", "-n", namespace)
_, err := utils.Run(cmd)
g.Expect(err).NotTo(HaveOccurred())
}
Eventually(verifyCertManager).Should(Succeed())
})
It("should have CA injection for mutating webhooks", func() {
By("checking CA injection for mutating webhooks")
verifyCAInjection := func(g Gomega) {
cmd := exec.Command("kubectl", "get",
"mutatingwebhookconfigurations.admissionregistration.k8s.io",
"netbird-operator-mpod-webhook",
"-o", "go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}")
mwhOutput, err := utils.Run(cmd)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(len(mwhOutput)).To(BeNumerically(">", 10))
}
Eventually(verifyCAInjection).Should(Succeed())
})
It("should have CA injection for validating webhooks", func() {
By("checking CA injection for validating webhooks")
verifyCAInjection := func(g Gomega) {
cmd := exec.Command("kubectl", "get",
"validatingwebhookconfigurations.admissionregistration.k8s.io",
"netbird-operator-vnbsetupkey-webhook",
"-o", "go-template={{ range .webhooks }}{{ .clientConfig.caBundle }}{{ end }}")
vwhOutput, err := utils.Run(cmd)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(len(vwhOutput)).To(BeNumerically(">", 10))
}
Eventually(verifyCAInjection).Should(Succeed())
})
Context("NBSetupKey", Ordered, func() {
Describe("Basic functionality", Ordered, func() {
BeforeAll(func() {
cmd := exec.Command(
"kubectl", "create", "secret", "generic",
"--from-literal", "sk=EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE",
"-n", "default",
"netbird-sk",
)
_, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())
})
AfterAll(func() {
cmd := exec.Command("kubectl", "delete", "--ignore-not-found", "secret", "netbird-sk")
_, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())
cmd = exec.Command("kubectl", "delete", "--ignore-not-found", "NBSetupKey", "main")
_, err = utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())
})
It("should allow setupkey resource to be created", func() {
cmd := exec.Command("kubectl", "apply", "-f", "-")
cmd.Stdin = strings.NewReader(`{
"apiVersion": "netbird.io/v1",
"kind": "NBSetupKey",
"metadata": {
"name": "main"
},
"spec": {
"managementURL": "https://netbird.example.com",
"secretKeyRef": {
"name": "netbird-sk",
"key": "sk"
}
}
}`)
_, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())
})
It("should set status.conditions[0].status to True", func() {
verifyNBSetupKeyStatus := func(g Gomega) {
cmd := exec.Command(
"kubectl", "get",
"nbsetupkeys", "main",
"-o", "jsonpath={.status.conditions[0].status}",
)
vnbskOutput, err := utils.Run(cmd)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(vnbskOutput).To(ContainSubstring("True"))
}
Eventually(verifyNBSetupKeyStatus).Should(Succeed())
})
It("should inject netbird container into a new pod with annotation", func() {
cmd := exec.Command(
"kubectl", "run", "test-pod-inject",
"--dry-run=server", "--image=busybox",
"--annotations", "netbird.io/setup-key=main",
"-n", "default",
"-o", "jsonpath={.spec.containers[1].name}",
)
out, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())
Expect(out).To(ContainSubstring("netbird"))
})
It("should not inject netbird container into a new pod without annotation", func() {
cmd := exec.Command(
"kubectl", "run", "test-pod-inject",
"--dry-run=server", "--image=busybox",
"-n", "default",
"-o", "jsonpath={.spec.containers}",
)
out, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())
Expect(out).NotTo(ContainSubstring("netbird"))
})
It("should fail new pod with incorrect annotation", func() {
cmd := exec.Command(
"kubectl", "run", "test-pod-inject",
"--dry-run=server", "--image=busybox",
"--annotations", "netbird.io/setup-key=nothing",
"-n", "default",
"-o", "jsonpath={.spec.containers}",
)
out, err := utils.Run(cmd)
Expect(err).To(HaveOccurred())
Expect(out).To(ContainSubstring("admission"))
})
})
Describe("Post-create validation", Ordered, func() {
BeforeAll(func() {
cmd := exec.Command(
"kubectl", "create", "secret", "generic",
"--from-literal", "sk=EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE",
"-n", "default",
"netbird-sk",
)
_, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())
cmd = exec.Command("kubectl", "apply", "-f", "-")
cmd.Stdin = strings.NewReader(`{
"apiVersion": "netbird.io/v1",
"kind": "NBSetupKey",
"metadata": {
"name": "main"
},
"spec": {
"managementURL": "https://netbird.example.com",
"secretKeyRef": {
"name": "netbird-sk",
"key": "sk"
}
}
}`)
_, err = utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())
})
AfterAll(func() {
cmd := exec.Command("kubectl", "delete", "--ignore-not-found", "secret", "netbird-sk")
_, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())
cmd = exec.Command("kubectl", "delete", "--ignore-not-found", "NBSetupKey", "main")
_, err = utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())
})
It("should set status.conditions[0].status to True", func() {
verifyNBSetupKeyStatus := func(g Gomega) {
cmd := exec.Command(
"kubectl", "get",
"nbsetupkeys", "main",
"-o", "jsonpath={.status.conditions[0].status}",
)
vnbskOutput, err := utils.Run(cmd)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(vnbskOutput).To(ContainSubstring("True"))
}
Eventually(verifyNBSetupKeyStatus).Should(Succeed())
})
It("should update status after secret is updated", func() {
cmd := exec.Command(
"kubectl", "apply", "-f", "-",
)
cmd.Stdin = strings.NewReader(`{
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": "netbird-sk"
},
"stringData": {
"sk": "WewWewInvalidWewWew"
}
}`)
_, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred())
})
It("should update status.conditions[0].status to False", func() {
verifyNBSetupKeyStatus := func(g Gomega) {
cmd := exec.Command(
"kubectl", "get",
"nbsetupkeys", "main",
"-o", "jsonpath={.status.conditions[0].status}",
)
vnbskOutput, err := utils.Run(cmd)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(vnbskOutput).To(ContainSubstring("False"))
}
Eventually(verifyNBSetupKeyStatus).Should(Succeed())
})
})
})
})
})
// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint.
func getMetricsOutput() string {
By("getting the curl-metrics logs")
cmd := exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace)
metricsOutput, err := utils.Run(cmd)
Expect(err).NotTo(HaveOccurred(), "Failed to retrieve logs from curl pod")
Expect(metricsOutput).To(ContainSubstring("< HTTP/1.1 200 OK"))
return metricsOutput
}
+174
View File
@@ -0,0 +1,174 @@
/*
Copyright 2025.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"fmt"
"os"
"os/exec"
"strings"
. "github.com/onsi/ginkgo/v2" //nolint:golint,revive
)
const (
certmanagerVersion = "v1.16.3"
certmanagerURLTmpl = "https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml"
)
func warnError(err error) {
_, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err)
}
// Run executes the provided command within this context
func Run(cmd *exec.Cmd) (string, error) {
dir, _ := GetProjectDir()
cmd.Dir = dir
if err := os.Chdir(cmd.Dir); err != nil {
_, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err)
}
cmd.Env = append(os.Environ(), "GO111MODULE=on")
command := strings.Join(cmd.Args, " ")
_, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command)
output, err := cmd.CombinedOutput()
if err != nil {
return string(output), fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output))
}
return string(output), nil
}
// UninstallCertManager uninstalls the cert manager
func UninstallCertManager() {
url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)
cmd := exec.Command("kubectl", "delete", "-f", url)
if _, err := Run(cmd); err != nil {
warnError(err)
}
cmd = exec.Command(
"kubectl", "delete", "--ignore-not-found",
"leases.coordination.k8s.io",
"-n", "kube-system",
"cert-manager-controller", "cert-manager-cainjector-leader-election",
)
if _, err := Run(cmd); err != nil {
warnError(err)
}
}
// InstallCertManager installs the cert manager bundle.
func InstallCertManager() error {
url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)
cmd := exec.Command("kubectl", "apply", "-f", url)
if _, err := Run(cmd); err != nil {
return err
}
// Wait for cert-manager-webhook to be ready, which can take time if cert-manager
// was re-installed after uninstalling on a cluster.
cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook",
"--for", "condition=Available",
"--namespace", "cert-manager",
"--timeout", "5m",
)
_, err := Run(cmd)
if err != nil {
return err
}
cmd = exec.Command("kubectl", "wait", "leases/cert-manager-controller",
"--for", "jsonpath={.spec.holderIdentity}",
"-n", "kube-system",
"--timeout", "5m",
)
_, err = Run(cmd)
return err
}
// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed
// by verifying the existence of key CRDs related to Cert Manager.
func IsCertManagerCRDsInstalled() bool {
// List of common Cert Manager CRDs
certManagerCRDs := []string{
"certificates.cert-manager.io",
"issuers.cert-manager.io",
"clusterissuers.cert-manager.io",
"certificaterequests.cert-manager.io",
"orders.acme.cert-manager.io",
"challenges.acme.cert-manager.io",
}
// Execute the kubectl command to get all CRDs
cmd := exec.Command("kubectl", "get", "crds")
output, err := Run(cmd)
if err != nil {
return false
}
// Check if any of the Cert Manager CRDs are present
crdList := GetNonEmptyLines(output)
for _, crd := range certManagerCRDs {
for _, line := range crdList {
if strings.Contains(line, crd) {
return true
}
}
}
return false
}
// LoadImageToKindClusterWithName loads a local docker image to the kind cluster
func LoadImageToKindClusterWithName(name string) error {
cluster := "kind"
if v, ok := os.LookupEnv("KIND_CLUSTER"); ok {
cluster = v
}
kindOptions := []string{"load", "docker-image", name, "--name", cluster}
cmd := exec.Command("kind", kindOptions...)
_, err := Run(cmd)
return err
}
// GetNonEmptyLines converts given command output string into individual objects
// according to line breakers, and ignores the empty elements in it.
func GetNonEmptyLines(output string) []string {
var res []string
elements := strings.Split(output, "\n")
for _, element := range elements {
if element != "" {
res = append(res, element)
}
}
return res
}
// GetProjectDir will return the directory where the project is
func GetProjectDir() (string, error) {
wd, err := os.Getwd()
if err != nil {
return wd, err
}
wd = strings.Replace(wd, "/test/e2e", "", -1)
return wd, nil
}