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 <philip.laine@gmail.com>
This commit is contained in:
Philip Laine
2026-04-13 12:20:35 +02:00
committed by GitHub
parent 3efacfd4c8
commit 26479a19c0
20 changed files with 1206 additions and 4 deletions
+1
View File
@@ -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
+1 -1
View File
@@ -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.
+8
View File
@@ -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"
+23
View File
@@ -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
)
+67
View File
@@ -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{})
}
+116
View File
@@ -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
}
+13
View File
@@ -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(),
+7
View File
@@ -0,0 +1,7 @@
apiVersion: netbird.io/v1alpha1
kind: SetupKey
metadata:
name: test
namespace: default
spec:
expiration: 0
+2 -2
View File
@@ -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
)
+36
View File
@@ -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=
@@ -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: {}
+186
View File
@@ -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)
}
@@ -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]))
})
})
})
+6 -1
View File
@@ -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())
})
+22
View File
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
`)
+32
View File
@@ -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())
}