From b0c1050794521906f4cb665a76aee9e365bcbed5 Mon Sep 17 00:00:00 2001 From: Georgy Khromov Date: Mon, 26 May 2025 00:04:03 +0200 Subject: [PATCH 1/9] feat(appProjectRBAC)[#36]: add ArgoCDProjectRole and ArgoCDProjectRoleBinding CRDs + APIs Signed-off-by: Georgy Khromov --- Makefile | 2 +- PROJECT | 18 ++ api/v1alpha1/argocdprojectrole_types.go | 103 +++++++ .../argocdprojectrolebinding_types.go | 103 +++++++ api/v1alpha1/argocdrole_types.go | 6 +- api/v1alpha1/argocdrolebinding_types.go | 8 +- api/v1alpha1/status.go | 66 ++++ api/v1alpha1/zz_generated.deepcopy.go | 287 +++++++++++++++++- cmd/main.go | 14 + ...roj-labs.io_argocdprojectrolebindings.yaml | 133 ++++++++ ...r.argoproj-labs.io_argocdprojectroles.yaml | 140 +++++++++ ...r.argoproj-labs.io_argocdrolebindings.yaml | 3 +- ...operator.argoproj-labs.io_argocdroles.yaml | 3 +- config/crd/kustomization.yaml | 2 + config/rbac/argocdprojectrole_admin_role.yaml | 27 ++ .../rbac/argocdprojectrole_editor_role.yaml | 33 ++ .../rbac/argocdprojectrole_viewer_role.yaml | 29 ++ .../argocdprojectrolebinding_admin_role.yaml | 27 ++ .../argocdprojectrolebinding_editor_role.yaml | 33 ++ .../argocdprojectrolebinding_viewer_role.yaml | 29 ++ config/rbac/kustomization.yaml | 10 + config/rbac/role.yaml | 52 ++++ config/samples/kustomization.yaml | 2 + ...c-operator_v1alpha1_argocdprojectrole.yaml | 9 + ...tor_v1alpha1_argocdprojectrolebinding.yaml | 9 + helm/argocd-rbac-operator/Chart.yaml | 4 +- ...roj-labs.io_argocdprojectrolebindings.yaml | 133 ++++++++ ...r.argoproj-labs.io_argocdprojectroles.yaml | 140 +++++++++ ...r.argoproj-labs.io_argocdrolebindings.yaml | 3 +- ...operator.argoproj-labs.io_argocdroles.yaml | 3 +- .../argocdprojectrole_controller.go | 63 ++++ .../argocdprojectrole_controller_test.go | 84 +++++ .../argocdprojectrolebinding_controller.go | 63 ++++ ...rgocdprojectrolebinding_controller_test.go | 84 +++++ internal/controller/configmap.go | 4 +- internal/controller/suite_test.go | 116 +++++++ internal/controller/testing.go | 12 +- 37 files changed, 1823 insertions(+), 34 deletions(-) create mode 100644 api/v1alpha1/argocdprojectrole_types.go create mode 100644 api/v1alpha1/argocdprojectrolebinding_types.go create mode 100644 config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml create mode 100644 config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml create mode 100644 config/rbac/argocdprojectrole_admin_role.yaml create mode 100644 config/rbac/argocdprojectrole_editor_role.yaml create mode 100644 config/rbac/argocdprojectrole_viewer_role.yaml create mode 100644 config/rbac/argocdprojectrolebinding_admin_role.yaml create mode 100644 config/rbac/argocdprojectrolebinding_editor_role.yaml create mode 100644 config/rbac/argocdprojectrolebinding_viewer_role.yaml create mode 100644 config/samples/rbac-operator_v1alpha1_argocdprojectrole.yaml create mode 100644 config/samples/rbac-operator_v1alpha1_argocdprojectrolebinding.yaml create mode 100644 helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml create mode 100644 helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml create mode 100644 internal/controller/argocdprojectrole_controller.go create mode 100644 internal/controller/argocdprojectrole_controller_test.go create mode 100644 internal/controller/argocdprojectrolebinding_controller.go create mode 100644 internal/controller/argocdprojectrolebinding_controller_test.go create mode 100644 internal/controller/suite_test.go diff --git a/Makefile b/Makefile index d400742..9d82d81 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # VERSION defines the project version for the bundle. # Update this value when you upgrade the version of your project. -VERSION ?= 0.1.9 +VERSION ?= 0.2.0 # Try to detect Docker or Podman CONTAINER_TOOL := $(shell command -v docker 2> /dev/null) diff --git a/PROJECT b/PROJECT index a7b3754..7c34e89 100644 --- a/PROJECT +++ b/PROJECT @@ -25,4 +25,22 @@ resources: kind: ArgoCDRoleBinding path: github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: argoproj-labs.io + group: rbac-operator + kind: ArgoCDProjectRole + path: github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: argoproj-labs.io + group: rbac-operator + kind: ArgoCDProjectRoleBinding + path: github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/api/v1alpha1/argocdprojectrole_types.go b/api/v1alpha1/argocdprojectrole_types.go new file mode 100644 index 0000000..0fbf62d --- /dev/null +++ b/api/v1alpha1/argocdprojectrole_types.go @@ -0,0 +1,103 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "slices" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ArgoCDProjectRoleSpec defines the desired state of an AppProject scoped Role (patched to binded AppProject). +type ArgoCDProjectRoleSpec struct { + // Description of the role. + Description string `json:"description"` + Rules []ProjectRule `json:"rules"` +} + +// Rules define the desired set of permissions. +type ProjectRule struct { + // +kubebuilder:validation:Enum=clusters;applications;repositories;logs;exec;projects + // +kubebuilder:validation:example=applications + // Target resource type. + Resource string `json:"resource"` + // Verbs define the operations that are being performed on the resource. + Verbs []string `json:"verbs"` + // List of resource's objects the permissions are granted for. + Objects []string `json:"objects"` +} + +// ArgoCDProjectRoleStatus defines the observed state of ArgoCDProjectRole. +type ArgoCDProjectRoleStatus struct { + // argocdProjectRoleBindingRef defines the reference to the ArgoCDProjectRoleBinding Resource. + ArgoCDProjectRoleBindingRef string `json:"argocdProjectRoleBindingRef,omitempty"` + // +listType=map + // +listMapKey=type + // Conditions defines the list of conditions. + Conditions []Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +genclient + +// ArgoCDProjectRole is the Schema for the argocdprojectroles API. +type ArgoCDProjectRole struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ArgoCDProjectRoleSpec `json:"spec,omitempty"` + Status ArgoCDProjectRoleStatus `json:"status,omitempty"` +} + +// IsBeingDeleted returns true if a deletion timestamp is set +func (r *ArgoCDProjectRole) IsBeingDeleted() bool { + return !r.ObjectMeta.DeletionTimestamp.IsZero() +} + +// ArgoCDRoleFinalizerName is the name of the finalizer used to delete the Role +const ArgoCDProjectRoleFinalizerName = "rbac-operator.argoproj-labs.io/finalizer" + +// HasFinalizer returns true if the Role has the finalizer +func (r *ArgoCDProjectRole) HasFinalizer(finalizerName string) bool { + return slices.Contains(r.ObjectMeta.Finalizers, finalizerName) +} + +// AddFinalizer adds the finalizer to the Role +func (r *ArgoCDProjectRole) AddFinalizer(finalizerName string) { + r.ObjectMeta.Finalizers = append(r.ObjectMeta.Finalizers, finalizerName) +} + +// RemoveFinalizer removes the finalizer from the Role +func (r *ArgoCDProjectRole) RemoveFinalizer(finalizerName string) { + r.ObjectMeta.Finalizers = slices.DeleteFunc(r.ObjectMeta.Finalizers, func(s string) bool { + return s == finalizerName + }) +} + +// +kubebuilder:object:root=true + +// ArgoCDProjectRoleList contains a list of ArgoCDProjectRole. +type ArgoCDProjectRoleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ArgoCDProjectRole `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ArgoCDProjectRole{}, &ArgoCDProjectRoleList{}) +} diff --git a/api/v1alpha1/argocdprojectrolebinding_types.go b/api/v1alpha1/argocdprojectrolebinding_types.go new file mode 100644 index 0000000..44c4ad9 --- /dev/null +++ b/api/v1alpha1/argocdprojectrolebinding_types.go @@ -0,0 +1,103 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "slices" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ArgoCDProjectRoleBindingSpec defines the desired state of ArgoCDProjectRoleBinding. +type ArgoCDProjectRoleBindingSpec struct { + // List of subjects being bound to ArgoCDProjectRole (argocdProjectRoleRef). + Subjects []AppProjectSubject `json:"subjects,omitempty"` + ArgoCDProjectRoleRef ArgoCDProjectRoleRef `json:"argocdProjectRoleRef"` +} + +// AppProjectSubject defines the subject being bound to an AppProject scoped ArgoCDRole. +type AppProjectSubject struct { + // Reference to the AppProject the ArgoCDRole is bound to. + AppProjectRef string `json:"appProjectRef"` + // List of groups the role will be granted to. + Groups []string `json:"groups"` +} + +// ArgocdProjectRoleRef defines the reference to the role being granted. +type ArgoCDProjectRoleRef struct { + // Name of the ArgoCDProjectRole. Should not start with "role:" + Name string `json:"name"` +} + +// ArgoCDProjectRoleBindingStatus defines the observed state of ArgoCDProjectRoleBinding. +type ArgoCDProjectRoleBindingStatus struct { + // +listType=map + // +listMapKey=type + // Conditions defines the list of conditions. + Conditions []Condition `json:"conditions,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +genclient + +// ArgoCDProjectRoleBinding is the Schema for the argocdprojectrolebindings API. +type ArgoCDProjectRoleBinding struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ArgoCDProjectRoleBindingSpec `json:"spec,omitempty"` + Status ArgoCDProjectRoleBindingStatus `json:"status,omitempty"` +} + +// IsBeingDeleted returns true if a deletion timestamp is set +func (r *ArgoCDProjectRoleBinding) IsBeingDeleted() bool { + return !r.ObjectMeta.DeletionTimestamp.IsZero() +} + +// ArgoCDProjectRoleFinalizerName is the name of the finalizer used to delete the Role +const ArgoCDProjectRoleBindingFinalizerName = "rbac-operator.argoproj-labs.io/finalizer" + +// HasFinalizer returns true if the Role has the finalizer +func (r *ArgoCDProjectRoleBinding) HasFinalizer(finalizerName string) bool { + return slices.Contains(r.ObjectMeta.Finalizers, finalizerName) +} + +// AddFinalizer adds the finalizer to the Role +func (r *ArgoCDProjectRoleBinding) AddFinalizer(finalizerName string) { + r.ObjectMeta.Finalizers = append(r.ObjectMeta.Finalizers, finalizerName) +} + +// RemoveFinalizer removes the finalizer from the Role +func (r *ArgoCDProjectRoleBinding) RemoveFinalizer(finalizerName string) { + r.ObjectMeta.Finalizers = slices.DeleteFunc(r.ObjectMeta.Finalizers, func(s string) bool { + return s == finalizerName + }) +} + +// +kubebuilder:object:root=true + +// ArgoCDProjectRoleBindingList contains a list of ArgoCDProjectRoleBinding. +type ArgoCDProjectRoleBindingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ArgoCDProjectRoleBinding `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ArgoCDProjectRoleBinding{}, &ArgoCDProjectRoleBindingList{}) +} diff --git a/api/v1alpha1/argocdrole_types.go b/api/v1alpha1/argocdrole_types.go index b83f742..f814726 100644 --- a/api/v1alpha1/argocdrole_types.go +++ b/api/v1alpha1/argocdrole_types.go @@ -22,13 +22,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// ArgoCDRoleSpec defines the desired state of Role +// ArgoCDRoleSpec defines the desired state of global scoped Role (written to argocd-rbac-cm ConfigMap) type ArgoCDRoleSpec struct { - Rules []Rule `json:"rules"` + Rules []GlobalRule `json:"rules"` } // Rules define the desired set of permissions. -type Rule struct { +type GlobalRule struct { // +kubebuilder:validation:Enum=clusters;projects;applications;applicationsets;repositories;certificates;accounts;gpgkeys;logs;exec;extensions // +kubebuilder:validation:example=clusters // Target resource type. diff --git a/api/v1alpha1/argocdrolebinding_types.go b/api/v1alpha1/argocdrolebinding_types.go index 2c276c6..5b056df 100644 --- a/api/v1alpha1/argocdrolebinding_types.go +++ b/api/v1alpha1/argocdrolebinding_types.go @@ -25,12 +25,12 @@ import ( // ArgoCDRoleBindingSpec defines the desired state of ArgoCDRoleBinding type ArgoCDRoleBindingSpec struct { // List of subjects being bound to ArgoCDRole (argocdRoleRef). - Subjects []Subject `json:"subjects"` - ArgoCDRoleRef ArgoCDRoleRef `json:"argocdRoleRef"` + Subjects []GlobalSubject `json:"subjects,omitempty"` + ArgoCDRoleRef ArgoCDRoleRef `json:"argocdRoleRef"` } -// Subject defines the subject being bound to ArgoCDRole. -type Subject struct { +// GlobalSubject defines the subject being bound to ArgoCDRole. +type GlobalSubject struct { // +kubebuilder:validation:Enum=sso;local;role // Kind of the subject (sso, local or role). Kind string `json:"kind"` diff --git a/api/v1alpha1/status.go b/api/v1alpha1/status.go index a3a2056..3fc2418 100644 --- a/api/v1alpha1/status.go +++ b/api/v1alpha1/status.go @@ -135,6 +135,43 @@ func (r *ArgoCDRole) HasArgoCDRoleBindingRef() bool { return r.Status.ArgoCDRoleBindingRef != "" } +// SetConditions sets the supplied conditions, replacing any existing conditions +// of the same type. This is a no-op if all supplied conditions are identical, +// ignoring the last transition time, to those already set. +// Observed generation is updated if higher than the existing one. +func (r *ArgoCDProjectRole) SetConditions(c ...Condition) { + for _, new := range c { + exists := false + for i, existing := range r.Status.Conditions { + if existing.Type != new.Type { + continue + } + + if existing.Equal(new) { + exists = true + if existing.ObservedGeneration < new.ObservedGeneration { + existing.ObservedGeneration = new.ObservedGeneration + } + continue + } + + r.Status.Conditions[i] = new + exists = true + } + if !exists { + r.Status.Conditions = append(r.Status.Conditions, new) + } + } +} + +func (r *ArgoCDProjectRole) SetArgoCDProjectRoleBindingRef(ref string) { + r.Status.ArgoCDProjectRoleBindingRef = ref +} + +func (r *ArgoCDProjectRole) HasArgoCDProjectRoleBindingRef() bool { + return r.Status.ArgoCDProjectRoleBindingRef != "" +} + // SetConditions sets the supplied conditions, replacing any existing conditions // of the same type. This is a no-op if all supplied conditions are identical, // ignoring the last transition time, to those already set. @@ -164,6 +201,35 @@ func (rb *ArgoCDRoleBinding) SetConditions(c ...Condition) { } } +// SetConditions sets the supplied conditions, replacing any existing conditions +// of the same type. This is a no-op if all supplied conditions are identical, +// ignoring the last transition time, to those already set. +// Observed generation is updated if higher than the existing one. +func (rb *ArgoCDProjectRoleBinding) SetConditions(c ...Condition) { + for _, new := range c { + exists := false + for i, existing := range rb.Status.Conditions { + if existing.Type != new.Type { + continue + } + + if existing.Equal(new) { + exists = true + if existing.ObservedGeneration < new.ObservedGeneration { + existing.ObservedGeneration = new.ObservedGeneration + } + continue + } + + rb.Status.Conditions[i] = new + exists = true + } + if !exists { + rb.Status.Conditions = append(rb.Status.Conditions, new) + } + } +} + // Deleting returns a condition that indicates the resource is currently // being deleted. func Deleting() Condition { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index e47cff6..db43fee 100755 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -24,6 +24,248 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppProjectSubject) DeepCopyInto(out *AppProjectSubject) { + *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppProjectSubject. +func (in *AppProjectSubject) DeepCopy() *AppProjectSubject { + if in == nil { + return nil + } + out := new(AppProjectSubject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArgoCDProjectRole) DeepCopyInto(out *ArgoCDProjectRole) { + *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 ArgoCDProjectRole. +func (in *ArgoCDProjectRole) DeepCopy() *ArgoCDProjectRole { + if in == nil { + return nil + } + out := new(ArgoCDProjectRole) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ArgoCDProjectRole) 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 *ArgoCDProjectRoleBinding) DeepCopyInto(out *ArgoCDProjectRoleBinding) { + *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 ArgoCDProjectRoleBinding. +func (in *ArgoCDProjectRoleBinding) DeepCopy() *ArgoCDProjectRoleBinding { + if in == nil { + return nil + } + out := new(ArgoCDProjectRoleBinding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ArgoCDProjectRoleBinding) 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 *ArgoCDProjectRoleBindingList) DeepCopyInto(out *ArgoCDProjectRoleBindingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ArgoCDProjectRoleBinding, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDProjectRoleBindingList. +func (in *ArgoCDProjectRoleBindingList) DeepCopy() *ArgoCDProjectRoleBindingList { + if in == nil { + return nil + } + out := new(ArgoCDProjectRoleBindingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ArgoCDProjectRoleBindingList) 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 *ArgoCDProjectRoleBindingSpec) DeepCopyInto(out *ArgoCDProjectRoleBindingSpec) { + *out = *in + if in.Subjects != nil { + in, out := &in.Subjects, &out.Subjects + *out = make([]AppProjectSubject, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + out.ArgoCDProjectRoleRef = in.ArgoCDProjectRoleRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDProjectRoleBindingSpec. +func (in *ArgoCDProjectRoleBindingSpec) DeepCopy() *ArgoCDProjectRoleBindingSpec { + if in == nil { + return nil + } + out := new(ArgoCDProjectRoleBindingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArgoCDProjectRoleBindingStatus) DeepCopyInto(out *ArgoCDProjectRoleBindingStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDProjectRoleBindingStatus. +func (in *ArgoCDProjectRoleBindingStatus) DeepCopy() *ArgoCDProjectRoleBindingStatus { + if in == nil { + return nil + } + out := new(ArgoCDProjectRoleBindingStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArgoCDProjectRoleList) DeepCopyInto(out *ArgoCDProjectRoleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ArgoCDProjectRole, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDProjectRoleList. +func (in *ArgoCDProjectRoleList) DeepCopy() *ArgoCDProjectRoleList { + if in == nil { + return nil + } + out := new(ArgoCDProjectRoleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ArgoCDProjectRoleList) 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 *ArgoCDProjectRoleRef) DeepCopyInto(out *ArgoCDProjectRoleRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDProjectRoleRef. +func (in *ArgoCDProjectRoleRef) DeepCopy() *ArgoCDProjectRoleRef { + if in == nil { + return nil + } + out := new(ArgoCDProjectRoleRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArgoCDProjectRoleSpec) DeepCopyInto(out *ArgoCDProjectRoleSpec) { + *out = *in + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]ProjectRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDProjectRoleSpec. +func (in *ArgoCDProjectRoleSpec) DeepCopy() *ArgoCDProjectRoleSpec { + if in == nil { + return nil + } + out := new(ArgoCDProjectRoleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArgoCDProjectRoleStatus) DeepCopyInto(out *ArgoCDProjectRoleStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDProjectRoleStatus. +func (in *ArgoCDProjectRoleStatus) DeepCopy() *ArgoCDProjectRoleStatus { + if in == nil { + return nil + } + out := new(ArgoCDProjectRoleStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ArgoCDRole) DeepCopyInto(out *ArgoCDRole) { *out = *in @@ -115,7 +357,7 @@ func (in *ArgoCDRoleBindingSpec) DeepCopyInto(out *ArgoCDRoleBindingSpec) { *out = *in if in.Subjects != nil { in, out := &in.Subjects, &out.Subjects - *out = make([]Subject, len(*in)) + *out = make([]GlobalSubject, len(*in)) copy(*out, *in) } out.ArgoCDRoleRef = in.ArgoCDRoleRef @@ -205,7 +447,7 @@ func (in *ArgoCDRoleSpec) DeepCopyInto(out *ArgoCDRoleSpec) { *out = *in if in.Rules != nil { in, out := &in.Rules, &out.Rules - *out = make([]Rule, len(*in)) + *out = make([]GlobalRule, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -261,7 +503,7 @@ func (in *Condition) DeepCopy() *Condition { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Rule) DeepCopyInto(out *Rule) { +func (in *GlobalRule) DeepCopyInto(out *GlobalRule) { *out = *in if in.Verbs != nil { in, out := &in.Verbs, &out.Verbs @@ -275,27 +517,52 @@ func (in *Rule) DeepCopyInto(out *Rule) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule. -func (in *Rule) DeepCopy() *Rule { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalRule. +func (in *GlobalRule) DeepCopy() *GlobalRule { + if in == nil { + return nil + } + out := new(GlobalRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GlobalSubject) DeepCopyInto(out *GlobalSubject) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalSubject. +func (in *GlobalSubject) DeepCopy() *GlobalSubject { if in == nil { return nil } - out := new(Rule) + out := new(GlobalSubject) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Subject) DeepCopyInto(out *Subject) { +func (in *ProjectRule) DeepCopyInto(out *ProjectRule) { *out = *in + if in.Verbs != nil { + in, out := &in.Verbs, &out.Verbs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Objects != nil { + in, out := &in.Objects, &out.Objects + *out = make([]string, len(*in)) + copy(*out, *in) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subject. -func (in *Subject) DeepCopy() *Subject { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectRule. +func (in *ProjectRule) DeepCopy() *ProjectRule { if in == nil { return nil } - out := new(Subject) + out := new(ProjectRule) in.DeepCopyInto(out) return out } diff --git a/cmd/main.go b/cmd/main.go index 74ccfe2..8aa4fa2 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -146,6 +146,20 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "ArgoCDRoleBinding") os.Exit(1) } + if err := (&controller.ArgoCDProjectRoleReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ArgoCDProjectRole") + os.Exit(1) + } + if err := (&controller.ArgoCDProjectRoleBindingReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ArgoCDProjectRoleBinding") + os.Exit(1) + } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml new file mode 100644 index 0000000..fe415db --- /dev/null +++ b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml @@ -0,0 +1,133 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: argocdprojectrolebindings.rbac-operator.argoproj-labs.io +spec: + group: rbac-operator.argoproj-labs.io + names: + kind: ArgoCDProjectRoleBinding + listKind: ArgoCDProjectRoleBindingList + plural: argocdprojectrolebindings + singular: argocdprojectrolebinding + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ArgoCDProjectRoleBinding is the Schema for the argocdprojectrolebindings + 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: ArgoCDProjectRoleBindingSpec defines the desired state of + ArgoCDProjectRoleBinding. + properties: + argocdProjectRoleRef: + description: ArgocdProjectRoleRef defines the reference to the role + being granted. + properties: + name: + description: Name of the ArgoCDProjectRole. Should not start with + "role:" + type: string + required: + - name + type: object + subjects: + description: List of subjects being bound to ArgoCDProjectRole (argocdProjectRoleRef). + items: + description: AppProjectSubject defines the subject being bound to + an AppProject scoped ArgoCDRole. + properties: + appProjectRef: + description: Reference to the AppProject the ArgoCDRole is bound + to. + type: string + groups: + description: List of groups the role will be granted to. + items: + type: string + type: array + required: + - appProjectRef + - groups + type: object + type: array + required: + - argocdProjectRoleRef + type: object + status: + description: ArgoCDProjectRoleBindingStatus defines the observed state + of ArgoCDProjectRoleBinding. + properties: + conditions: + description: Conditions defines the list of conditions. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time this condition transitioned from one + status to another. + format: date-time + type: string + message: + description: |- + A Message containing details about this condition's last transition from + one status to another, if any. + 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 + type: integer + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown? + type: string + type: + description: |- + Type of this condition. At most one of each condition type may apply to + a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml new file mode 100644 index 0000000..d797764 --- /dev/null +++ b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml @@ -0,0 +1,140 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: argocdprojectroles.rbac-operator.argoproj-labs.io +spec: + group: rbac-operator.argoproj-labs.io + names: + kind: ArgoCDProjectRole + listKind: ArgoCDProjectRoleList + plural: argocdprojectroles + singular: argocdprojectrole + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ArgoCDProjectRole is the Schema for the argocdprojectroles 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: ArgoCDProjectRoleSpec defines the desired state of an AppProject + scoped Role (patched to binded AppProject). + properties: + description: + description: Description of the role. + type: string + rules: + items: + description: Rules define the desired set of permissions. + properties: + objects: + description: List of resource's objects the permissions are + granted for. + items: + type: string + type: array + resource: + description: Target resource type. + enum: + - clusters + - applications + - repositories + - logs + - exec + - projects + type: string + verbs: + description: Verbs define the operations that are being performed + on the resource. + items: + type: string + type: array + required: + - objects + - resource + - verbs + type: object + type: array + required: + - description + - rules + type: object + status: + description: ArgoCDProjectRoleStatus defines the observed state of ArgoCDProjectRole. + properties: + argocdProjectRoleBindingRef: + description: argocdProjectRoleBindingRef defines the reference to + the ArgoCDProjectRoleBinding Resource. + type: string + conditions: + description: Conditions defines the list of conditions. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time this condition transitioned from one + status to another. + format: date-time + type: string + message: + description: |- + A Message containing details about this condition's last transition from + one status to another, if any. + 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 + type: integer + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown? + type: string + type: + description: |- + Type of this condition. At most one of each condition type may apply to + a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml index 6f03c1f..ad15872 100644 --- a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml +++ b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml @@ -52,7 +52,7 @@ spec: subjects: description: List of subjects being bound to ArgoCDRole (argocdRoleRef). items: - description: Subject defines the subject being bound to ArgoCDRole. + description: GlobalSubject defines the subject being bound to ArgoCDRole. properties: kind: description: Kind of the subject (sso, local or role). @@ -72,7 +72,6 @@ spec: type: array required: - argocdRoleRef - - subjects type: object status: description: ArgoCDRoleBindingStatus defines the observed state of ArgoCDRoleBinding diff --git a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdroles.yaml b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdroles.yaml index cff43cc..e0b23ff 100644 --- a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdroles.yaml +++ b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdroles.yaml @@ -37,7 +37,8 @@ spec: metadata: type: object spec: - description: ArgoCDRoleSpec defines the desired state of Role + description: ArgoCDRoleSpec defines the desired state of global scoped + Role (written to argocd-rbac-cm ConfigMap) properties: rules: items: diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 180383c..2d27e9b 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -4,6 +4,8 @@ resources: - bases/rbac-operator.argoproj-labs.io_argocdroles.yaml - bases/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml +- bases/rbac-operator_argocdprojectroles.yaml +- bases/rbac-operator_argocdprojectrolebindings.yaml # +kubebuilder:scaffold:crdkustomizeresource patches: diff --git a/config/rbac/argocdprojectrole_admin_role.yaml b/config/rbac/argocdprojectrole_admin_role.yaml new file mode 100644 index 0000000..0295422 --- /dev/null +++ b/config/rbac/argocdprojectrole_admin_role.yaml @@ -0,0 +1,27 @@ +# This rule is not used by the project argocd-rbac-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants full permissions ('*') over rbac-operator. +# This role is intended for users authorized to modify roles and bindings within the cluster, +# enabling them to delegate specific permissions to other users or groups as needed. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: argocd-rbac-operator + app.kubernetes.io/managed-by: kustomize + name: argocdprojectrole-admin-role +rules: +- apiGroups: + - rbac-operator + resources: + - argocdprojectroles + verbs: + - '*' +- apiGroups: + - rbac-operator + resources: + - argocdprojectroles/status + verbs: + - get diff --git a/config/rbac/argocdprojectrole_editor_role.yaml b/config/rbac/argocdprojectrole_editor_role.yaml new file mode 100644 index 0000000..9b46fab --- /dev/null +++ b/config/rbac/argocdprojectrole_editor_role.yaml @@ -0,0 +1,33 @@ +# This rule is not used by the project argocd-rbac-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants permissions to create, update, and delete resources within the rbac-operator. +# This role is intended for users who need to manage these resources +# but should not control RBAC or manage permissions for others. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: argocd-rbac-operator + app.kubernetes.io/managed-by: kustomize + name: argocdprojectrole-editor-role +rules: +- apiGroups: + - rbac-operator + resources: + - argocdprojectroles + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac-operator + resources: + - argocdprojectroles/status + verbs: + - get diff --git a/config/rbac/argocdprojectrole_viewer_role.yaml b/config/rbac/argocdprojectrole_viewer_role.yaml new file mode 100644 index 0000000..0d57824 --- /dev/null +++ b/config/rbac/argocdprojectrole_viewer_role.yaml @@ -0,0 +1,29 @@ +# This rule is not used by the project argocd-rbac-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants read-only access to rbac-operator resources. +# This role is intended for users who need visibility into these resources +# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: argocd-rbac-operator + app.kubernetes.io/managed-by: kustomize + name: argocdprojectrole-viewer-role +rules: +- apiGroups: + - rbac-operator + resources: + - argocdprojectroles + verbs: + - get + - list + - watch +- apiGroups: + - rbac-operator + resources: + - argocdprojectroles/status + verbs: + - get diff --git a/config/rbac/argocdprojectrolebinding_admin_role.yaml b/config/rbac/argocdprojectrolebinding_admin_role.yaml new file mode 100644 index 0000000..8b41ef1 --- /dev/null +++ b/config/rbac/argocdprojectrolebinding_admin_role.yaml @@ -0,0 +1,27 @@ +# This rule is not used by the project argocd-rbac-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants full permissions ('*') over rbac-operator. +# This role is intended for users authorized to modify roles and bindings within the cluster, +# enabling them to delegate specific permissions to other users or groups as needed. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: argocd-rbac-operator + app.kubernetes.io/managed-by: kustomize + name: argocdprojectrolebinding-admin-role +rules: +- apiGroups: + - rbac-operator + resources: + - argocdprojectrolebindings + verbs: + - '*' +- apiGroups: + - rbac-operator + resources: + - argocdprojectrolebindings/status + verbs: + - get diff --git a/config/rbac/argocdprojectrolebinding_editor_role.yaml b/config/rbac/argocdprojectrolebinding_editor_role.yaml new file mode 100644 index 0000000..185b164 --- /dev/null +++ b/config/rbac/argocdprojectrolebinding_editor_role.yaml @@ -0,0 +1,33 @@ +# This rule is not used by the project argocd-rbac-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants permissions to create, update, and delete resources within the rbac-operator. +# This role is intended for users who need to manage these resources +# but should not control RBAC or manage permissions for others. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: argocd-rbac-operator + app.kubernetes.io/managed-by: kustomize + name: argocdprojectrolebinding-editor-role +rules: +- apiGroups: + - rbac-operator + resources: + - argocdprojectrolebindings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac-operator + resources: + - argocdprojectrolebindings/status + verbs: + - get diff --git a/config/rbac/argocdprojectrolebinding_viewer_role.yaml b/config/rbac/argocdprojectrolebinding_viewer_role.yaml new file mode 100644 index 0000000..62cdf13 --- /dev/null +++ b/config/rbac/argocdprojectrolebinding_viewer_role.yaml @@ -0,0 +1,29 @@ +# This rule is not used by the project argocd-rbac-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants read-only access to rbac-operator resources. +# This role is intended for users who need visibility into these resources +# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: argocd-rbac-operator + app.kubernetes.io/managed-by: kustomize + name: argocdprojectrolebinding-viewer-role +rules: +- apiGroups: + - rbac-operator + resources: + - argocdprojectrolebindings + verbs: + - get + - list + - watch +- apiGroups: + - rbac-operator + resources: + - argocdprojectrolebindings/status + verbs: + - get diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 2ccedea..5382467 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -18,3 +18,13 @@ resources: - argocdrole_editor_role.yaml - argocdrole_viewer_role.yaml +# For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by +# default, aiding admins in cluster management. Those roles are +# not used by the argocd-rbac-operator itself. You can comment the following lines +# if you do not want those helpers be installed with your Project. +- argocdprojectrolebinding_admin_role.yaml +- argocdprojectrolebinding_editor_role.yaml +- argocdprojectrolebinding_viewer_role.yaml +- argocdprojectrole_admin_role.yaml +- argocdprojectrole_editor_role.yaml +- argocdprojectrole_viewer_role.yaml \ No newline at end of file diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 6229dae..988da7f 100755 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -12,6 +12,58 @@ rules: - get - list - watch +- apiGroups: + - rbac-operator + resources: + - argocdprojectrolebindings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac-operator + resources: + - argocdprojectrolebindings/finalizers + verbs: + - update +- apiGroups: + - rbac-operator + resources: + - argocdprojectrolebindings/status + verbs: + - get + - patch + - update +- apiGroups: + - rbac-operator + resources: + - argocdprojectroles + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac-operator + resources: + - argocdprojectroles/finalizers + verbs: + - update +- apiGroups: + - rbac-operator + resources: + - argocdprojectroles/status + verbs: + - get + - patch + - update - apiGroups: - rbac-operator.argoproj-labs.io resources: diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index ffa3e64..2ff30f4 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -2,4 +2,6 @@ resources: - argocdrole.yaml - argocdrolebinding.yaml +- rbac-operator_v1alpha1_argocdprojectrole.yaml +- rbac-operator_v1alpha1_argocdprojectrolebinding.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/rbac-operator_v1alpha1_argocdprojectrole.yaml b/config/samples/rbac-operator_v1alpha1_argocdprojectrole.yaml new file mode 100644 index 0000000..37e6f76 --- /dev/null +++ b/config/samples/rbac-operator_v1alpha1_argocdprojectrole.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac-operator/v1alpha1 +kind: ArgoCDProjectRole +metadata: + labels: + app.kubernetes.io/name: argocd-rbac-operator + app.kubernetes.io/managed-by: kustomize + name: argocdprojectrole-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/rbac-operator_v1alpha1_argocdprojectrolebinding.yaml b/config/samples/rbac-operator_v1alpha1_argocdprojectrolebinding.yaml new file mode 100644 index 0000000..181bed4 --- /dev/null +++ b/config/samples/rbac-operator_v1alpha1_argocdprojectrolebinding.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac-operator/v1alpha1 +kind: ArgoCDProjectRoleBinding +metadata: + labels: + app.kubernetes.io/name: argocd-rbac-operator + app.kubernetes.io/managed-by: kustomize + name: argocdprojectrolebinding-sample +spec: + # TODO(user): Add fields here diff --git a/helm/argocd-rbac-operator/Chart.yaml b/helm/argocd-rbac-operator/Chart.yaml index be66399..e21aea0 100644 --- a/helm/argocd-rbac-operator/Chart.yaml +++ b/helm/argocd-rbac-operator/Chart.yaml @@ -1,9 +1,9 @@ apiVersion: v2 -appVersion: v0.1.9 +appVersion: v0.2.0 name: argocd-rbac-operator description: A Helm chart for Argo CD RBAC Operator, a Kubernetes Operator for Argo CD RBAC Management. type: application -version: 0.3.2 +version: 0.4.0 keywords: - argocd - operator diff --git a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml new file mode 100644 index 0000000..fe415db --- /dev/null +++ b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml @@ -0,0 +1,133 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: argocdprojectrolebindings.rbac-operator.argoproj-labs.io +spec: + group: rbac-operator.argoproj-labs.io + names: + kind: ArgoCDProjectRoleBinding + listKind: ArgoCDProjectRoleBindingList + plural: argocdprojectrolebindings + singular: argocdprojectrolebinding + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ArgoCDProjectRoleBinding is the Schema for the argocdprojectrolebindings + 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: ArgoCDProjectRoleBindingSpec defines the desired state of + ArgoCDProjectRoleBinding. + properties: + argocdProjectRoleRef: + description: ArgocdProjectRoleRef defines the reference to the role + being granted. + properties: + name: + description: Name of the ArgoCDProjectRole. Should not start with + "role:" + type: string + required: + - name + type: object + subjects: + description: List of subjects being bound to ArgoCDProjectRole (argocdProjectRoleRef). + items: + description: AppProjectSubject defines the subject being bound to + an AppProject scoped ArgoCDRole. + properties: + appProjectRef: + description: Reference to the AppProject the ArgoCDRole is bound + to. + type: string + groups: + description: List of groups the role will be granted to. + items: + type: string + type: array + required: + - appProjectRef + - groups + type: object + type: array + required: + - argocdProjectRoleRef + type: object + status: + description: ArgoCDProjectRoleBindingStatus defines the observed state + of ArgoCDProjectRoleBinding. + properties: + conditions: + description: Conditions defines the list of conditions. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time this condition transitioned from one + status to another. + format: date-time + type: string + message: + description: |- + A Message containing details about this condition's last transition from + one status to another, if any. + 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 + type: integer + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown? + type: string + type: + description: |- + Type of this condition. At most one of each condition type may apply to + a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml new file mode 100644 index 0000000..d797764 --- /dev/null +++ b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml @@ -0,0 +1,140 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: argocdprojectroles.rbac-operator.argoproj-labs.io +spec: + group: rbac-operator.argoproj-labs.io + names: + kind: ArgoCDProjectRole + listKind: ArgoCDProjectRoleList + plural: argocdprojectroles + singular: argocdprojectrole + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ArgoCDProjectRole is the Schema for the argocdprojectroles 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: ArgoCDProjectRoleSpec defines the desired state of an AppProject + scoped Role (patched to binded AppProject). + properties: + description: + description: Description of the role. + type: string + rules: + items: + description: Rules define the desired set of permissions. + properties: + objects: + description: List of resource's objects the permissions are + granted for. + items: + type: string + type: array + resource: + description: Target resource type. + enum: + - clusters + - applications + - repositories + - logs + - exec + - projects + type: string + verbs: + description: Verbs define the operations that are being performed + on the resource. + items: + type: string + type: array + required: + - objects + - resource + - verbs + type: object + type: array + required: + - description + - rules + type: object + status: + description: ArgoCDProjectRoleStatus defines the observed state of ArgoCDProjectRole. + properties: + argocdProjectRoleBindingRef: + description: argocdProjectRoleBindingRef defines the reference to + the ArgoCDProjectRoleBinding Resource. + type: string + conditions: + description: Conditions defines the list of conditions. + items: + description: A Condition that may apply to a resource. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the last time this condition transitioned from one + status to another. + format: date-time + type: string + message: + description: |- + A Message containing details about this condition's last transition from + one status to another, if any. + 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 + type: integer + reason: + description: A Reason for this condition's last transition from + one status to another. + type: string + status: + description: Status of this condition; is it currently True, + False, or Unknown? + type: string + type: + description: |- + Type of this condition. At most one of each condition type may apply to + a resource at any point in time. + type: string + required: + - lastTransitionTime + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml index 215d396..62a2eb0 100644 --- a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml +++ b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml @@ -52,7 +52,7 @@ spec: subjects: description: List of subjects being bound to ArgoCDRole (argocdRoleRef). items: - description: Subject defines the subject being bound to ArgoCDRole. + description: GlobalSubject defines the subject being bound to ArgoCDRole. properties: kind: description: Kind of the subject (sso, local or role). @@ -72,7 +72,6 @@ spec: type: array required: - argocdRoleRef - - subjects type: object status: description: ArgoCDRoleBindingStatus defines the observed state of ArgoCDRoleBinding diff --git a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdroles.yaml b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdroles.yaml index 31c7f25..604e639 100644 --- a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdroles.yaml +++ b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdroles.yaml @@ -37,7 +37,8 @@ spec: metadata: type: object spec: - description: ArgoCDRoleSpec defines the desired state of Role + description: ArgoCDRoleSpec defines the desired state of global scoped + Role (written to argocd-rbac-cm ConfigMap) properties: rules: items: diff --git a/internal/controller/argocdprojectrole_controller.go b/internal/controller/argocdprojectrole_controller.go new file mode 100644 index 0000000..13104f0 --- /dev/null +++ b/internal/controller/argocdprojectrole_controller.go @@ -0,0 +1,63 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" +) + +// ArgoCDProjectRoleReconciler reconciles a ArgoCDProjectRole object +type ArgoCDProjectRoleReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=rbac-operator,resources=argocdprojectroles,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=rbac-operator,resources=argocdprojectroles/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=rbac-operator,resources=argocdprojectroles/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the ArgoCDProjectRole object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile +func (r *ArgoCDProjectRoleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = logf.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ArgoCDProjectRoleReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&rbacoperatorv1alpha1.ArgoCDProjectRole{}). + Named("argocdprojectrole"). + Complete(r) +} diff --git a/internal/controller/argocdprojectrole_controller_test.go b/internal/controller/argocdprojectrole_controller_test.go new file mode 100644 index 0000000..17464d5 --- /dev/null +++ b/internal/controller/argocdprojectrole_controller_test.go @@ -0,0 +1,84 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" +) + +var _ = Describe("ArgoCDProjectRole Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + argocdprojectrole := &rbacoperatorv1alpha1.ArgoCDProjectRole{} + + BeforeEach(func() { + By("creating the custom resource for the Kind ArgoCDProjectRole") + err := k8sClient.Get(ctx, typeNamespacedName, argocdprojectrole) + if err != nil && errors.IsNotFound(err) { + resource := &rbacoperatorv1alpha1.ArgoCDProjectRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + // TODO(user): Specify other spec details if needed. + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + // TODO(user): Cleanup logic after each test, like removing the resource instance. + resource := &rbacoperatorv1alpha1.ArgoCDProjectRole{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance ArgoCDProjectRole") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &ArgoCDProjectRoleReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/internal/controller/argocdprojectrolebinding_controller.go b/internal/controller/argocdprojectrolebinding_controller.go new file mode 100644 index 0000000..e750182 --- /dev/null +++ b/internal/controller/argocdprojectrolebinding_controller.go @@ -0,0 +1,63 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" +) + +// ArgoCDProjectRoleBindingReconciler reconciles a ArgoCDProjectRoleBinding object +type ArgoCDProjectRoleBindingReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=rbac-operator,resources=argocdprojectrolebindings,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=rbac-operator,resources=argocdprojectrolebindings/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=rbac-operator,resources=argocdprojectrolebindings/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the ArgoCDProjectRoleBinding object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile +func (r *ArgoCDProjectRoleBindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = logf.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ArgoCDProjectRoleBindingReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{}). + Named("argocdprojectrolebinding"). + Complete(r) +} diff --git a/internal/controller/argocdprojectrolebinding_controller_test.go b/internal/controller/argocdprojectrolebinding_controller_test.go new file mode 100644 index 0000000..49684db --- /dev/null +++ b/internal/controller/argocdprojectrolebinding_controller_test.go @@ -0,0 +1,84 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" +) + +var _ = Describe("ArgoCDProjectRoleBinding Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + argocdprojectrolebinding := &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{} + + BeforeEach(func() { + By("creating the custom resource for the Kind ArgoCDProjectRoleBinding") + err := k8sClient.Get(ctx, typeNamespacedName, argocdprojectrolebinding) + if err != nil && errors.IsNotFound(err) { + resource := &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + // TODO(user): Specify other spec details if needed. + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + // TODO(user): Cleanup logic after each test, like removing the resource instance. + resource := &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance ArgoCDProjectRoleBinding") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &ArgoCDProjectRoleBindingReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/internal/controller/configmap.go b/internal/controller/configmap.go index c934661..b29315c 100644 --- a/internal/controller/configmap.go +++ b/internal/controller/configmap.go @@ -212,7 +212,7 @@ func (r *ArgoCDRoleBindingReconciler) createBuiltInAdminRole() *rbacoperatorv1al Namespace: r.ArgoCDRBACConfigMapNamespace, }, Spec: rbacoperatorv1alpha1.ArgoCDRoleSpec{ - Rules: []rbacoperatorv1alpha1.Rule{ + Rules: []rbacoperatorv1alpha1.GlobalRule{ { Resource: "applications", Verbs: []string{"override", "sync", "create", "update", "delete", "action", "get"}, @@ -276,7 +276,7 @@ func (r *ArgoCDRoleBindingReconciler) createBuiltInReadOnlyRole() *rbacoperatorv Namespace: r.ArgoCDRBACConfigMapNamespace, }, Spec: rbacoperatorv1alpha1.ArgoCDRoleSpec{ - Rules: []rbacoperatorv1alpha1.Rule{ + Rules: []rbacoperatorv1alpha1.GlobalRule{ { Resource: "applications", Verbs: []string{"get"}, diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go new file mode 100644 index 0000000..9dc487f --- /dev/null +++ b/internal/controller/suite_test.go @@ -0,0 +1,116 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "os" + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" + // +kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var ( + ctx context.Context + cancel context.CancelFunc + testEnv *envtest.Environment + cfg *rest.Config + k8sClient client.Client +) + +func TestControllers(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + var err error + err = rbacoperatorv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:scheme + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + // Retrieve the first found binary directory to allow running tests from IDEs + if getFirstFoundEnvTestBinaryDir() != "" { + testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir() + } + + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + cancel() + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) + +// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path. +// ENVTEST-based tests depend on specific binaries, usually located in paths set by +// controller-runtime. When running tests directly (e.g., via an IDE) without using +// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured. +// +// This function streamlines the process by finding the required binaries, similar to +// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are +// properly set up, run 'make setup-envtest' beforehand. +func getFirstFoundEnvTestBinaryDir() string { + basePath := filepath.Join("..", "..", "bin", "k8s") + entries, err := os.ReadDir(basePath) + if err != nil { + logf.Log.Error(err, "Failed to read directory", "path", basePath) + return "" + } + for _, entry := range entries { + if entry.IsDir() { + return filepath.Join(basePath, entry.Name()) + } + } + return "" +} diff --git a/internal/controller/testing.go b/internal/controller/testing.go index dd8e1e8..e9da618 100644 --- a/internal/controller/testing.go +++ b/internal/controller/testing.go @@ -97,7 +97,7 @@ func makeTestRole(opts ...argocdRoleOpt) *rbacoperatorv1alpha1.ArgoCDRole { Namespace: testNamespace, }, Spec: rbacoperatorv1alpha1.ArgoCDRoleSpec{ - Rules: []rbacoperatorv1alpha1.Rule{ + Rules: []rbacoperatorv1alpha1.GlobalRule{ { Resource: "applications", Verbs: []string{"get", "list"}, @@ -122,7 +122,7 @@ func makeTestRoleBindingWithRoleSubject(opts ...argocdRoleBindingOpt) *rbacopera ArgoCDRoleRef: rbacoperatorv1alpha1.ArgoCDRoleRef{ Name: testRoleName, }, - Subjects: []rbacoperatorv1alpha1.Subject{ + Subjects: []rbacoperatorv1alpha1.GlobalSubject{ { Kind: "role", Name: "rb-role-test", @@ -146,7 +146,7 @@ func makeTestRoleBindingWithSSOSubject(opts ...argocdRoleBindingOpt) *rbacoperat ArgoCDRoleRef: rbacoperatorv1alpha1.ArgoCDRoleRef{ Name: testRoleName, }, - Subjects: []rbacoperatorv1alpha1.Subject{ + Subjects: []rbacoperatorv1alpha1.GlobalSubject{ { Kind: "sso", Name: "gosha", @@ -170,7 +170,7 @@ func makeTestRoleBindingWithLocalSubject(opts ...argocdRoleBindingOpt) *rbacoper ArgoCDRoleRef: rbacoperatorv1alpha1.ArgoCDRoleRef{ Name: testRoleName, }, - Subjects: []rbacoperatorv1alpha1.Subject{ + Subjects: []rbacoperatorv1alpha1.GlobalSubject{ { Kind: "local", Name: "localUser", @@ -194,7 +194,7 @@ func makeTestRoleBindingForBuiltInAdmin(opts ...argocdRoleBindingOpt) *rbacopera ArgoCDRoleRef: rbacoperatorv1alpha1.ArgoCDRoleRef{ Name: common.ArgoCDRoleAdmin, }, - Subjects: []rbacoperatorv1alpha1.Subject{ + Subjects: []rbacoperatorv1alpha1.GlobalSubject{ { Kind: "role", Name: "rb-role-test", @@ -218,7 +218,7 @@ func makeTestRoleBindingForBuiltInReadOnly(opts ...argocdRoleBindingOpt) *rbacop ArgoCDRoleRef: rbacoperatorv1alpha1.ArgoCDRoleRef{ Name: common.ArgoCDRoleReadOnly, }, - Subjects: []rbacoperatorv1alpha1.Subject{ + Subjects: []rbacoperatorv1alpha1.GlobalSubject{ { Kind: "role", Name: "rb-role-test", From 6c0fead35400adf7da53624938077f0ede2679bc Mon Sep 17 00:00:00 2001 From: Georgy Khromov Date: Fri, 30 May 2025 21:30:04 +0200 Subject: [PATCH 2/9] feat(appProjectRBAC)[#36]: add ArgoCDProjectRole Controller Signed-off-by: Georgy Khromov --- .../argocdprojectrole_controller.go | 54 ++++++++++++++++--- .../argocdrbac_operator_finalizer.go | 23 ++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/internal/controller/argocdprojectrole_controller.go b/internal/controller/argocdprojectrole_controller.go index 13104f0..a9afeb5 100644 --- a/internal/controller/argocdprojectrole_controller.go +++ b/internal/controller/argocdprojectrole_controller.go @@ -18,11 +18,13 @@ package controller import ( "context" + "fmt" + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - logf "sigs.k8s.io/controller-runtime/pkg/log" rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" ) @@ -30,12 +32,13 @@ import ( // ArgoCDProjectRoleReconciler reconciles a ArgoCDProjectRole object type ArgoCDProjectRoleReconciler struct { client.Client + Log logr.Logger Scheme *runtime.Scheme } -// +kubebuilder:rbac:groups=rbac-operator,resources=argocdprojectroles,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=rbac-operator,resources=argocdprojectroles/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=rbac-operator,resources=argocdprojectroles/finalizers,verbs=update +// +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectroles,verbs=* +// +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectroles/status,verbs=* +// +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectroles/finalizers,verbs=* // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -47,9 +50,48 @@ type ArgoCDProjectRoleReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *ArgoCDProjectRoleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = logf.FromContext(ctx) + _ = r.Log.WithValues("argocdprojectrole", req.NamespacedName) - // TODO(user): your logic here + r.Log.Info("Reconciling ArgoCDProjectRole", "name", req.Name, "namespace", req.Namespace) + + projectRole := rbacoperatorv1alpha1.ArgoCDProjectRole{} + if err := r.Get(ctx, req.NamespacedName, &projectRole); err != nil { + if errors.IsNotFound(err) { + r.Log.Info("ArgoCDProjectRole not found, skipping reconcile", "name", req.Name) + return ctrl.Result{}, nil + } + projectRole.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) + if err := r.Status().Update(ctx, &projectRole); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRole status", "name", req.Name) + return ctrl.Result{}, err + } + } + + if projectRole.IsBeingDeleted() { + if err := r.handleFinalizer(ctx, &projectRole); err != nil { + projectRole.SetConditions(rbacoperatorv1alpha1.Deleting()) + if err := r.Status().Update(ctx, &projectRole); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRole status during finalizer handling", "name", req.Name) + } + return ctrl.Result{}, fmt.Errorf("error when handling finalizer: %v", err) + } + return ctrl.Result{}, nil + } + + if !projectRole.HasFinalizer(rbacoperatorv1alpha1.ArgoCDProjectRoleFinalizerName) { + if err := r.addFinalizer(ctx, &projectRole); err != nil { + projectRole.SetConditions(rbacoperatorv1alpha1.Deleting().WithMessage(err.Error())) + if err := r.Status().Update(ctx, &projectRole); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRole status after adding finalizer", "name", req.Name) + } + return ctrl.Result{}, fmt.Errorf("error when adding finalizer: %v", err) + } + return ctrl.Result{}, nil + } + + if projectRole.HasArgoCDProjectRoleBindingRef() { + projectRb := rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{} + } return ctrl.Result{}, nil } diff --git a/internal/controller/argocdrbac_operator_finalizer.go b/internal/controller/argocdrbac_operator_finalizer.go index 2ea01b4..c1a8f34 100644 --- a/internal/controller/argocdrbac_operator_finalizer.go +++ b/internal/controller/argocdrbac_operator_finalizer.go @@ -57,6 +57,29 @@ func (r *ArgoCDRoleReconciler) delete(role *rbacoperatorv1alpha1.ArgoCDRole) err return nil } +func (r *ArgoCDProjectRoleReconciler) addFinalizer(ctx context.Context, projectRole *rbacoperatorv1alpha1.ArgoCDProjectRole) error { + projectRole.AddFinalizer(rbacoperatorv1alpha1.ArgoCDProjectRoleFinalizerName) + return r.Update(ctx, projectRole) +} + +func (r *ArgoCDProjectRoleReconciler) handleFinalizer(ctx context.Context, projectRole *rbacoperatorv1alpha1.ArgoCDProjectRole) error { + if !projectRole.HasFinalizer(rbacoperatorv1alpha1.ArgoCDProjectRoleFinalizerName) { + return nil + } + + if err := r.delete(projectRole); err != nil { + return err + } + + projectRole.RemoveFinalizer(rbacoperatorv1alpha1.ArgoCDProjectRoleFinalizerName) + return r.Update(ctx, projectRole) +} + +func (r *ArgoCDProjectRoleReconciler) delete(projectRole *rbacoperatorv1alpha1.ArgoCDProjectRole) error { + // TODO: Implement deletion logic for ArgoCDProjectRole + return nil +} + func (r *ArgoCDRoleBindingReconciler) addFinalizer(ctx context.Context, rb *rbacoperatorv1alpha1.ArgoCDRoleBinding) error { rb.AddFinalizer(rbacoperatorv1alpha1.ArgoCDRoleBindingFinalizerName) return r.Update(ctx, rb) From 2e80ae50792f3f7e92f5cc04b31f1a39d4e59112 Mon Sep 17 00:00:00 2001 From: Georgy Khromov Date: Thu, 5 Jun 2025 00:43:59 +0200 Subject: [PATCH 3/9] feat(appProjectRBAC)[#36]: add logic for reconciling AppProject's projectRoles Signed-off-by: Georgy Khromov --- .../argocdprojectrolebinding_types.go | 6 +- ...roj-labs.io_argocdprojectrolebindings.yaml | 6 +- ...r.argoproj-labs.io_argocdprojectroles.yaml | 2 +- config/rbac/role.yaml | 32 +- go.mod | 111 ++++- go.sum | 429 +++++++++++++++++- internal/controller/appproject.go | 92 ++++ .../argocdprojectrole_controller.go | 24 +- .../argocdrbac_operator_finalizer.go | 27 +- 9 files changed, 679 insertions(+), 50 deletions(-) create mode 100644 internal/controller/appproject.go diff --git a/api/v1alpha1/argocdprojectrolebinding_types.go b/api/v1alpha1/argocdprojectrolebinding_types.go index 44c4ad9..05aa941 100644 --- a/api/v1alpha1/argocdprojectrolebinding_types.go +++ b/api/v1alpha1/argocdprojectrolebinding_types.go @@ -25,11 +25,12 @@ import ( // ArgoCDProjectRoleBindingSpec defines the desired state of ArgoCDProjectRoleBinding. type ArgoCDProjectRoleBindingSpec struct { // List of subjects being bound to ArgoCDProjectRole (argocdProjectRoleRef). - Subjects []AppProjectSubject `json:"subjects,omitempty"` + // +kubebuilder:validation:MinItems=1 + Subjects []AppProjectSubject `json:"subjects"` ArgoCDProjectRoleRef ArgoCDProjectRoleRef `json:"argocdProjectRoleRef"` } -// AppProjectSubject defines the subject being bound to an AppProject scoped ArgoCDRole. +// AppProjectSubject defines the subject being bound to ArgoCDProjectRole. type AppProjectSubject struct { // Reference to the AppProject the ArgoCDRole is bound to. AppProjectRef string `json:"appProjectRef"` @@ -44,6 +45,7 @@ type ArgoCDProjectRoleRef struct { } // ArgoCDProjectRoleBindingStatus defines the observed state of ArgoCDProjectRoleBinding. +// TODO: Add field to track of AppProjects the Role is bound to. type ArgoCDProjectRoleBindingStatus struct { // +listType=map // +listMapKey=type diff --git a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml index fe415db..86e44fc 100644 --- a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml +++ b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.15.0 + controller-gen.kubebuilder.io/version: v0.18.0 name: argocdprojectrolebindings.rbac-operator.argoproj-labs.io spec: group: rbac-operator.argoproj-labs.io @@ -56,7 +56,7 @@ spec: description: List of subjects being bound to ArgoCDProjectRole (argocdProjectRoleRef). items: description: AppProjectSubject defines the subject being bound to - an AppProject scoped ArgoCDRole. + ArgoCDProjectRole. properties: appProjectRef: description: Reference to the AppProject the ArgoCDRole is bound @@ -71,9 +71,11 @@ spec: - appProjectRef - groups type: object + minItems: 1 type: array required: - argocdProjectRoleRef + - subjects type: object status: description: ArgoCDProjectRoleBindingStatus defines the observed state diff --git a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml index d797764..f9bcbf9 100644 --- a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml +++ b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.15.0 + controller-gen.kubebuilder.io/version: v0.18.0 name: argocdprojectroles.rbac-operator.argoproj-labs.io spec: group: rbac-operator.argoproj-labs.io diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 988da7f..d853464 100755 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -39,31 +39,23 @@ rules: - patch - update - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: - - argocdprojectroles + - argocdprojectrolebindings verbs: - - create - - delete - get - list - - patch - - update - - watch - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: + - argocdprojectroles - argocdprojectroles/finalizers - verbs: - - update -- apiGroups: - - rbac-operator - resources: - argocdprojectroles/status + - argocdrolebindings/finalizers + - argocdrolebindings/status + - argocdroles/finalizers verbs: - - get - - patch - - update + - '*' - apiGroups: - rbac-operator.argoproj-labs.io resources: @@ -71,14 +63,6 @@ rules: - argocdroles verbs: - '*' -- apiGroups: - - rbac-operator.argoproj-labs.io - resources: - - argocdrolebindings/finalizers - - argocdrolebindings/status - - argocdroles/finalizers - verbs: - - '*' - apiGroups: - rbac-operator.argoproj-labs.io resources: diff --git a/go.mod b/go.mod index 09ca6b0..93c0959 100644 --- a/go.mod +++ b/go.mod @@ -1,35 +1,129 @@ module github.com/argoproj-labs/argocd-rbac-operator -go 1.24.0 +go 1.24.1 toolchain go1.24.3 require ( + github.com/argoproj/argo-cd/v3 v3.0.5 github.com/onsi/ginkgo/v2 v2.23.4 github.com/onsi/gomega v1.37.0 github.com/stretchr/testify v1.10.0 k8s.io/apimachinery v0.33.1 - k8s.io/client-go v0.33.1 - sigs.k8s.io/controller-runtime v0.21.0 + k8s.io/client-go v0.32.2 + sigs.k8s.io/controller-runtime v0.20.1 ) require ( + cloud.google.com/go/compute/metadata v0.6.0 // indirect + dario.cat/mergo v1.0.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/semver/v3 v3.3.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.5 // indirect + github.com/argoproj/gitops-engine v0.7.1-0.20250521000818-c08b0a72c1f1 // indirect + github.com/argoproj/pkg v0.13.7-0.20250305113207-cbc37dc61de5 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect + github.com/bombsimon/logrusr/v4 v4.1.0 // indirect + github.com/bradleyfalzon/ghinstallation/v2 v2.14.0 // indirect + github.com/casbin/casbin/v2 v2.103.0 // indirect + github.com/casbin/govaluate v1.3.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/cloudflare/circl v1.6.0 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/fatih/camelcase v1.0.0 // indirect github.com/fxamacker/cbor/v2 v2.8.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-git/v5 v5.14.0 // indirect + github.com/go-redis/cache/v9 v9.0.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/google/go-github/v66 v66.0.0 // indirect + github.com/google/go-github/v69 v69.2.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jonboulle/clockwork v0.4.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/redis/go-redis/v9 v9.7.3 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/vmihailenco/go-tinylfu v0.2.2 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/xlab/treeprint v1.2.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect + golang.org/x/crypto v0.38.0 // indirect golang.org/x/sync v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect + google.golang.org/grpc v1.71.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/apiserver v0.32.2 // indirect + k8s.io/cli-runtime v0.32.2 // indirect + k8s.io/component-base v0.32.2 // indirect + k8s.io/component-helpers v0.32.2 // indirect + k8s.io/controller-manager v0.0.0 // indirect + k8s.io/kube-aggregator v0.32.2 // indirect + k8s.io/kubectl v0.32.2 // indirect + k8s.io/kubernetes v1.32.2 // indirect + oras.land/oras-go/v2 v2.5.0 // indirect + sigs.k8s.io/kustomize/api v0.18.0 // indirect + sigs.k8s.io/kustomize/kyaml v0.18.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect @@ -68,8 +162,8 @@ require ( google.golang.org/protobuf v1.36.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.1 - k8s.io/apiextensions-apiserver v0.33.1 // indirect + k8s.io/api v0.32.2 + k8s.io/apiextensions-apiserver v0.32.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 // indirect @@ -77,3 +171,6 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) + +// we have to replace due to argo-cd package +replace k8s.io/controller-manager => k8s.io/controller-manager v0.32.2 diff --git a/go.sum b/go.sum index 79f500a..8cd7221 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,129 @@ +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= +github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= +github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0= +github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqRWqM3nksiyXk5s8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/argoproj/argo-cd/v3 v3.0.5 h1:cjJekH0MFBtJES2PTGDqnY9dJ26Wr9SVF8tkvVAgvdM= +github.com/argoproj/argo-cd/v3 v3.0.5/go.mod h1:3rGF/Ea2Mcn+UioTO1MshzEVGKuH0AtNmXbL9JX3DAM= +github.com/argoproj/gitops-engine v0.7.1-0.20250521000818-c08b0a72c1f1 h1:Ze4U6kV49vSzlUBhH10HkO52bYKAIXS4tHr/MlNDfdU= +github.com/argoproj/gitops-engine v0.7.1-0.20250521000818-c08b0a72c1f1/go.mod h1:WsnykM8idYRUnneeT31cM/Fq/ZsjkefCbjiD8ioCJkU= +github.com/argoproj/pkg v0.13.7-0.20250305113207-cbc37dc61de5 h1:YBoLSjpoaJXaXAldVvBRKJuOPvIXz9UOv6S96gMJM/Q= +github.com/argoproj/pkg v0.13.7-0.20250305113207-cbc37dc61de5/go.mod h1:ebVOzFJphdN1p6EG2mIMECv/3Rk/almSaxIYuFAmsSw= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 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/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= +github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bombsimon/logrusr/v4 v4.1.0 h1:uZNPbwusB0eUXlO8hIUwStE6Lr5bLN6IgYgG+75kuh4= +github.com/bombsimon/logrusr/v4 v4.1.0/go.mod h1:pjfHC5e59CvjTBIU3V3sGhFWFAnsnhOR03TRc6im0l8= +github.com/bradleyfalzon/ghinstallation/v2 v2.14.0 h1:0D4vKCHOvYrDU8u61TnE2JfNT4VRrBLphmxtqazTO+M= +github.com/bradleyfalzon/ghinstallation/v2 v2.14.0/go.mod h1:LOVmdZYVZ8jqdr4n9wWm1ocDiMz9IfMGfRkaYC1a52A= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic= +github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco= +github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc= +github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= +github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= +github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= +github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= @@ -27,55 +132,180 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-playground/webhooks/v6 v6.4.0 h1:KLa6y7bD19N48rxJDHM0DpE3T4grV7GxMy1b/aHMWPY= +github.com/go-playground/webhooks/v6 v6.4.0/go.mod h1:5lBxopx+cAJiBI4+kyRbuHrEi+hYRDdRHuRR4Ya5Ums= +github.com/go-redis/cache/v9 v9.0.0 h1:0thdtFo0xJi0/WXbRVu8B066z8OvVymXTJGaXrVWnN0= +github.com/go-redis/cache/v9 v9.0.0/go.mod h1:cMwi1N8ASBOufbIvk7cdXe2PbPjK/WMRL95FFHWsSgI= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gogits/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:04sojTxgYxu1L4Hn7Tgf7UVtIosVa6CuHtvNY+7T1K4= +github.com/gogits/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:cY2AIrMgHm6oOHmR7jY+9TtjzSjQ3iG7tURJG3Y6XH0= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-github/v66 v66.0.0 h1:ADJsaXj9UotwdgK8/iFZtv7MLc8E8WBl62WLd/D/9+M= +github.com/google/go-github/v66 v66.0.0/go.mod h1:+4SO9Zkuyf8ytMj0csN1NR/5OTR+MfqPp8P8dVlcvY4= +github.com/google/go-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzeaUUbEHE= +github.com/google/go-github/v69 v69.2.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4= github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +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.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= +github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= @@ -86,20 +316,74 @@ github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQP github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/r3labs/diff/v3 v3.0.1 h1:CBKqf3XmNRHXKmdU7mZP1w7TV0pDyVCis1AUHtA4Xtg= +github.com/r3labs/diff/v3 v3.0.1/go.mod h1:f1S9bourRbiM66NskseyUdo0fTmEE0qKrikYJX63dgo= +github.com/redis/go-redis/v9 v9.0.0-rc.4/go.mod h1:Vo3EsyWnicKnSKCA7HhgnvnyA74wOA69Cd2Meli5mmA= +github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= +github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vmihailenco/go-tinylfu v0.2.2 h1:H1eiG6HM36iniK6+21n9LLpzx1G9R3DJa2UjUjbynsI= +github.com/vmihailenco/go-tinylfu v0.2.2/go.mod h1:CutYi2Q9puTxfcolkliPq4npPuofg9N9t8JVrjzwa3Q= +github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -111,38 +395,106 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -151,35 +503,88 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +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/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apiextensions-apiserver v0.33.1 h1:N7ccbSlRN6I2QBcXevB73PixX2dQNIW0ZRuguEE91zI= -k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA= +k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= +k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= +k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= +k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= +k8s.io/apiserver v0.32.2 h1:WzyxAu4mvLkQxwD9hGa4ZfExo3yZZaYzoYvvVDlM6vw= +k8s.io/apiserver v0.32.2/go.mod h1:PEwREHiHNU2oFdte7BjzA1ZyjWjuckORLIK/wLV5goM= +k8s.io/cli-runtime v0.32.2 h1:aKQR4foh9qeyckKRkNXUccP9moxzffyndZAvr+IXMks= +k8s.io/cli-runtime v0.32.2/go.mod h1:a/JpeMztz3xDa7GCyyShcwe55p8pbcCVQxvqZnIwXN8= +k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= +k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= +k8s.io/component-base v0.32.2 h1:1aUL5Vdmu7qNo4ZsE+569PV5zFatM9hl+lb3dEea2zU= +k8s.io/component-base v0.32.2/go.mod h1:PXJ61Vx9Lg+P5mS8TLd7bCIr+eMJRQTyXe8KvkrvJq0= +k8s.io/component-helpers v0.32.2 h1:2usSAm3zNE5yu5DdAdrKBWLfSYNpU4OPjZywJY5ovP8= +k8s.io/component-helpers v0.32.2/go.mod h1:fvQAoiiOP7jUEUBc9qR0PXiBPuB0I56WTxTkkpcI8g8= +k8s.io/controller-manager v0.32.2 h1:/9XuHWEqofO2Aqa4l7KJGckJUcLVRWfx+qnVkdXoStI= +k8s.io/controller-manager v0.32.2/go.mod h1:o5uo2tLCQhuoMt0RfKcQd0eqaNmSKOKiT+0YELCqXOk= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-aggregator v0.32.2 h1:kg9pke+i2qRbJwX1UKwZV4fsCRvmbaCEFk38R4FqHmw= +k8s.io/kube-aggregator v0.32.2/go.mod h1:rRm+xY1yIFIt3zBc727nG5SBLYywywD87klfIAw+7+c= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us= +k8s.io/kubectl v0.32.2/go.mod h1:+h/NQFSPxiDZYX/WZaWw9fwYezGLISP0ud8nQKg+3g8= +k8s.io/kubernetes v1.32.2 h1:mShetlA102UpjRVSGzB+5vjJwy8oPy8FMWrkTH5f37o= +k8s.io/kubernetes v1.32.2/go.mod h1:tiIKO63GcdPRBHW2WiUFm3C0eoLczl3f7qi56Dm1W8I= k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 h1:jgJW5IePPXLGB8e/1wvd0Ich9QE97RvvF3a8J3fP/Lg= k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= -sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= +oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= +oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= +sigs.k8s.io/controller-runtime v0.20.1 h1:JbGMAG/X94NeM3xvjenVUaBjy6Ui4Ogd/J5ZtjZnHaE= +sigs.k8s.io/controller-runtime v0.20.1/go.mod h1:BrP3w158MwvB3ZbNpaAcIKkHQ7YGpYnzpoSTZ8E14WU= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo= +sigs.k8s.io/kustomize/api v0.18.0/go.mod h1:f8isXnX+8b+SGLHQ6yO4JG1rdkZlvhaCf/uZbLVMb0U= +sigs.k8s.io/kustomize/kyaml v0.18.1 h1:WvBo56Wzw3fjS+7vBjN6TeivvpbW9GmRaWZ9CIVmt4E= +sigs.k8s.io/kustomize/kyaml v0.18.1/go.mod h1:C3L2BFVU1jgcddNBE1TxuVLgS46TjObMwW5FT9FcjYo= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= diff --git a/internal/controller/appproject.go b/internal/controller/appproject.go new file mode 100644 index 0000000..75f1474 --- /dev/null +++ b/internal/controller/appproject.go @@ -0,0 +1,92 @@ +package controller + +import ( + "context" + "fmt" + + argocdv1alpha "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" +) + +func buildCasbinPolicyStrings(pr *rbacoperatorv1alpha1.ArgoCDProjectRole, appProject *argocdv1alpha.AppProject) []string { + policies := []string{} + for _, rule := range pr.Spec.Rules { + resource := rule.Resource + for _, verb := range rule.Verbs { + for _, object := range rule.Objects { + policy := fmt.Sprintf("p, proj:%s:%s, %s, %s, %s, allow", appProject.Name, pr.Name, resource, verb, object) + policies = append(policies, policy) + } + } + } + return policies +} + +func newAppProject(name, namespace string) *argocdv1alpha.AppProject { + return &argocdv1alpha.AppProject{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } +} + +func (r *ArgoCDProjectRoleReconciler) patchAppProject(appProject *argocdv1alpha.AppProject, pr *rbacoperatorv1alpha1.ArgoCDProjectRole, appProjectSubject *rbacoperatorv1alpha1.AppProjectSubject) error { + changed := false + apProjectRole := &argocdv1alpha.ProjectRole{ + Name: pr.Name, + Description: pr.Spec.Description, + Groups: appProjectSubject.Groups, + Policies: buildCasbinPolicyStrings(pr, appProject), + } + + ogAppProject := appProject.DeepCopy() + + role, index := getRoleInAppProject(appProject, pr.Name) + if role == nil { + appProject.Spec.Roles = append(appProject.Spec.Roles, *apProjectRole) + changed = true + } + if role != nil && !areProjectRolesEqual(role, apProjectRole) { + appProject.Spec.Roles[index] = *apProjectRole + changed = true + } + if changed { + return r.Client.Patch(context.TODO(), appProject, client.MergeFrom(ogAppProject)) + } + return nil +} + +func getRoleInAppProject(appProject *argocdv1alpha.AppProject, roleName string) (role *argocdv1alpha.ProjectRole, index int) { + for i, role := range appProject.Spec.Roles { + if role.Name == roleName { + return &role, i + } + } + return nil, -1 +} + +func areProjectRolesEqual(r1, r2 *argocdv1alpha.ProjectRole) bool { + if r1.Description != r2.Description || + len(r1.Groups) != len(r2.Groups) || + len(r1.Policies) != len(r2.Policies) { + return false + } + + for i, group := range r1.Groups { + if group != r2.Groups[i] { + return false + } + } + + for i, policy := range r1.Policies { + if policy != r2.Policies[i] { + return false + } + } + + return true +} \ No newline at end of file diff --git a/internal/controller/argocdprojectrole_controller.go b/internal/controller/argocdprojectrole_controller.go index a9afeb5..b5ceddc 100644 --- a/internal/controller/argocdprojectrole_controller.go +++ b/internal/controller/argocdprojectrole_controller.go @@ -39,6 +39,7 @@ type ArgoCDProjectRoleReconciler struct { // +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectroles,verbs=* // +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectroles/status,verbs=* // +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectroles/finalizers,verbs=* +// +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectrolebindings,verbs=get;list // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -91,8 +92,29 @@ func (r *ArgoCDProjectRoleReconciler) Reconcile(ctx context.Context, req ctrl.Re if projectRole.HasArgoCDProjectRoleBindingRef() { projectRb := rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{} - } + projectRBObjectKey := client.ObjectKey{ + Name: projectRole.Status.ArgoCDProjectRoleBindingRef, + Namespace: req.Namespace, + } + + if err := r.Get(ctx, projectRBObjectKey, &projectRb); err != nil { + if errors.IsNotFound(err) { + r.Log.Info("ArgoCDProjectRoleBinding not found", "name", projectRBObjectKey.Name) + projectRole.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) + projectRole.Status.ArgoCDProjectRoleBindingRef = "" + if err := r.Status().Update(ctx, &projectRole); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRole status after binding not found", "name", req.Name) + } + return ctrl.Result{}, nil + } + projectRole.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) + if err := r.Status().Update(ctx, &projectRole); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRole status", "name", req.Name) + } + return ctrl.Result{}, fmt.Errorf("error fetching ArgoCDProjectRoleBinding: %v", err) + } + } return ctrl.Result{}, nil } diff --git a/internal/controller/argocdrbac_operator_finalizer.go b/internal/controller/argocdrbac_operator_finalizer.go index c1a8f34..045c507 100644 --- a/internal/controller/argocdrbac_operator_finalizer.go +++ b/internal/controller/argocdrbac_operator_finalizer.go @@ -21,6 +21,7 @@ import ( "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" "github.com/argoproj-labs/argocd-rbac-operator/internal/controller/common" @@ -76,7 +77,26 @@ func (r *ArgoCDProjectRoleReconciler) handleFinalizer(ctx context.Context, proje } func (r *ArgoCDProjectRoleReconciler) delete(projectRole *rbacoperatorv1alpha1.ArgoCDProjectRole) error { - // TODO: Implement deletion logic for ArgoCDProjectRole + rbName := projectRole.Status.ArgoCDProjectRoleBindingRef + if rbName == "" { + return nil // Role not bound to any AppProject, nothing to delete + } + rb := &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: rbName, + Namespace: projectRole.Namespace, + }, + } + if !IsObjectFound(r.Client, rb.Namespace, rb.Name, rb) { + // RoleBinding does not exist, nothing to delete + return nil + } + appProjectNames := []string{} + // get all AppProjects this role is bound to + for _, subject := range rb.Spec.Subjects { + appProjectNames = append(appProjectNames, subject.AppProjectRef) + } + return nil } @@ -127,3 +147,8 @@ func (r *ArgoCDRoleBindingReconciler) delete(rb *rbacoperatorv1alpha1.ArgoCDRole } return nil } + +func deleteProjectRoles(client client.Client, appProjects []string, roleName string) error { + // TODO: Implement deletion logic fo projectRoles from AppProjects + return nil +} \ No newline at end of file From b8a11fae36d8e4bf849f4b2ee56a9f353a37bcc3 Mon Sep 17 00:00:00 2001 From: Georgy Khromov Date: Sun, 8 Jun 2025 00:31:50 +0200 Subject: [PATCH 4/9] feat(appProjectRBAC)[#36]: add logic for reconciling ArgoCDProjectRoleBindings Signed-off-by: Georgy Khromov --- .golangci.yml | 41 ++-- Makefile | 4 +- api/v1alpha1/argocdprojectrole_types.go | 8 +- .../argocdprojectrolebinding_types.go | 11 +- api/v1alpha1/argocdrole_types.go | 8 +- api/v1alpha1/argocdrolebinding_types.go | 8 +- api/v1alpha1/zz_generated.deepcopy.go | 7 +- cmd/main.go | 2 + ...roj-labs.io_argocdprojectrolebindings.yaml | 6 + config/crd/kustomization.yaml | 4 +- config/rbac/role.yaml | 35 +--- config/samples/appprojects.yaml | 19 ++ config/samples/argocdprojectrole.yaml | 21 ++ config/samples/argocdprojectrolebinding.yaml | 19 ++ config/samples/kustomization.yaml | 4 +- ...c-operator_v1alpha1_argocdprojectrole.yaml | 9 - ...tor_v1alpha1_argocdprojectrolebinding.yaml | 9 - go.mod | 2 +- hack/boilerplate.go.txt | 2 +- internal/controller/appproject.go | 27 ++- .../argocdprojectrole_controller.go | 8 +- .../argocdprojectrolebinding_controller.go | 186 ++++++++++++++++-- .../argocdrbac_operator_finalizer.go | 60 +++++- internal/controller/argocdrole_controller.go | 7 - .../controller/argocdrole_controller_test.go | 39 ++-- .../argocdrolebinding_controller.go | 7 - .../argocdrolebinding_controller_test.go | 65 +++--- internal/controller/configmap.go | 12 +- internal/controller/testing.go | 29 ++- test/utils/utils.go | 4 +- 30 files changed, 454 insertions(+), 209 deletions(-) create mode 100644 config/samples/appprojects.yaml create mode 100644 config/samples/argocdprojectrole.yaml create mode 100644 config/samples/argocdprojectrolebinding.yaml delete mode 100644 config/samples/rbac-operator_v1alpha1_argocdprojectrole.yaml delete mode 100644 config/samples/rbac-operator_v1alpha1_argocdprojectrolebinding.yaml diff --git a/.golangci.yml b/.golangci.yml index f4e9d90..55ff760 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,32 +1,15 @@ +version: "2" run: timeout: 5m allow-parallel-runners: true - -issues: - # don't skip warning about doc comments - # don't exclude the default set of lint - exclude-use-default: false - # restore some of the defaults - # (fill in the rest as needed) - exclude-rules: - - path: "api/*" - linters: - - lll - - path: "internal/*" - linters: - - dupl - - lll linters: - disable-all: true + default: none enable: - dupl - errcheck - ginkgolinter - goconst - gocyclo - - gofmt - - goimports - - gosimple - govet - ineffassign - lll @@ -34,7 +17,25 @@ linters: - nakedret - prealloc - staticcheck - - typecheck - unconvert - unparam - unused + exclusions: + rules: + - path: "api/*" + linters: + - lll + - path: "internal/*" + linters: + - dupl + - lll +formatters: + enable: + - gofmt + - goimports + settings: + gofmt: + simplify: true + goimports: + local-prefixes: + - github.com/argoproj-labs/argocd-rbac-operator \ No newline at end of file diff --git a/Makefile b/Makefile index 9d82d81..4a7b97a 100644 --- a/Makefile +++ b/Makefile @@ -176,7 +176,7 @@ GOLANGCI_LINT = $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION) KUSTOMIZE_VERSION ?= v5.6.0 CONTROLLER_TOOLS_VERSION ?= v0.18.0 ENVTEST_VERSION ?= release-0.18 -GOLANGCI_LINT_VERSION ?= v1.64.8 +GOLANGCI_LINT_VERSION ?= v2.1.6 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. @@ -196,7 +196,7 @@ $(ENVTEST): $(LOCALBIN) .PHONY: golangci-lint golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. $(GOLANGCI_LINT): $(LOCALBIN) - $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,${GOLANGCI_LINT_VERSION}) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,${GOLANGCI_LINT_VERSION}) # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist # $1 - target path with name of binary (ideally with version) diff --git a/api/v1alpha1/argocdprojectrole_types.go b/api/v1alpha1/argocdprojectrole_types.go index 0fbf62d..e448551 100644 --- a/api/v1alpha1/argocdprojectrole_types.go +++ b/api/v1alpha1/argocdprojectrole_types.go @@ -66,7 +66,7 @@ type ArgoCDProjectRole struct { // IsBeingDeleted returns true if a deletion timestamp is set func (r *ArgoCDProjectRole) IsBeingDeleted() bool { - return !r.ObjectMeta.DeletionTimestamp.IsZero() + return !r.DeletionTimestamp.IsZero() } // ArgoCDRoleFinalizerName is the name of the finalizer used to delete the Role @@ -74,17 +74,17 @@ const ArgoCDProjectRoleFinalizerName = "rbac-operator.argoproj-labs.io/finalizer // HasFinalizer returns true if the Role has the finalizer func (r *ArgoCDProjectRole) HasFinalizer(finalizerName string) bool { - return slices.Contains(r.ObjectMeta.Finalizers, finalizerName) + return slices.Contains(r.Finalizers, finalizerName) } // AddFinalizer adds the finalizer to the Role func (r *ArgoCDProjectRole) AddFinalizer(finalizerName string) { - r.ObjectMeta.Finalizers = append(r.ObjectMeta.Finalizers, finalizerName) + r.Finalizers = append(r.Finalizers, finalizerName) } // RemoveFinalizer removes the finalizer from the Role func (r *ArgoCDProjectRole) RemoveFinalizer(finalizerName string) { - r.ObjectMeta.Finalizers = slices.DeleteFunc(r.ObjectMeta.Finalizers, func(s string) bool { + r.Finalizers = slices.DeleteFunc(r.Finalizers, func(s string) bool { return s == finalizerName }) } diff --git a/api/v1alpha1/argocdprojectrolebinding_types.go b/api/v1alpha1/argocdprojectrolebinding_types.go index 05aa941..c0b39bf 100644 --- a/api/v1alpha1/argocdprojectrolebinding_types.go +++ b/api/v1alpha1/argocdprojectrolebinding_types.go @@ -45,12 +45,13 @@ type ArgoCDProjectRoleRef struct { } // ArgoCDProjectRoleBindingStatus defines the observed state of ArgoCDProjectRoleBinding. -// TODO: Add field to track of AppProjects the Role is bound to. type ArgoCDProjectRoleBindingStatus struct { // +listType=map // +listMapKey=type // Conditions defines the list of conditions. Conditions []Condition `json:"conditions,omitempty"` + // AppProjectsBound is a list of AppProjects that the role is bound to. + AppProjectsBound []string `json:"appProjectsBound,omitempty"` } // +kubebuilder:object:root=true @@ -68,7 +69,7 @@ type ArgoCDProjectRoleBinding struct { // IsBeingDeleted returns true if a deletion timestamp is set func (r *ArgoCDProjectRoleBinding) IsBeingDeleted() bool { - return !r.ObjectMeta.DeletionTimestamp.IsZero() + return !r.DeletionTimestamp.IsZero() } // ArgoCDProjectRoleFinalizerName is the name of the finalizer used to delete the Role @@ -76,17 +77,17 @@ const ArgoCDProjectRoleBindingFinalizerName = "rbac-operator.argoproj-labs.io/fi // HasFinalizer returns true if the Role has the finalizer func (r *ArgoCDProjectRoleBinding) HasFinalizer(finalizerName string) bool { - return slices.Contains(r.ObjectMeta.Finalizers, finalizerName) + return slices.Contains(r.Finalizers, finalizerName) } // AddFinalizer adds the finalizer to the Role func (r *ArgoCDProjectRoleBinding) AddFinalizer(finalizerName string) { - r.ObjectMeta.Finalizers = append(r.ObjectMeta.Finalizers, finalizerName) + r.Finalizers = append(r.Finalizers, finalizerName) } // RemoveFinalizer removes the finalizer from the Role func (r *ArgoCDProjectRoleBinding) RemoveFinalizer(finalizerName string) { - r.ObjectMeta.Finalizers = slices.DeleteFunc(r.ObjectMeta.Finalizers, func(s string) bool { + r.Finalizers = slices.DeleteFunc(r.Finalizers, func(s string) bool { return s == finalizerName }) } diff --git a/api/v1alpha1/argocdrole_types.go b/api/v1alpha1/argocdrole_types.go index f814726..8f9be3e 100644 --- a/api/v1alpha1/argocdrole_types.go +++ b/api/v1alpha1/argocdrole_types.go @@ -64,7 +64,7 @@ type ArgoCDRole struct { // IsBeingDeleted returns true if a deletion timestamp is set func (r *ArgoCDRole) IsBeingDeleted() bool { - return !r.ObjectMeta.DeletionTimestamp.IsZero() + return !r.DeletionTimestamp.IsZero() } // ArgoCDRoleFinalizerName is the name of the finalizer used to delete the Role @@ -72,17 +72,17 @@ const ArgoCDRoleFinalizerName = "rbac-operator.argoproj-labs.io/finalizer" // HasFinalizer returns true if the Role has the finalizer func (r *ArgoCDRole) HasFinalizer(finalizerName string) bool { - return slices.Contains(r.ObjectMeta.Finalizers, finalizerName) + return slices.Contains(r.Finalizers, finalizerName) } // AddFinalizer adds the finalizer to the Role func (r *ArgoCDRole) AddFinalizer(finalizerName string) { - r.ObjectMeta.Finalizers = append(r.ObjectMeta.Finalizers, finalizerName) + r.Finalizers = append(r.Finalizers, finalizerName) } // RemoveFinalizer removes the finalizer from the Role func (r *ArgoCDRole) RemoveFinalizer(finalizerName string) { - r.ObjectMeta.Finalizers = slices.DeleteFunc(r.ObjectMeta.Finalizers, func(s string) bool { + r.Finalizers = slices.DeleteFunc(r.Finalizers, func(s string) bool { return s == finalizerName }) } diff --git a/api/v1alpha1/argocdrolebinding_types.go b/api/v1alpha1/argocdrolebinding_types.go index 5b056df..591ac3d 100644 --- a/api/v1alpha1/argocdrolebinding_types.go +++ b/api/v1alpha1/argocdrolebinding_types.go @@ -76,7 +76,7 @@ type ArgoCDRoleBindingList struct { // IsBeingDeleted returns true if a deletion timestamp is set func (r *ArgoCDRoleBinding) IsBeingDeleted() bool { - return !r.ObjectMeta.DeletionTimestamp.IsZero() + return !r.DeletionTimestamp.IsZero() } // ArgoCDRoleFinalizerName is the name of the finalizer used to delete the Role @@ -84,17 +84,17 @@ const ArgoCDRoleBindingFinalizerName = "rbac-operator.argoproj-labs.io/finalizer // HasFinalizer returns true if the Role has the finalizer func (r *ArgoCDRoleBinding) HasFinalizer(finalizerName string) bool { - return slices.Contains(r.ObjectMeta.Finalizers, finalizerName) + return slices.Contains(r.Finalizers, finalizerName) } // AddFinalizer adds the finalizer to the Role func (r *ArgoCDRoleBinding) AddFinalizer(finalizerName string) { - r.ObjectMeta.Finalizers = append(r.ObjectMeta.Finalizers, finalizerName) + r.Finalizers = append(r.Finalizers, finalizerName) } // RemoveFinalizer removes the finalizer from the Role func (r *ArgoCDRoleBinding) RemoveFinalizer(finalizerName string) { - r.ObjectMeta.Finalizers = slices.DeleteFunc(r.ObjectMeta.Finalizers, func(s string) bool { + r.Finalizers = slices.DeleteFunc(r.Finalizers, func(s string) bool { return s == finalizerName }) } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index db43fee..1984db1 100755 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated /* -Copyright 2024. +Copyright 2025. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -163,6 +163,11 @@ func (in *ArgoCDProjectRoleBindingStatus) DeepCopyInto(out *ArgoCDProjectRoleBin (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.AppProjectsBound != nil { + in, out := &in.AppProjectsBound, &out.AppProjectsBound + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDProjectRoleBindingStatus. diff --git a/cmd/main.go b/cmd/main.go index 8aa4fa2..06a9099 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -25,6 +25,7 @@ import ( // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" + argocdv1alpha "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -46,6 +47,7 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(argocdv1alpha.AddToScheme(scheme)) utilruntime.Must(argoprojiov1alpha1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme diff --git a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml index 86e44fc..86902b2 100644 --- a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml +++ b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml @@ -81,6 +81,12 @@ spec: description: ArgoCDProjectRoleBindingStatus defines the observed state of ArgoCDProjectRoleBinding. properties: + appProjectsBound: + description: AppProjectsBound is a list of AppProjects that the role + is bound to. + items: + type: string + type: array conditions: description: Conditions defines the list of conditions. items: diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 2d27e9b..ea92e57 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -4,8 +4,8 @@ resources: - bases/rbac-operator.argoproj-labs.io_argocdroles.yaml - bases/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml -- bases/rbac-operator_argocdprojectroles.yaml -- bases/rbac-operator_argocdprojectrolebindings.yaml +- bases/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml +- bases/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml # +kubebuilder:scaffold:crdkustomizeresource patches: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index d853464..ed67f6a 100755 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -13,44 +13,28 @@ rules: - list - watch - apiGroups: - - rbac-operator + - argoproj.io resources: - - argocdprojectrolebindings + - appprojects verbs: - - create - - delete - get - list - patch - - update - - watch -- apiGroups: - - rbac-operator - resources: - - argocdprojectrolebindings/finalizers - verbs: - - update -- apiGroups: - - rbac-operator - resources: - - argocdprojectrolebindings/status - verbs: - - get - - patch - - update - apiGroups: - rbac-operator.argoproj-labs.io resources: - argocdprojectrolebindings + - argocdprojectroles + - argocdrolebindings + - argocdroles verbs: - - get - - list + - '*' - apiGroups: - rbac-operator.argoproj-labs.io resources: - - argocdprojectroles + - argocdprojectrolebindings/finalizers + - argocdprojectrolebindings/status - argocdprojectroles/finalizers - - argocdprojectroles/status - argocdrolebindings/finalizers - argocdrolebindings/status - argocdroles/finalizers @@ -59,8 +43,7 @@ rules: - apiGroups: - rbac-operator.argoproj-labs.io resources: - - argocdrolebindings - - argocdroles + - argocdprojectroles/status verbs: - '*' - apiGroups: diff --git a/config/samples/appprojects.yaml b/config/samples/appprojects.yaml new file mode 100644 index 0000000..d585f37 --- /dev/null +++ b/config/samples/appprojects.yaml @@ -0,0 +1,19 @@ +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: test-appproject-1 + labels: + app.kubernetes.io/name: argocd-rbac-operator + app.kubernetes.io/managed-by: kustomize +spec: + description: "Test AppProject 1 for ArgoCD's RBAC Operator" +--- +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: test-appproject-2 + labels: + app.kubernetes.io/name: argocd-rbac-operator + app.kubernetes.io/managed-by: kustomize +spec: + description: "Test AppProject 2 for ArgoCD's RBAC Operator" diff --git a/config/samples/argocdprojectrole.yaml b/config/samples/argocdprojectrole.yaml new file mode 100644 index 0000000..00ebbcd --- /dev/null +++ b/config/samples/argocdprojectrole.yaml @@ -0,0 +1,21 @@ +apiVersion: rbac-operator.argoproj-labs.io/v1alpha1 +kind: ArgoCDProjectRole +metadata: + labels: + app.kubernetes.io/name: argocd-rbac-operator + app.kubernetes.io/managed-by: kustomize + name: test-project-role +spec: + description: "Test role for ArgoCD's AppProjects" + rules: + - resource: clusters + verbs: + - get + - watch + objects: + - "*" + - resource: applications + verbs: + - get + objects: + - "*" diff --git a/config/samples/argocdprojectrolebinding.yaml b/config/samples/argocdprojectrolebinding.yaml new file mode 100644 index 0000000..fec210b --- /dev/null +++ b/config/samples/argocdprojectrolebinding.yaml @@ -0,0 +1,19 @@ +apiVersion: rbac-operator.argoproj-labs.io/v1alpha1 +kind: ArgoCDProjectRoleBinding +metadata: + labels: + app.kubernetes.io/name: argocd-rbac-operator + app.kubernetes.io/managed-by: kustomize + name: test-project-role-binding +spec: + argocdProjectRoleRef: + name: test-project-role + subjects: + - appProjectRef: test-appproject-1 + groups: + - test-group-1 + - test-group-2 + - appProjectRef: test-appproject-2 + groups: + - test-group-3 + - test-group-4 diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 2ff30f4..975721e 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -2,6 +2,6 @@ resources: - argocdrole.yaml - argocdrolebinding.yaml -- rbac-operator_v1alpha1_argocdprojectrole.yaml -- rbac-operator_v1alpha1_argocdprojectrolebinding.yaml +- argocdprojectrole.yaml +- argocdprojectrolebinding.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/rbac-operator_v1alpha1_argocdprojectrole.yaml b/config/samples/rbac-operator_v1alpha1_argocdprojectrole.yaml deleted file mode 100644 index 37e6f76..0000000 --- a/config/samples/rbac-operator_v1alpha1_argocdprojectrole.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: rbac-operator/v1alpha1 -kind: ArgoCDProjectRole -metadata: - labels: - app.kubernetes.io/name: argocd-rbac-operator - app.kubernetes.io/managed-by: kustomize - name: argocdprojectrole-sample -spec: - # TODO(user): Add fields here diff --git a/config/samples/rbac-operator_v1alpha1_argocdprojectrolebinding.yaml b/config/samples/rbac-operator_v1alpha1_argocdprojectrolebinding.yaml deleted file mode 100644 index 181bed4..0000000 --- a/config/samples/rbac-operator_v1alpha1_argocdprojectrolebinding.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: rbac-operator/v1alpha1 -kind: ArgoCDProjectRoleBinding -metadata: - labels: - app.kubernetes.io/name: argocd-rbac-operator - app.kubernetes.io/managed-by: kustomize - name: argocdprojectrolebinding-sample -spec: - # TODO(user): Add fields here diff --git a/go.mod b/go.mod index 93c0959..b15ad3c 100644 --- a/go.mod +++ b/go.mod @@ -143,7 +143,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.64.0 // indirect diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index ff72ff2..221dcbe 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright 2024. +Copyright 2025. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/internal/controller/appproject.go b/internal/controller/appproject.go index 75f1474..3448ecc 100644 --- a/internal/controller/appproject.go +++ b/internal/controller/appproject.go @@ -5,6 +5,7 @@ import ( "fmt" argocdv1alpha "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" + "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -34,13 +35,13 @@ func newAppProject(name, namespace string) *argocdv1alpha.AppProject { } } -func (r *ArgoCDProjectRoleReconciler) patchAppProject(appProject *argocdv1alpha.AppProject, pr *rbacoperatorv1alpha1.ArgoCDProjectRole, appProjectSubject *rbacoperatorv1alpha1.AppProjectSubject) error { +func (r *ArgoCDProjectRoleBindingReconciler) patchAppProject(appProject *argocdv1alpha.AppProject, pr *rbacoperatorv1alpha1.ArgoCDProjectRole, groups *[]string) error { changed := false apProjectRole := &argocdv1alpha.ProjectRole{ - Name: pr.Name, + Name: pr.Name, Description: pr.Spec.Description, - Groups: appProjectSubject.Groups, - Policies: buildCasbinPolicyStrings(pr, appProject), + Groups: *groups, + Policies: buildCasbinPolicyStrings(pr, appProject), } ogAppProject := appProject.DeepCopy() @@ -55,7 +56,7 @@ func (r *ArgoCDProjectRoleReconciler) patchAppProject(appProject *argocdv1alpha. changed = true } if changed { - return r.Client.Patch(context.TODO(), appProject, client.MergeFrom(ogAppProject)) + return r.Patch(context.TODO(), appProject, client.MergeFrom(ogAppProject)) } return nil } @@ -89,4 +90,18 @@ func areProjectRolesEqual(r1, r2 *argocdv1alpha.ProjectRole) bool { } return true -} \ No newline at end of file +} + +func removeRoleFromAppProject(rClient client.Client, appProject *argocdv1alpha.AppProject, roleName string) error { + ogAppProject := appProject.DeepCopy() + + _, index := getRoleInAppProject(appProject, roleName) + if index == -1 { + return nil // Role not found in AppProject, nothing to delete + } + appProject.Spec.Roles = append(appProject.Spec.Roles[:index], appProject.Spec.Roles[index+1:]...) + if err := rClient.Patch(context.TODO(), appProject, client.MergeFrom(ogAppProject)); err != nil { + return errors.Wrapf(err, "failed to patch AppProject %s/%s to remove role %s", appProject.Namespace, appProject.Name, roleName) + } + return nil +} diff --git a/internal/controller/argocdprojectrole_controller.go b/internal/controller/argocdprojectrole_controller.go index b5ceddc..16f7b0e 100644 --- a/internal/controller/argocdprojectrole_controller.go +++ b/internal/controller/argocdprojectrole_controller.go @@ -40,16 +40,10 @@ type ArgoCDProjectRoleReconciler struct { // +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectroles/status,verbs=* // +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectroles/finalizers,verbs=* // +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectrolebindings,verbs=get;list +// +kubebuilder:rbac:groups=argoproj.io,resources=appprojects,verbs=get;list;patch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the ArgoCDProjectRole object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile func (r *ArgoCDProjectRoleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = r.Log.WithValues("argocdprojectrole", req.NamespacedName) diff --git a/internal/controller/argocdprojectrolebinding_controller.go b/internal/controller/argocdprojectrolebinding_controller.go index e750182..08f1ef4 100644 --- a/internal/controller/argocdprojectrolebinding_controller.go +++ b/internal/controller/argocdprojectrolebinding_controller.go @@ -18,11 +18,15 @@ package controller import ( "context" + "fmt" + "slices" + "time" + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - logf "sigs.k8s.io/controller-runtime/pkg/log" rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" ) @@ -30,28 +34,176 @@ import ( // ArgoCDProjectRoleBindingReconciler reconciles a ArgoCDProjectRoleBinding object type ArgoCDProjectRoleBindingReconciler struct { client.Client + Log logr.Logger Scheme *runtime.Scheme } -// +kubebuilder:rbac:groups=rbac-operator,resources=argocdprojectrolebindings,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=rbac-operator,resources=argocdprojectrolebindings/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=rbac-operator,resources=argocdprojectrolebindings/finalizers,verbs=update +// +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectrolebindings,verbs=* +// +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectrolebindings/status,verbs=* +// +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectrolebindings/finalizers,verbs=* +// +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectroles,verbs=get;list +// +kubebuilder:rbac:groups=rbac-operator.argoproj-labs.io,resources=argocdprojectroles/status,verbs=get;list;update +// +kubebuilder:rbac:groups=argoproj.io,resources=appprojects,verbs=get;list;patch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the ArgoCDProjectRoleBinding object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/reconcile -func (r *ArgoCDProjectRoleBindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = logf.FromContext(ctx) - - // TODO(user): your logic here - - return ctrl.Result{}, nil +func (r *ArgoCDProjectRoleBindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { //nolint:gocyclo + _ = r.Log.WithValues("argocdprojectrolebinding", req.NamespacedName) + + r.Log.Info("Reconciling ArgoCDProjectRoleBinding", "name", req.Name, "namespace", req.Namespace) + + projectRoleBinding := rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{} + if err := r.Get(ctx, req.NamespacedName, &projectRoleBinding); err != nil { + if errors.IsNotFound(err) { + r.Log.Info("ArgoCDProjectRoleBinding not found, skipping reconcile", "name", req.Name) + return ctrl.Result{}, nil + } + projectRoleBinding.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) + if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status", "name", req.Name) + return ctrl.Result{}, err + } + } + + if projectRoleBinding.IsBeingDeleted() { + if err := r.handleFinalizer(ctx, &projectRoleBinding); err != nil { + projectRoleBinding.SetConditions(rbacoperatorv1alpha1.Deleting()) + if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status during finalizer handling", "name", req.Name) + } + return ctrl.Result{}, fmt.Errorf("error when handling finalizer: %v", err) + } + return ctrl.Result{}, nil + } + + if !projectRoleBinding.HasFinalizer(rbacoperatorv1alpha1.ArgoCDProjectRoleBindingFinalizerName) { + if err := r.addFinalizer(ctx, &projectRoleBinding); err != nil { + projectRoleBinding.SetConditions(rbacoperatorv1alpha1.Deleting().WithMessage(err.Error())) + if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status after adding finalizer", "name", req.Name) + } + return ctrl.Result{}, fmt.Errorf("error when adding finalizer: %v", err) + } + return ctrl.Result{}, nil + } + + projectRoleName := projectRoleBinding.Spec.ArgoCDProjectRoleRef.Name + projectRole := rbacoperatorv1alpha1.ArgoCDProjectRole{} + projectRoleObjectKey := client.ObjectKey{ + Name: projectRoleName, + Namespace: req.Namespace, + } + if err := r.Get(ctx, projectRoleObjectKey, &projectRole); err != nil { + if errors.IsNotFound(err) { + r.Log.Info("ArgoCDProjectRole not found, skipping reconcile", "name", projectRoleName) + projectRoleBinding.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) + if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status", "name", req.Name) + } + return ctrl.Result{}, nil + } + projectRoleBinding.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) + if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status after project role not found", "name", req.Name) + } + return ctrl.Result{}, fmt.Errorf("error when getting ArgoCDProjectRole: %v", err) + } + + if !projectRole.HasArgoCDProjectRoleBindingRef() { + projectRole.SetArgoCDProjectRoleBindingRef(projectRoleBinding.Name) + if err := r.Status().Update(ctx, &projectRole); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRole status with binding reference", "name", projectRole.Name) + } + } + + appProjectSubjectSet := makeAppProjectSubjectsSet(projectRoleBinding.Spec.Subjects) + for _, boundAppProject := range projectRoleBinding.Status.AppProjectsBound { + if _, exists := appProjectSubjectSet[boundAppProject]; !exists { + appProject := newAppProject(boundAppProject, req.Namespace) + if !IsObjectFound(r.Client, appProject.Namespace, appProject.Name, appProject) { + r.Log.Info("AppProject not found", "name", boundAppProject) + continue + } + r.Log.Info("Removing Role from AppProject", "appProject", boundAppProject, "role", projectRoleName) + if err := removeRoleFromAppProject(r.Client, appProject, projectRoleName); err != nil { + if errors.IsConflict(err) { + r.Log.Info("Conflict while patching AppProject, requeuing", "appProject", appProject.Name) + return ctrl.Result{RequeueAfter: time.Second}, nil + } + r.Log.Error(err, "Failed to remove role from AppProject", "appProject", boundAppProject, "role", projectRoleName) + projectRoleBinding.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) + return ctrl.Result{}, fmt.Errorf("error when removing role from AppProject: %v", err) + } + r.Log.Info("Role removed from AppProject", "appProject", boundAppProject, "role", projectRoleName) + projectRoleBinding.Status.AppProjectsBound = removeStringFromSlice(projectRoleBinding.Status.AppProjectsBound, boundAppProject) + } + } + if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status after removing roles from AppProjects", "name", req.Name) + } + + r.Log.Info("Reconciling AppProjects with ArgoCDProjectRoleBinding", "name", req.Name) + + for appProjectRef, groups := range appProjectSubjectSet { + appProject := newAppProject(appProjectRef, req.Namespace) + if !IsObjectFound(r.Client, appProject.Namespace, appProject.Name, appProject) { + projectRoleBinding.SetConditions(rbacoperatorv1alpha1.Pending(fmt.Errorf("AppProject %s not found", appProjectRef))) + if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status", "name", req.Name) + } + continue + } + r.Log.Info("Reconciling AppProject", "appProject", appProjectRef) + if err := r.patchAppProject(appProject, &projectRole, &groups); err != nil { + if errors.IsConflict(err) { + r.Log.Info("Conflict while patching AppProject, requeuing", "appProject", appProjectRef) + return ctrl.Result{RequeueAfter: time.Second}, nil + } + projectRoleBinding.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) + if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status after patching AppProject", "name", req.Name) + } + return ctrl.Result{RequeueAfter: time.Second}, fmt.Errorf("error when patching AppProject: %v", err) + } + r.Log.Info("AppProject patched successfully", "appProject", appProjectRef) + if !isAppProjectInStatus(projectRoleBinding.Status.AppProjectsBound, appProjectRef) { + projectRoleBinding.Status.AppProjectsBound = append(projectRoleBinding.Status.AppProjectsBound, appProjectRef) + r.Log.Info("AppProject added to status", "appProject", appProjectRef) + if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status after patching AppProject", "name", req.Name) + return ctrl.Result{RequeueAfter: time.Second}, fmt.Errorf("error when updating status: %v", err) + } + } + } + r.Log.Info("ArgoCDProjectRoleBinding reconciliation completed", "name", req.Name) + + projectRoleBinding.SetConditions(rbacoperatorv1alpha1.ReconcileSuccess().WithObservedGeneration(projectRoleBinding.GetGeneration())) + if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { + r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status after reconciliation", "name", req.Name) + } + + return ctrl.Result{RequeueAfter: time.Minute * 10}, nil +} + +func makeAppProjectSubjectsSet(appProjectSubjects []rbacoperatorv1alpha1.AppProjectSubject) map[string][]string { + appProjectSubjectSet := make(map[string][]string, len(appProjectSubjects)) + for _, subject := range appProjectSubjects { + appProjectSubjectSet[subject.AppProjectRef] = subject.Groups + } + return appProjectSubjectSet +} + +func removeStringFromSlice(slice []string, item string) []string { + for i, v := range slice { + if v == item { + return append(slice[:i], slice[i+1:]...) + } + } + return slice +} + +func isAppProjectInStatus(appProjects []string, appProject string) bool { + return slices.Contains(appProjects, appProject) } // SetupWithManager sets up the controller with the Manager. diff --git a/internal/controller/argocdrbac_operator_finalizer.go b/internal/controller/argocdrbac_operator_finalizer.go index 045c507..bc98f25 100644 --- a/internal/controller/argocdrbac_operator_finalizer.go +++ b/internal/controller/argocdrbac_operator_finalizer.go @@ -20,6 +20,8 @@ import ( "context" "fmt" + argocdv1alpha "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" + "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -47,10 +49,10 @@ func (r *ArgoCDRoleReconciler) handleFinalizer(ctx context.Context, role *rbacop func (r *ArgoCDRoleReconciler) delete(role *rbacoperatorv1alpha1.ArgoCDRole) error { cm := newConfigMap(r.ArgoCDRBACConfigMapName, r.ArgoCDRBACConfigMapNamespace) - overlayKey := fmt.Sprintf("policy.%s.%s.csv", role.Namespace, role.ObjectMeta.Name) + overlayKey := fmt.Sprintf("policy.%s.%s.csv", role.Namespace, role.Name) if IsObjectFound(r.Client, cm.Namespace, cm.Name, cm) { delete(cm.Data, overlayKey) - if err := r.Client.Update(context.TODO(), cm); err != nil { + if err := r.Update(context.TODO(), cm); err != nil { return err } } @@ -96,8 +98,7 @@ func (r *ArgoCDProjectRoleReconciler) delete(projectRole *rbacoperatorv1alpha1.A for _, subject := range rb.Spec.Subjects { appProjectNames = append(appProjectNames, subject.AppProjectRef) } - - return nil + return deleteProjectRoles(r.Client, appProjectNames, projectRole.Name, projectRole.Namespace) } func (r *ArgoCDRoleBindingReconciler) addFinalizer(ctx context.Context, rb *rbacoperatorv1alpha1.ArgoCDRoleBinding) error { @@ -125,7 +126,7 @@ func (r *ArgoCDRoleBindingReconciler) delete(rb *rbacoperatorv1alpha1.ArgoCDRole overlayKey := fmt.Sprintf("policy.%s.%s.csv", rb.Namespace, roleRefName) if IsObjectFound(r.Client, cm.Namespace, cm.Name, cm) { delete(cm.Data, overlayKey) - if err := r.Client.Update(context.TODO(), cm); err != nil { + if err := r.Update(context.TODO(), cm); err != nil { return err } } @@ -141,14 +142,55 @@ func (r *ArgoCDRoleBindingReconciler) delete(rb *rbacoperatorv1alpha1.ArgoCDRole if IsObjectFound(r.Client, role.Namespace, role.Name, role) { role.Status.ArgoCDRoleBindingRef = "" - if err := r.Client.Status().Update(context.TODO(), role); err != nil { + if err := r.Status().Update(context.TODO(), role); err != nil { return err } } return nil } -func deleteProjectRoles(client client.Client, appProjects []string, roleName string) error { - // TODO: Implement deletion logic fo projectRoles from AppProjects +func (r *ArgoCDProjectRoleBindingReconciler) addFinalizer(ctx context.Context, projectRoleBinding *rbacoperatorv1alpha1.ArgoCDProjectRoleBinding) error { + projectRoleBinding.AddFinalizer(rbacoperatorv1alpha1.ArgoCDProjectRoleBindingFinalizerName) + return r.Update(ctx, projectRoleBinding) +} + +func (r *ArgoCDProjectRoleBindingReconciler) handleFinalizer(ctx context.Context, projectRoleBinding *rbacoperatorv1alpha1.ArgoCDProjectRoleBinding) error { + if !projectRoleBinding.HasFinalizer(rbacoperatorv1alpha1.ArgoCDProjectRoleBindingFinalizerName) { + return nil + } + + if err := r.delete(projectRoleBinding); err != nil { + return err + } + + projectRoleBinding.RemoveFinalizer(rbacoperatorv1alpha1.ArgoCDProjectRoleBindingFinalizerName) + return r.Update(ctx, projectRoleBinding) +} + +func (r *ArgoCDProjectRoleBindingReconciler) delete(projectRoleBinding *rbacoperatorv1alpha1.ArgoCDProjectRoleBinding) error { + roleName := projectRoleBinding.Spec.ArgoCDProjectRoleRef.Name + + appProjectNames := []string{} + for _, subject := range projectRoleBinding.Spec.Subjects { + appProjectNames = append(appProjectNames, subject.AppProjectRef) + } + return deleteProjectRoles(r.Client, appProjectNames, roleName, projectRoleBinding.Namespace) +} + +func deleteProjectRoles(rClient client.Client, appProjects []string, roleName string, namespace string) error { + for _, appProjectName := range appProjects { + appProject := &argocdv1alpha.AppProject{ + ObjectMeta: metav1.ObjectMeta{ + Name: appProjectName, + Namespace: namespace, + }, + } + if !IsObjectFound(rClient, appProject.Namespace, appProject.Name, appProject) { + continue // AppProject does not exist, nothing to delete + } + if err := removeRoleFromAppProject(rClient, appProject, roleName); err != nil { + return errors.Wrapf(err, "failed to remove role %s from AppProject %s", roleName, appProjectName) + } + } return nil -} \ No newline at end of file +} diff --git a/internal/controller/argocdrole_controller.go b/internal/controller/argocdrole_controller.go index 142609d..248a6f1 100644 --- a/internal/controller/argocdrole_controller.go +++ b/internal/controller/argocdrole_controller.go @@ -52,13 +52,6 @@ type ArgoCDRoleReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the Role object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.18.2/pkg/reconcile func (r *ArgoCDRoleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = r.Log.WithValues("argocdrole", req.NamespacedName) diff --git a/internal/controller/argocdrole_controller_test.go b/internal/controller/argocdrole_controller_test.go index 0824105..e5628d8 100644 --- a/internal/controller/argocdrole_controller_test.go +++ b/internal/controller/argocdrole_controller_test.go @@ -21,13 +21,14 @@ import ( "testing" "time" - rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" ) var _ reconcile.Reconciler = &ArgoCDRoleReconciler{} @@ -43,8 +44,8 @@ func TestArgoCDRoleReconciler_Reconcile(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap_WithChangedPolicyCSV())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap_WithChangedPolicyCSV())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -60,7 +61,7 @@ func TestArgoCDRoleReconciler_Reconcile(t *testing.T) { } cm := &corev1.ConfigMap{} - err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) assert.NoError(t, err) resCM := makeTestCMArgoCDRoleExpected() assert.Equal(t, resCM.Data, cm.Data) @@ -77,8 +78,8 @@ func TestArgoCDRoleReconciler_AddFinalizer(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -94,7 +95,7 @@ func TestArgoCDRoleReconciler_AddFinalizer(t *testing.T) { } argocdRoleRes := &rbacoperatorv1alpha1.ArgoCDRole{} - err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: argocdRole.Name, Namespace: argocdRole.Namespace}, argocdRoleRes) + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: argocdRole.Name, Namespace: argocdRole.Namespace}, argocdRoleRes) assert.NoError(t, err) assert.Contains(t, argocdRoleRes.GetFinalizers(), rbacoperatorv1alpha1.ArgoCDRoleFinalizerName) @@ -109,8 +110,8 @@ func TestArgoCDRoleReconciler_RoleNotFound(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -124,7 +125,7 @@ func TestArgoCDRoleReconciler_RoleNotFound(t *testing.T) { if res.RequeueAfter > 0 { t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) } - assert.Error(t, reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: testRoleName, Namespace: testNamespace}, &rbacoperatorv1alpha1.ArgoCDRole{})) + assert.Error(t, reconciler.Get(context.TODO(), types.NamespacedName{Name: testRoleName, Namespace: testNamespace}, &rbacoperatorv1alpha1.ArgoCDRole{})) } func TestArgoCDRoleReconciler_CMNotFound(t *testing.T) { @@ -138,7 +139,7 @@ func TestArgoCDRoleReconciler_CMNotFound(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -163,8 +164,8 @@ func TestArgoCDRoleReconciler_HandleFinalizer(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestCMArgoCDRoleExpected())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestCMArgoCDRoleExpected())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -180,7 +181,7 @@ func TestArgoCDRoleReconciler_HandleFinalizer(t *testing.T) { } cm := &corev1.ConfigMap{} - err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) assert.NoError(t, err) wantCM := makeTestRBACConfigMap() assert.Equal(t, wantCM.Data, cm.Data) @@ -199,8 +200,8 @@ func TestArgoCDRoleReconciler_RoleHasRoleBinding(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap_WithChangedPolicyCSV())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap_WithChangedPolicyCSV())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -216,7 +217,7 @@ func TestArgoCDRoleReconciler_RoleHasRoleBinding(t *testing.T) { } cm := &corev1.ConfigMap{} - err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) assert.NoError(t, err) resCM := makeTestCM_ArgoCDRole_WithRoleBindingRoleSubject_Expected() assert.Equal(t, resCM.Data, cm.Data) @@ -234,8 +235,8 @@ func TestArgoCDRoleReconciler_RoleBindingObjectMissing(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ diff --git a/internal/controller/argocdrolebinding_controller.go b/internal/controller/argocdrolebinding_controller.go index 7638c89..5f1ed58 100644 --- a/internal/controller/argocdrolebinding_controller.go +++ b/internal/controller/argocdrolebinding_controller.go @@ -50,13 +50,6 @@ type ArgoCDRoleBindingReconciler struct { // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the ArgoCDRoleBinding object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.18.2/pkg/reconcile func (r *ArgoCDRoleBindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = r.Log.WithValues("argocdrole", req.NamespacedName) diff --git a/internal/controller/argocdrolebinding_controller_test.go b/internal/controller/argocdrolebinding_controller_test.go index 2be671b..476945a 100644 --- a/internal/controller/argocdrolebinding_controller_test.go +++ b/internal/controller/argocdrolebinding_controller_test.go @@ -21,13 +21,14 @@ import ( "testing" "time" - rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" ) var _ reconcile.Reconciler = &ArgoCDRoleReconciler{} @@ -45,8 +46,8 @@ func TestArgoCDRoleBindingReconciler_ReconcileRoleSubject(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleBindingReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -62,7 +63,7 @@ func TestArgoCDRoleBindingReconciler_ReconcileRoleSubject(t *testing.T) { } cm := &corev1.ConfigMap{} - err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) assert.NoError(t, err) resCM := makeTestCM_ArgoCDRole_WithRoleBindingRoleSubject_Expected() assert.Equal(t, resCM.Data, cm.Data) @@ -79,8 +80,8 @@ func TestArgoCDRoleBindingReconciler_AddFinalizer(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleBindingReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -96,7 +97,7 @@ func TestArgoCDRoleBindingReconciler_AddFinalizer(t *testing.T) { } argocdRoleBindingRes := &rbacoperatorv1alpha1.ArgoCDRoleBinding{} - err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: argocdRoleBinding.Name, Namespace: argocdRoleBinding.Namespace}, argocdRoleBindingRes) + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: argocdRoleBinding.Name, Namespace: argocdRoleBinding.Namespace}, argocdRoleBindingRes) assert.NoError(t, err) assert.Contains(t, argocdRoleBindingRes.Finalizers, rbacoperatorv1alpha1.ArgoCDRoleBindingFinalizerName) @@ -111,8 +112,8 @@ func TestArgoCDRoleBindingReconciler_RoleBindingNotFound(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleBindingReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -126,7 +127,7 @@ func TestArgoCDRoleBindingReconciler_RoleBindingNotFound(t *testing.T) { if res.RequeueAfter > 0 { t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) } - assert.Error(t, reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: testRoleBindingName, Namespace: testNamespace}, &rbacoperatorv1alpha1.ArgoCDRoleBinding{})) + assert.Error(t, reconciler.Get(context.TODO(), types.NamespacedName{Name: testRoleBindingName, Namespace: testNamespace}, &rbacoperatorv1alpha1.ArgoCDRoleBinding{})) } func TestArgoCDRoleBindingReconciler_CMNotFound(t *testing.T) { @@ -140,7 +141,7 @@ func TestArgoCDRoleBindingReconciler_CMNotFound(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleBindingReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -167,8 +168,8 @@ func TestArgoCDRoleBindingReconciler_HandleFinalizer(t *testing.T) { reconciler := makeTestArgoCDRoleBindingReconciler(client, scheme) roleReconciler := makeTestArgoCDRoleReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -197,7 +198,7 @@ func TestArgoCDRoleBindingReconciler_HandleFinalizer(t *testing.T) { } cm := &corev1.ConfigMap{} - err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) assert.NoError(t, err) wantCM := makeTestCMArgoCDRoleExpected() assert.Equal(t, wantCM.Data, cm.Data) @@ -214,8 +215,8 @@ func TestArgoCDRoleBindingReconciler_RoleNotFound(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleBindingReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -229,7 +230,7 @@ func TestArgoCDRoleBindingReconciler_RoleNotFound(t *testing.T) { if res.RequeueAfter > 0 { t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) } - assert.Error(t, reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: argocdRoleBinding.Spec.ArgoCDRoleRef.Name, Namespace: testNamespace}, &rbacoperatorv1alpha1.ArgoCDRole{})) + assert.Error(t, reconciler.Get(context.TODO(), types.NamespacedName{Name: argocdRoleBinding.Spec.ArgoCDRoleRef.Name, Namespace: testNamespace}, &rbacoperatorv1alpha1.ArgoCDRole{})) } func TestArgoCDRoleBindingReconciler_ReconcileSSOSubject(t *testing.T) { @@ -244,8 +245,8 @@ func TestArgoCDRoleBindingReconciler_ReconcileSSOSubject(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleBindingReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -261,7 +262,7 @@ func TestArgoCDRoleBindingReconciler_ReconcileSSOSubject(t *testing.T) { } cm := &corev1.ConfigMap{} - err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) assert.NoError(t, err) resCM := makeTestCM_ArgoCDRole_WithRoleBindingSSOSubject_Expected() assert.Equal(t, resCM.Data, cm.Data) @@ -279,8 +280,8 @@ func TestArgoCDRoleBindingReconciler_ReconcileLocalSubject(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleBindingReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap_WithChangedPolicyCSV())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap_WithChangedPolicyCSV())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -296,7 +297,7 @@ func TestArgoCDRoleBindingReconciler_ReconcileLocalSubject(t *testing.T) { } cm := &corev1.ConfigMap{} - err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) assert.NoError(t, err) resCM := makeTestCM_ArgoCDRole_WithRoleBindingLocalSubject_Expected() assert.Equal(t, resCM.Data, cm.Data) @@ -313,8 +314,8 @@ func TestArgoCDRoleBindingReconciler_ReconcileBuiltInAdmin(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleBindingReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap_WithChangedPolicyCSV())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap_WithChangedPolicyCSV())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -330,7 +331,7 @@ func TestArgoCDRoleBindingReconciler_ReconcileBuiltInAdmin(t *testing.T) { } cm := &corev1.ConfigMap{} - err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) assert.NoError(t, err) resCM := makeTestCM_BuiltInAdmin_WithRoleBinding_Expected() assert.Equal(t, resCM.Data, cm.Data) @@ -347,8 +348,8 @@ func TestArgoCDRoleBindingReconciler_ReconcileBuiltInReadOnly(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleBindingReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap_WithChangedPolicyCSV())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap_WithChangedPolicyCSV())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -364,7 +365,7 @@ func TestArgoCDRoleBindingReconciler_ReconcileBuiltInReadOnly(t *testing.T) { } cm := &corev1.ConfigMap{} - err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) assert.NoError(t, err) resCM := makeTestCM_BuiltInReadOnly_WithRoleBinding_Expected() assert.Equal(t, resCM.Data, cm.Data) @@ -381,8 +382,8 @@ func TestArgoCDRoleBindingReconciler_HandleFinalizerBuiltInRole(t *testing.T) { client := makeTestReconcilerClient(scheme, resObjs, subresObjs) reconciler := makeTestArgoCDRoleBindingReconciler(client, scheme) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestArgoCDNamespace())) - assert.NoError(t, reconciler.Client.Create(context.TODO(), makeTestRBACConfigMap())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestArgoCDNamespace())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestRBACConfigMap())) req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -398,7 +399,7 @@ func TestArgoCDRoleBindingReconciler_HandleFinalizerBuiltInRole(t *testing.T) { } cm := &corev1.ConfigMap{} - err = reconciler.Client.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testRBACCMName, Namespace: testRBACCMNamespace}, cm) assert.NoError(t, err) resCM := makeTestRBACConfigMap() assert.Equal(t, resCM.Data, cm.Data) diff --git a/internal/controller/configmap.go b/internal/controller/configmap.go index b29315c..6168a5c 100644 --- a/internal/controller/configmap.go +++ b/internal/controller/configmap.go @@ -37,7 +37,7 @@ func getDefaultRBACPolicy() string { func getRBACPolicyCSV(role *rbacoperatorv1alpha1.ArgoCDRole, rb *rbacoperatorv1alpha1.ArgoCDRoleBinding) string { policy := "" - roleName := fmt.Sprintf("role:%s", role.ObjectMeta.Name) + roleName := fmt.Sprintf("role:%s", role.Name) policy += buildPolicyStringRules(role, roleName) policy += buildPolicyStringSubjects(rb, role) @@ -62,7 +62,7 @@ func buildPolicyStringRules(role *rbacoperatorv1alpha1.ArgoCDRole, roleName stri // buildPolicyStringSubjects will build the policy string for Subjects field of the given role. func buildPolicyStringSubjects(rb *rbacoperatorv1alpha1.ArgoCDRoleBinding, role *rbacoperatorv1alpha1.ArgoCDRole) string { policy := "" - roleName := fmt.Sprintf("role:%s", role.ObjectMeta.Name) + roleName := fmt.Sprintf("role:%s", role.Name) for _, subject := range rb.Spec.Subjects { switch subject.Kind { case "sso": @@ -109,7 +109,7 @@ func (r *ArgoCDRoleReconciler) reconcileRBACConfigMap(cm *corev1.ConfigMap, role } if changed { - return r.Client.Update(context.TODO(), cm) + return r.Update(context.TODO(), cm) } return nil } @@ -135,7 +135,7 @@ func (r *ArgoCDRoleReconciler) reconcileRBACConfigMapWithRoleBinding(cm *corev1. } if changed { - return r.Client.Update(context.TODO(), cm) + return r.Update(context.TODO(), cm) } return nil } @@ -161,7 +161,7 @@ func (r *ArgoCDRoleBindingReconciler) reconcileRBACConfigMap(cm *corev1.ConfigMa } if changed { - return r.Client.Update(context.TODO(), cm) + return r.Update(context.TODO(), cm) } return nil } @@ -187,7 +187,7 @@ func (r *ArgoCDRoleBindingReconciler) reconcileRBACConfigMapForBuiltInRole(cm *c } if changed { - return r.Client.Update(context.TODO(), cm) + return r.Update(context.TODO(), cm) } return nil } diff --git a/internal/controller/testing.go b/internal/controller/testing.go index e9da618..f1fc946 100644 --- a/internal/controller/testing.go +++ b/internal/controller/testing.go @@ -20,18 +20,17 @@ import ( "fmt" "time" + "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" - - "github.com/argoproj-labs/argocd-rbac-operator/internal/controller/common" - - rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" - "github.com/go-logr/logr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/log/zap" + + rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" + "github.com/argoproj-labs/argocd-rbac-operator/internal/controller/common" ) const ( @@ -67,6 +66,20 @@ func makeTestArgoCDRoleBindingReconciler(client client.Client, sch *runtime.Sche } } +func makeTestArgoCDProjectRoleReconciler(client client.Client, sch *runtime.Scheme) *ArgoCDProjectRoleReconciler { + return &ArgoCDProjectRoleReconciler{ + Client: client, + Scheme: sch, + } +} + +func makeTestArgoCDProjectRoleBindingReconciler(client client.Client, sch *runtime.Scheme) *ArgoCDProjectRoleBindingReconciler { + return &ArgoCDProjectRoleBindingReconciler{ + Client: client, + Scheme: sch, + } +} + func makeTestReconcilerClient(sch *runtime.Scheme, resObjs, subresObjs []client.Object) client.Client { client := fake.NewClientBuilder().WithScheme(sch) if len(resObjs) > 0 { @@ -86,6 +99,8 @@ func makeTestReconcilerScheme(schOpts ...SchemeOpt) *runtime.Scheme { return s } +// Global RBAC objects used in tests + type argocdRoleOpt func(*rbacoperatorv1alpha1.ArgoCDRole) type argocdRoleBindingOpt func(*rbacoperatorv1alpha1.ArgoCDRoleBinding) @@ -360,7 +375,7 @@ func addFinalizerRole() argocdRoleOpt { func roleDeletedAt(now time.Time) argocdRoleOpt { return func(r *rbacoperatorv1alpha1.ArgoCDRole) { wrapped := metav1.NewTime(now) - r.ObjectMeta.DeletionTimestamp = &wrapped + r.DeletionTimestamp = &wrapped } } @@ -379,6 +394,6 @@ func addFinalizerRoleBinding() argocdRoleBindingOpt { func roleBindingDeletedAt(now time.Time) argocdRoleBindingOpt { return func(r *rbacoperatorv1alpha1.ArgoCDRoleBinding) { wrapped := metav1.NewTime(now) - r.ObjectMeta.DeletionTimestamp = &wrapped + r.DeletionTimestamp = &wrapped } } diff --git a/test/utils/utils.go b/test/utils/utils.go index 0a6357e..0e9570b 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -22,7 +22,7 @@ import ( "os/exec" "strings" - . "github.com/onsi/ginkgo/v2" //nolint:golint,revive + . "github.com/onsi/ginkgo/v2" //nolint:revive,staticcheck ) const ( @@ -135,6 +135,6 @@ func GetProjectDir() (string, error) { if err != nil { return wd, err } - wd = strings.Replace(wd, "/test/e2e", "", -1) + wd = strings.ReplaceAll(wd, "/test/e2e", "") return wd, nil } From 89f8ecc356dab49842a3d2c50ac276d35cf4b7a2 Mon Sep 17 00:00:00 2001 From: Georgy Khromov Date: Sun, 8 Jun 2025 04:05:57 +0200 Subject: [PATCH 5/9] feat(appProjectRBAC)[#36]: added unit tests for ArgoCDProjectRole and ArgoCDProjectRoleBinding + updated docs Signed-off-by: Georgy Khromov --- README.md | 142 ++++++++- config/manager/kustomization.yaml | 2 +- .../rbac/argocdprojectrole_editor_role.yaml | 4 +- .../rbac/argocdprojectrole_viewer_role.yaml | 6 +- .../argocdprojectrolebinding_editor_role.yaml | 4 +- .../argocdprojectrolebinding_viewer_role.yaml | 4 +- config/rbac/kustomization.yaml | 14 +- helm/argocd-rbac-operator/README.md | 144 +++++++++- helm/argocd-rbac-operator/README.md.gotmpl | 148 +++++++++- .../templates/editor_roles.yaml | 51 +++- .../templates/manager_role.yaml | 19 ++ ...roj-labs.io_argocdprojectrolebindings.yaml | 12 +- ...r.argoproj-labs.io_argocdprojectroles.yaml | 2 +- ...r.argoproj-labs.io_argocdrolebindings.yaml | 2 +- ...operator.argoproj-labs.io_argocdroles.yaml | 2 +- .../templates/viewer_roles.yaml | 43 ++- .../argocdprojectrole_controller.go | 15 +- .../argocdprojectrole_controller_test.go | 208 ++++++++++---- .../argocdprojectrolebinding_controller.go | 16 +- ...rgocdprojectrolebinding_controller_test.go | 270 ++++++++++++++---- internal/controller/testing.go | 161 +++++++++++ 21 files changed, 1091 insertions(+), 178 deletions(-) diff --git a/README.md b/README.md index b6ff53d..31b9a12 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/argoproj-labs/argocd-rbac-operator)](https://goreportcard.com/report/github.com/argoproj-labs/argocd-rbac-operator) [![go.mod Go version](https://img.shields.io/github/go-mod/go-version/argoproj-labs/argocd-rbac-operator)](https://github.com/argoproj-labs/argocd-rbac-operator) -[![GitHub Release](https://img.shields.io/github/v/release/argoproj-labs/argocd-rbac-operator)](https://github.com/argoproj-labs/argocd-rbac-operator/releases/tag/v0.1.9) +[![GitHub Release](https://img.shields.io/github/v/release/argoproj-labs/argocd-rbac-operator)](https://github.com/argoproj-labs/argocd-rbac-operator/releases/tag/v0.2.0) [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/argocd-rbac-operator)](https://artifacthub.io/packages/search?repo=argocd-rbac-operator) Kubernetes Operator for Argo CD RBAC Management. ## Introduction -The Argo CD RBAC Operator provides a CRD based API for the RBAC management of Argo CD. It provides a structured and easy to use way to define RBAC policies. The Operator uses the CRs as a single source of truth for RBAC management and converts them into a policy string that is patched into the Argo CD RBAC ConfigMap. +The Argo CD RBAC Operator provides a CRD based API for the RBAC management of Argo CD. It provides a structured and easy to use way to define RBAC policies. The Operator uses the CRs as a single source of truth for RBAC management and converts them into a policy string that is patched into the Argo CD RBAC ConfigMap or AppProjects. ## Installation @@ -64,6 +64,8 @@ helm install argocd-rbac-operator argocd-rbac-operator/argocd-rbac-operator -f v ## Usage +### Global-scoped RBAC + The following example shows a manifest to create a new ArgoCDRole `test-role`: ```yaml @@ -105,7 +107,7 @@ spec: name: "test-role" ``` -### Create +#### Create ArgoCDRoles and ArgoCDRoleBindings Make sure that the `argocd` Namespace exists, so that the ConfigMap can be created properly. @@ -141,7 +143,7 @@ metadata: namespace: argocd ``` -### Delete +#### Delete ArgoCDRoles and ArgoCDRoleBindings To delete a Role you can use `kubectl` @@ -152,16 +154,138 @@ kubectl delete argocdrolebinding.rbac-operator.argoproj-labs.io/test-role-bindin After the Resource is deleted, the policy string will be also deleted from the RBAC-CM. -### Change the Policy.CSV +#### Change the Policy.CSV To change the policy.csv you have to make changes in the `internal/controller/common/defaults.go` file. -### Deployment types +#### Deployment types As for now only single Argo CD deployment type is supported. The default Argo CD namespace is defined as `argocd`, to change that you have to provide a flag `--argocd-rbac-cm-namespace="your-argocd-namespace"`. +### AppProject-scoped RBAC + +The following example shows a manifest to create a new ArgoCDProjectRole `test-project-role`: + +```yaml +apiVersion: rbac-operator.argoproj-labs.io/v1alpha1 +kind: ArgoCDProjectRole +metadata: + name: test-project-role + namespace: test-ns +spec: + description: "Test role for ArgoCD's AppProjects" + rules: + - resource: clusters + verbs: + - get + - watch + objects: + - "*" + - resource: applications + verbs: + - get + objects: + - "*" +``` + +And a ArgoCDProjectRoleBinding `test-project-role-binding` to bind the specified role to a single or multiple AppProjects: + +```yaml +apiVersion: rbac-operator.argoproj-labs.io/v1alpha1 +kind: ArgoCDProjectRoleBinding +metadata: + name: test-project-role-binding + namespace: test-ns +spec: + argocdProjectRoleRef: + name: test-project-role + subjects: + - appProjectRef: test-appproject-1 + groups: + - test-group-1 + - test-group-2 + - appProjectRef: test-appproject-2 + groups: + - test-group-3 + - test-group-4 +``` + +#### Create ArgoCDProjectRoles and ArgoCDProjectRoleBindings + +Create a new ArgoCDProjectRole and ArgoCDProjectRoleBinding using the provided example. (Make sure that both CRs and AppProjects are created in the same Namespace) + +```bash +kubectl create -f test-project-role.yaml +kubectl create -f test-project-role-binding.yaml +``` + +After the reconciliation a following role will be added to the specified AppProjects: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: test-appproject-1 + namespace: test-ns +spec: + description: "Test AppProject 1 for ArgoCD's RBAC Operator" + roles: + ... + - description: Test role for ArgoCD's AppProjects + groups: + - test-group-1 + - test-group-2 + name: test-project-role + policies: + - p, proj:test-appproject-1:test-project-role, clusters, get, *, allow + - p, proj:test-appproject-1:test-project-role, clusters, watch, *, allow + - p, proj:test-appproject-1:test-project-role, applications, get, *, allow + ... +--- +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: test-appproject-2 + namespace: test-ns +spec: + description: "Test AppProject 2 for ArgoCD's RBAC Operator" + roles: + ... + - description: Test role for ArgoCD's AppProjects + groups: + - test-group-3 + - test-group-4 + name: test-project-role + policies: + - p, proj:test-appproject-2:test-project-role, clusters, get, *, allow + - p, proj:test-appproject-2:test-project-role, clusters, watch, *, allow + - p, proj:test-appproject-2:test-project-role, applications, get, *, allow + ... +``` + +#### Changes to ArgoCDProjectRoles and ArgoCDProjectRoleBindings + +If changes there made to the CRs, they also will be reflected in referenced AppProjects: + +- changes to `spec.rules` of ArgoCDProjectRole + - will be pacthed to AppProject on next reconcile of ArgoCDProjectRoleBinding +- changes to `spec.subjects` of ArgoCDProjectRoleBindings + - deletion of a subject, will delete the role in AppProject + - change to subject will be reflected in AppProject on next reconcile + +#### Delete ArgoCDProjectRoles and ArgoCDProjectRoleBindings + +To delete a Role you can use `kubectl` + +```bash +kubectl delete argocdprojectroles test-project-role +kubectl delete argocdprojectrolebindings test-project-role-binding +``` + +After the deletion of the Role or RoleBinding, the Role will also be deleted in AppProject. + ## Roadmap -- extend the operator with functionality to manage Argo CD AppProject RBAC -- achieve test coverage of >= 80% (current: ~75%) -- allow management for multi-instances set-up of Argo CD +- [x] extend the operator with functionality to manage Argo CD AppProject RBAC +- [ ] achieve test coverage of >= 80% (current: ~75%) +- [ ] allow management for multi-instances set-up of Argo CD diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 2eb8630..a1b0dcd 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: quay.io/argoprojlabs/argocd-rbac-operator - newTag: v0.1.9 + newTag: v0.2.0 diff --git a/config/rbac/argocdprojectrole_editor_role.yaml b/config/rbac/argocdprojectrole_editor_role.yaml index 9b46fab..ceaef1d 100644 --- a/config/rbac/argocdprojectrole_editor_role.yaml +++ b/config/rbac/argocdprojectrole_editor_role.yaml @@ -14,7 +14,7 @@ metadata: name: argocdprojectrole-editor-role rules: - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: - argocdprojectroles verbs: @@ -26,7 +26,7 @@ rules: - update - watch - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: - argocdprojectroles/status verbs: diff --git a/config/rbac/argocdprojectrole_viewer_role.yaml b/config/rbac/argocdprojectrole_viewer_role.yaml index 0d57824..899ef54 100644 --- a/config/rbac/argocdprojectrole_viewer_role.yaml +++ b/config/rbac/argocdprojectrole_viewer_role.yaml @@ -14,7 +14,7 @@ metadata: name: argocdprojectrole-viewer-role rules: - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: - argocdprojectroles verbs: @@ -22,8 +22,8 @@ rules: - list - watch - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: - argocdprojectroles/status verbs: - - get + - get \ No newline at end of file diff --git a/config/rbac/argocdprojectrolebinding_editor_role.yaml b/config/rbac/argocdprojectrolebinding_editor_role.yaml index 185b164..6da9619 100644 --- a/config/rbac/argocdprojectrolebinding_editor_role.yaml +++ b/config/rbac/argocdprojectrolebinding_editor_role.yaml @@ -14,7 +14,7 @@ metadata: name: argocdprojectrolebinding-editor-role rules: - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: - argocdprojectrolebindings verbs: @@ -26,7 +26,7 @@ rules: - update - watch - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: - argocdprojectrolebindings/status verbs: diff --git a/config/rbac/argocdprojectrolebinding_viewer_role.yaml b/config/rbac/argocdprojectrolebinding_viewer_role.yaml index 62cdf13..3b20a35 100644 --- a/config/rbac/argocdprojectrolebinding_viewer_role.yaml +++ b/config/rbac/argocdprojectrolebinding_viewer_role.yaml @@ -14,7 +14,7 @@ metadata: name: argocdprojectrolebinding-viewer-role rules: - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: - argocdprojectrolebindings verbs: @@ -22,7 +22,7 @@ rules: - list - watch - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: - argocdprojectrolebindings/status verbs: diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 5382467..10b3f35 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -9,14 +9,6 @@ resources: - role_binding.yaml - leader_election_role.yaml - leader_election_role_binding.yaml -# For each CRD, "Editor" and "Viewer" roles are scaffolded by -# default, aiding admins in cluster management. Those roles are -# not used by the Project itself. You can comment the following lines -# if you do not want those helpers be installed with your Project. -- argocdrolebinding_editor_role.yaml -- argocdrolebinding_viewer_role.yaml -- argocdrole_editor_role.yaml -- argocdrole_viewer_role.yaml # For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by # default, aiding admins in cluster management. Those roles are @@ -27,4 +19,8 @@ resources: - argocdprojectrolebinding_viewer_role.yaml - argocdprojectrole_admin_role.yaml - argocdprojectrole_editor_role.yaml -- argocdprojectrole_viewer_role.yaml \ No newline at end of file +- argocdprojectrole_viewer_role.yaml +- argocdrolebinding_editor_role.yaml +- argocdrolebinding_viewer_role.yaml +- argocdrole_editor_role.yaml +- argocdrole_viewer_role.yaml \ No newline at end of file diff --git a/helm/argocd-rbac-operator/README.md b/helm/argocd-rbac-operator/README.md index 0da7356..2c534ab 100644 --- a/helm/argocd-rbac-operator/README.md +++ b/helm/argocd-rbac-operator/README.md @@ -2,14 +2,14 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/argoproj-labs/argocd-rbac-operator)](https://goreportcard.com/report/github.com/argoproj-labs/argocd-rbac-operator) [![go.mod Go version](https://img.shields.io/github/go-mod/go-version/argoproj-labs/argocd-rbac-operator)](https://github.com/argoproj-labs/argocd-rbac-operator) -[![GitHub Release](https://img.shields.io/github/v/release/argoproj-labs/argocd-rbac-operator)](https://github.com/argoproj-labs/argocd-rbac-operator/releases/tag/v0.1.9) +[![GitHub Release](https://img.shields.io/github/v/release/argoproj-labs/argocd-rbac-operator)](https://github.com/argoproj-labs/argocd-rbac-operator/releases/tag/v0.2.0) [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/argocd-rbac-operator)](https://artifacthub.io/packages/search?repo=argocd-rbac-operator) Kubernetes Operator for Argo CD RBAC Management. ## Introduction -The Argo CD RBAC Operator provides a CRD based API for the RBAC management of Argo CD. It provides a structured and easy to use way to define RBAC policies. The Operator uses the CRs as a single source of truth for RBAC management and converts them into a policy string that is patched into the Argo CD RBAC ConfigMap. +The Argo CD RBAC Operator provides a CRD based API for the RBAC management of Argo CD. It provides a structured and easy to use way to define RBAC policies. The Operator uses the CRs as a single source of truth for RBAC management and converts them into a policy string that is patched into the Argo CD RBAC ConfigMap or AppProjects. ## Installation @@ -33,6 +33,8 @@ helm install argocd-rbac-operator argocd-rbac-operator/argocd-rbac-operator -f v ## Usage +### Global-scoped RBAC + The following example shows a manifest to create a new ArgoCDRole `test-role`: ```yaml @@ -74,7 +76,7 @@ spec: name: "test-role" ``` -### Create +#### Create ArgoCDRoles and ArgoCDRoleBindings Make sure that the `argocd` Namespace exists, so that the ConfigMap can be created properly. @@ -110,7 +112,7 @@ metadata: namespace: argocd ``` -### Delete +#### Delete ArgoCDRoles and ArgoCDRoleBindings To delete a Role you can use `kubectl` @@ -121,13 +123,141 @@ kubectl delete argocdrolebinding.rbac-operator.argoproj-labs.io/test-role-bindin After the Resource is deleted, the policy string will be also deleted from the RBAC-CM. -### Change the Policy.CSV +#### Change the Policy.CSV To change the policy.csv you have to make changes in the `internal/controller/common/defaults.go` file. -### Deployment types +#### Deployment types + +As for now only single Argo CD deployment type is supported. The default Argo CD namespace is defined as `argocd`, to change that you have to provide a flag `--argocd-rbac-cm-namespace="your-argocd-namespace"`. + +### AppProject-scoped RBAC + +The following example shows a manifest to create a new ArgoCDProjectRole `test-project-role`: + +```yaml +apiVersion: rbac-operator.argoproj-labs.io/v1alpha1 +kind: ArgoCDProjectRole +metadata: + name: test-project-role + namespace: test-ns +spec: + description: "Test role for ArgoCD's AppProjects" + rules: + - resource: clusters + verbs: + - get + - watch + objects: + - "*" + - resource: applications + verbs: + - get + objects: + - "*" +``` + +And a ArgoCDProjectRoleBinding `test-project-role-binding` to bind the specified role to a single or multiple AppProjects: + +```yaml +apiVersion: rbac-operator.argoproj-labs.io/v1alpha1 +kind: ArgoCDProjectRoleBinding +metadata: + name: test-project-role-binding + namespace: test-ns +spec: + argocdProjectRoleRef: + name: test-project-role + subjects: + - appProjectRef: test-appproject-1 + groups: + - test-group-1 + - test-group-2 + - appProjectRef: test-appproject-2 + groups: + - test-group-3 + - test-group-4 +``` + +#### Create ArgoCDProjectRoles and ArgoCDProjectRoleBindings + +Create a new ArgoCDProjectRole and ArgoCDProjectRoleBinding using the provided example. (Make sure that both CRs and AppProjects are created in the same Namespace) + +```bash +kubectl create -f test-project-role.yaml +kubectl create -f test-project-role-binding.yaml +``` + +After the reconciliation a following role will be added to the specified AppProjects: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: test-appproject-1 + namespace: test-ns +spec: + description: "Test AppProject 1 for ArgoCD's RBAC Operator" + roles: + ... + - description: Test role for ArgoCD's AppProjects + groups: + - test-group-1 + - test-group-2 + name: test-project-role + policies: + - p, proj:test-appproject-1:test-project-role, clusters, get, *, allow + - p, proj:test-appproject-1:test-project-role, clusters, watch, *, allow + - p, proj:test-appproject-1:test-project-role, applications, get, *, allow + ... +--- +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: test-appproject-2 + namespace: test-ns +spec: + description: "Test AppProject 2 for ArgoCD's RBAC Operator" + roles: + ... + - description: Test role for ArgoCD's AppProjects + groups: + - test-group-3 + - test-group-4 + name: test-project-role + policies: + - p, proj:test-appproject-2:test-project-role, clusters, get, *, allow + - p, proj:test-appproject-2:test-project-role, clusters, watch, *, allow + - p, proj:test-appproject-2:test-project-role, applications, get, *, allow + ... +``` + +#### Changes to ArgoCDProjectRoles and ArgoCDProjectRoleBindings + +If changes there made to the CRs, they also will be reflected in referenced AppProjects: + +- changes to `spec.rules` of ArgoCDProjectRole + - will be pacthed to AppProject on next reconcile of ArgoCDProjectRoleBinding +- changes to `spec.subjects` of ArgoCDProjectRoleBindings + - deletion of a subject, will delete the role in AppProject + - change to subject will be reflected in AppProject on next reconcile + +#### Delete ArgoCDProjectRoles and ArgoCDProjectRoleBindings + +To delete a Role you can use `kubectl` + +```bash +kubectl delete argocdprojectroles test-project-role +kubectl delete argocdprojectrolebindings test-project-role-binding +``` + +After the deletion of the Role or RoleBinding, the Role will also be deleted in AppProject. + +## Roadmap -As for now only single Argo CD deployment type is supported. The default Argo CD namespace is defined as `argocd`, to change that you have to make a change in `internal/controller/common/values.go`. +- [x] extend the operator with functionality to manage Argo CD AppProject RBAC +- [ ] achieve test coverage of >= 80% (current: ~75%) +- [ ] allow management for multi-instances set-up of Argo CD ## General parameters diff --git a/helm/argocd-rbac-operator/README.md.gotmpl b/helm/argocd-rbac-operator/README.md.gotmpl index 37822bc..b3694a5 100644 --- a/helm/argocd-rbac-operator/README.md.gotmpl +++ b/helm/argocd-rbac-operator/README.md.gotmpl @@ -2,14 +2,14 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/argoproj-labs/argocd-rbac-operator)](https://goreportcard.com/report/github.com/argoproj-labs/argocd-rbac-operator) [![go.mod Go version](https://img.shields.io/github/go-mod/go-version/argoproj-labs/argocd-rbac-operator)](https://github.com/argoproj-labs/argocd-rbac-operator) -[![GitHub Release](https://img.shields.io/github/v/release/argoproj-labs/argocd-rbac-operator)](https://github.com/argoproj-labs/argocd-rbac-operator/releases/tag/v0.1.6) +[![GitHub Release](https://img.shields.io/github/v/release/argoproj-labs/argocd-rbac-operator)](https://github.com/argoproj-labs/argocd-rbac-operator/releases/tag/v0.2.0) [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/argocd-rbac-operator)](https://artifacthub.io/packages/search?repo=argocd-rbac-operator) Kubernetes Operator for Argo CD RBAC Management. ## Introduction -The Argo CD RBAC Operator provides a CRD based API for the RBAC management of Argo CD. It provides a structured and easy to use way to define RBAC policies. The Operator uses the CRs as a single source of truth for RBAC management and converts them into a policy string that is patched into the Argo CD RBAC ConfigMap. +The Argo CD RBAC Operator provides a CRD based API for the RBAC management of Argo CD. It provides a structured and easy to use way to define RBAC policies. The Operator uses the CRs as a single source of truth for RBAC management and converts them into a policy string that is patched into the Argo CD RBAC ConfigMap or AppProjects. ## Installation @@ -33,6 +33,8 @@ helm install argocd-rbac-operator argocd-rbac-operator/argocd-rbac-operator -f v ## Usage +### Global-scoped RBAC + The following example shows a manifest to create a new ArgoCDRole `test-role`: ```yaml @@ -74,7 +76,7 @@ spec: name: "test-role" ``` -### Create +#### Create ArgoCDRoles and ArgoCDRoleBindings Make sure that the `argocd` Namespace exists, so that the ConfigMap can be created properly. @@ -110,22 +112,152 @@ metadata: namespace: argocd ``` -### Delete +#### Delete ArgoCDRoles and ArgoCDRoleBindings To delete a Role you can use `kubectl` -``` + +```bash kubectl delete argocdrole.rbac-operator.argoproj-labs.io/test-role kubectl delete argocdrolebinding.rbac-operator.argoproj-labs.io/test-role-binding ``` + After the Resource is deleted, the policy string will be also deleted from the RBAC-CM. -### Change the Policy.CSV +#### Change the Policy.CSV To change the policy.csv you have to make changes in the `internal/controller/common/defaults.go` file. -### Deployment types +#### Deployment types + +As for now only single Argo CD deployment type is supported. The default Argo CD namespace is defined as `argocd`, to change that you have to provide a flag `--argocd-rbac-cm-namespace="your-argocd-namespace"`. + +### AppProject-scoped RBAC + +The following example shows a manifest to create a new ArgoCDProjectRole `test-project-role`: + +```yaml +apiVersion: rbac-operator.argoproj-labs.io/v1alpha1 +kind: ArgoCDProjectRole +metadata: + name: test-project-role + namespace: test-ns +spec: + description: "Test role for ArgoCD's AppProjects" + rules: + - resource: clusters + verbs: + - get + - watch + objects: + - "*" + - resource: applications + verbs: + - get + objects: + - "*" +``` + +And a ArgoCDProjectRoleBinding `test-project-role-binding` to bind the specified role to a single or multiple AppProjects: + +```yaml +apiVersion: rbac-operator.argoproj-labs.io/v1alpha1 +kind: ArgoCDProjectRoleBinding +metadata: + name: test-project-role-binding + namespace: test-ns +spec: + argocdProjectRoleRef: + name: test-project-role + subjects: + - appProjectRef: test-appproject-1 + groups: + - test-group-1 + - test-group-2 + - appProjectRef: test-appproject-2 + groups: + - test-group-3 + - test-group-4 +``` + +#### Create ArgoCDProjectRoles and ArgoCDProjectRoleBindings + +Create a new ArgoCDProjectRole and ArgoCDProjectRoleBinding using the provided example. (Make sure that both CRs and AppProjects are created in the same Namespace) + +```bash +kubectl create -f test-project-role.yaml +kubectl create -f test-project-role-binding.yaml +``` + +After the reconciliation a following role will be added to the specified AppProjects: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: test-appproject-1 + namespace: test-ns +spec: + description: "Test AppProject 1 for ArgoCD's RBAC Operator" + roles: + ... + - description: Test role for ArgoCD's AppProjects + groups: + - test-group-1 + - test-group-2 + name: test-project-role + policies: + - p, proj:test-appproject-1:test-project-role, clusters, get, *, allow + - p, proj:test-appproject-1:test-project-role, clusters, watch, *, allow + - p, proj:test-appproject-1:test-project-role, applications, get, *, allow + ... +--- +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: test-appproject-2 + namespace: test-ns +spec: + description: "Test AppProject 2 for ArgoCD's RBAC Operator" + roles: + ... + - description: Test role for ArgoCD's AppProjects + groups: + - test-group-3 + - test-group-4 + name: test-project-role + policies: + - p, proj:test-appproject-2:test-project-role, clusters, get, *, allow + - p, proj:test-appproject-2:test-project-role, clusters, watch, *, allow + - p, proj:test-appproject-2:test-project-role, applications, get, *, allow + ... +``` + +#### Changes to ArgoCDProjectRoles and ArgoCDProjectRoleBindings + +If changes there made to the CRs, they also will be reflected in referenced AppProjects: + +- changes to `spec.rules` of ArgoCDProjectRole + - will be pacthed to AppProject on next reconcile of ArgoCDProjectRoleBinding +- changes to `spec.subjects` of ArgoCDProjectRoleBindings + - deletion of a subject, will delete the role in AppProject + - change to subject will be reflected in AppProject on next reconcile + +#### Delete ArgoCDProjectRoles and ArgoCDProjectRoleBindings + +To delete a Role you can use `kubectl` + +```bash +kubectl delete argocdprojectroles test-project-role +kubectl delete argocdprojectrolebindings test-project-role-binding +``` + +After the deletion of the Role or RoleBinding, the Role will also be deleted in AppProject. + +## Roadmap -As for now only single Argo CD deployment type is supported. The default Argo CD namespace is defined as `argocd`, to change that you have to make a change in `internal/controller/common/values.go`. +- [x] extend the operator with functionality to manage Argo CD AppProject RBAC +- [ ] achieve test coverage of >= 80% (current: ~75%) +- [ ] allow management for multi-instances set-up of Argo CD ## General parameters diff --git a/helm/argocd-rbac-operator/templates/editor_roles.yaml b/helm/argocd-rbac-operator/templates/editor_roles.yaml index dd3b732..27a2e0e 100644 --- a/helm/argocd-rbac-operator/templates/editor_roles.yaml +++ b/helm/argocd-rbac-operator/templates/editor_roles.yaml @@ -28,7 +28,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: {{- include "argocd-rbac-operator.labels" . | nindent 4 }} - name: argocd-rbac-operator-role-editor-role + name: argocd-rbac-operator-argocdrole-editor-role rules: - apiGroups: - rbac-operator.argoproj-labs.io @@ -49,3 +49,52 @@ rules: verbs: - get --- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: {{- include "argocd-rbac-operator.labels" . | nindent 4 }} + name: argocd-rbac-operator-argocdprojectrole-editor-role +rules: +- apiGroups: + - rbac-operator.argoproj-labs.io + resources: + - argocdprojectroles + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac-operator.argoproj-labs.io + resources: + - argocdprojectroles/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: {{- include "argocd-rbac-operator.labels" . | nindent 4 }} + name: argocd-rbac-operator-argocdprojectrolebinding-editor-role +rules: +- apiGroups: + - rbac-operator.argoproj-labs.io + resources: + - argocdprojectrolebindings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rbac-operator.argoproj-labs.io + resources: + - argocdprojectrolebindings/status + verbs: + - get diff --git a/helm/argocd-rbac-operator/templates/manager_role.yaml b/helm/argocd-rbac-operator/templates/manager_role.yaml index e0b8277..390b4cd 100644 --- a/helm/argocd-rbac-operator/templates/manager_role.yaml +++ b/helm/argocd-rbac-operator/templates/manager_role.yaml @@ -12,9 +12,19 @@ rules: - get - list - watch +- apiGroups: + - argoproj.io + resources: + - appprojects + verbs: + - get + - list + - patch - apiGroups: - rbac-operator.argoproj-labs.io resources: + - argocdprojectrolebindings + - argocdprojectroles - argocdrolebindings - argocdroles verbs: @@ -22,11 +32,20 @@ rules: - apiGroups: - rbac-operator.argoproj-labs.io resources: + - argocdprojectrolebindings/finalizers + - argocdprojectrolebindings/status + - argocdprojectroles/finalizers - argocdrolebindings/finalizers - argocdrolebindings/status - argocdroles/finalizers verbs: - '*' +- apiGroups: + - rbac-operator.argoproj-labs.io + resources: + - argocdprojectroles/status + verbs: + - '*' - apiGroups: - rbac-operator.argoproj-labs.io resources: diff --git a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml index fe415db..86902b2 100644 --- a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml +++ b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectrolebindings.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.15.0 + controller-gen.kubebuilder.io/version: v0.18.0 name: argocdprojectrolebindings.rbac-operator.argoproj-labs.io spec: group: rbac-operator.argoproj-labs.io @@ -56,7 +56,7 @@ spec: description: List of subjects being bound to ArgoCDProjectRole (argocdProjectRoleRef). items: description: AppProjectSubject defines the subject being bound to - an AppProject scoped ArgoCDRole. + ArgoCDProjectRole. properties: appProjectRef: description: Reference to the AppProject the ArgoCDRole is bound @@ -71,14 +71,22 @@ spec: - appProjectRef - groups type: object + minItems: 1 type: array required: - argocdProjectRoleRef + - subjects type: object status: description: ArgoCDProjectRoleBindingStatus defines the observed state of ArgoCDProjectRoleBinding. properties: + appProjectsBound: + description: AppProjectsBound is a list of AppProjects that the role + is bound to. + items: + type: string + type: array conditions: description: Conditions defines the list of conditions. items: diff --git a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml index d797764..f9bcbf9 100644 --- a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml +++ b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdprojectroles.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.15.0 + controller-gen.kubebuilder.io/version: v0.18.0 name: argocdprojectroles.rbac-operator.argoproj-labs.io spec: group: rbac-operator.argoproj-labs.io diff --git a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml index 62a2eb0..ad15872 100644 --- a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml +++ b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.15.0 + controller-gen.kubebuilder.io/version: v0.18.0 name: argocdrolebindings.rbac-operator.argoproj-labs.io spec: group: rbac-operator.argoproj-labs.io diff --git a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdroles.yaml b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdroles.yaml index 604e639..e0b23ff 100644 --- a/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdroles.yaml +++ b/helm/argocd-rbac-operator/templates/rbac-operator.argoproj-labs.io_argocdroles.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.15.0 + controller-gen.kubebuilder.io/version: v0.18.0 name: argocdroles.rbac-operator.argoproj-labs.io spec: group: rbac-operator.argoproj-labs.io diff --git a/helm/argocd-rbac-operator/templates/viewer_roles.yaml b/helm/argocd-rbac-operator/templates/viewer_roles.yaml index cbe7fe1..3a4053e 100644 --- a/helm/argocd-rbac-operator/templates/viewer_roles.yaml +++ b/helm/argocd-rbac-operator/templates/viewer_roles.yaml @@ -24,7 +24,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: {{- include "argocd-rbac-operator.labels" . | nindent 4 }} - name: argocd-rbac-operator-role-viewer-role + name: argocd-rbac-operator-argocdrole-viewer-role rules: - apiGroups: - rbac-operator.argoproj-labs.io @@ -41,3 +41,44 @@ rules: verbs: - get --- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: {{- include "argocd-rbac-operator.labels" . | nindent 4 }} + name: argocd-rbac-operator-argocdprojectrole-viewer-role +rules: +- apiGroups: + - rbac-operator.argoproj-labs.io + resources: + - argocdprojectroles + verbs: + - get + - list + - watch +- apiGroups: + - rbac-operator.argoproj-labs.io + resources: + - argocdprojectroles/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: {{- include "argocd-rbac-operator.labels" . | nindent 4 }} + name: argocd-rbac-operator-argocdprojectrolebinding-viewer-role +rules: +- apiGroups: + - rbac-operator.argoproj-labs.io + resources: + - argocdprojectrolebindings + verbs: + - get + - list + - watch +- apiGroups: + - rbac-operator.argoproj-labs.io + resources: + - argocdprojectrolebindings/status + verbs: + - get diff --git a/internal/controller/argocdprojectrole_controller.go b/internal/controller/argocdprojectrole_controller.go index 16f7b0e..a739315 100644 --- a/internal/controller/argocdprojectrole_controller.go +++ b/internal/controller/argocdprojectrole_controller.go @@ -19,6 +19,7 @@ package controller import ( "context" "fmt" + "time" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/errors" @@ -58,8 +59,8 @@ func (r *ArgoCDProjectRoleReconciler) Reconcile(ctx context.Context, req ctrl.Re projectRole.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) if err := r.Status().Update(ctx, &projectRole); err != nil { r.Log.Error(err, "Failed to update ArgoCDProjectRole status", "name", req.Name) - return ctrl.Result{}, err } + return ctrl.Result{RequeueAfter: time.Minute * 2}, err } if projectRole.IsBeingDeleted() { @@ -68,7 +69,7 @@ func (r *ArgoCDProjectRoleReconciler) Reconcile(ctx context.Context, req ctrl.Re if err := r.Status().Update(ctx, &projectRole); err != nil { r.Log.Error(err, "Failed to update ArgoCDProjectRole status during finalizer handling", "name", req.Name) } - return ctrl.Result{}, fmt.Errorf("error when handling finalizer: %v", err) + return ctrl.Result{RequeueAfter: time.Minute * 2}, fmt.Errorf("error when handling finalizer: %v", err) } return ctrl.Result{}, nil } @@ -79,9 +80,9 @@ func (r *ArgoCDProjectRoleReconciler) Reconcile(ctx context.Context, req ctrl.Re if err := r.Status().Update(ctx, &projectRole); err != nil { r.Log.Error(err, "Failed to update ArgoCDProjectRole status after adding finalizer", "name", req.Name) } - return ctrl.Result{}, fmt.Errorf("error when adding finalizer: %v", err) + return ctrl.Result{RequeueAfter: time.Minute * 2}, fmt.Errorf("error when adding finalizer: %v", err) } - return ctrl.Result{}, nil + return ctrl.Result{RequeueAfter: time.Second}, nil } if projectRole.HasArgoCDProjectRoleBindingRef() { @@ -100,16 +101,16 @@ func (r *ArgoCDProjectRoleReconciler) Reconcile(ctx context.Context, req ctrl.Re if err := r.Status().Update(ctx, &projectRole); err != nil { r.Log.Error(err, "Failed to update ArgoCDProjectRole status after binding not found", "name", req.Name) } - return ctrl.Result{}, nil + return ctrl.Result{RequeueAfter: time.Minute * 2}, nil } projectRole.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) if err := r.Status().Update(ctx, &projectRole); err != nil { r.Log.Error(err, "Failed to update ArgoCDProjectRole status", "name", req.Name) } - return ctrl.Result{}, fmt.Errorf("error fetching ArgoCDProjectRoleBinding: %v", err) + return ctrl.Result{RequeueAfter: time.Minute * 2}, fmt.Errorf("error fetching ArgoCDProjectRoleBinding: %v", err) } } - return ctrl.Result{}, nil + return ctrl.Result{RequeueAfter: time.Minute * 5}, nil } // SetupWithManager sets up the controller with the Manager. diff --git a/internal/controller/argocdprojectrole_controller_test.go b/internal/controller/argocdprojectrole_controller_test.go index 17464d5..f9e8867 100644 --- a/internal/controller/argocdprojectrole_controller_test.go +++ b/internal/controller/argocdprojectrole_controller_test.go @@ -18,67 +18,161 @@ package controller import ( "context" + "testing" + "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" + argocdv1alpha "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" + "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" ) -var _ = Describe("ArgoCDProjectRole Controller", func() { - Context("When reconciling a resource", func() { - const resourceName = "test-resource" - - ctx := context.Background() - - typeNamespacedName := types.NamespacedName{ - Name: resourceName, - Namespace: "default", // TODO(user):Modify as needed - } - argocdprojectrole := &rbacoperatorv1alpha1.ArgoCDProjectRole{} - - BeforeEach(func() { - By("creating the custom resource for the Kind ArgoCDProjectRole") - err := k8sClient.Get(ctx, typeNamespacedName, argocdprojectrole) - if err != nil && errors.IsNotFound(err) { - resource := &rbacoperatorv1alpha1.ArgoCDProjectRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourceName, - Namespace: "default", - }, - // TODO(user): Specify other spec details if needed. - } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - } - }) - - AfterEach(func() { - // TODO(user): Cleanup logic after each test, like removing the resource instance. - resource := &rbacoperatorv1alpha1.ArgoCDProjectRole{} - err := k8sClient.Get(ctx, typeNamespacedName, resource) - Expect(err).NotTo(HaveOccurred()) - - By("Cleanup the specific resource instance ArgoCDProjectRole") - Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) - }) - It("should successfully reconcile the resource", func() { - By("Reconciling the created resource") - controllerReconciler := &ArgoCDProjectRoleReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), - } - - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, - }) - Expect(err).NotTo(HaveOccurred()) - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. - }) - }) -}) +var _ reconcile.Reconciler = &ArgoCDProjectRoleReconciler{} + +func TestArgoCDProjectRoleReconciler_Reconcile(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + argocdProjectRole := makeTestProjectRole(addFinalizerProjectRole()) + + resObjs := []client.Object{argocdProjectRole} + subresObjs := []client.Object{argocdProjectRole} + scheme := makeTestReconcilerScheme(rbacoperatorv1alpha1.AddToScheme) + client := makeTestReconcilerClient(scheme, resObjs, subresObjs) + reconciler := makeTestArgoCDProjectRoleReconciler(client, scheme) + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: argocdProjectRole.Name, + Namespace: argocdProjectRole.Namespace, + }, + } + + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + if res.RequeueAfter < 5*time.Minute { + t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) + } + + projectRoleRes := &rbacoperatorv1alpha1.ArgoCDProjectRole{} + err = reconciler.Get(context.TODO(), req.NamespacedName, projectRoleRes) + assert.NoError(t, err) + assert.Equal(t, projectRoleRes, argocdProjectRole) +} + +func TestArgoCDProjectRoleReconciler_AddFinalizer(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + argocdProjectRole := makeTestProjectRole() + + resObjs := []client.Object{argocdProjectRole} + subresObjs := []client.Object{argocdProjectRole} + scheme := makeTestReconcilerScheme(rbacoperatorv1alpha1.AddToScheme) + client := makeTestReconcilerClient(scheme, resObjs, subresObjs) + reconciler := makeTestArgoCDProjectRoleReconciler(client, scheme) + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: argocdProjectRole.Name, + Namespace: argocdProjectRole.Namespace, + }, + } + + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + if res.RequeueAfter < time.Second { + t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) + } + projectRoleRes := &rbacoperatorv1alpha1.ArgoCDProjectRole{} + err = reconciler.Get(context.TODO(), req.NamespacedName, projectRoleRes) + assert.NoError(t, err) + + assert.Contains(t, projectRoleRes.GetFinalizers(), rbacoperatorv1alpha1.ArgoCDProjectRoleFinalizerName) +} + +func TestArgoCDProjectRole_RoleNotFound(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + + resObjs := []client.Object{} + subresObjs := []client.Object{} + scheme := makeTestReconcilerScheme(rbacoperatorv1alpha1.AddToScheme) + client := makeTestReconcilerClient(scheme, resObjs, subresObjs) + reconciler := makeTestArgoCDProjectRoleReconciler(client, scheme) + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: testProjectRoleName, + Namespace: testNamespace, + }, + } + + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + if res.RequeueAfter > 0 { + t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) + } + assert.Error(t, reconciler.Get(context.TODO(), types.NamespacedName{Name: testProjectRoleName, Namespace: testNamespace}, &rbacoperatorv1alpha1.ArgoCDProjectRole{})) +} + +func TestArgoCDProjectRole_HandleFinalizer(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + argocdProjectRole := makeTestProjectRole(addFinalizerProjectRole(), projectRoleDeletedAt(time.Now()), addProjectRoleBinding(testProjectRoleBindingName)) + + resObjs := []client.Object{argocdProjectRole} + subresObjs := []client.Object{argocdProjectRole} + scheme := makeTestReconcilerScheme(rbacoperatorv1alpha1.AddToScheme, addArgoCDPkgToScheme()) + client := makeTestReconcilerClient(scheme, resObjs, subresObjs) + reconciler := makeTestArgoCDProjectRoleReconciler(client, scheme) + + assert.NoError(t, reconciler.Create(context.TODO(), makeTestProjectRoleBinding())) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestAppProject(addTestRoleToAppProject()))) + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: argocdProjectRole.Name, + Namespace: argocdProjectRole.Namespace, + }, + } + + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + if res.RequeueAfter > 0 { + t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) + } + + appProject := &argocdv1alpha.AppProject{} + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testAppProjectName, Namespace: testNamespace}, appProject) + assert.NoError(t, err) + wantAppProject := makeTestAppProject() + assert.Equal(t, wantAppProject.Spec.Roles, appProject.Spec.Roles) +} + +func TestArgoCDProjectRole_RoleBindingMissing(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + argocdProjectRole := makeTestProjectRole(addFinalizerProjectRole(), addProjectRoleBinding(testProjectRoleBindingName)) + + resObjs := []client.Object{argocdProjectRole} + subresObjs := []client.Object{argocdProjectRole} + scheme := makeTestReconcilerScheme(rbacoperatorv1alpha1.AddToScheme) + client := makeTestReconcilerClient(scheme, resObjs, subresObjs) + reconciler := makeTestArgoCDProjectRoleReconciler(client, scheme) + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: argocdProjectRole.Name, + Namespace: argocdProjectRole.Namespace, + }, + } + + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + if res.RequeueAfter < time.Minute*2 { + t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) + } + + projectRoleRes := &rbacoperatorv1alpha1.ArgoCDProjectRole{} + err = reconciler.Get(context.TODO(), req.NamespacedName, projectRoleRes) + assert.NoError(t, err) + assert.Equal(t, projectRoleRes.Status.ArgoCDProjectRoleBindingRef, "") +} diff --git a/internal/controller/argocdprojectrolebinding_controller.go b/internal/controller/argocdprojectrolebinding_controller.go index 08f1ef4..b2d678e 100644 --- a/internal/controller/argocdprojectrolebinding_controller.go +++ b/internal/controller/argocdprojectrolebinding_controller.go @@ -61,7 +61,7 @@ func (r *ArgoCDProjectRoleBindingReconciler) Reconcile(ctx context.Context, req projectRoleBinding.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status", "name", req.Name) - return ctrl.Result{}, err + return ctrl.Result{RequeueAfter: time.Minute * 2}, err } } @@ -71,7 +71,7 @@ func (r *ArgoCDProjectRoleBindingReconciler) Reconcile(ctx context.Context, req if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status during finalizer handling", "name", req.Name) } - return ctrl.Result{}, fmt.Errorf("error when handling finalizer: %v", err) + return ctrl.Result{RequeueAfter: time.Minute * 2}, fmt.Errorf("error when handling finalizer: %v", err) } return ctrl.Result{}, nil } @@ -82,9 +82,9 @@ func (r *ArgoCDProjectRoleBindingReconciler) Reconcile(ctx context.Context, req if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status after adding finalizer", "name", req.Name) } - return ctrl.Result{}, fmt.Errorf("error when adding finalizer: %v", err) + return ctrl.Result{RequeueAfter: time.Minute * 2}, fmt.Errorf("error when adding finalizer: %v", err) } - return ctrl.Result{}, nil + return ctrl.Result{RequeueAfter: time.Second}, nil } projectRoleName := projectRoleBinding.Spec.ArgoCDProjectRoleRef.Name @@ -100,13 +100,13 @@ func (r *ArgoCDProjectRoleBindingReconciler) Reconcile(ctx context.Context, req if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status", "name", req.Name) } - return ctrl.Result{}, nil + return ctrl.Result{RequeueAfter: time.Second}, nil } projectRoleBinding.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) if err := r.Status().Update(ctx, &projectRoleBinding); err != nil { r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status after project role not found", "name", req.Name) } - return ctrl.Result{}, fmt.Errorf("error when getting ArgoCDProjectRole: %v", err) + return ctrl.Result{RequeueAfter: time.Minute * 2}, fmt.Errorf("error when getting ArgoCDProjectRole: %v", err) } if !projectRole.HasArgoCDProjectRoleBindingRef() { @@ -132,7 +132,7 @@ func (r *ArgoCDProjectRoleBindingReconciler) Reconcile(ctx context.Context, req } r.Log.Error(err, "Failed to remove role from AppProject", "appProject", boundAppProject, "role", projectRoleName) projectRoleBinding.SetConditions(rbacoperatorv1alpha1.ReconcileError(err)) - return ctrl.Result{}, fmt.Errorf("error when removing role from AppProject: %v", err) + return ctrl.Result{RequeueAfter: time.Second}, fmt.Errorf("error when removing role from AppProject: %v", err) } r.Log.Info("Role removed from AppProject", "appProject", boundAppProject, "role", projectRoleName) projectRoleBinding.Status.AppProjectsBound = removeStringFromSlice(projectRoleBinding.Status.AppProjectsBound, boundAppProject) @@ -182,7 +182,7 @@ func (r *ArgoCDProjectRoleBindingReconciler) Reconcile(ctx context.Context, req r.Log.Error(err, "Failed to update ArgoCDProjectRoleBinding status after reconciliation", "name", req.Name) } - return ctrl.Result{RequeueAfter: time.Minute * 10}, nil + return ctrl.Result{RequeueAfter: time.Minute * 5}, nil } func makeAppProjectSubjectsSet(appProjectSubjects []rbacoperatorv1alpha1.AppProjectSubject) map[string][]string { diff --git a/internal/controller/argocdprojectrolebinding_controller_test.go b/internal/controller/argocdprojectrolebinding_controller_test.go index 49684db..bf7578c 100644 --- a/internal/controller/argocdprojectrolebinding_controller_test.go +++ b/internal/controller/argocdprojectrolebinding_controller_test.go @@ -18,67 +18,225 @@ package controller import ( "context" + "fmt" + "testing" + "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" + argocdv1alpha "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - rbacoperatorv1alpha1 "github.com/argoproj-labs/argocd-rbac-operator/api/v1alpha1" ) -var _ = Describe("ArgoCDProjectRoleBinding Controller", func() { - Context("When reconciling a resource", func() { - const resourceName = "test-resource" - - ctx := context.Background() - - typeNamespacedName := types.NamespacedName{ - Name: resourceName, - Namespace: "default", // TODO(user):Modify as needed - } - argocdprojectrolebinding := &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{} - - BeforeEach(func() { - By("creating the custom resource for the Kind ArgoCDProjectRoleBinding") - err := k8sClient.Get(ctx, typeNamespacedName, argocdprojectrolebinding) - if err != nil && errors.IsNotFound(err) { - resource := &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourceName, - Namespace: "default", - }, - // TODO(user): Specify other spec details if needed. - } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - } - }) - - AfterEach(func() { - // TODO(user): Cleanup logic after each test, like removing the resource instance. - resource := &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{} - err := k8sClient.Get(ctx, typeNamespacedName, resource) - Expect(err).NotTo(HaveOccurred()) - - By("Cleanup the specific resource instance ArgoCDProjectRoleBinding") - Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) - }) - It("should successfully reconcile the resource", func() { - By("Reconciling the created resource") - controllerReconciler := &ArgoCDProjectRoleBindingReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), - } - - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, - }) - Expect(err).NotTo(HaveOccurred()) - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. - }) +var _ reconcile.Reconciler = &ArgoCDProjectRoleBindingReconciler{} + +func TestArgoCDProjectRoleBindingReconciler_Reconcile(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + argocdProjectRoleBinding := makeTestProjectRoleBinding(addFinalizerProjectRoleBinding()) + argocdProjectRole := makeTestProjectRole() + + resObjs := []client.Object{argocdProjectRoleBinding} + subresObjs := []client.Object{argocdProjectRoleBinding, argocdProjectRole} + scheme := makeTestReconcilerScheme(rbacoperatorv1alpha1.AddToScheme, addArgoCDPkgToScheme()) + client := makeTestReconcilerClient(scheme, resObjs, subresObjs) + reconciler := makeTestArgoCDProjectRoleBindingReconciler(client, scheme) + + assert.NoError(t, reconciler.Create(context.TODO(), argocdProjectRole)) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestAppProject())) + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: argocdProjectRoleBinding.Name, + Namespace: argocdProjectRoleBinding.Namespace, + }, + } + + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + if res.RequeueAfter < 5*time.Minute { + t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) + } + projectRoleBindingRes := &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{} + err = reconciler.Get(context.TODO(), req.NamespacedName, projectRoleBindingRes) + assert.NoError(t, err) + assert.Equal(t, projectRoleBindingRes.Status.AppProjectsBound, []string{testAppProjectName}) + + appProject := &argocdv1alpha.AppProject{} + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testAppProjectName, Namespace: testNamespace}, appProject) + assert.NoError(t, err) + + wantAppProject := makeTestAppProject(addTestRoleToAppProject()) + assert.Equal(t, wantAppProject.Spec.Roles, appProject.Spec.Roles) + + projectRole := &rbacoperatorv1alpha1.ArgoCDProjectRole{} + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: argocdProjectRoleBinding.Spec.ArgoCDProjectRoleRef.Name, Namespace: testNamespace}, projectRole) + assert.NoError(t, err) + + wantProjectRole := makeTestProjectRole(addProjectRoleBinding(argocdProjectRoleBinding.Name)) + assert.Equal(t, wantProjectRole.Status.ArgoCDProjectRoleBindingRef, projectRole.Status.ArgoCDProjectRoleBindingRef) +} + +func TestArgoCDProjectRoleBindingReconciler_AddFinalizer(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + argocdProjectRoleBinding := makeTestProjectRoleBinding() + + resObjs := []client.Object{argocdProjectRoleBinding} + subresObjs := []client.Object{argocdProjectRoleBinding} + scheme := makeTestReconcilerScheme(rbacoperatorv1alpha1.AddToScheme) + client := makeTestReconcilerClient(scheme, resObjs, subresObjs) + reconciler := makeTestArgoCDProjectRoleBindingReconciler(client, scheme) + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: argocdProjectRoleBinding.Name, + Namespace: argocdProjectRoleBinding.Namespace, + }, + } + + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + if res.RequeueAfter < time.Second { + t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) + } + projectRoleBindingRes := &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{} + err = reconciler.Get(context.TODO(), req.NamespacedName, projectRoleBindingRes) + assert.NoError(t, err) + + assert.Contains(t, projectRoleBindingRes.Finalizers, rbacoperatorv1alpha1.ArgoCDProjectRoleBindingFinalizerName) +} + +func TestArgoCDProjectRoleBindingReconciler_RoleBindingNotFound(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + + resObjs := []client.Object{} + subresObjs := []client.Object{} + scheme := makeTestReconcilerScheme(rbacoperatorv1alpha1.AddToScheme) + client := makeTestReconcilerClient(scheme, resObjs, subresObjs) + reconciler := makeTestArgoCDProjectRoleBindingReconciler(client, scheme) + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: testProjectRoleBindingName, + Namespace: testNamespace, + }, + } + + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + if res.RequeueAfter > 0 { + t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) + } + assert.Error(t, reconciler.Get(context.TODO(), types.NamespacedName{Name: testProjectRoleBindingName, Namespace: testNamespace}, &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{})) +} + +func TestArgoCDProjectRoleBindingReconciler_HandleFinalizer(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + argocdProjectRoleBinding := makeTestProjectRoleBinding(addFinalizerProjectRoleBinding(), projectRoleBindingDeletedAt(time.Now())) + + resObjs := []client.Object{argocdProjectRoleBinding} + subresObjs := []client.Object{argocdProjectRoleBinding} + scheme := makeTestReconcilerScheme(rbacoperatorv1alpha1.AddToScheme, addArgoCDPkgToScheme()) + client := makeTestReconcilerClient(scheme, resObjs, subresObjs) + reconciler := makeTestArgoCDProjectRoleBindingReconciler(client, scheme) + + assert.NoError(t, reconciler.Create(context.TODO(), makeTestAppProject(addTestRoleToAppProject()))) + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: argocdProjectRoleBinding.Name, + Namespace: argocdProjectRoleBinding.Namespace, + }, + } + + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + if res.RequeueAfter > 0 { + t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) + } + + appProject := &argocdv1alpha.AppProject{} + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: testAppProjectName, Namespace: testNamespace}, appProject) + assert.NoError(t, err) + wantAppProject := makeTestAppProject() + assert.Equal(t, wantAppProject.Spec.Roles, appProject.Spec.Roles) +} + +func TestArgoCDProjectRoleBindingReconciler_RoleNotFound(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + + argocdProjectRoleBinding := makeTestProjectRoleBinding(addFinalizerProjectRoleBinding()) + + resObjs := []client.Object{argocdProjectRoleBinding} + subresObjs := []client.Object{argocdProjectRoleBinding} + scheme := makeTestReconcilerScheme(rbacoperatorv1alpha1.AddToScheme) + client := makeTestReconcilerClient(scheme, resObjs, subresObjs) + reconciler := makeTestArgoCDProjectRoleBindingReconciler(client, scheme) + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: argocdProjectRoleBinding.Name, + Namespace: argocdProjectRoleBinding.Namespace, + }, + } + + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + if res.RequeueAfter < time.Second { + t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) + } + projectRoleBindingRes := &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{} + err = reconciler.Get(context.TODO(), req.NamespacedName, projectRoleBindingRes) + assert.NoError(t, err) + assert.Contains(t, projectRoleBindingRes.Status.Conditions, rbacoperatorv1alpha1.Condition{ + Type: rbacoperatorv1alpha1.TypeSynced, + Status: corev1.ConditionFalse, + Reason: rbacoperatorv1alpha1.ReasonReconcileError, + Message: fmt.Sprintf("argocdprojectroles.rbac-operator.argoproj-labs.io \"%s\" not found", argocdProjectRoleBinding.Spec.ArgoCDProjectRoleRef.Name), + LastTransitionTime: projectRoleBindingRes.Status.Conditions[0].LastTransitionTime, }) -}) +} + +func TestArgoCDProjectRoleBindingReconciler_BoundAppProjectNotInSpec(t *testing.T) { + logf.SetLogger(ZapLogger(true)) + + argocdProjectRoleBinding := makeTestProjectRoleBinding(addFinalizerProjectRoleBinding(), addBoundAppProjects([]string{testAppProjectName, "another-app-project"})) + + resObjs := []client.Object{argocdProjectRoleBinding} + subresObjs := []client.Object{argocdProjectRoleBinding} + scheme := makeTestReconcilerScheme(rbacoperatorv1alpha1.AddToScheme, addArgoCDPkgToScheme()) + client := makeTestReconcilerClient(scheme, resObjs, subresObjs) + reconciler := makeTestArgoCDProjectRoleBindingReconciler(client, scheme) + + assert.NoError(t, reconciler.Create(context.TODO(), makeTestAppProject(addTestRoleToAppProject(), setAppProjectName("another-app-project")))) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestAppProject(addTestRoleToAppProject()))) + assert.NoError(t, reconciler.Create(context.TODO(), makeTestProjectRole(addProjectRoleBinding(argocdProjectRoleBinding.Name)))) + + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: argocdProjectRoleBinding.Name, + Namespace: argocdProjectRoleBinding.Namespace, + }, + } + + res, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + if res.RequeueAfter < 5*time.Minute { + t.Fatalf("reconcile requeued request after %s", res.RequeueAfter) + } + + projectRoleBindingRes := &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{} + err = reconciler.Get(context.TODO(), req.NamespacedName, projectRoleBindingRes) + assert.NoError(t, err) + assert.Equal(t, projectRoleBindingRes.Status.AppProjectsBound, []string{testAppProjectName}) + + appProject := &argocdv1alpha.AppProject{} + err = reconciler.Get(context.TODO(), types.NamespacedName{Name: "another-app-project", Namespace: testNamespace}, appProject) + assert.NoError(t, err) + wantAppProject := makeTestAppProject(setAppProjectName("another-app-project")) + assert.Equal(t, wantAppProject.Spec.Roles, appProject.Spec.Roles) +} diff --git a/internal/controller/testing.go b/internal/controller/testing.go index f1fc946..1b6d923 100644 --- a/internal/controller/testing.go +++ b/internal/controller/testing.go @@ -20,6 +20,7 @@ import ( "fmt" "time" + argocdv1alpha "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -40,6 +41,11 @@ const ( testRoleName = "test-role" testRoleBindingName = "test-role-binding" + + testProjectRoleName = "test-project-role" + testProjectRoleBindingName = "test-project-role-binding" + + testAppProjectName = "test-appproject" ) func ZapLogger(development bool) logr.Logger { @@ -48,6 +54,15 @@ func ZapLogger(development bool) logr.Logger { type SchemeOpt func(*runtime.Scheme) error +func addArgoCDPkgToScheme() SchemeOpt { + return func(s *runtime.Scheme) error { + if err := argocdv1alpha.AddToScheme(s); err != nil { + return fmt.Errorf("failed to add ArgoCD API to scheme: %w", err) + } + return nil + } +} + func makeTestArgoCDRoleReconciler(client client.Client, sch *runtime.Scheme) *ArgoCDRoleReconciler { return &ArgoCDRoleReconciler{ Client: client, @@ -397,3 +412,149 @@ func roleBindingDeletedAt(now time.Time) argocdRoleBindingOpt { r.DeletionTimestamp = &wrapped } } + +// AppProject RBAC Objects used in tests + +// Options for ArgoCDProjectRole and ArgoCDProjectRoleBinding +type argocdProjectRoleOpt func(*rbacoperatorv1alpha1.ArgoCDProjectRole) + +func addFinalizerProjectRole() argocdProjectRoleOpt { + return func(r *rbacoperatorv1alpha1.ArgoCDProjectRole) { + r.Finalizers = append(r.Finalizers, rbacoperatorv1alpha1.ArgoCDProjectRoleFinalizerName) + } +} + +func projectRoleDeletedAt(now time.Time) argocdProjectRoleOpt { + return func(r *rbacoperatorv1alpha1.ArgoCDProjectRole) { + wrapped := metav1.NewTime(now) + r.DeletionTimestamp = &wrapped + } +} + +func addProjectRoleBinding(roleBindingName string) argocdProjectRoleOpt { + return func(r *rbacoperatorv1alpha1.ArgoCDProjectRole) { + r.Status.ArgoCDProjectRoleBindingRef = roleBindingName + } +} + +type argocdProjectRoleBindingOpt func(*rbacoperatorv1alpha1.ArgoCDProjectRoleBinding) + +func addFinalizerProjectRoleBinding() argocdProjectRoleBindingOpt { + return func(r *rbacoperatorv1alpha1.ArgoCDProjectRoleBinding) { + r.Finalizers = append(r.Finalizers, rbacoperatorv1alpha1.ArgoCDProjectRoleBindingFinalizerName) + } +} + +func projectRoleBindingDeletedAt(now time.Time) argocdProjectRoleBindingOpt { + return func(r *rbacoperatorv1alpha1.ArgoCDProjectRoleBinding) { + wrapped := metav1.NewTime(now) + r.DeletionTimestamp = &wrapped + } +} + +func addBoundAppProjects(appProjects []string) argocdProjectRoleBindingOpt { + return func(r *rbacoperatorv1alpha1.ArgoCDProjectRoleBinding) { + r.Status.AppProjectsBound = appProjects + } +} + +type argocdAppProjectOpt func(*argocdv1alpha.AppProject) + +func addTestRoleToAppProject() argocdAppProjectOpt { + return func(ap *argocdv1alpha.AppProject) { + ap.Spec.Roles = append(ap.Spec.Roles, argocdv1alpha.ProjectRole{ + Name: testProjectRoleName, + Description: "Test Project Role", + Policies: []string{ + fmt.Sprintf("p, proj:%s:%s, applications, get, */*, allow", testAppProjectName, testProjectRoleName), + fmt.Sprintf("p, proj:%s:%s, applications, list, */*, allow", testAppProjectName, testProjectRoleName), + fmt.Sprintf("p, proj:%s:%s, projects, get, *, allow", testAppProjectName, testProjectRoleName), + }, + Groups: []string{"group1", "group2"}, + }) + } +} + +func setAppProjectName(name string) argocdAppProjectOpt { + return func(ap *argocdv1alpha.AppProject) { + ap.Name = name + } +} + +// AppProject RBAC Objects + +func makeTestAppProject(opts ...argocdAppProjectOpt) *argocdv1alpha.AppProject { + ap := &argocdv1alpha.AppProject{ + ObjectMeta: metav1.ObjectMeta{ + Name: testAppProjectName, + Namespace: testNamespace, + }, + Spec: argocdv1alpha.AppProjectSpec{ + Description: "Test App Project", + Roles: []argocdv1alpha.ProjectRole{ + { + Name: "existing-role", + Description: "Existing Role", + }, + }, + }, + } + for _, opt := range opts { + opt(ap) + } + return ap +} + +func makeTestProjectRole(opts ...argocdProjectRoleOpt) *rbacoperatorv1alpha1.ArgoCDProjectRole { + r := &rbacoperatorv1alpha1.ArgoCDProjectRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: testProjectRoleName, + Namespace: testNamespace, + }, + Spec: rbacoperatorv1alpha1.ArgoCDProjectRoleSpec{ + Description: "Test Project Role", + Rules: []rbacoperatorv1alpha1.ProjectRule{ + { + Resource: "applications", + Verbs: []string{"get", "list"}, + Objects: []string{"*/*"}, + }, + { + Resource: "projects", + Verbs: []string{"get"}, + Objects: []string{"*"}, + }, + }, + }, + } + + for _, opt := range opts { + opt(r) + } + return r +} + +func makeTestProjectRoleBinding(opts ...argocdProjectRoleBindingOpt) *rbacoperatorv1alpha1.ArgoCDProjectRoleBinding { + rb := &rbacoperatorv1alpha1.ArgoCDProjectRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: testProjectRoleBindingName, + Namespace: testNamespace, + }, + Spec: rbacoperatorv1alpha1.ArgoCDProjectRoleBindingSpec{ + ArgoCDProjectRoleRef: rbacoperatorv1alpha1.ArgoCDProjectRoleRef{ + Name: testProjectRoleName, + }, + Subjects: []rbacoperatorv1alpha1.AppProjectSubject{ + { + AppProjectRef: testAppProjectName, + Groups: []string{"group1", "group2"}, + }, + }, + }, + } + + for _, opt := range opts { + opt(rb) + } + return rb +} From 7f5d2f6dbfda2b2331a1474a8ff2f857cae7fe2d Mon Sep 17 00:00:00 2001 From: Georgy Khromov Date: Sun, 8 Jun 2025 04:15:39 +0200 Subject: [PATCH 6/9] feat(appProjectRBAC)[#36]: bump golangci-lint version in GitHub CI Pipeline Signed-off-by: Georgy Khromov --- .github/workflows/golangci-lint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml index ddd1d4c..ac9e70e 100644 --- a/.github/workflows/golangci-lint.yaml +++ b/.github/workflows/golangci-lint.yaml @@ -28,7 +28,7 @@ jobs: # Require: The version of golangci-lint to use. # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. - version: v1.64.8 + version: v2.1.6 # Optional: working directory, useful for monorepos # working-directory: somedir From 53d7cff1984d9a0c44970d592989d7e434581cfe Mon Sep 17 00:00:00 2001 From: Georgy Khromov Date: Sun, 8 Jun 2025 04:24:02 +0200 Subject: [PATCH 7/9] feat(appProjectRBAC)[#36]: bump golangci-lint version in GitHub CI Pipeline Signed-off-by: Georgy Khromov --- .github/workflows/golangci-lint.yaml | 30 ++-------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml index ac9e70e..1afba38 100644 --- a/.github/workflows/golangci-lint.yaml +++ b/.github/workflows/golangci-lint.yaml @@ -23,33 +23,7 @@ jobs: go-version: '1.24' cache: false - name: golangci-lint - uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # pin@v3 + uses: golangci/golangci-lint-action@v8 with: - # Require: The version of golangci-lint to use. - # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. - # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. version: v2.1.6 - # Optional: working directory, useful for monorepos - # working-directory: somedir - - # Optional: golangci-lint command line arguments. - # - # Note: By default, the `.golangci.yml` file should be at the root of the repository. - # The location of the configuration file can be changed by using `--config=` - # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0 - - # Optional: show only new issues if it's a pull request. The default value is `false`. - # only-new-issues: true - - # Optional: if set to true, then all caching functionality will be completely disabled, - # takes precedence over all other caching options. - # skip-cache: true - - # Optional: if set to true, then the action won't cache or restore ~/go/pkg. - # skip-pkg-cache: true - - # Optional: if set to true, then the action won't cache or restore ~/.cache/go-build. - # skip-build-cache: true - - # Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'. - # install-mode: "goinstall" \ No newline at end of file + \ No newline at end of file From 8ea248fec533eea2e18e5c9029219b623b9c853f Mon Sep 17 00:00:00 2001 From: Georgy Khromov Date: Sun, 8 Jun 2025 04:28:24 +0200 Subject: [PATCH 8/9] feat(appProjectRBAC)[#36]: fix typo in readme, make subjects required again Signed-off-by: Georgy Khromov --- README.md | 2 +- api/v1alpha1/argocdrolebinding_types.go | 2 +- .../rbac-operator.argoproj-labs.io_argocdrolebindings.yaml | 1 + config/rbac/argocdprojectrole_admin_role.yaml | 4 ++-- config/rbac/argocdprojectrolebinding_admin_role.yaml | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 31b9a12..7fe49d1 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,7 @@ spec: If changes there made to the CRs, they also will be reflected in referenced AppProjects: - changes to `spec.rules` of ArgoCDProjectRole - - will be pacthed to AppProject on next reconcile of ArgoCDProjectRoleBinding + - will be patched to AppProject on next reconcile of ArgoCDProjectRoleBinding - changes to `spec.subjects` of ArgoCDProjectRoleBindings - deletion of a subject, will delete the role in AppProject - change to subject will be reflected in AppProject on next reconcile diff --git a/api/v1alpha1/argocdrolebinding_types.go b/api/v1alpha1/argocdrolebinding_types.go index 591ac3d..efd70d1 100644 --- a/api/v1alpha1/argocdrolebinding_types.go +++ b/api/v1alpha1/argocdrolebinding_types.go @@ -25,7 +25,7 @@ import ( // ArgoCDRoleBindingSpec defines the desired state of ArgoCDRoleBinding type ArgoCDRoleBindingSpec struct { // List of subjects being bound to ArgoCDRole (argocdRoleRef). - Subjects []GlobalSubject `json:"subjects,omitempty"` + Subjects []GlobalSubject `json:"subjects"` ArgoCDRoleRef ArgoCDRoleRef `json:"argocdRoleRef"` } diff --git a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml index ad15872..4730916 100644 --- a/config/crd/bases/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml +++ b/config/crd/bases/rbac-operator.argoproj-labs.io_argocdrolebindings.yaml @@ -72,6 +72,7 @@ spec: type: array required: - argocdRoleRef + - subjects type: object status: description: ArgoCDRoleBindingStatus defines the observed state of ArgoCDRoleBinding diff --git a/config/rbac/argocdprojectrole_admin_role.yaml b/config/rbac/argocdprojectrole_admin_role.yaml index 0295422..0cfc6b8 100644 --- a/config/rbac/argocdprojectrole_admin_role.yaml +++ b/config/rbac/argocdprojectrole_admin_role.yaml @@ -14,13 +14,13 @@ metadata: name: argocdprojectrole-admin-role rules: - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: - argocdprojectroles verbs: - '*' - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: - argocdprojectroles/status verbs: diff --git a/config/rbac/argocdprojectrolebinding_admin_role.yaml b/config/rbac/argocdprojectrolebinding_admin_role.yaml index 8b41ef1..f4c8f15 100644 --- a/config/rbac/argocdprojectrolebinding_admin_role.yaml +++ b/config/rbac/argocdprojectrolebinding_admin_role.yaml @@ -14,13 +14,13 @@ metadata: name: argocdprojectrolebinding-admin-role rules: - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.io resources: - argocdprojectrolebindings verbs: - '*' - apiGroups: - - rbac-operator + - rbac-operator.argoproj-labs.ios resources: - argocdprojectrolebindings/status verbs: From 9cda3b7ea3c929c3998d345bacd588305495036b Mon Sep 17 00:00:00 2001 From: Georgy Khromov Date: Mon, 9 Jun 2025 14:51:28 +0200 Subject: [PATCH 9/9] feat(appProjectRBAC)[#36]: fix observedGeneration update logic Signed-off-by: Georgy Khromov --- api/v1alpha1/status.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/api/v1alpha1/status.go b/api/v1alpha1/status.go index 3fc2418..2eea479 100644 --- a/api/v1alpha1/status.go +++ b/api/v1alpha1/status.go @@ -112,8 +112,8 @@ func (r *ArgoCDRole) SetConditions(c ...Condition) { if existing.Equal(new) { exists = true - if existing.ObservedGeneration < new.ObservedGeneration { - existing.ObservedGeneration = new.ObservedGeneration + if r.Status.Conditions[i].ObservedGeneration < new.ObservedGeneration { + r.Status.Conditions[i].ObservedGeneration = new.ObservedGeneration } continue } @@ -149,8 +149,8 @@ func (r *ArgoCDProjectRole) SetConditions(c ...Condition) { if existing.Equal(new) { exists = true - if existing.ObservedGeneration < new.ObservedGeneration { - existing.ObservedGeneration = new.ObservedGeneration + if r.Status.Conditions[i].ObservedGeneration < new.ObservedGeneration { + r.Status.Conditions[i].ObservedGeneration = new.ObservedGeneration } continue } @@ -186,8 +186,8 @@ func (rb *ArgoCDRoleBinding) SetConditions(c ...Condition) { if existing.Equal(new) { exists = true - if existing.ObservedGeneration < new.ObservedGeneration { - existing.ObservedGeneration = new.ObservedGeneration + if rb.Status.Conditions[i].ObservedGeneration < new.ObservedGeneration { + rb.Status.Conditions[i].ObservedGeneration = new.ObservedGeneration } continue } @@ -215,8 +215,8 @@ func (rb *ArgoCDProjectRoleBinding) SetConditions(c ...Condition) { if existing.Equal(new) { exists = true - if existing.ObservedGeneration < new.ObservedGeneration { - existing.ObservedGeneration = new.ObservedGeneration + if rb.Status.Conditions[i].ObservedGeneration < new.ObservedGeneration { + rb.Status.Conditions[i].ObservedGeneration = new.ObservedGeneration } continue }