Implement group resource (#181)

This change implements a new group resource. 

It also sets the standard for a resource reference will be done through
out the controller. A resource reference can either be done by ID or as
a local named reference to the actual resource. This allows end users to
chose if they want to manage things completely in the cluster or not.

Part of #172

Signed-off-by: Philip Laine <philip.laine@gmail.com>
This commit is contained in:
Philip Laine
2026-04-15 10:16:10 +02:00
committed by GitHub
parent 26479a19c0
commit 9c4ca73712
19 changed files with 1026 additions and 11 deletions
+8
View File
@@ -83,4 +83,12 @@ resources:
kind: SetupKey
path: github.com/netbirdio/kubernetes-operator/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: netbird.io
kind: Group
path: github.com/netbirdio/kubernetes-operator/api/v1alpha1
version: v1alpha1
version: "3"
+58
View File
@@ -0,0 +1,58 @@
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// GroupSpec defines the desired state of Group
type GroupSpec struct {
// name of the group.
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
}
// GroupStatus defines the observed state of Group.
type GroupStatus struct {
// +optional
GroupID *string `json:"groupID,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
// Group is the Schema for the groups API
type Group struct {
metav1.TypeMeta `json:",inline"`
// metadata is a standard object metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitzero"`
// spec defines the desired state of Group
// +required
Spec GroupSpec `json:"spec"`
// status defines the observed state of Group
// +optional
Status GroupStatus `json:"status,omitzero"`
}
// +kubebuilder:object:root=true
// GroupList contains a list of Group
type GroupList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitzero"`
Items []Group `json:"items"`
}
func init() {
SchemeBuilder.Register(&Group{}, &GroupList{})
}
+14
View File
@@ -0,0 +1,14 @@
package v1alpha1
import corev1 "k8s.io/api/core/v1"
// +kubebuilder:validation:XValidation:rule="(has(self.id) && !has(self.localRef)) || (!has(self.id) && has(self.localRef))",message="exactly one of id or localRef must be set"
type ResourceReference struct {
// id of the resource in the Netbird API.
// +optional
ID *string `json:"id,omitempty"`
// local reference to the object in the same namespace.
// +optional
LocalRef *corev1.LocalObjectReference `json:"localRef,omitempty"`
}
+4
View File
@@ -14,6 +14,10 @@ type SetupKeySpec struct {
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="duration is immutable"
// +optional
Duration *metav1.Duration `json:"duration,omitempty"`
// Groups that will be automatically assigned to resources using setup key.
// +optional
AutoGroups []ResourceReference `json:"autoGroups,omitempty"`
}
// SetupKeyStatus defines the observed state of SetupKey.
+134
View File
@@ -5,10 +5,137 @@
package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
"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 *Group) DeepCopyInto(out *Group) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Group.
func (in *Group) DeepCopy() *Group {
if in == nil {
return nil
}
out := new(Group)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Group) 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 *GroupList) DeepCopyInto(out *GroupList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Group, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupList.
func (in *GroupList) DeepCopy() *GroupList {
if in == nil {
return nil
}
out := new(GroupList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *GroupList) 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 *GroupSpec) DeepCopyInto(out *GroupSpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupSpec.
func (in *GroupSpec) DeepCopy() *GroupSpec {
if in == nil {
return nil
}
out := new(GroupSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GroupStatus) DeepCopyInto(out *GroupStatus) {
*out = *in
if in.GroupID != nil {
in, out := &in.GroupID, &out.GroupID
*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 GroupStatus.
func (in *GroupStatus) DeepCopy() *GroupStatus {
if in == nil {
return nil
}
out := new(GroupStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceReference) DeepCopyInto(out *ResourceReference) {
*out = *in
if in.ID != nil {
in, out := &in.ID, &out.ID
*out = new(string)
**out = **in
}
if in.LocalRef != nil {
in, out := &in.LocalRef, &out.LocalRef
*out = new(corev1.LocalObjectReference)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceReference.
func (in *ResourceReference) DeepCopy() *ResourceReference {
if in == nil {
return nil
}
out := new(ResourceReference)
in.DeepCopyInto(out)
return out
}
// 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
@@ -76,6 +203,13 @@ func (in *SetupKeySpec) DeepCopyInto(out *SetupKeySpec) {
*out = new(v1.Duration)
**out = **in
}
if in.AutoGroups != nil {
in, out := &in.AutoGroups, &out.AutoGroups
*out = make([]ResourceReference, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SetupKeySpec.
+8
View File
@@ -290,6 +290,14 @@ func main() {
setupLog.Error(err, "Failed to create controller", "controller", "SetupKey")
os.Exit(1)
}
if err := (&controller.GroupReconciler{
Client: mgr.GetClient(),
Netbird: netbird,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "Failed to create controller", "controller", "Group")
os.Exit(1)
}
if gatewayAPIEnabled {
if err = (&controller.GatewayClassReconciler{
Client: mgr.GetClient(),
+7
View File
@@ -0,0 +1,7 @@
apiVersion: netbird.io/v1alpha1
kind: Group
metadata:
name: test
namespace: default
spec:
name: test
@@ -0,0 +1,122 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.20.1
name: groups.netbird.io
spec:
group: netbird.io
names:
kind: Group
listKind: GroupList
plural: groups
singular: group
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Group is the Schema for the groups 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 Group
properties:
name:
description: name of the group.
minLength: 1
type: string
required:
- name
type: object
status:
description: status defines the observed state of Group
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
groupID:
type: string
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
@@ -39,6 +39,34 @@ spec:
spec:
description: spec defines the desired state of SetupKey
properties:
autoGroups:
description: Groups that will be automatically assigned to resources
using setup key.
items:
properties:
id:
description: id of the resource in the Netbird API.
type: string
localRef:
description: local reference to the object in the same namespace.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
type: object
x-kubernetes-validations:
- message: exactly one of id or localRef must be set
rule: (has(self.id) && !has(self.localRef)) || (!has(self.id)
&& has(self.localRef))
type: array
duration:
description: Duration sets how long the setup key is valid for.
type: string
+103
View File
@@ -0,0 +1,103 @@
package controller
import (
"context"
netbird "github.com/netbirdio/netbird/shared/management/client/rest"
"github.com/netbirdio/netbird/shared/management/http/api"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
nbv1alpha1 "github.com/netbirdio/kubernetes-operator/api/v1alpha1"
nbv1alpha1ac "github.com/netbirdio/kubernetes-operator/pkg/applyconfigurations/api/v1alpha1"
)
const (
GroupFinalizer = "netbird.io/group"
)
// GroupReconciler reconciles a Group object
type GroupReconciler struct {
client.Client
Netbird *netbird.Client
}
// +kubebuilder:rbac:groups=netbird.io,resources=groups,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=netbird.io,resources=groups/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=netbird.io,resources=groups/finalizers,verbs=update
func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
group := nbv1alpha1.Group{}
err := r.Get(ctx, req.NamespacedName, &group)
if err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if !group.DeletionTimestamp.IsZero() {
return r.reconcileDelete(ctx, group)
}
groupAC := nbv1alpha1ac.Group(req.Name, req.Namespace).WithFinalizers(SetupKeyFinalizer)
err = r.Client.Apply(ctx, groupAC)
if err != nil {
return ctrl.Result{}, err
}
groupID, err := func() (string, error) {
if group.Status.GroupID != nil {
groupReq := api.GroupRequest{
Name: group.Spec.Name,
}
resp, err := r.Netbird.Groups.Update(ctx, *group.Status.GroupID, groupReq)
if err != nil && !netbird.IsNotFound(err) {
return "", err
}
if err == nil {
return resp.Id, nil
}
}
groupReq := api.GroupRequest{
Name: group.Spec.Name,
}
resp, err := r.Netbird.Groups.Create(ctx, groupReq)
if err != nil {
return "", err
}
return resp.Id, nil
}()
if err != nil {
return ctrl.Result{}, err
}
groupAC = nbv1alpha1ac.Group(req.Name, req.Namespace).WithStatus(nbv1alpha1ac.GroupStatus().WithGroupID(groupID))
err = r.Client.Status().Apply(ctx, groupAC)
if err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *GroupReconciler) reconcileDelete(ctx context.Context, group nbv1alpha1.Group) (ctrl.Result, error) {
if group.Status.GroupID != nil {
err := r.Netbird.Groups.Delete(ctx, *group.Status.GroupID)
if err != nil && !netbird.IsNotFound(err) {
return ctrl.Result{}, err
}
}
groupAC := nbv1alpha1ac.Group(group.Name, group.Namespace).WithFinalizers()
err := r.Client.Apply(ctx, groupAC)
if err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&nbv1alpha1.Group{}).
Complete(r)
}
@@ -0,0 +1,139 @@
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/netbirdio/netbird/shared/management/http/util"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
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("Group Controller", func() {
Context("When reconciling a resource", func() {
ctx := context.Background()
r := rand.New(rand.NewSource(GinkgoRandomSeed()))
groupStore := map[string]*api.Group{}
mux := &http.ServeMux{}
mux.Handle("POST /api/groups", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
b, err := io.ReadAll(req.Body)
Expect(err).NotTo(HaveOccurred())
groupReq := api.GroupRequest{}
err = json.Unmarshal(b, &groupReq)
Expect(err).NotTo(HaveOccurred())
groupResp := &api.Group{
Id: fmt.Sprintf("id-%d", r.Int63()),
Name: groupReq.Name,
}
groupStore[groupResp.Id] = groupResp
b, err = json.Marshal(groupResp)
Expect(err).NotTo(HaveOccurred())
_, err = rw.Write(b)
Expect(err).NotTo(HaveOccurred())
}))
mux.Handle("PUT /api/groups/{id}", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
id := req.PathValue("id")
groupResp, ok := groupStore[id]
if !ok {
util.WriteErrorResponse("Not Found", http.StatusNotFound, rw)
return
}
b, err := io.ReadAll(req.Body)
Expect(err).NotTo(HaveOccurred())
groupReq := api.GroupRequest{}
err = json.Unmarshal(b, &groupReq)
Expect(err).NotTo(HaveOccurred())
groupResp.Name = groupReq.Name
b, err = json.Marshal(groupResp)
Expect(err).NotTo(HaveOccurred())
_, err = rw.Write(b)
Expect(err).NotTo(HaveOccurred())
}))
mux.Handle("DELETE /api/groups/{id}", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
id := req.PathValue("id")
_, ok := groupStore[id]
if !ok {
util.WriteErrorResponse("Not Found", http.StatusNotFound, rw)
return
}
delete(groupStore, id)
}))
server := httptest.NewServer(mux)
nbClient := netbird.New(server.URL, "ABC")
var controllerReconciler *GroupReconciler
nn := client.ObjectKey{
Name: "test-resource",
Namespace: "default",
}
BeforeEach(func() {
controllerReconciler = &GroupReconciler{
Client: k8sClient,
Netbird: nbClient,
}
})
AfterEach(func() {
groupStore = map[string]*api.Group{}
group := &nbv1alpha1.Group{}
err := k8sClient.Get(ctx, nn, group)
if kerrors.IsNotFound(err) {
return
}
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient.Delete(ctx, group)).To(Succeed())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: nn})
Expect(err).ToNot(HaveOccurred())
})
It("ensures a Netbird group exists on reconcile", func() {
group := &nbv1alpha1.Group{
ObjectMeta: metav1.ObjectMeta{
Name: nn.Name,
Namespace: nn.Namespace,
},
Spec: nbv1alpha1.GroupSpec{
Name: "foobar",
},
}
Expect(k8sClient.Create(ctx, group)).To(Succeed())
By("creating a group on initial creation")
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: nn})
Expect(err).NotTo(HaveOccurred())
err = k8sClient.Get(ctx, nn, group)
Expect(err).NotTo(HaveOccurred())
Expect(*group.Status.GroupID).NotTo(BeEmpty())
By("crerating a new group when deleted from API")
delete(groupStore, *group.Status.GroupID)
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: nn})
Expect(err).NotTo(HaveOccurred())
newGroup := &nbv1alpha1.Group{}
err = k8sClient.Get(ctx, nn, newGroup)
Expect(err).NotTo(HaveOccurred())
Expect(*newGroup.Status.GroupID).NotTo(BeEmpty())
Expect(*newGroup.Status.GroupID).NotTo(Equal(*group.Status.GroupID))
})
})
})
+38 -10
View File
@@ -3,12 +3,14 @@ package controller
import (
"context"
"errors"
"fmt"
"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"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1ac "k8s.io/client-go/applyconfigurations/core/v1"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
@@ -44,6 +46,34 @@ func (r *SetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
return r.reconcileDelete(ctx, setupKey)
}
// Get ids for auto groups.
autoGroupIDs := []string{}
for _, ref := range setupKey.Spec.AutoGroups {
switch {
case ref.ID != nil:
_, err := r.Netbird.Groups.Get(ctx, *ref.ID)
if err != nil {
return ctrl.Result{}, err
}
autoGroupIDs = append(autoGroupIDs, *ref.ID)
case ref.LocalRef != nil:
group := nbv1alpha1.Group{
ObjectMeta: metav1.ObjectMeta{
Name: ref.LocalRef.Name,
Namespace: setupKey.Namespace,
},
}
err = r.Client.Get(ctx, client.ObjectKeyFromObject(&group), &group)
if err != nil {
return ctrl.Result{}, err
}
if group.Status.GroupID == nil {
return ctrl.Result{}, fmt.Errorf("group %s in auto groups list is not ready", group.Name)
}
autoGroupIDs = append(autoGroupIDs, *group.Status.GroupID)
}
}
// Set finalizer on the setup key.
setupKeyAC := nbv1alpha1ac.SetupKey(req.Name, req.Namespace).WithFinalizers(SetupKeyFinalizer)
err = r.Client.Apply(ctx, setupKeyAC)
@@ -89,7 +119,7 @@ func (r *SetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
// Auto groups have not been changed.
setupKeyReq := api.PutApiSetupKeysKeyIdJSONRequestBody{
AutoGroups: []string{},
AutoGroups: autoGroupIDs,
}
_, err = r.Netbird.SetupKeys.Update(ctx, *setupKey.Status.SetupKeyID, setupKeyReq)
if err != nil {
@@ -113,7 +143,7 @@ func (r *SetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
}
setupKeyReq := api.PostApiSetupKeysJSONRequestBody{
AllowExtraDnsLabels: ptr.To(false),
AutoGroups: []string{},
AutoGroups: autoGroupIDs,
Ephemeral: ptr.To(setupKey.Spec.Ephemeral),
ExpiresIn: expiresIn,
Name: req.Name,
@@ -160,17 +190,15 @@ func (r *SetupKeyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
}
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
if setupKey.Status.SetupKeyID != 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)
err := r.Client.Apply(ctx, setupKeyAC)
if err != nil {
return ctrl.Result{}, err
}
@@ -27,7 +27,6 @@ var _ = Describe("SetupKey Controller", 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) {
@@ -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"
)
// GroupApplyConfiguration represents a declarative configuration of the Group type for use
// with apply.
//
// Group is the Schema for the groups API
type GroupApplyConfiguration struct {
v1.TypeMetaApplyConfiguration `json:",inline"`
// metadata is a standard object metadata
*v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
// spec defines the desired state of Group
Spec *GroupSpecApplyConfiguration `json:"spec,omitempty"`
// status defines the observed state of Group
Status *GroupStatusApplyConfiguration `json:"status,omitempty"`
}
// Group constructs a declarative configuration of the Group type for use with
// apply.
func Group(name, namespace string) *GroupApplyConfiguration {
b := &GroupApplyConfiguration{}
b.WithName(name)
b.WithNamespace(namespace)
b.WithKind("Group")
b.WithAPIVersion("netbird.io/v1alpha1")
return b
}
func (b GroupApplyConfiguration) 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 *GroupApplyConfiguration) WithKind(value string) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithAPIVersion(value string) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithName(value string) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithGenerateName(value string) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithNamespace(value string) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithUID(value types.UID) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithResourceVersion(value string) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithGeneration(value int64) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithCreationTimestamp(value metav1.Time) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithLabels(entries map[string]string) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithAnnotations(entries map[string]string) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithFinalizers(values ...string) *GroupApplyConfiguration {
b.ensureObjectMetaApplyConfigurationExists()
for i := range values {
b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i])
}
return b
}
func (b *GroupApplyConfiguration) 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 *GroupApplyConfiguration) WithSpec(value *GroupSpecApplyConfiguration) *GroupApplyConfiguration {
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 *GroupApplyConfiguration) WithStatus(value *GroupStatusApplyConfiguration) *GroupApplyConfiguration {
b.Status = value
return b
}
// GetKind retrieves the value of the Kind field in the declarative configuration.
func (b *GroupApplyConfiguration) GetKind() *string {
return b.TypeMetaApplyConfiguration.Kind
}
// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration.
func (b *GroupApplyConfiguration) GetAPIVersion() *string {
return b.TypeMetaApplyConfiguration.APIVersion
}
// GetName retrieves the value of the Name field in the declarative configuration.
func (b *GroupApplyConfiguration) GetName() *string {
b.ensureObjectMetaApplyConfigurationExists()
return b.ObjectMetaApplyConfiguration.Name
}
// GetNamespace retrieves the value of the Namespace field in the declarative configuration.
func (b *GroupApplyConfiguration) GetNamespace() *string {
b.ensureObjectMetaApplyConfigurationExists()
return b.ObjectMetaApplyConfiguration.Namespace
}
@@ -0,0 +1,26 @@
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
// GroupSpecApplyConfiguration represents a declarative configuration of the GroupSpec type for use
// with apply.
//
// GroupSpec defines the desired state of Group
type GroupSpecApplyConfiguration struct {
// name of the group.
Name *string `json:"name,omitempty"`
}
// GroupSpecApplyConfiguration constructs a declarative configuration of the GroupSpec type for use with
// apply.
func GroupSpec() *GroupSpecApplyConfiguration {
return &GroupSpecApplyConfiguration{}
}
// 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 *GroupSpecApplyConfiguration) WithName(value string) *GroupSpecApplyConfiguration {
b.Name = &value
return b
}
@@ -0,0 +1,44 @@
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
import (
v1 "k8s.io/client-go/applyconfigurations/meta/v1"
)
// GroupStatusApplyConfiguration represents a declarative configuration of the GroupStatus type for use
// with apply.
//
// GroupStatus defines the observed state of Group.
type GroupStatusApplyConfiguration struct {
GroupID *string `json:"groupID,omitempty"`
// The status of each condition is one of True, False, or Unknown.
Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"`
}
// GroupStatusApplyConfiguration constructs a declarative configuration of the GroupStatus type for use with
// apply.
func GroupStatus() *GroupStatusApplyConfiguration {
return &GroupStatusApplyConfiguration{}
}
// WithGroupID sets the GroupID 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 GroupID field is set to the value of the last call.
func (b *GroupStatusApplyConfiguration) WithGroupID(value string) *GroupStatusApplyConfiguration {
b.GroupID = &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 *GroupStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *GroupStatusApplyConfiguration {
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,38 @@
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
import (
v1 "k8s.io/api/core/v1"
)
// ResourceReferenceApplyConfiguration represents a declarative configuration of the ResourceReference type for use
// with apply.
type ResourceReferenceApplyConfiguration struct {
// id of the resource in the Netbird API.
ID *string `json:"id,omitempty"`
// local reference to the object in the same namespace.
LocalRef *v1.LocalObjectReference `json:"localRef,omitempty"`
}
// ResourceReferenceApplyConfiguration constructs a declarative configuration of the ResourceReference type for use with
// apply.
func ResourceReference() *ResourceReferenceApplyConfiguration {
return &ResourceReferenceApplyConfiguration{}
}
// WithID sets the ID 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 ID field is set to the value of the last call.
func (b *ResourceReferenceApplyConfiguration) WithID(value string) *ResourceReferenceApplyConfiguration {
b.ID = &value
return b
}
// WithLocalRef sets the LocalRef 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 LocalRef field is set to the value of the last call.
func (b *ResourceReferenceApplyConfiguration) WithLocalRef(value v1.LocalObjectReference) *ResourceReferenceApplyConfiguration {
b.LocalRef = &value
return b
}
@@ -15,6 +15,8 @@ type SetupKeySpecApplyConfiguration struct {
Ephemeral *bool `json:"ephemeral,omitempty"`
// Duration sets how long the setup key is valid for.
Duration *v1.Duration `json:"duration,omitempty"`
// Groups that will be automatically assigned to resources using setup key.
AutoGroups []ResourceReferenceApplyConfiguration `json:"autoGroups,omitempty"`
}
// SetupKeySpecApplyConfiguration constructs a declarative configuration of the SetupKeySpec type for use with
@@ -38,3 +40,16 @@ func (b *SetupKeySpecApplyConfiguration) WithDuration(value v1.Duration) *SetupK
b.Duration = &value
return b
}
// WithAutoGroups adds the given value to the AutoGroups 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 AutoGroups field.
func (b *SetupKeySpecApplyConfiguration) WithAutoGroups(values ...*ResourceReferenceApplyConfiguration) *SetupKeySpecApplyConfiguration {
for i := range values {
if values[i] == nil {
panic("nil value passed to WithAutoGroups")
}
b.AutoGroups = append(b.AutoGroups, *values[i])
}
return b
}
+8
View File
@@ -16,6 +16,14 @@ import (
func ForKind(kind schema.GroupVersionKind) interface{} {
switch kind {
// Group=netbird.io, Version=v1alpha1
case v1alpha1.SchemeGroupVersion.WithKind("Group"):
return &apiv1alpha1.GroupApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("GroupSpec"):
return &apiv1alpha1.GroupSpecApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("GroupStatus"):
return &apiv1alpha1.GroupStatusApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("ResourceReference"):
return &apiv1alpha1.ResourceReferenceApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("SetupKey"):
return &apiv1alpha1.SetupKeyApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("SetupKeySpec"):