From 26479a19c03dedb91c7bfc91897e7b6f615e6044 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Mon, 13 Apr 2026 12:20:35 +0200 Subject: [PATCH] Implement new setup key resource (#178) This change implements a new resource called SetupKey that manages the lifecycle of setup keys and stores them in secrets. A major change here is that we are also switching to using SSA for resource management. Part of #172 Signed-off-by: Philip Laine --- Dockerfile | 1 + Makefile | 2 +- PROJECT | 8 + api/v1alpha1/groupversion_info.go | 23 ++ api/v1alpha1/setupkey_types.go | 67 +++++ api/v1alpha1/zz_generated.deepcopy.go | 116 +++++++++ cmd/main.go | 13 + examples/refactor/setup-key.yaml | 7 + go.mod | 4 +- go.sum | 36 +++ .../crds/netbird.io_setupkeys.yaml | 132 ++++++++++ internal/controller/setupkey_controller.go | 186 ++++++++++++++ .../controller/setupkey_controller_test.go | 191 ++++++++++++++ internal/controller/suite_test.go | 7 +- internal/ssautil/ssautil.go | 22 ++ .../api/v1alpha1/setupkey.go | 232 ++++++++++++++++++ .../api/v1alpha1/setupkeyspec.go | 40 +++ .../api/v1alpha1/setupkeystatus.go | 45 ++++ pkg/applyconfigurations/internal/internal.go | 46 ++++ pkg/applyconfigurations/utils.go | 32 +++ 20 files changed, 1206 insertions(+), 4 deletions(-) create mode 100644 api/v1alpha1/groupversion_info.go create mode 100644 api/v1alpha1/setupkey_types.go create mode 100644 api/v1alpha1/zz_generated.deepcopy.go create mode 100644 examples/refactor/setup-key.yaml create mode 100644 helm/kubernetes-operator/crds/netbird.io_setupkeys.yaml create mode 100644 internal/controller/setupkey_controller.go create mode 100644 internal/controller/setupkey_controller_test.go create mode 100644 internal/ssautil/ssautil.go create mode 100644 pkg/applyconfigurations/api/v1alpha1/setupkey.go create mode 100644 pkg/applyconfigurations/api/v1alpha1/setupkeyspec.go create mode 100644 pkg/applyconfigurations/api/v1alpha1/setupkeystatus.go create mode 100644 pkg/applyconfigurations/internal/internal.go create mode 100644 pkg/applyconfigurations/utils.go diff --git a/Dockerfile b/Dockerfile index 7df1026..f5c9d66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,7 @@ RUN go mod download COPY cmd/main.go cmd/main.go COPY api/ api/ COPY internal/ internal/ +COPY pkg/ pkg/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command diff --git a/Makefile b/Makefile index 8530644..953fd54 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ manifests: ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefin .PHONY: generate generate: ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. - go tool controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..." + go tool controller-gen applyconfiguration:headerFile="hack/boilerplate.go.txt" object:headerFile="hack/boilerplate.go.txt" paths="./..." .PHONY: fmt fmt: ## Run go fmt against code. diff --git a/PROJECT b/PROJECT index 193376e..8b05654 100644 --- a/PROJECT +++ b/PROJECT @@ -75,4 +75,12 @@ resources: kind: NBPolicy path: github.com/netbirdio/kubernetes-operator/api/v1 version: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: netbird.io + kind: SetupKey + path: github.com/netbirdio/kubernetes-operator/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go new file mode 100644 index 0000000..d2bf4a2 --- /dev/null +++ b/api/v1alpha1/groupversion_info.go @@ -0,0 +1,23 @@ +// Package v1alpha1 contains API Schema definitions for the v1alpha1 API group. +// +kubebuilder:object:generate=true +// +groupName=netbird.io +// +kubebuilder:ac:generate=true +// +kubebuilder:ac:output:package=../../pkg/applyconfigurations +package v1alpha1 + +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: "v1alpha1"} + SchemeGroupVersion = GroupVersion + + // 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 +) diff --git a/api/v1alpha1/setupkey_types.go b/api/v1alpha1/setupkey_types.go new file mode 100644 index 0000000..b43f446 --- /dev/null +++ b/api/v1alpha1/setupkey_types.go @@ -0,0 +1,67 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// SetupKeySpec defines the desired state of SetupKey +type SetupKeySpec struct { + // Ephemeral decides if peers added with the key are ephemeral or not. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ephemeral is immutable" + Ephemeral bool `json:"ephemeral"` + + // Duration sets how long the setup key is valid for. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="duration is immutable" + // +optional + Duration *metav1.Duration `json:"duration,omitempty"` +} + +// SetupKeyStatus defines the observed state of SetupKey. +type SetupKeyStatus struct { + // SetupKeyID of the setup key. + SetupKeyID *string `json:"setupKeyID,omitempty"` + + // The status of each condition is one of True, False, or Unknown. + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource + +// SetupKey is the Schema for the setupkeys API +type SetupKey struct { + metav1.TypeMeta `json:",inline"` + + // metadata is a standard object metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitzero"` + + // spec defines the desired state of SetupKey + // +required + Spec SetupKeySpec `json:"spec"` + + // status defines the observed state of SetupKey + // +optional + Status SetupKeyStatus `json:"status,omitzero"` +} + +func (sk SetupKey) SecretName() string { + return "setup-key-" + sk.Name +} + +// +kubebuilder:object:root=true + +// SetupKeyList contains a list of SetupKey +type SetupKeyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitzero"` + Items []SetupKey `json:"items"` +} + +func init() { + SchemeBuilder.Register(&SetupKey{}, &SetupKeyList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000..fbeb852 --- /dev/null +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,116 @@ +//go:build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" + 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 *SetupKey) DeepCopyInto(out *SetupKey) { + *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 SetupKey. +func (in *SetupKey) DeepCopy() *SetupKey { + if in == nil { + return nil + } + out := new(SetupKey) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SetupKey) 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 *SetupKeyList) DeepCopyInto(out *SetupKeyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SetupKey, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SetupKeyList. +func (in *SetupKeyList) DeepCopy() *SetupKeyList { + if in == nil { + return nil + } + out := new(SetupKeyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SetupKeyList) 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 *SetupKeySpec) DeepCopyInto(out *SetupKeySpec) { + *out = *in + if in.Duration != nil { + in, out := &in.Duration, &out.Duration + *out = new(v1.Duration) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SetupKeySpec. +func (in *SetupKeySpec) DeepCopy() *SetupKeySpec { + if in == nil { + return nil + } + out := new(SetupKeySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SetupKeyStatus) DeepCopyInto(out *SetupKeyStatus) { + *out = *in + if in.SetupKeyID != nil { + in, out := &in.SetupKeyID, &out.SetupKeyID + *out = new(string) + **out = **in + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SetupKeyStatus. +func (in *SetupKeyStatus) DeepCopy() *SetupKeyStatus { + if in == nil { + return nil + } + out := new(SetupKeyStatus) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/main.go b/cmd/main.go index e43e749..931f3dc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -36,6 +36,7 @@ import ( 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/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" @@ -44,6 +45,7 @@ import ( gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" + netbirdiov1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1" "github.com/netbirdio/kubernetes-operator/internal/controller" webhooknetbirdiov1 "github.com/netbirdio/kubernetes-operator/internal/webhook/v1" // +kubebuilder:scaffold:imports @@ -61,6 +63,7 @@ func init() { utilruntime.Must(corev1.AddToScheme(scheme)) utilruntime.Must(gatewayv1.Install(scheme)) utilruntime.Must(gatewayv1alpha2.Install(scheme)) + utilruntime.Must(netbirdiov1alpha1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -189,6 +192,9 @@ func main() { Metrics: metricsserver.Options{ BindAddress: metricsAddr, }, + Client: client.Options{ + FieldOwner: "netbird-operator", + }, WebhookServer: webhookServer, HealthProbeBindAddress: probeAddr, LeaderElectionNamespace: runtimeNamespace, @@ -277,6 +283,13 @@ func main() { } } + if err := (&controller.SetupKeyReconciler{ + Client: mgr.GetClient(), + Netbird: netbird, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "Failed to create controller", "controller", "SetupKey") + os.Exit(1) + } if gatewayAPIEnabled { if err = (&controller.GatewayClassReconciler{ Client: mgr.GetClient(), diff --git a/examples/refactor/setup-key.yaml b/examples/refactor/setup-key.yaml new file mode 100644 index 0000000..12d07df --- /dev/null +++ b/examples/refactor/setup-key.yaml @@ -0,0 +1,7 @@ +apiVersion: netbird.io/v1alpha1 +kind: SetupKey +metadata: + name: test + namespace: default +spec: + expiration: 0 diff --git a/go.mod b/go.mod index e00718a..97514dd 100644 --- a/go.mod +++ b/go.mod @@ -13,8 +13,10 @@ require ( k8s.io/api v0.35.2 k8s.io/apimachinery v0.35.2 k8s.io/client-go v0.35.2 + k8s.io/utils v0.0.0-20260108192941-914a6e750570 sigs.k8s.io/controller-runtime v0.23.3 sigs.k8s.io/gateway-api v1.5.1 + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 ) require ( @@ -87,11 +89,9 @@ require ( k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/utils v0.0.0-20260108192941-914a6e750570 // indirect sigs.k8s.io/controller-tools v0.20.1 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index f6fca31..46e8956 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= @@ -19,6 +21,8 @@ github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible h1:hqcTK6ZISdip65SR792lwYJTa/axESA0889D3UlZbLo= github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible/go.mod h1:6B1nuc1MUs6c62ODZDl7hVE5Pv7O2XGSkgg2olnq34I= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= @@ -53,6 +57,8 @@ github.com/beevik/etree v1.6.0 h1:u8Kwy8pp9D9XeITj2Z0XtA5qqZEmtJtuXZRQi+j03eE= github.com/beevik/etree v1.6.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc= 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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/c-robinson/iplib v1.0.3 h1:NG0UF0GoEsrC1/vyfX1Lx2Ss7CySWl3KqqXh3q4DdPU= github.com/c-robinson/iplib v1.0.3/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo= @@ -157,6 +163,8 @@ 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/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= +github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -180,6 +188,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -282,10 +292,14 @@ github.com/netbirdio/management-integrations/integrations v0.0.0-20260210160626- github.com/netbirdio/management-integrations/integrations v0.0.0-20260210160626-df4b180c7b25/go.mod h1:y7CxagMYzg9dgu+masRqYM7BQlOGA5Y8US85MCNFPlY= github.com/netbirdio/netbird v0.66.4 h1:jZt9e4yDjeBybLPcW4SQhhlsn15o2Jx0OzVPM9RY8mE= github.com/netbirdio/netbird v0.66.4/go.mod h1:Yeh8fZDsfngjcWedgGrzLKzsxKbGw2dQBOrJpLntpiw= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/okta/okta-sdk-golang/v2 v2.18.0 h1:cfDasMb7CShbZvOrF6n+DnLevWwiHgedWMGJ8M8xKDc= github.com/okta/okta-sdk-golang/v2 v2.18.0/go.mod h1:dz30v3ctAiMb7jpsCngGfQUAEGm1/NsWT92uTbNDQIs= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= @@ -340,6 +354,8 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= 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= @@ -389,6 +405,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6h go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= go.opentelemetry.io/otel/exporters/prometheus v0.48.0 h1:sBQe3VNGUjY9IKWQC6z2lNqa5iGbDSxhs60ABwK4y0s= go.opentelemetry.io/otel/exporters/prometheus v0.48.0/go.mod h1:DtrbMzoZWwQHyrQmCfLam5DZbnmorsGbOtTbYHycU5o= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= @@ -399,6 +419,8 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6 go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= 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/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= @@ -438,6 +460,10 @@ golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1 h1:EY138uSo1JYlDq+97u1FtcOUwPpIU6WL1Lkt7WpYjPA= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= @@ -447,6 +473,8 @@ 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/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA= google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= @@ -470,6 +498,8 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -489,10 +519,14 @@ k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJa k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8= k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/apiserver v0.35.0 h1:CUGo5o+7hW9GcAEF3x3usT3fX4f9r8xmgQeCBDaOgX4= +k8s.io/apiserver v0.35.0/go.mod h1:QUy1U4+PrzbJaM3XGu2tQ7U9A4udRRo5cyxkFX0GEds= k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o= k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g= k8s.io/code-generator v0.35.0 h1:TvrtfKYZTm9oDF2z+veFKSCcgZE3Igv0svY+ehCmjHQ= k8s.io/code-generator v0.35.0/go.mod h1:iS1gvVf3c/T71N5DOGYO+Gt3PdJ6B9LYSvIyQ4FHzgc= +k8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94= +k8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0= k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= @@ -501,6 +535,8 @@ k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZ k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY= k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= sigs.k8s.io/controller-tools v0.20.1 h1:gkfMt9YodI0K85oT8rVi80NTXO/kDmabKR5Ajn5GYxs= diff --git a/helm/kubernetes-operator/crds/netbird.io_setupkeys.yaml b/helm/kubernetes-operator/crds/netbird.io_setupkeys.yaml new file mode 100644 index 0000000..5d4a42b --- /dev/null +++ b/helm/kubernetes-operator/crds/netbird.io_setupkeys.yaml @@ -0,0 +1,132 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: setupkeys.netbird.io +spec: + group: netbird.io + names: + kind: SetupKey + listKind: SetupKeyList + plural: setupkeys + singular: setupkey + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: SetupKey is the Schema for the setupkeys 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: spec defines the desired state of SetupKey + properties: + duration: + description: Duration sets how long the setup key is valid for. + type: string + x-kubernetes-validations: + - message: duration is immutable + rule: self == oldSelf + ephemeral: + description: Ephemeral decides if peers added with the key are ephemeral + or not. + type: boolean + x-kubernetes-validations: + - message: ephemeral is immutable + rule: self == oldSelf + required: + - ephemeral + type: object + status: + description: status defines the observed state of SetupKey + properties: + conditions: + description: The status of each condition is one of True, False, or + Unknown. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + setupKeyID: + description: SetupKeyID of the setup key. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/internal/controller/setupkey_controller.go b/internal/controller/setupkey_controller.go new file mode 100644 index 0000000..d6bbf01 --- /dev/null +++ b/internal/controller/setupkey_controller.go @@ -0,0 +1,186 @@ +package controller + +import ( + "context" + "errors" + "time" + + netbird "github.com/netbirdio/netbird/shared/management/client/rest" + "github.com/netbirdio/netbird/shared/management/http/api" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + corev1ac "k8s.io/client-go/applyconfigurations/core/v1" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + nbv1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1" + "github.com/netbirdio/kubernetes-operator/internal/ssautil" + nbv1alpha1ac "github.com/netbirdio/kubernetes-operator/pkg/applyconfigurations/api/v1alpha1" +) + +const ( + SetupKeyFinalizer = "netbird.io/setupkey" + SetupKeySecretKey = "setup-key" +) + +type SetupKeyReconciler struct { + client.Client + + Netbird *netbird.Client +} + +// +kubebuilder:rbac:groups=netbird.io,resources=setupkeys,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=netbird.io,resources=setupkeys/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=netbird.io,resources=setupkeys/finalizers,verbs=update +func (r *SetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + setupKey := nbv1alpha1.SetupKey{} + err := r.Get(ctx, req.NamespacedName, &setupKey) + if err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if !setupKey.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, setupKey) + } + + // Set finalizer on the setup key. + setupKeyAC := nbv1alpha1ac.SetupKey(req.Name, req.Namespace).WithFinalizers(SetupKeyFinalizer) + err = r.Client.Apply(ctx, setupKeyAC) + if err != nil { + return ctrl.Result{}, err + } + + // Check if setup key is up to date. + ok, err := func() (bool, error) { + if setupKey.Status.SetupKeyID == nil { + return false, nil + } + + // Check setup key in Netbird. + resp, err := r.Netbird.SetupKeys.Get(ctx, *setupKey.Status.SetupKeyID) + if netbird.IsNotFound(err) { + return false, nil + } + if err != nil { + return false, err + } + + switch resp.State { + case "valid": + case "overused": + return false, errors.New("setup key is overused") + default: + return false, nil + } + + // Secret exists and has not been modified. + secret := &corev1.Secret{} + err = r.Client.Get(ctx, client.ObjectKey{Name: setupKey.SecretName(), Namespace: req.Namespace}, secret) + if kerrors.IsNotFound(err) { + return false, nil + } + if err != nil { + return false, err + } + if resp.Key[:5] != string(secret.Data[SetupKeySecretKey])[:5] { + return false, nil + } + + // Auto groups have not been changed. + setupKeyReq := api.PutApiSetupKeysKeyIdJSONRequestBody{ + AutoGroups: []string{}, + } + _, err = r.Netbird.SetupKeys.Update(ctx, *setupKey.Status.SetupKeyID, setupKeyReq) + if err != nil { + return false, err + } + + return true, nil + }() + if err != nil { + return ctrl.Result{}, err + } + if ok { + return ctrl.Result{RequeueAfter: 15 * time.Minute}, nil + } + oldSetupKeyID := setupKey.Status.SetupKeyID + + // Setup key does not exist so we create one. + expiresIn := 0 + if setupKey.Spec.Duration != nil { + expiresIn = int(setupKey.Spec.Duration.Seconds()) + } + setupKeyReq := api.PostApiSetupKeysJSONRequestBody{ + AllowExtraDnsLabels: ptr.To(false), + AutoGroups: []string{}, + Ephemeral: ptr.To(setupKey.Spec.Ephemeral), + ExpiresIn: expiresIn, + Name: req.Name, + Type: "reusable", + UsageLimit: 0, + } + resp, err := r.Netbird.SetupKeys.Create(ctx, setupKeyReq) + if err != nil { + return ctrl.Result{}, err + } + + // Update the status with the id. + setupKeyAC = nbv1alpha1ac.SetupKey(req.Name, req.Namespace).WithStatus(nbv1alpha1ac.SetupKeyStatus().WithSetupKeyID(resp.Id)) + err = r.Client.Status().Apply(ctx, setupKeyAC) + if err != nil { + return ctrl.Result{}, err + } + + // Create the secret containing the key. + owner, err := ssautil.OwnerReference(&setupKey, r.Scheme()) + if err != nil { + return ctrl.Result{}, err + } + data := map[string]string{ + SetupKeySecretKey: resp.Key, + } + secret := corev1ac.Secret(setupKey.SecretName(), req.Namespace). + WithStringData(data). + WithOwnerReferences(owner) + err = r.Client.Apply(ctx, secret) + if err != nil { + return ctrl.Result{}, err + } + + // Delete the old status key if we are recreating. + if oldSetupKeyID != nil { + err = r.Netbird.SetupKeys.Delete(ctx, *oldSetupKeyID) + if err != nil && !netbird.IsNotFound(err) { + return ctrl.Result{}, err + } + } + + return ctrl.Result{RequeueAfter: 15 * time.Minute}, nil +} + +func (r *SetupKeyReconciler) reconcileDelete(ctx context.Context, setupKey nbv1alpha1.SetupKey) (ctrl.Result, error) { + if setupKey.Status.SetupKeyID == nil { + return ctrl.Result{}, nil + } + + err := r.Netbird.SetupKeys.Delete(ctx, *setupKey.Status.SetupKeyID) + if err != nil && !netbird.IsNotFound(err) { + return ctrl.Result{}, err + } + + setupKeyAC := nbv1alpha1ac.SetupKey(setupKey.Name, setupKey.Namespace).WithFinalizers() + err = r.Client.Apply(ctx, setupKeyAC) + if err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +func (r *SetupKeyReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&nbv1alpha1.SetupKey{}). + Owns(&corev1.Secret{}). + Complete(r) +} diff --git a/internal/controller/setupkey_controller_test.go b/internal/controller/setupkey_controller_test.go new file mode 100644 index 0000000..748c746 --- /dev/null +++ b/internal/controller/setupkey_controller_test.go @@ -0,0 +1,191 @@ +package controller + +import ( + "context" + "encoding/json" + "fmt" + "io" + "math/rand" + "net/http" + "net/http/httptest" + + netbird "github.com/netbirdio/netbird/shared/management/client/rest" + "github.com/netbirdio/netbird/shared/management/http/api" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + nbv1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1" +) + +var _ = Describe("SetupKey Controller", func() { + Context("When reconciling a resource", func() { + ctx := context.Background() + + r := rand.New(rand.NewSource(GinkgoRandomSeed())) + + setupKeyStore := map[string]*api.SetupKey{} + mux := &http.ServeMux{} + mux.HandleFunc("/api/setup-keys", func(rw http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodPost: + resp := api.SetupKeyClear{ + Id: fmt.Sprintf("id-%d", r.Int63()), + Key: fmt.Sprintf("%d", r.Int63()), + State: "valid", + } + b, err := json.Marshal(resp) + Expect(err).NotTo(HaveOccurred()) + _, err = rw.Write(b) + Expect(err).NotTo(HaveOccurred()) + + setupKey := api.SetupKey{ + Id: resp.Id, + Key: resp.Key, + State: resp.State, + } + setupKeyStore[resp.Id] = &setupKey + default: + rw.WriteHeader(http.StatusNotFound) + } + }) + mux.HandleFunc("/api/setup-keys/{id}", func(rw http.ResponseWriter, req *http.Request) { + id := req.PathValue("id") + setupKey, ok := setupKeyStore[id] + if !ok { + rw.WriteHeader(http.StatusNotFound) + return + } + + switch req.Method { + case http.MethodDelete: + delete(setupKeyStore, id) + rw.WriteHeader(http.StatusOK) + case http.MethodGet: + b, err := json.Marshal(setupKey) + Expect(err).NotTo(HaveOccurred()) + _, err = rw.Write(b) + Expect(err).NotTo(HaveOccurred()) + case http.MethodPut: + b, err := io.ReadAll(req.Body) + Expect(err).NotTo(HaveOccurred()) + putReq := api.SetupKeyRequest{} + err = json.Unmarshal(b, &putReq) + Expect(err).NotTo(HaveOccurred()) + + setupKey.AutoGroups = putReq.AutoGroups + + b, err = json.Marshal(setupKey) + Expect(err).NotTo(HaveOccurred()) + _, err = rw.Write(b) + Expect(err).NotTo(HaveOccurred()) + default: + rw.WriteHeader(http.StatusNotFound) + } + }) + server := httptest.NewServer(mux) + nbClient := netbird.New(server.URL, "ABC") + + var controllerReconciler *SetupKeyReconciler + nn := client.ObjectKey{ + Name: "test-resource", + Namespace: "default", + } + + BeforeEach(func() { + controllerReconciler = &SetupKeyReconciler{ + Client: k8sClient, + Netbird: nbClient, + } + setupKeyStore = map[string]*api.SetupKey{} + }) + + AfterEach(func() { + setupKey := &nbv1alpha1.SetupKey{} + err := k8sClient.Get(ctx, nn, setupKey) + if kerrors.IsNotFound(err) { + return + } + Expect(err).ToNot(HaveOccurred()) + Expect(k8sClient.Delete(ctx, setupKey)).To(Succeed()) + _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: nn}) + Expect(err).ToNot(HaveOccurred()) + }) + + It("creates a secret containing the setup key", func() { + setupKey := &nbv1alpha1.SetupKey{ + ObjectMeta: metav1.ObjectMeta{ + Name: nn.Name, + Namespace: nn.Namespace, + }, + } + Expect(k8sClient.Create(ctx, setupKey)).To(Succeed()) + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: nn}) + Expect(err).NotTo(HaveOccurred()) + + err = k8sClient.Get(ctx, nn, setupKey) + Expect(err).NotTo(HaveOccurred()) + Expect(*setupKey.Status.SetupKeyID).NotTo(BeEmpty()) + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: setupKey.SecretName(), + Namespace: "default", + }, + } + err = k8sClient.Get(ctx, client.ObjectKeyFromObject(secret), secret) + Expect(err).NotTo(HaveOccurred()) + Expect(string(secret.Data[SetupKeySecretKey])).To(Equal(setupKeyStore[*setupKey.Status.SetupKeyID].Key)) + }) + + It("creates a new setup key when the secret is deleted", func() { + setupKey := &nbv1alpha1.SetupKey{ + ObjectMeta: metav1.ObjectMeta{ + Name: nn.Name, + Namespace: nn.Namespace, + }, + } + Expect(k8sClient.Create(ctx, setupKey)).To(Succeed()) + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: nn}) + Expect(err).NotTo(HaveOccurred()) + + firstSetupKey := nbv1alpha1.SetupKey{} + err = k8sClient.Get(ctx, nn, &firstSetupKey) + Expect(err).NotTo(HaveOccurred()) + + firstSecret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: firstSetupKey.SecretName(), + Namespace: nn.Namespace, + }, + } + err = k8sClient.Get(ctx, client.ObjectKeyFromObject(&firstSecret), &firstSecret) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient.Delete(ctx, &firstSecret)).To(Succeed()) + + _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: nn}) + Expect(err).NotTo(HaveOccurred()) + secondSetupKey := nbv1alpha1.SetupKey{} + err = k8sClient.Get(ctx, nn, &secondSetupKey) + Expect(err).NotTo(HaveOccurred()) + + secondSecret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secondSetupKey.SecretName(), + Namespace: nn.Namespace, + }, + } + err = k8sClient.Get(ctx, client.ObjectKeyFromObject(&secondSecret), &secondSecret) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient.Delete(ctx, &secondSecret)).To(Succeed()) + + Expect(setupKeyStore).To(HaveLen(1)) + Expect(*firstSetupKey.Status.SetupKeyID).ToNot(Equal(*secondSetupKey.Status.SetupKeyID)) + Expect(firstSecret.Data[SetupKeySecretKey]).ToNot(BeEquivalentTo(secondSecret.Data[SetupKeySecretKey])) + }) + }) +}) diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 4e0db20..3e6333b 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -35,6 +35,7 @@ import ( foobarv1 "k8s.io/api/core/v1" netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1" + netbirdiov1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1" // +kubebuilder:scaffold:imports ) @@ -67,6 +68,9 @@ var _ = BeforeSuite(func() { err = foobarv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = netbirdiov1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + // +kubebuilder:scaffold:scheme By("bootstrapping test environment") @@ -85,7 +89,8 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme, FieldOwner: "netbird-operator"}) + Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) }) diff --git a/internal/ssautil/ssautil.go b/internal/ssautil/ssautil.go new file mode 100644 index 0000000..18289ae --- /dev/null +++ b/internal/ssautil/ssautil.go @@ -0,0 +1,22 @@ +package ssautil + +import ( + "k8s.io/apimachinery/pkg/runtime" + metav1ac "k8s.io/client-go/applyconfigurations/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +func OwnerReference(owner client.Object, scheme *runtime.Scheme) (*metav1ac.OwnerReferenceApplyConfiguration, error) { + gvk, err := apiutil.GVKForObject(owner, scheme) + if err != nil { + return nil, err + } + return metav1ac.OwnerReference(). + WithAPIVersion(gvk.GroupVersion().String()). + WithKind(gvk.Kind). + WithName(owner.GetName()). + WithUID(owner.GetUID()). + WithController(true). + WithBlockOwnerDeletion(true), nil +} diff --git a/pkg/applyconfigurations/api/v1alpha1/setupkey.go b/pkg/applyconfigurations/api/v1alpha1/setupkey.go new file mode 100644 index 0000000..1d46c98 --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/setupkey.go @@ -0,0 +1,232 @@ +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// SetupKeyApplyConfiguration represents a declarative configuration of the SetupKey type for use +// with apply. +// +// SetupKey is the Schema for the setupkeys API +type SetupKeyApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + // metadata is a standard object metadata + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + // spec defines the desired state of SetupKey + Spec *SetupKeySpecApplyConfiguration `json:"spec,omitempty"` + // status defines the observed state of SetupKey + Status *SetupKeyStatusApplyConfiguration `json:"status,omitempty"` +} + +// SetupKey constructs a declarative configuration of the SetupKey type for use with +// apply. +func SetupKey(name, namespace string) *SetupKeyApplyConfiguration { + b := &SetupKeyApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("SetupKey") + b.WithAPIVersion("netbird.io/v1alpha1") + return b +} + +func (b SetupKeyApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithKind(value string) *SetupKeyApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithAPIVersion(value string) *SetupKeyApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithName(value string) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithGenerateName(value string) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithNamespace(value string) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithUID(value types.UID) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithResourceVersion(value string) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithGeneration(value int64) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithCreationTimestamp(value metav1.Time) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *SetupKeyApplyConfiguration) WithLabels(entries map[string]string) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *SetupKeyApplyConfiguration) WithAnnotations(entries map[string]string) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *SetupKeyApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *SetupKeyApplyConfiguration) WithFinalizers(values ...string) *SetupKeyApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *SetupKeyApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithSpec(value *SetupKeySpecApplyConfiguration) *SetupKeyApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *SetupKeyApplyConfiguration) WithStatus(value *SetupKeyStatusApplyConfiguration) *SetupKeyApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *SetupKeyApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *SetupKeyApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *SetupKeyApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *SetupKeyApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/applyconfigurations/api/v1alpha1/setupkeyspec.go b/pkg/applyconfigurations/api/v1alpha1/setupkeyspec.go new file mode 100644 index 0000000..938c2db --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/setupkeyspec.go @@ -0,0 +1,40 @@ +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// SetupKeySpecApplyConfiguration represents a declarative configuration of the SetupKeySpec type for use +// with apply. +// +// SetupKeySpec defines the desired state of SetupKey +type SetupKeySpecApplyConfiguration struct { + // Ephemeral decides if peers added with the key are ephemeral or not. + Ephemeral *bool `json:"ephemeral,omitempty"` + // Duration sets how long the setup key is valid for. + Duration *v1.Duration `json:"duration,omitempty"` +} + +// SetupKeySpecApplyConfiguration constructs a declarative configuration of the SetupKeySpec type for use with +// apply. +func SetupKeySpec() *SetupKeySpecApplyConfiguration { + return &SetupKeySpecApplyConfiguration{} +} + +// WithEphemeral sets the Ephemeral field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Ephemeral field is set to the value of the last call. +func (b *SetupKeySpecApplyConfiguration) WithEphemeral(value bool) *SetupKeySpecApplyConfiguration { + b.Ephemeral = &value + return b +} + +// WithDuration sets the Duration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Duration field is set to the value of the last call. +func (b *SetupKeySpecApplyConfiguration) WithDuration(value v1.Duration) *SetupKeySpecApplyConfiguration { + b.Duration = &value + return b +} diff --git a/pkg/applyconfigurations/api/v1alpha1/setupkeystatus.go b/pkg/applyconfigurations/api/v1alpha1/setupkeystatus.go new file mode 100644 index 0000000..a323a73 --- /dev/null +++ b/pkg/applyconfigurations/api/v1alpha1/setupkeystatus.go @@ -0,0 +1,45 @@ +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// SetupKeyStatusApplyConfiguration represents a declarative configuration of the SetupKeyStatus type for use +// with apply. +// +// SetupKeyStatus defines the observed state of SetupKey. +type SetupKeyStatusApplyConfiguration struct { + // SetupKeyID of the setup key. + SetupKeyID *string `json:"setupKeyID,omitempty"` + // The status of each condition is one of True, False, or Unknown. + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` +} + +// SetupKeyStatusApplyConfiguration constructs a declarative configuration of the SetupKeyStatus type for use with +// apply. +func SetupKeyStatus() *SetupKeyStatusApplyConfiguration { + return &SetupKeyStatusApplyConfiguration{} +} + +// WithSetupKeyID sets the SetupKeyID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SetupKeyID field is set to the value of the last call. +func (b *SetupKeyStatusApplyConfiguration) WithSetupKeyID(value string) *SetupKeyStatusApplyConfiguration { + b.SetupKeyID = &value + return b +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *SetupKeyStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *SetupKeyStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} diff --git a/pkg/applyconfigurations/internal/internal.go b/pkg/applyconfigurations/internal/internal.go new file mode 100644 index 0000000..35ead19 --- /dev/null +++ b/pkg/applyconfigurations/internal/internal.go @@ -0,0 +1,46 @@ +// Code generated by controller-gen. DO NOT EDIT. + +package internal + +import ( + fmt "fmt" + sync "sync" + + typed "sigs.k8s.io/structured-merge-diff/v6/typed" +) + +func Parser() *typed.Parser { + parserOnce.Do(func() { + var err error + parser, err = typed.NewParser(schemaYAML) + if err != nil { + panic(fmt.Sprintf("Failed to parse schema: %v", err)) + } + }) + return parser +} + +var parserOnce sync.Once +var parser *typed.Parser +var schemaYAML = typed.YAMLObject(`types: +- name: __untyped_atomic_ + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic +- name: __untyped_deduced_ + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable +`) diff --git a/pkg/applyconfigurations/utils.go b/pkg/applyconfigurations/utils.go new file mode 100644 index 0000000..2115ed4 --- /dev/null +++ b/pkg/applyconfigurations/utils.go @@ -0,0 +1,32 @@ +// Code generated by controller-gen. DO NOT EDIT. + +package applyconfigurations + +import ( + v1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1" + apiv1alpha1 "github.com/netbirdio/kubernetes-operator/pkg/applyconfigurations/api/v1alpha1" + internal "github.com/netbirdio/kubernetes-operator/pkg/applyconfigurations/internal" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" +) + +// ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no +// apply configuration type exists for the given GroupVersionKind. +func ForKind(kind schema.GroupVersionKind) interface{} { + switch kind { + // Group=netbird.io, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithKind("SetupKey"): + return &apiv1alpha1.SetupKeyApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SetupKeySpec"): + return &apiv1alpha1.SetupKeySpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("SetupKeyStatus"): + return &apiv1alpha1.SetupKeyStatusApplyConfiguration{} + + } + return nil +} + +func NewTypeConverter(scheme *runtime.Scheme) managedfields.TypeConverter { + return managedfields.NewSchemeTypeConverter(scheme, internal.Parser()) +}