diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index dbc9e17..3fd7246 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -5,6 +5,7 @@ on: paths: # update this file to trigger helm chart release - 'helm/kubernetes-operator/Chart.yaml' + - 'helm/netbird-operator-config/Chart.yaml' branches: - main diff --git a/.github/workflows/kubectl-docker.yml b/.github/workflows/kubectl-docker.yml new file mode 100644 index 0000000..bbaf92d --- /dev/null +++ b/.github/workflows/kubectl-docker.yml @@ -0,0 +1,63 @@ +name: Docker + +on: + push: + paths: + - 'Dockerfile.kubectl' + tags: + - "v*" + branches: + - main + pull_request: + +jobs: + kubectl-docker: + 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: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + # list of Docker images to use as base name for tags + images: | + netbirdio/kubectl + # generate Docker tags based on the following events/attributes + tags: | + type=ref,event=pr + type=ref,event=branch + type=semver,pattern={{version}} + + - name: Login to Docker Hub + if: github.repository == github.event.pull_request.head.repo.full_name || !github.head_ref + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + file: "Dockerfile.kubectl" + labels: | + "org.opencontainers.image.created={{.Date}}" + "org.opencontainers.image.title={{.ProjectName}}" + "org.opencontainers.image.version={{.Version}}" + "org.opencontainers.image.revision={{.FullCommit}}" + "org.opencontainers.image.version={{.Version}}" + "maintainer=dev@netbird.io" diff --git a/Dockerfile.kubectl b/Dockerfile.kubectl new file mode 100644 index 0000000..c7dcf87 --- /dev/null +++ b/Dockerfile.kubectl @@ -0,0 +1,10 @@ +FROM alpine:3 AS builder + +RUN apk update && apk add curl +RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && \ + chmod +x kubectl && \ + mv kubectl /usr/local/bin/kubectl + +FROM alpine:3 AS final + +COPY --from=builder /usr/local/bin/kubectl /usr/local/bin/kubectl \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index cfdc68b..03aeda9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -294,16 +294,6 @@ func main() { } if enableWebhooks { - if err = webhooknetbirdiov1.SetupNBResourceWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "NBResource") - os.Exit(1) - } - - if err = webhooknetbirdiov1.SetupNBRoutingPeerWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "NBRoutingPeer") - os.Exit(1) - } - if err = webhooknetbirdiov1.SetupNBGroupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "NBGroup") os.Exit(1) diff --git a/examples/ingress/values.yaml b/examples/ingress/values-kubernetes-operator.yaml similarity index 73% rename from examples/ingress/values.yaml rename to examples/ingress/values-kubernetes-operator.yaml index a310b76..426cda5 100644 --- a/examples/ingress/values.yaml +++ b/examples/ingress/values-kubernetes-operator.yaml @@ -3,13 +3,6 @@ # tag: "0.1.0" ingress: enabled: true - router: - enabled: true -# policies: -# default: -# name: Kubernetes Default Policy -# sourceGroups: -# - All netbirdAPI: # Replace with valid NetBird Service Account token (PAT) diff --git a/examples/ingress/values-netbird-operator-config.yaml b/examples/ingress/values-netbird-operator-config.yaml new file mode 100644 index 0000000..3f948bc --- /dev/null +++ b/examples/ingress/values-netbird-operator-config.yaml @@ -0,0 +1,7 @@ +router: + enabled: true +policies: + default: + name: Kubernetes Default Policy + sourceGroups: + - All diff --git a/helm/kubernetes-operator/Chart.yaml b/helm/kubernetes-operator/Chart.yaml index 29b99c0..ca9b941 100644 --- a/helm/kubernetes-operator/Chart.yaml +++ b/helm/kubernetes-operator/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: kubernetes-operator description: NetBird Kubernetes Operator type: application -version: 0.1.15 -appVersion: "0.1.5" +version: 0.2.0 +appVersion: "0.2.0" diff --git a/helm/kubernetes-operator/templates/kubernetes-nbresource.yaml b/helm/kubernetes-operator/templates/kubernetes-nbresource.yaml index 375366f..9554d04 100644 --- a/helm/kubernetes-operator/templates/kubernetes-nbresource.yaml +++ b/helm/kubernetes-operator/templates/kubernetes-nbresource.yaml @@ -27,7 +27,7 @@ spec: spec: initContainers: - name: wait-network-ready - image: "bitnami/kubectl:latest" + image: "netbirdio/kubectl:latest" command: - bash - -c @@ -35,7 +35,7 @@ spec: - kubectl wait --for 'jsonpath={.status.networkID}' -n {{ $routerNS }} nbroutingpeer router; containers: - name: apply-nbresource - image: "bitnami/kubectl:latest" + image: "netbirdio/kubectl:latest" env: - name: NBRESOURCE_VALUE value: | diff --git a/helm/kubernetes-operator/templates/nbpolicies.yaml b/helm/kubernetes-operator/templates/nbpolicies.yaml index 5171b1f..0590b91 100644 --- a/helm/kubernetes-operator/templates/nbpolicies.yaml +++ b/helm/kubernetes-operator/templates/nbpolicies.yaml @@ -8,6 +8,8 @@ metadata: labels: app.kubernetes.io/component: operator {{- include "kubernetes-operator.labels" $ | nindent 4 }} + annotations: + helm.sh/resource-policy: keep name: {{ $k }} spec: name: {{ $v.name }} diff --git a/helm/kubernetes-operator/templates/nbroutingpeers.yaml b/helm/kubernetes-operator/templates/nbroutingpeers.yaml index 7742b41..505d3a7 100644 --- a/helm/kubernetes-operator/templates/nbroutingpeers.yaml +++ b/helm/kubernetes-operator/templates/nbroutingpeers.yaml @@ -10,6 +10,8 @@ metadata: labels: app.kubernetes.io/component: operator {{- include "kubernetes-operator.labels" $ | nindent 4 }} + annotations: + helm.sh/resource-policy: keep name: router namespace: {{ $k }} {{ $spec := merge $defaults $v }} @@ -51,6 +53,8 @@ metadata: labels: app.kubernetes.io/component: operator {{- include "kubernetes-operator.labels" $ | nindent 4 }} + annotations: + helm.sh/resource-policy: keep name: router {{- if or (or (or .replicas .resources) (or .labels .annotations)) (or .nodeSelector .tolerations) }} spec: diff --git a/helm/kubernetes-operator/templates/pre-delete.yaml b/helm/kubernetes-operator/templates/pre-delete.yaml index 4a0d159..a13a0d1 100644 --- a/helm/kubernetes-operator/templates/pre-delete.yaml +++ b/helm/kubernetes-operator/templates/pre-delete.yaml @@ -1,69 +1,41 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ include "kubernetes-operator.fullname" . }}-delete-routers - labels: - app.kubernetes.io/component: operator - {{- include "kubernetes-operator.labels" . | nindent 4 }} - annotations: - helm.sh/hook: pre-delete - helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded -spec: - backoffLimit: 3 - template: - metadata: - name: {{ include "kubernetes-operator.fullname" . }} - labels: - app.kubernetes.io/component: operator - {{- include "kubernetes-operator.labels" . | nindent 8 }} - {{- with .Values.operator.podLabels }} - {{- toYaml . | nindent 8 }} - {{- end }} - spec: - containers: - - name: pre-delete - image: "bitnami/kubectl:latest" - args: - - delete - - --all - - -A - - --cascade=foreground - - --ignore-not-found - - NBRoutingPeer - serviceAccountName: {{ include "kubernetes-operator.serviceAccountName" . }} - restartPolicy: Never ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ include "kubernetes-operator.fullname" . }}-delete-policies - labels: - app.kubernetes.io/component: operator - {{- include "kubernetes-operator.labels" . | nindent 4 }} - annotations: - helm.sh/hook: pre-delete - helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded -spec: - backoffLimit: 3 - template: - metadata: - name: {{ include "kubernetes-operator.fullname" . }} - labels: - app.kubernetes.io/component: operator - {{- include "kubernetes-operator.labels" . | nindent 8 }} - {{- with .Values.operator.podLabels }} - {{- toYaml . | nindent 8 }} - {{- end }} - spec: - containers: - - name: pre-delete - image: "bitnami/kubectl:latest" - args: - - delete - - --all - - --cascade=foreground - - --ignore-not-found - - NBPolicy - serviceAccountName: {{ include "kubernetes-operator.serviceAccountName" . }} - restartPolicy: Never ---- \ No newline at end of file +{{/*apiVersion: batch/v1*/}} +{{/*kind: Job*/}} +{{/*metadata:*/}} +{{/* name: {{ include "kubernetes-operator.fullname" . }}-delete-router-deployments*/}} +{{/* labels:*/}} +{{/* app.kubernetes.io/component: operator*/}} +{{/* {{- include "kubernetes-operator.labels" . | nindent 4 }}*/}} +{{/* annotations:*/}} +{{/* helm.sh/hook: pre-delete*/}} +{{/* helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded*/}} +{{/*spec:*/}} +{{/* backoffLimit: 3*/}} +{{/* template:*/}} +{{/* metadata:*/}} +{{/* name: {{ include "kubernetes-operator.fullname" . }}*/}} +{{/* labels:*/}} +{{/* app.kubernetes.io/component: operator*/}} +{{/* {{- include "kubernetes-operator.labels" . | nindent 8 }}*/}} +{{/* {{- with .Values.operator.podLabels }}*/}} +{{/* {{- toYaml . | nindent 8 }}*/}} +{{/* {{- end }}*/}} +{{/* spec:*/}} +{{/* containers:*/}} +{{/* - name: pre-delete*/}} +{{/* image: "netbirdio/kubectl:latest"*/}} +{{/* imagePullPolicy: {{ .Values.operator.image.pullPolicy }}*/}} +{{/* command:*/}} +{{/* - sh*/}} +{{/* - -c*/}} +{{/* args:*/}} +{{/* - kubectl get NBRoutingPeer -A --no-headers -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name | while read "L"; do kubectl patch --type=json -p '[{"op":"replace","path":"/spec/disableDeployment","value":true}]' NBRoutingPeer -n $(echo "$L" | awk '{print $1}') $(echo "$L" | awk '{print $2}'); done*/}} +{{/* - name: delete-wait*/}} +{{/* image: "netbirdio/kubectl:latest"*/}} +{{/* imagePullPolicy: {{ .Values.operator.image.pullPolicy }}*/}} +{{/* command:*/}} +{{/* - sh*/}} +{{/* - -c*/}} +{{/* args:*/}} +{{/* - kubectl get NBRoutingPeer -A --no-headers -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name | while read "L"; do kubectl wait --for=delete deployment -n $(echo "$L" | awk '{print $1}') $(echo "$L" | awk '{print $2}'); done*/}} +{{/* serviceAccountName: {{ include "kubernetes-operator.serviceAccountName" . }}*/}} +{{/* restartPolicy: Never*/}} diff --git a/helm/kubernetes-operator/templates/webhook.yaml b/helm/kubernetes-operator/templates/webhook.yaml index 3f55934..e96d8bc 100644 --- a/helm/kubernetes-operator/templates/webhook.yaml +++ b/helm/kubernetes-operator/templates/webhook.yaml @@ -96,84 +96,6 @@ webhooks: apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: -{{- if $.Values.webhook.enableCertManager }} - annotations: - cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ template "kubernetes-operator.fullname" . }}-serving-cert -{{- end }} - name: {{ include "kubernetes-operator.fullname" . }}-vnbresource-webhook - labels: - {{- include "kubernetes-operator.labels" . | nindent 4 }} -webhooks: -- clientConfig: - {{- if not $.Values.webhook.enableCertManager }} - caBundle: {{ $tls.caCert }} - {{ end }} - service: - name: {{ template "kubernetes-operator.webhookService" . }} - namespace: {{ $.Release.Namespace }} - path: /validate-netbird-io-v1-nbresource - failurePolicy: {{ .Values.webhook.failurePolicy }} - name: vnbresource-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: - - DELETE - resources: - - "nbresources" - sideEffects: None ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: -{{- if $.Values.webhook.enableCertManager }} - annotations: - cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ template "kubernetes-operator.fullname" . }}-serving-cert -{{- end }} - name: {{ include "kubernetes-operator.fullname" . }}-vnbroutingpeer-webhook - labels: - {{- include "kubernetes-operator.labels" . | nindent 4 }} -webhooks: -- clientConfig: - {{- if not $.Values.webhook.enableCertManager }} - caBundle: {{ $tls.caCert }} - {{ end }} - service: - name: {{ template "kubernetes-operator.webhookService" . }} - namespace: {{ $.Release.Namespace }} - path: /validate-netbird-io-v1-nbroutingpeer - failurePolicy: {{ .Values.webhook.failurePolicy }} - name: vnbroutingpeer-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: - - DELETE - resources: - - "nbroutingpeers" - sideEffects: None ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: {{- if $.Values.webhook.enableCertManager }} annotations: cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ template "kubernetes-operator.fullname" . }}-serving-cert diff --git a/helm/kubernetes-operator/values.yaml b/helm/kubernetes-operator/values.yaml index b02113a..4355a2c 100644 --- a/helm/kubernetes-operator/values.yaml +++ b/helm/kubernetes-operator/values.yaml @@ -140,14 +140,14 @@ ingress: namespacedNetworks: false # Allow creating policies through Service annotations allowAutomaticPolicyCreation: false - kubernetesAPI: + kubernetesAPI: # DEPRECATED: Use netbirdio/netbird-operator-configs Chart instead enabled: false groups: [] # - group1 # - group2 policies: [] # - default - router: + router: # DEPRECATED: Use netbirdio/netbird-operator-configs Chart instead # Deploy routing peer(s) enabled: false # replicas: 3 @@ -178,7 +178,7 @@ ingress: # nodeSelector: {} # tolerations: [] # NetBird Policies for use with exposed services - policies: {} + policies: {} # DEPRECATED: Use netbirdio/netbird-operator-configs Chart instead # default: # name: Kubernetes Default Policy # sourceGroups: diff --git a/helm/netbird-operator-config/.helmignore b/helm/netbird-operator-config/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/netbird-operator-config/.helmignore @@ -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/ diff --git a/helm/netbird-operator-config/Chart.yaml b/helm/netbird-operator-config/Chart.yaml new file mode 100644 index 0000000..f09806a --- /dev/null +++ b/helm/netbird-operator-config/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: netbird-operator-config +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 +appVersion: "0.0.0" diff --git a/helm/netbird-operator-config/templates/_helpers.tpl b/helm/netbird-operator-config/templates/_helpers.tpl new file mode 100644 index 0000000..d560c83 --- /dev/null +++ b/helm/netbird-operator-config/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +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-config.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 }} + +{{/* +Expand the name of the chart. +*/}} +{{- define "netbird-operator-config.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "netbird-operator-config.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "netbird-operator-config.labels" -}} +helm.sh/chart: {{ include "netbird-operator-config.chart" . }} +{{ include "netbird-operator-config.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-config.selectorLabels" -}} +app.kubernetes.io/name: {{ include "netbird-operator-config.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "netbird-operator-config.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "netbird-operator-config.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/netbird-operator-config/templates/kubernetes-nbresource.yaml b/helm/netbird-operator-config/templates/kubernetes-nbresource.yaml new file mode 100644 index 0000000..aef1a5f --- /dev/null +++ b/helm/netbird-operator-config/templates/kubernetes-nbresource.yaml @@ -0,0 +1,71 @@ +{{- if and .Values.ingress.enabled .Values.ingress.kubernetesAPI.enabled }} +{{- $routerNS := .Release.Namespace }} +{{- if .Values.ingress.namespacedNetworks }} +{{- $routerNS = "default" }} +{{- end }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "netbird-operator-config.fullname" . }}-kubernetes-service-expose + labels: + app.kubernetes.io/component: operator + {{- include "netbird-operator-config.labels" . | nindent 4 }} + annotations: + helm.sh/hook: post-upgrade,post-install + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + backoffLimit: 3 + template: + metadata: + name: {{ include "netbird-operator-config.fullname" . }} + labels: + app.kubernetes.io/component: operator + {{- include "netbird-operator-config.labels" . | nindent 8 }} + {{- with .Values.operator.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + initContainers: + - name: wait-network-ready + image: "netbirdio/kubectl:latest" + command: + - bash + - -c + args: + - kubectl wait --for 'jsonpath={.status.networkID}' -n {{ $routerNS }} nbroutingpeer router; + containers: + - name: apply-nbresource + image: "netbirdio/kubectl:latest" + env: + - name: NBRESOURCE_VALUE + value: | + apiVersion: netbird.io/v1 + kind: NBResource + metadata: + finalizers: + - netbird.io/cleanup + name: kubernetes + namespace: default + spec: + address: kubernetes.default.{{.Values.cluster.dns}} + groups: + {{- if .Values.kubernetesAPI.groups }} + {{ toYaml .Values.kubernetesAPI.groups }} + {{- else }} + - {{ .Values.cluster.name }}-default-api-access + {{- end }} + name: {{ .Values.kubernetesAPI.resourceName | default "default-kubernetes-api" }} + networkID: ${NETWORK_ID} + {{- if .Values.kubernetesAPI.policies }} + policyName: "{{ join "," .Values.kubernetesAPI.policies }}" + {{- end }} + tcpPorts: + - 443 + command: + - bash + - -c + args: + - kubectl delete NBResource --ignore-not-found -n default kubernetes; export NETWORK_ID=$(kubectl get NBRoutingPeer -n {{ $routerNS }} router -o 'jsonpath={.status.networkID}'); echo "$NBRESOURCE_VALUE" | envsubst | kubectl apply -f - + serviceAccountName: {{ include "netbird-operator-config.serviceAccountName" . }} + restartPolicy: Never +{{- end }} diff --git a/helm/netbird-operator-config/templates/nbpolicies.yaml b/helm/netbird-operator-config/templates/nbpolicies.yaml new file mode 100644 index 0000000..c86e76f --- /dev/null +++ b/helm/netbird-operator-config/templates/nbpolicies.yaml @@ -0,0 +1,30 @@ +{{- range $k, $v := $.Values.policies }} +--- +apiVersion: netbird.io/v1 +kind: NBPolicy +metadata: + annotations: + helm.sh/resource-policy: keep + finalizers: + - netbird.io/cleanup + labels: + app.kubernetes.io/component: operator + {{- include "netbird-operator-config.labels" $ | nindent 4 }} + name: {{ $k }} +spec: + name: {{ $v.name }} + sourceGroups: + {{ toYaml $v.sourceGroups | nindent 4}} + {{- if $v.description }} + description: {{ $v.description }} + {{- end }} + {{- if $v.protocols }} + protocols: {{ $v.protocols }} + {{- end }} + {{- if $v.ports }} + ports: {{ $v.ports }} + {{- end }} + {{- if hasKey $v "bidirectional" }} + bidirectional: {{ $v.bidirectional }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/netbird-operator-config/templates/nbroutingpeers.yaml b/helm/netbird-operator-config/templates/nbroutingpeers.yaml new file mode 100644 index 0000000..eaa1d74 --- /dev/null +++ b/helm/netbird-operator-config/templates/nbroutingpeers.yaml @@ -0,0 +1,83 @@ +{{- if .Values.router.enabled }} +{{- if .Values.namespacedNetworks }} +{{ $defaults := .Values.router }} +{{ range $k, $v := .Values.router.namespaces }} +apiVersion: netbird.io/v1 +kind: NBRoutingPeer +metadata: + finalizers: + - netbird.io/cleanup + labels: + app.kubernetes.io/component: operator + {{- include "netbird-operator-config.labels" $ | nindent 4 }} + name: router + namespace: {{ $k }} +{{ $spec := merge $defaults $v }} +{{- if or (or (or $spec.replicas $spec.resources) (or $spec.labels $spec.annotations)) (or $spec.nodeSelector $spec.tolerations) }} +spec: + {{- if $spec.replicas }} + replicas: {{ $spec.replicas }} + {{- end }} + {{- if $spec.resources }} + resources: + {{- toYaml $spec.resources | nindent 4 }} + {{- end }} + {{- if $spec.labels }} + labels: + {{- toYaml $spec.labels | nindent 4 }} + {{- end }} + {{- if $spec.annotations }} + annotations: + {{- toYaml $spec.annotations | nindent 4 }} + {{- end }} + {{- if $spec.nodeSelector }} + nodeSelector: + {{- toYaml $spec.nodeSelector | nindent 4 }} + {{- end }} + {{- if $spec.tolerations }} + tolerations: + {{- toYaml $spec.tolerations | nindent 4 }} + {{- end }} +{{- end }} +--- +{{- end }} +{{- else }} +{{- with .Values.router }} +apiVersion: netbird.io/v1 +kind: NBRoutingPeer +metadata: + finalizers: + - netbird.io/cleanup + labels: + app.kubernetes.io/component: operator + {{- include "netbird-operator-config.labels" $ | nindent 4 }} + name: router +{{- if or (or (or .replicas .resources) (or .labels .annotations)) (or .nodeSelector .tolerations) }} +spec: + {{- if .replicas }} + replicas: {{ .replicas }} + {{- end }} + {{- if .resources }} + resources: + {{- toYaml .resources | nindent 4 }} + {{- end }} + {{- if .labels }} + labels: + {{- toYaml .labels | nindent 4 }} + {{- end }} + {{- if .annotations }} + annotations: + {{- toYaml .annotations | nindent 4 }} + {{- end }} + {{- if .nodeSelector }} + nodeSelector: + {{- toYaml .nodeSelector | nindent 4 }} + {{- end }} + {{- if .tolerations }} + tolerations: + {{- toYaml .tolerations | nindent 4 }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/helm/netbird-operator-config/templates/rbac.yaml b/helm/netbird-operator-config/templates/rbac.yaml new file mode 100644 index 0000000..678ef16 --- /dev/null +++ b/helm/netbird-operator-config/templates/rbac.yaml @@ -0,0 +1,45 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "netbird-operator-config.fullname" . }} + labels: + {{- include "netbird-operator-config.labels" . | nindent 4 }} +rules: +- apiGroups: + - netbird.io + resources: + - nbresources + verbs: + - patch + - update + - list + - watch + - create + - delete +- apiGroups: + - netbird.io + resources: + - nbroutingpeers + verbs: + - get +- apiGroups: + - netbird.io + resources: + - nbresources/finalizers + verbs: + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "netbird-operator-config.fullname" . }} + labels: + {{- include "netbird-operator-config.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "netbird-operator-config.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "netbird-operator-config.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/helm/netbird-operator-config/templates/serviceaccount.yaml b/helm/netbird-operator-config/templates/serviceaccount.yaml new file mode 100644 index 0000000..2cb66f5 --- /dev/null +++ b/helm/netbird-operator-config/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if and .Values.kubernetesAPI.enabled .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "netbird-operator-config.serviceAccountName" . }} + labels: + {{- include "netbird-operator-config.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: true +{{- end }} diff --git a/helm/netbird-operator-config/values.yaml b/helm/netbird-operator-config/values.yaml new file mode 100644 index 0000000..2bf1cd0 --- /dev/null +++ b/helm/netbird-operator-config/values.yaml @@ -0,0 +1,57 @@ +cluster: + name: "kubernetes" + dns: "svc.cluster.local" + +# Create router per namespace, useful for strict networking requirements +namespacedNetworks: false + +router: + # Deploy routing peer(s) + enabled: false + # replicas: 3 + # resources: + # requests: + # cpu: 100m + # memory: 100Mi + # limits: + # cpu: 100m + # memory: 100Mi + # labels: {} + # annotations: {} + # nodeSelector: {} + # tolerations: [] + # Only needed if namespacedNetworks is set to true + namespaces: {} + # default: + # replicas: 3 + # resources: + # requests: + # cpu: 100m + # memory: 100Mi + # limits: + # cpu: 100m + # memory: 100Mi + # labels: {} + # annotations: {} + # nodeSelector: {} + # tolerations: [] +# NetBird Policies for use with exposed services +policies: {} + # default: + # name: Kubernetes Default Policy + # sourceGroups: +# - All + +kubernetesAPI: + enabled: false + groups: [] + # - group1 + # - group2 + policies: [] + # - default + # resourceName: "my-cluster-kubernetes" + +serviceAccount: + create: true + name: "" + annotations: {} \ No newline at end of file diff --git a/internal/controller/nbgroup_controller.go b/internal/controller/nbgroup_controller.go index 415e593..008d304 100644 --- a/internal/controller/nbgroup_controller.go +++ b/internal/controller/nbgroup_controller.go @@ -151,7 +151,7 @@ func (r *NBGroupReconciler) handleDelete(ctx context.Context, nbGroup netbirdiov return err } - if err != nil && strings.Contains(err.Error(), "linked") { + if err != nil && strings.Contains(err.Error(), "linked") && !nbGroup.DeletionTimestamp.Add(time.Minute).Before(time.Now()) { logger.Info("group still linked to resources on netbird", "err", err) // Check if group is defined elsewhere in the cluster var groups netbirdiov1.NBGroupList diff --git a/internal/controller/nbresource_controller.go b/internal/controller/nbresource_controller.go index 9d31586..e740ab1 100644 --- a/internal/controller/nbresource_controller.go +++ b/internal/controller/nbresource_controller.go @@ -2,11 +2,15 @@ package controller import ( "context" + nerrors "errors" "fmt" "slices" "strings" "time" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/log" + "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -23,6 +27,10 @@ import ( "github.com/netbirdio/netbird/management/server/http/api" ) +var ( + errDuplicateResource = fmt.Errorf("duplicate resource") +) + // NBResourceReconciler reconciles a NBResource object type NBResourceReconciler struct { client.Client @@ -87,6 +95,10 @@ func (r *NBResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) } resource, err := r.handleNetBirdResource(ctx, nbResource, groupIDs, logger) + if err != nil && nerrors.Is(err, errDuplicateResource) { + return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil + } + if err != nil { nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling NetBird Network Resource: %v", err)) return ctrl.Result{}, err @@ -406,6 +418,12 @@ func (r *NBResourceReconciler) handleNetBirdResource(ctx context.Context, nbReso Name: nbResource.Spec.Name, }) + if err != nil && strings.Contains(err.Error(), "already exists") { + log.Log.Error(errNetBirdAPI, "network resource with the same name already exists", "err", err) + nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("DuplicateName", "Resource name already exists") + return nil, errDuplicateResource + } + if err != nil { logger.Error(errNetBirdAPI, "error creating resource", "err", err) return nil, err @@ -562,6 +580,22 @@ func (r *NBResourceReconciler) handleGroups(ctx context.Context, req ctrl.Reques } func (r *NBResourceReconciler) handleDelete(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, logger logr.Logger) error { + var svc corev1.Service + err := r.Client.Get(ctx, req.NamespacedName, &svc) + if !errors.IsNotFound(err) { + logger.Error(errKubernetesAPI, "error getting Service", "err", err, "svc", req.NamespacedName.String()) + return err + } else if err == nil { + if _, ok := svc.Annotations[ServiceExposeAnnotation]; ok { + delete(svc.Annotations, ServiceExposeAnnotation) + err = r.Client.Update(ctx, &svc) + if err != nil { + logger.Error(errKubernetesAPI, "error updating Service", "err", err, "svc", req.NamespacedName.String()) + return err + } + } + } + if nbResource.Status.PolicyName != nil { for _, policy := range util.SplitTrim(*nbResource.Status.PolicyName, ",") { var nbPolicy netbirdiov1.NBPolicy @@ -593,7 +627,7 @@ func (r *NBResourceReconciler) handleDelete(ctx context.Context, req ctrl.Reques } nbGroupList := netbirdiov1.NBGroupList{} - err := r.Client.List(ctx, &nbGroupList, &client.ListOptions{Namespace: req.Namespace}) + err = r.Client.List(ctx, &nbGroupList, &client.ListOptions{Namespace: req.Namespace}) if err != nil { logger.Error(errKubernetesAPI, "error listing NBGroup", "err", err) return err diff --git a/internal/controller/nbroutingpeer_controller.go b/internal/controller/nbroutingpeer_controller.go index 5c8b697..1e87692 100644 --- a/internal/controller/nbroutingpeer_controller.go +++ b/internal/controller/nbroutingpeer_controller.go @@ -52,12 +52,6 @@ func (r *NBRoutingPeerReconciler) Reconcile(ctx context.Context, req ctrl.Reques originalNBRP := nbrp.DeepCopy() defer func() { - if err != nil { - // double check result is nil, otherwise error is not printed - // and exponential backoff doesn't work properly - res = ctrl.Result{} - return - } if originalNBRP.DeletionTimestamp != nil && len(nbrp.Finalizers) == 0 { return } @@ -67,6 +61,12 @@ func (r *NBRoutingPeerReconciler) Reconcile(ctx context.Context, req ctrl.Reques logger.Error(errKubernetesAPI, "error updating NBRoutingPeer Status", "err", err) } } + if err != nil { + // double check result is nil, otherwise error is not printed + // and exponential backoff doesn't work properly + res = ctrl.Result{} + return + } if !res.Requeue && res.RequeueAfter == 0 { res.RequeueAfter = defaultRequeueAfter } @@ -210,7 +210,7 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl nbrp.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("error creating Deployment: %v", err)) return err } - } else { + } else if err == nil { updatedDeployment := routingPeerDeployment.DeepCopy() updatedDeployment.ObjectMeta.Name = nbrp.Name updatedDeployment.ObjectMeta.Namespace = nbrp.Namespace @@ -291,7 +291,7 @@ func (r *NBRoutingPeerReconciler) handleDeployment(ctx context.Context, req ctrl } } - return nil + return err } // handleRouter reconcile network routing peer in NetBird management API @@ -393,6 +393,14 @@ func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.R } err = r.Client.Create(ctx, &skSecret) if errors.IsAlreadyExists(err) { + err = r.Client.Get(ctx, req.NamespacedName, &skSecret) + if err != nil { + logger.Error(errNetBirdAPI, "error getting secret", "err", err) + return &ctrl.Result{}, err + } + skSecret.Data = map[string][]byte{ + "setupKey": []byte(setupKey.Key), + } err = r.Client.Update(ctx, &skSecret) } @@ -410,7 +418,7 @@ func (r *NBRoutingPeerReconciler) handleSetupKey(ctx context.Context, req ctrl.R return &ctrl.Result{}, err } - if err != nil || setupKey.Revoked { + if (err != nil && strings.Contains(err.Error(), "not found")) || setupKey == nil || setupKey.Revoked { if setupKey != nil && setupKey.Revoked { err = r.netbird.SetupKeys.Delete(ctx, *nbrp.Status.SetupKeyID) @@ -586,16 +594,6 @@ func (r *NBRoutingPeerReconciler) handleDelete(ctx context.Context, req ctrl.Req logger.Info("Setup key deleted", "id", setupKeyID) } - if nbrp.Status.RouterID != nil { - err = r.netbird.Networks.Routers(*nbrp.Status.NetworkID).Delete(ctx, *nbrp.Status.RouterID) - if err != nil && !strings.Contains(err.Error(), "not found") { - logger.Error(errNetBirdAPI, "error deleting Network Router", "err", err) - return ctrl.Result{}, err - } - - nbrp.Status.RouterID = nil - } - nbGroup := netbirdiov1.NBGroup{} err = r.Client.Get(ctx, req.NamespacedName, &nbGroup) if err != nil && !errors.IsNotFound(err) { @@ -631,6 +629,7 @@ func (r *NBRoutingPeerReconciler) handleDelete(ctx context.Context, req ctrl.Req } nbrp.Status.NetworkID = nil + nbrp.Status.RouterID = nil } } diff --git a/internal/controller/nbroutingpeer_controller_test.go b/internal/controller/nbroutingpeer_controller_test.go index b5ca41e..395083b 100644 --- a/internal/controller/nbroutingpeer_controller_test.go +++ b/internal/controller/nbroutingpeer_controller_test.go @@ -912,10 +912,8 @@ var _ = Describe("NBRoutingPeer Controller", func() { }) When("NBRoutingPeer is set for deletion", func() { networkDeleted := false - routerDeleted := false BeforeEach(func() { networkDeleted = false - routerDeleted = false nbroutingpeer.Status.SetupKeyID = util.Ptr("skid") Expect(k8sClient.Status().Update(ctx, nbroutingpeer)).To(Succeed()) @@ -953,14 +951,6 @@ var _ = Describe("NBRoutingPeer Controller", func() { Expect(err).NotTo(HaveOccurred()) networkDeleted = true }) - - mux.HandleFunc("/api/networks/test/routers/test", func(w http.ResponseWriter, r *http.Request) { - defer GinkgoRecover() - Expect(r.Method).To(Equal(http.MethodDelete)) - _, err = w.Write([]byte(`{}`)) - Expect(err).NotTo(HaveOccurred()) - routerDeleted = true - }) }) It("should remove finalizer from NBGroup", func() { @@ -980,7 +970,7 @@ var _ = Describe("NBRoutingPeer Controller", func() { NamespacedName: typeNamespacedName, }) Expect(err).NotTo(HaveOccurred()) - Expect(routerDeleted).To(BeTrue()) + Expect(networkDeleted).To(BeTrue()) }) It("should delete Network", func() { diff --git a/internal/webhook/v1/nbresource_webhook.go b/internal/webhook/v1/nbresource_webhook.go deleted file mode 100644 index 0a63fb2..0000000 --- a/internal/webhook/v1/nbresource_webhook.go +++ /dev/null @@ -1,72 +0,0 @@ -package v1 - -import ( - "context" - "fmt" - - 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" - - netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" - "github.com/netbirdio/kubernetes-operator/internal/controller" -) - -// nolint:unused -// log is for logging in this package. -var nbresourcelog = logf.Log.WithName("nbresource-resource") - -// SetupNBResourceWebhookWithManager registers the webhook for NBResource in the manager. -func SetupNBResourceWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr).For(&netbirdiov1.NBResource{}). - WithValidator(&NBResourceCustomValidator{client: mgr.GetClient()}). - Complete() -} - -// NBResourceCustomValidator struct is responsible for validating the NBResource resource -// when it is created, updated, or deleted. -type NBResourceCustomValidator struct { - client client.Client -} - -var _ webhook.CustomValidator = &NBResourceCustomValidator{} - -// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type NBResource. -func (v *NBResourceCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - return nil, nil -} - -// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type NBResource. -func (v *NBResourceCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { - return nil, nil -} - -// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type NBResource. -func (v *NBResourceCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - nbresource, ok := obj.(*netbirdiov1.NBResource) - if !ok { - return nil, fmt.Errorf("expected a NBResource object but got %T", obj) - } - nbresourcelog.Info("Validation for NBResource upon deletion", "name", nbresource.GetName()) - - var svc corev1.Service - err := v.client.Get(ctx, types.NamespacedName{Namespace: nbresource.Namespace, Name: nbresource.Name}, &svc) - if errors.IsNotFound(err) { - return nil, nil - } - if err != nil { - return nil, err - } - - if _, ok := svc.Annotations[controller.ServiceExposeAnnotation]; ok && svc.DeletionTimestamp == nil { - return nil, fmt.Errorf("service %s/%s still has netbird.io/expose annotation", svc.Namespace, svc.Name) - } - - return nil, nil -} diff --git a/internal/webhook/v1/nbresource_webhook_test.go b/internal/webhook/v1/nbresource_webhook_test.go deleted file mode 100644 index 928c3cf..0000000 --- a/internal/webhook/v1/nbresource_webhook_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package v1 - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - - netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" -) - -var _ = Describe("NBResource Webhook", func() { - var ( - obj *netbirdiov1.NBResource - oldObj *netbirdiov1.NBResource - validator NBResourceCustomValidator - ) - - BeforeEach(func() { - obj = &netbirdiov1.NBResource{} - oldObj = &netbirdiov1.NBResource{} - validator = NBResourceCustomValidator{ - client: k8sClient, - } - }) - - Context("When creating or updating NBResource under Validating Webhook", func() { - It("should allow creation", func() { - Expect(validator.ValidateCreate(ctx, obj)).Error().NotTo(HaveOccurred()) - }) - It("should allow update", func() { - Expect(validator.ValidateUpdate(ctx, oldObj, obj)).Error().NotTo(HaveOccurred()) - }) - When("No services are exposed", func() { - BeforeEach(func() { - obj.Name = "maw" - obj.Namespace = "default" - svc := &corev1.Service{ - ObjectMeta: v1.ObjectMeta{ - Name: "ne", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Protocol: corev1.ProtocolTCP, - Port: 80, - TargetPort: intstr.FromInt32(80), - }, - }, - }, - } - Expect(k8sClient.Create(ctx, svc)).To(Succeed()) - }) - AfterEach(func() { - svc := &netbirdiov1.NBResource{} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "ne"}, svc) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - if len(svc.Finalizers) > 0 { - svc.Finalizers = nil - Expect(k8sClient.Update(ctx, svc)).To(Succeed()) - } - err = k8sClient.Delete(ctx, svc) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - } - } - }) - It("should allow deletion", func() { - Expect(validator.ValidateDelete(ctx, obj)).Error().NotTo(HaveOccurred()) - }) - }) - - When("A service is exposed", func() { - BeforeEach(func() { - obj.Name = "maw" - obj.Namespace = "default" - svc := &corev1.Service{ - ObjectMeta: v1.ObjectMeta{ - Name: "maw", - Namespace: "default", - Annotations: map[string]string{ - "netbird.io/expose": "true", - }, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Protocol: corev1.ProtocolTCP, - Port: 80, - TargetPort: intstr.FromInt32(80), - }, - }, - }, - } - Expect(k8sClient.Create(ctx, svc)).To(Succeed()) - }) - AfterEach(func() { - svc := &corev1.Service{} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "maw"}, svc) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - if len(svc.Finalizers) > 0 { - svc.Finalizers = nil - Expect(k8sClient.Update(ctx, svc)).To(Succeed()) - } - err = k8sClient.Delete(ctx, svc) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - } - } - }) - It("should deny deletion", func() { - Expect(validator.ValidateDelete(ctx, obj)).Error().To(HaveOccurred()) - }) - }) - }) -}) diff --git a/internal/webhook/v1/nbroutingpeer_webhook.go b/internal/webhook/v1/nbroutingpeer_webhook.go deleted file mode 100644 index df6fda3..0000000 --- a/internal/webhook/v1/nbroutingpeer_webhook.go +++ /dev/null @@ -1,76 +0,0 @@ -package v1 - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/runtime" - 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" - - netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" -) - -// nolint:unused -// log is for logging in this package. -var nbroutingpeerlog = logf.Log.WithName("nbroutingpeer-resource") - -// SetupNBRoutingPeerWebhookWithManager registers the webhook for NBRoutingPeer in the manager. -func SetupNBRoutingPeerWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr).For(&netbirdiov1.NBRoutingPeer{}). - WithValidator(&NBRoutingPeerCustomValidator{client: mgr.GetClient()}). - Complete() -} - -// NBRoutingPeerCustomValidator struct is responsible for validating the NBRoutingPeer resource -// when it is created, updated, or deleted. -type NBRoutingPeerCustomValidator struct { - client client.Client -} - -var _ webhook.CustomValidator = &NBRoutingPeerCustomValidator{} - -// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type NBRoutingPeer. -func (v *NBRoutingPeerCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - return nil, nil -} - -// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type NBRoutingPeer. -func (v *NBRoutingPeerCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { - return nil, nil -} - -// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type NBRoutingPeer. -func (v *NBRoutingPeerCustomValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { - nbroutingpeer, ok := obj.(*netbirdiov1.NBRoutingPeer) - if !ok { - return nil, fmt.Errorf("expected a NBRoutingPeer object but got %T", obj) - } - nbroutingpeerlog.Info("Validation for NBRoutingPeer upon deletion", "name", nbroutingpeer.GetName()) - - if nbroutingpeer.Status.NetworkID == nil { - return nil, nil - } - - var nbResources netbirdiov1.NBResourceList - err := v.client.List(ctx, &nbResources) - if err != nil { - return nil, err - } - - resourceValidator := &NBResourceCustomValidator{client: v.client} - - for _, r := range nbResources.Items { - if r.Spec.NetworkID == *nbroutingpeer.Status.NetworkID { - _, err = resourceValidator.ValidateDelete(ctx, &r) - if err != nil { - return nil, err - } - } - } - - return nil, nil -} diff --git a/internal/webhook/v1/nbroutingpeer_webhook_test.go b/internal/webhook/v1/nbroutingpeer_webhook_test.go deleted file mode 100644 index cd50344..0000000 --- a/internal/webhook/v1/nbroutingpeer_webhook_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package v1 - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - - netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" - "github.com/netbirdio/kubernetes-operator/internal/util" -) - -var _ = Describe("NBRoutingPeer Webhook", func() { - var ( - obj *netbirdiov1.NBRoutingPeer - oldObj *netbirdiov1.NBRoutingPeer - validator NBRoutingPeerCustomValidator - ) - - BeforeEach(func() { - obj = &netbirdiov1.NBRoutingPeer{} - oldObj = &netbirdiov1.NBRoutingPeer{} - validator = NBRoutingPeerCustomValidator{ - client: k8sClient, - } - }) - - Context("When creating or updating NBRoutingPeer under Validating Webhook", func() { - It("should allow creation", func() { - Expect(validator.ValidateCreate(ctx, obj)).Error().NotTo(HaveOccurred()) - }) - It("should allow update", func() { - Expect(validator.ValidateUpdate(ctx, oldObj, obj)).Error().NotTo(HaveOccurred()) - }) - When("No NBResources Exist", func() { - It("should allow deletion", func() { - Expect(validator.ValidateDelete(ctx, obj)).Error().NotTo(HaveOccurred()) - }) - }) - When("Deleteable NBResources Exist", func() { - BeforeEach(func() { - nbResource := &netbirdiov1.NBResource{ - ObjectMeta: v1.ObjectMeta{ - Name: "isexist", - Namespace: "default", - }, - Spec: netbirdiov1.NBResourceSpec{ - Name: "test1", - NetworkID: "test2", - Address: "test3", - Groups: []string{"test"}, - }, - } - - Expect(k8sClient.Create(ctx, nbResource)).To(Succeed()) - - obj = &netbirdiov1.NBRoutingPeer{ - Status: netbirdiov1.NBRoutingPeerStatus{ - NetworkID: util.Ptr("test2"), - }, - } - }) - - AfterEach(func() { - nbResource := &netbirdiov1.NBResource{} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "isexist"}, nbResource) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - if len(nbResource.Finalizers) > 0 { - nbResource.Finalizers = nil - Expect(k8sClient.Update(ctx, nbResource)).To(Succeed()) - } - err = k8sClient.Delete(ctx, nbResource) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - } - } - }) - It("should allow deletion", func() { - Expect(validator.ValidateDelete(ctx, obj)).Error().NotTo(HaveOccurred()) - }) - }) - When("Exposed Services for Network Exist", func() { - BeforeEach(func() { - nbResource := &netbirdiov1.NBResource{ - ObjectMeta: v1.ObjectMeta{ - Name: "maw", - Namespace: "default", - }, - Spec: netbirdiov1.NBResourceSpec{ - Name: "test1", - NetworkID: "test2", - Address: "test3", - Groups: []string{"test"}, - }, - } - - Expect(k8sClient.Create(ctx, nbResource)).To(Succeed()) - - svc := &corev1.Service{ - ObjectMeta: v1.ObjectMeta{ - Name: "maw", - Namespace: "default", - Annotations: map[string]string{ - "netbird.io/expose": "true", - }, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Protocol: corev1.ProtocolTCP, - Port: 80, - TargetPort: intstr.FromInt32(80), - }, - }, - }, - } - Expect(k8sClient.Create(ctx, svc)).To(Succeed()) - - obj = &netbirdiov1.NBRoutingPeer{ - Status: netbirdiov1.NBRoutingPeerStatus{ - NetworkID: util.Ptr("test2"), - }, - } - }) - - AfterEach(func() { - nbResource := &netbirdiov1.NBResource{} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "maw"}, nbResource) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - if len(nbResource.Finalizers) > 0 { - nbResource.Finalizers = nil - Expect(k8sClient.Update(ctx, nbResource)).To(Succeed()) - } - err = k8sClient.Delete(ctx, nbResource) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - } - } - - svc := &corev1.Service{} - err = k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "maw"}, svc) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - if len(svc.Finalizers) > 0 { - svc.Finalizers = nil - Expect(k8sClient.Update(ctx, svc)).To(Succeed()) - } - err = k8sClient.Delete(ctx, svc) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - } - } - }) - It("should deny deletion", func() { - Expect(validator.ValidateDelete(ctx, obj)).Error().To(HaveOccurred()) - }) - }) - When("Exposed NBResources do not belong to network", func() { - BeforeEach(func() { - nbResource := &netbirdiov1.NBResource{ - ObjectMeta: v1.ObjectMeta{ - Name: "maw", - Namespace: "default", - }, - Spec: netbirdiov1.NBResourceSpec{ - Name: "test1", - NetworkID: "test5", - Address: "test3", - Groups: []string{"test"}, - }, - } - - Expect(k8sClient.Create(ctx, nbResource)).To(Succeed()) - - svc := &corev1.Service{ - ObjectMeta: v1.ObjectMeta{ - Name: "maw", - Namespace: "default", - Annotations: map[string]string{ - "netbird.io/expose": "true", - }, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Protocol: corev1.ProtocolTCP, - Port: 80, - TargetPort: intstr.FromInt32(80), - }, - }, - }, - } - Expect(k8sClient.Create(ctx, svc)).To(Succeed()) - - obj = &netbirdiov1.NBRoutingPeer{ - Status: netbirdiov1.NBRoutingPeerStatus{ - NetworkID: util.Ptr("test2"), - }, - } - }) - - AfterEach(func() { - nbResource := &netbirdiov1.NBResource{} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "maw"}, nbResource) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - if len(nbResource.Finalizers) > 0 { - nbResource.Finalizers = nil - Expect(k8sClient.Update(ctx, nbResource)).To(Succeed()) - } - err = k8sClient.Delete(ctx, nbResource) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - } - } - - svc := &corev1.Service{} - err = k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "maw"}, svc) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - if len(svc.Finalizers) > 0 { - svc.Finalizers = nil - Expect(k8sClient.Update(ctx, svc)).To(Succeed()) - } - err = k8sClient.Delete(ctx, svc) - if !errors.IsNotFound(err) { - Expect(err).NotTo(HaveOccurred()) - } - } - }) - It("should allow deletion", func() { - Expect(validator.ValidateDelete(ctx, obj)).Error().NotTo(HaveOccurred()) - }) - }) - }) - -}) diff --git a/internal/webhook/v1/webhook_suite_test.go b/internal/webhook/v1/webhook_suite_test.go index 85236e9..457ca5e 100644 --- a/internal/webhook/v1/webhook_suite_test.go +++ b/internal/webhook/v1/webhook_suite_test.go @@ -124,12 +124,6 @@ var _ = BeforeSuite(func() { err = SetupNBSetupKeyWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) - err = SetupNBResourceWebhookWithManager(mgr) - Expect(err).NotTo(HaveOccurred()) - - err = SetupNBRoutingPeerWebhookWithManager(mgr) - Expect(err).NotTo(HaveOccurred()) - err = SetupNBGroupWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred())