mirror of
https://github.com/YuzuZensai/netbird-kubernetes-operator.git
synced 2026-09-13 10:49:15 +00:00
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:
@@ -0,0 +1,3 @@
|
|||||||
|
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
||||||
|
# Ignore build and test binaries.
|
||||||
|
bin/
|
||||||
@@ -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 }}
|
||||||
@@ -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 }}"
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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
|
||||||
|
*~
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
@@ -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"]
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# This dockerfile is used for goreleaser
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/static:nonroot
|
||||||
|
WORKDIR /
|
||||||
|
COPY manager .
|
||||||
|
USER 65532:65532
|
||||||
|
|
||||||
|
ENTRYPOINT ["/manager"]
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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{})
|
||||||
|
}
|
||||||
@@ -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
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+466
@@ -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
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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=
|
||||||
@@ -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/
|
||||||
@@ -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 }}
|
||||||
@@ -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 }}
|
||||||
@@ -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"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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 ""
|
||||||
|
}
|
||||||
@@ -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())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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 ""
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user