From 0687c3a5246b225ae26d5c4df9d84625160b0752 Mon Sep 17 00:00:00 2001 From: GKravale Date: Tue, 24 Feb 2026 15:28:45 +0200 Subject: [PATCH] feat: add EmailsOnPush managed resource (#278) Signed-off-by: GKravale --- .../v1alpha1/zz_emailsonpush_types.go | 93 +++++ .../v1alpha1/zz_generated.deepcopy.go | 168 +++++++++ .../projects/v1alpha1/zz_generated.managed.go | 50 +++ .../v1alpha1/zz_generated.managedlist.go | 9 + apis/cluster/projects/v1alpha1/zz_register.go | 9 + .../projects/v1alpha1/emailsonpush_types.go | 93 +++++ apis/namespaced/projects/v1alpha1/register.go | 9 + .../v1alpha1/zz_generated.deepcopy.go | 168 +++++++++ .../projects/v1alpha1/zz_generated.managed.go | 40 +++ .../v1alpha1/zz_generated.managedlist.go | 9 + examples/projects/emailsonpush.yaml | 15 + ...s.gitlab.crossplane.io_emailsonpushes.yaml | 326 ++++++++++++++++++ ...gitlab.m.crossplane.io_emailsonpushes.yaml | 290 ++++++++++++++++ .../clients/projects/zz_emailsonpush.go | 100 ++++++ .../projects/emailsonpush/zz_controller.go | 199 +++++++++++ .../emailsonpush/zz_controller_test.go | 163 +++++++++ pkg/cluster/controller/projects/zz_setup.go | 6 +- .../clients/projects/emailsonpush.go | 98 ++++++ .../projects/emailsonpush/controller.go | 197 +++++++++++ .../projects/emailsonpush/controller_test.go | 161 +++++++++ pkg/namespaced/controller/projects/setup.go | 6 +- 21 files changed, 2203 insertions(+), 6 deletions(-) create mode 100644 apis/cluster/projects/v1alpha1/zz_emailsonpush_types.go create mode 100644 apis/namespaced/projects/v1alpha1/emailsonpush_types.go create mode 100644 examples/projects/emailsonpush.yaml create mode 100644 package/crds/projects.gitlab.crossplane.io_emailsonpushes.yaml create mode 100644 package/crds/projects.gitlab.m.crossplane.io_emailsonpushes.yaml create mode 100644 pkg/cluster/clients/projects/zz_emailsonpush.go create mode 100644 pkg/cluster/controller/projects/emailsonpush/zz_controller.go create mode 100644 pkg/cluster/controller/projects/emailsonpush/zz_controller_test.go create mode 100644 pkg/namespaced/clients/projects/emailsonpush.go create mode 100644 pkg/namespaced/controller/projects/emailsonpush/controller.go create mode 100644 pkg/namespaced/controller/projects/emailsonpush/controller_test.go diff --git a/apis/cluster/projects/v1alpha1/zz_emailsonpush_types.go b/apis/cluster/projects/v1alpha1/zz_emailsonpush_types.go new file mode 100644 index 00000000..f314803a --- /dev/null +++ b/apis/cluster/projects/v1alpha1/zz_emailsonpush_types.go @@ -0,0 +1,93 @@ +// Code generated by hack/generate-cluster-scope.go - DO NOT EDIT. + +package v1alpha1 + +import ( + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EmailsOnPushParameters define desired state +type EmailsOnPushParameters struct { + + // ProjectID is the ID of the project. + // +optional + // +immutable + ProjectID *int64 `json:"projectId,omitempty"` + + // ProjectIDRef references a Project + // +optional + // +immutable + ProjectIDRef *xpv1.Reference `json:"projectIdRef,omitempty"` + + // ProjectIDSelector selects a Project + // +optional + ProjectIDSelector *xpv1.Selector `json:"projectIdSelector,omitempty"` + + // Recipients is a comma separated list of emails + // +optional + Recipients *string `json:"recipients,omitempty"` + + // DisableDiffs disables sending diffs in email + // +optional + DisableDiffs *bool `json:"disableDiffs,omitempty"` + + // SendFromCommitterEmail sends email from committer's email + // +optional + SendFromCommitterEmail *bool `json:"sendFromCommitterEmail,omitempty"` + + // PushEvents triggers emails on push events + // +optional + PushEvents *bool `json:"pushEvents,omitempty"` + + // TagPushEvents triggers emails on tag push events + // +optional + TagPushEvents *bool `json:"tagPushEvents,omitempty"` + + // BranchesToBeNotified defines which branches trigger notifications + // +optional + BranchesToBeNotified *string `json:"branchesToBeNotified,omitempty"` +} + +// EmailsOnPushObservation represents observed state +type EmailsOnPushObservation struct { + Recipients string `json:"recipients,omitempty"` + DisableDiffs bool `json:"disableDiffs,omitempty"` + SendFromCommitterEmail bool `json:"sendFromCommitterEmail,omitempty"` + PushEvents bool `json:"pushEvents,omitempty"` + TagPushEvents bool `json:"tagPushEvents,omitempty"` + BranchesToBeNotified string `json:"branchesToBeNotified,omitempty"` +} + +// Spec +type EmailsOnPushSpec struct { + xpv1.ResourceSpec `json:",inline"` + ForProvider EmailsOnPushParameters `json:"forProvider"` +} + +// Status +type EmailsOnPushStatus struct { + xpv1.ResourceStatus `json:",inline"` + AtProvider EmailsOnPushObservation `json:"atProvider,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:object:generate=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,gitlab} + +type EmailsOnPush struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec EmailsOnPushSpec `json:"spec"` + Status EmailsOnPushStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:object:generate=true +type EmailsOnPushList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []EmailsOnPush `json:"items"` +} diff --git a/apis/cluster/projects/v1alpha1/zz_generated.deepcopy.go b/apis/cluster/projects/v1alpha1/zz_generated.deepcopy.go index 51bd4787..85e02165 100644 --- a/apis/cluster/projects/v1alpha1/zz_generated.deepcopy.go +++ b/apis/cluster/projects/v1alpha1/zz_generated.deepcopy.go @@ -952,6 +952,174 @@ func (in *DeployTokenStatus) DeepCopy() *DeployTokenStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EmailsOnPush) DeepCopyInto(out *EmailsOnPush) { + *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 EmailsOnPush. +func (in *EmailsOnPush) DeepCopy() *EmailsOnPush { + if in == nil { + return nil + } + out := new(EmailsOnPush) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EmailsOnPush) 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 *EmailsOnPushList) DeepCopyInto(out *EmailsOnPushList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]EmailsOnPush, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmailsOnPushList. +func (in *EmailsOnPushList) DeepCopy() *EmailsOnPushList { + if in == nil { + return nil + } + out := new(EmailsOnPushList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EmailsOnPushList) 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 *EmailsOnPushObservation) DeepCopyInto(out *EmailsOnPushObservation) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmailsOnPushObservation. +func (in *EmailsOnPushObservation) DeepCopy() *EmailsOnPushObservation { + if in == nil { + return nil + } + out := new(EmailsOnPushObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EmailsOnPushParameters) DeepCopyInto(out *EmailsOnPushParameters) { + *out = *in + if in.ProjectID != nil { + in, out := &in.ProjectID, &out.ProjectID + *out = new(int64) + **out = **in + } + if in.ProjectIDRef != nil { + in, out := &in.ProjectIDRef, &out.ProjectIDRef + *out = new(v1.Reference) + (*in).DeepCopyInto(*out) + } + if in.ProjectIDSelector != nil { + in, out := &in.ProjectIDSelector, &out.ProjectIDSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } + if in.Recipients != nil { + in, out := &in.Recipients, &out.Recipients + *out = new(string) + **out = **in + } + if in.DisableDiffs != nil { + in, out := &in.DisableDiffs, &out.DisableDiffs + *out = new(bool) + **out = **in + } + if in.SendFromCommitterEmail != nil { + in, out := &in.SendFromCommitterEmail, &out.SendFromCommitterEmail + *out = new(bool) + **out = **in + } + if in.PushEvents != nil { + in, out := &in.PushEvents, &out.PushEvents + *out = new(bool) + **out = **in + } + if in.TagPushEvents != nil { + in, out := &in.TagPushEvents, &out.TagPushEvents + *out = new(bool) + **out = **in + } + if in.BranchesToBeNotified != nil { + in, out := &in.BranchesToBeNotified, &out.BranchesToBeNotified + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmailsOnPushParameters. +func (in *EmailsOnPushParameters) DeepCopy() *EmailsOnPushParameters { + if in == nil { + return nil + } + out := new(EmailsOnPushParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EmailsOnPushSpec) DeepCopyInto(out *EmailsOnPushSpec) { + *out = *in + in.ResourceSpec.DeepCopyInto(&out.ResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmailsOnPushSpec. +func (in *EmailsOnPushSpec) DeepCopy() *EmailsOnPushSpec { + if in == nil { + return nil + } + out := new(EmailsOnPushSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EmailsOnPushStatus) DeepCopyInto(out *EmailsOnPushStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + out.AtProvider = in.AtProvider +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmailsOnPushStatus. +func (in *EmailsOnPushStatus) DeepCopy() *EmailsOnPushStatus { + if in == nil { + return nil + } + out := new(EmailsOnPushStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ForkParent) DeepCopyInto(out *ForkParent) { *out = *in diff --git a/apis/cluster/projects/v1alpha1/zz_generated.managed.go b/apis/cluster/projects/v1alpha1/zz_generated.managed.go index 96c3296e..6d11e3a3 100644 --- a/apis/cluster/projects/v1alpha1/zz_generated.managed.go +++ b/apis/cluster/projects/v1alpha1/zz_generated.managed.go @@ -270,6 +270,56 @@ func (mg *DeployToken) SetWriteConnectionSecretToReference(r *xpv1.SecretReferen mg.Spec.WriteConnectionSecretToReference = r } +// GetCondition of this EmailsOnPush. +func (mg *EmailsOnPush) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetDeletionPolicy of this EmailsOnPush. +func (mg *EmailsOnPush) GetDeletionPolicy() xpv1.DeletionPolicy { + return mg.Spec.DeletionPolicy +} + +// GetManagementPolicies of this EmailsOnPush. +func (mg *EmailsOnPush) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this EmailsOnPush. +func (mg *EmailsOnPush) GetProviderConfigReference() *xpv1.Reference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this EmailsOnPush. +func (mg *EmailsOnPush) GetWriteConnectionSecretToReference() *xpv1.SecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this EmailsOnPush. +func (mg *EmailsOnPush) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetDeletionPolicy of this EmailsOnPush. +func (mg *EmailsOnPush) SetDeletionPolicy(r xpv1.DeletionPolicy) { + mg.Spec.DeletionPolicy = r +} + +// SetManagementPolicies of this EmailsOnPush. +func (mg *EmailsOnPush) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this EmailsOnPush. +func (mg *EmailsOnPush) SetProviderConfigReference(r *xpv1.Reference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this EmailsOnPush. +func (mg *EmailsOnPush) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} + // GetCondition of this Hook. func (mg *Hook) GetCondition(ct xpv1.ConditionType) xpv1.Condition { return mg.Status.GetCondition(ct) diff --git a/apis/cluster/projects/v1alpha1/zz_generated.managedlist.go b/apis/cluster/projects/v1alpha1/zz_generated.managedlist.go index 729c6f2a..10989170 100644 --- a/apis/cluster/projects/v1alpha1/zz_generated.managedlist.go +++ b/apis/cluster/projects/v1alpha1/zz_generated.managedlist.go @@ -65,6 +65,15 @@ func (l *DeployTokenList) GetItems() []resource.Managed { return items } +// GetItems of this EmailsOnPushList. +func (l *EmailsOnPushList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} + // GetItems of this HookList. func (l *HookList) GetItems() []resource.Managed { items := make([]resource.Managed, len(l.Items)) diff --git a/apis/cluster/projects/v1alpha1/zz_register.go b/apis/cluster/projects/v1alpha1/zz_register.go index 879ee246..d198e692 100644 --- a/apis/cluster/projects/v1alpha1/zz_register.go +++ b/apis/cluster/projects/v1alpha1/zz_register.go @@ -134,6 +134,14 @@ var ( BadgeGroupVersionKind = SchemeGroupVersion.WithKind(BadgeKind) ) +// EmailsOnPush type metadata +var ( + EmailsOnPushKind = reflect.TypeOf(EmailsOnPush{}).Name() + EmailsOnPushGroupKind = schema.GroupKind{Group: Group, Kind: EmailsOnPushKind}.String() + EmailsOnPushKindAPIVersion = EmailsOnPushKind + "." + SchemeGroupVersion.String() + EmailsOnPushGroupVersionKind = SchemeGroupVersion.WithKind(EmailsOnPushKind) +) + // IntegrationMattermost type metadata var ( IntegrationMattermostKind = reflect.TypeOf(IntegrationMattermost{}).Name() @@ -155,6 +163,7 @@ func init() { SchemeBuilder.Register(&Runner{}, &RunnerList{}) SchemeBuilder.Register(&ProtectedBranch{}, &ProtectedBranchList{}) SchemeBuilder.Register(&Badge{}, &BadgeList{}) + SchemeBuilder.Register(&EmailsOnPush{}, &EmailsOnPushList{}) // Mattermost SchemeBuilder.Register(&IntegrationMattermost{}, &IntegrationMattermostList{}) diff --git a/apis/namespaced/projects/v1alpha1/emailsonpush_types.go b/apis/namespaced/projects/v1alpha1/emailsonpush_types.go new file mode 100644 index 00000000..74b1bf61 --- /dev/null +++ b/apis/namespaced/projects/v1alpha1/emailsonpush_types.go @@ -0,0 +1,93 @@ +package v1alpha1 + +import ( + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + // +cluster-scope:delete=1 + xpv2 "github.com/crossplane/crossplane-runtime/v2/apis/common/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EmailsOnPushParameters define desired state +type EmailsOnPushParameters struct { + + // ProjectID is the ID of the project. + // +optional + // +immutable + ProjectID *int64 `json:"projectId,omitempty"` + + // ProjectIDRef references a Project + // +optional + // +immutable + ProjectIDRef *xpv1.NamespacedReference `json:"projectIdRef,omitempty"` + + // ProjectIDSelector selects a Project + // +optional + ProjectIDSelector *xpv1.NamespacedSelector `json:"projectIdSelector,omitempty"` + + // Recipients is a comma separated list of emails + // +optional + Recipients *string `json:"recipients,omitempty"` + + // DisableDiffs disables sending diffs in email + // +optional + DisableDiffs *bool `json:"disableDiffs,omitempty"` + + // SendFromCommitterEmail sends email from committer's email + // +optional + SendFromCommitterEmail *bool `json:"sendFromCommitterEmail,omitempty"` + + // PushEvents triggers emails on push events + // +optional + PushEvents *bool `json:"pushEvents,omitempty"` + + // TagPushEvents triggers emails on tag push events + // +optional + TagPushEvents *bool `json:"tagPushEvents,omitempty"` + + // BranchesToBeNotified defines which branches trigger notifications + // +optional + BranchesToBeNotified *string `json:"branchesToBeNotified,omitempty"` +} + +// EmailsOnPushObservation represents observed state +type EmailsOnPushObservation struct { + Recipients string `json:"recipients,omitempty"` + DisableDiffs bool `json:"disableDiffs,omitempty"` + SendFromCommitterEmail bool `json:"sendFromCommitterEmail,omitempty"` + PushEvents bool `json:"pushEvents,omitempty"` + TagPushEvents bool `json:"tagPushEvents,omitempty"` + BranchesToBeNotified string `json:"branchesToBeNotified,omitempty"` +} + +// Spec +type EmailsOnPushSpec struct { + xpv2.ManagedResourceSpec `json:",inline"` + ForProvider EmailsOnPushParameters `json:"forProvider"` +} + +// Status +type EmailsOnPushStatus struct { + xpv1.ResourceStatus `json:",inline"` + AtProvider EmailsOnPushObservation `json:"atProvider,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:object:generate=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Namespaced,categories={crossplane,managed,gitlab} + +type EmailsOnPush struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec EmailsOnPushSpec `json:"spec"` + Status EmailsOnPushStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:object:generate=true +type EmailsOnPushList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []EmailsOnPush `json:"items"` +} diff --git a/apis/namespaced/projects/v1alpha1/register.go b/apis/namespaced/projects/v1alpha1/register.go index 078c623b..ba42cdeb 100644 --- a/apis/namespaced/projects/v1alpha1/register.go +++ b/apis/namespaced/projects/v1alpha1/register.go @@ -132,6 +132,14 @@ var ( BadgeGroupVersionKind = SchemeGroupVersion.WithKind(BadgeKind) ) +// EmailsOnPush type metadata +var ( + EmailsOnPushKind = reflect.TypeOf(EmailsOnPush{}).Name() + EmailsOnPushGroupKind = schema.GroupKind{Group: Group, Kind: EmailsOnPushKind}.String() + EmailsOnPushKindAPIVersion = EmailsOnPushKind + "." + SchemeGroupVersion.String() + EmailsOnPushGroupVersionKind = SchemeGroupVersion.WithKind(EmailsOnPushKind) +) + // IntegrationMattermost type metadata var ( IntegrationMattermostKind = reflect.TypeOf(IntegrationMattermost{}).Name() @@ -153,6 +161,7 @@ func init() { SchemeBuilder.Register(&Runner{}, &RunnerList{}) SchemeBuilder.Register(&ProtectedBranch{}, &ProtectedBranchList{}) SchemeBuilder.Register(&Badge{}, &BadgeList{}) + SchemeBuilder.Register(&EmailsOnPush{}, &EmailsOnPushList{}) // Mattermost SchemeBuilder.Register(&IntegrationMattermost{}, &IntegrationMattermostList{}) diff --git a/apis/namespaced/projects/v1alpha1/zz_generated.deepcopy.go b/apis/namespaced/projects/v1alpha1/zz_generated.deepcopy.go index e3fcfe48..acce92ec 100644 --- a/apis/namespaced/projects/v1alpha1/zz_generated.deepcopy.go +++ b/apis/namespaced/projects/v1alpha1/zz_generated.deepcopy.go @@ -952,6 +952,174 @@ func (in *DeployTokenStatus) DeepCopy() *DeployTokenStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EmailsOnPush) DeepCopyInto(out *EmailsOnPush) { + *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 EmailsOnPush. +func (in *EmailsOnPush) DeepCopy() *EmailsOnPush { + if in == nil { + return nil + } + out := new(EmailsOnPush) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EmailsOnPush) 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 *EmailsOnPushList) DeepCopyInto(out *EmailsOnPushList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]EmailsOnPush, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmailsOnPushList. +func (in *EmailsOnPushList) DeepCopy() *EmailsOnPushList { + if in == nil { + return nil + } + out := new(EmailsOnPushList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EmailsOnPushList) 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 *EmailsOnPushObservation) DeepCopyInto(out *EmailsOnPushObservation) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmailsOnPushObservation. +func (in *EmailsOnPushObservation) DeepCopy() *EmailsOnPushObservation { + if in == nil { + return nil + } + out := new(EmailsOnPushObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EmailsOnPushParameters) DeepCopyInto(out *EmailsOnPushParameters) { + *out = *in + if in.ProjectID != nil { + in, out := &in.ProjectID, &out.ProjectID + *out = new(int64) + **out = **in + } + if in.ProjectIDRef != nil { + in, out := &in.ProjectIDRef, &out.ProjectIDRef + *out = new(v1.NamespacedReference) + (*in).DeepCopyInto(*out) + } + if in.ProjectIDSelector != nil { + in, out := &in.ProjectIDSelector, &out.ProjectIDSelector + *out = new(v1.NamespacedSelector) + (*in).DeepCopyInto(*out) + } + if in.Recipients != nil { + in, out := &in.Recipients, &out.Recipients + *out = new(string) + **out = **in + } + if in.DisableDiffs != nil { + in, out := &in.DisableDiffs, &out.DisableDiffs + *out = new(bool) + **out = **in + } + if in.SendFromCommitterEmail != nil { + in, out := &in.SendFromCommitterEmail, &out.SendFromCommitterEmail + *out = new(bool) + **out = **in + } + if in.PushEvents != nil { + in, out := &in.PushEvents, &out.PushEvents + *out = new(bool) + **out = **in + } + if in.TagPushEvents != nil { + in, out := &in.TagPushEvents, &out.TagPushEvents + *out = new(bool) + **out = **in + } + if in.BranchesToBeNotified != nil { + in, out := &in.BranchesToBeNotified, &out.BranchesToBeNotified + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmailsOnPushParameters. +func (in *EmailsOnPushParameters) DeepCopy() *EmailsOnPushParameters { + if in == nil { + return nil + } + out := new(EmailsOnPushParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EmailsOnPushSpec) DeepCopyInto(out *EmailsOnPushSpec) { + *out = *in + in.ManagedResourceSpec.DeepCopyInto(&out.ManagedResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmailsOnPushSpec. +func (in *EmailsOnPushSpec) DeepCopy() *EmailsOnPushSpec { + if in == nil { + return nil + } + out := new(EmailsOnPushSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EmailsOnPushStatus) DeepCopyInto(out *EmailsOnPushStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + out.AtProvider = in.AtProvider +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmailsOnPushStatus. +func (in *EmailsOnPushStatus) DeepCopy() *EmailsOnPushStatus { + if in == nil { + return nil + } + out := new(EmailsOnPushStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ForkParent) DeepCopyInto(out *ForkParent) { *out = *in diff --git a/apis/namespaced/projects/v1alpha1/zz_generated.managed.go b/apis/namespaced/projects/v1alpha1/zz_generated.managed.go index 2d1e3667..4b6e2410 100644 --- a/apis/namespaced/projects/v1alpha1/zz_generated.managed.go +++ b/apis/namespaced/projects/v1alpha1/zz_generated.managed.go @@ -220,6 +220,46 @@ func (mg *DeployToken) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretRe mg.Spec.WriteConnectionSecretToReference = r } +// GetCondition of this EmailsOnPush. +func (mg *EmailsOnPush) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetManagementPolicies of this EmailsOnPush. +func (mg *EmailsOnPush) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this EmailsOnPush. +func (mg *EmailsOnPush) GetProviderConfigReference() *xpv1.ProviderConfigReference { + return mg.Spec.ProviderConfigReference +} + +// GetWriteConnectionSecretToReference of this EmailsOnPush. +func (mg *EmailsOnPush) GetWriteConnectionSecretToReference() *xpv1.LocalSecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this EmailsOnPush. +func (mg *EmailsOnPush) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetManagementPolicies of this EmailsOnPush. +func (mg *EmailsOnPush) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this EmailsOnPush. +func (mg *EmailsOnPush) SetProviderConfigReference(r *xpv1.ProviderConfigReference) { + mg.Spec.ProviderConfigReference = r +} + +// SetWriteConnectionSecretToReference of this EmailsOnPush. +func (mg *EmailsOnPush) SetWriteConnectionSecretToReference(r *xpv1.LocalSecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} + // GetCondition of this Hook. func (mg *Hook) GetCondition(ct xpv1.ConditionType) xpv1.Condition { return mg.Status.GetCondition(ct) diff --git a/apis/namespaced/projects/v1alpha1/zz_generated.managedlist.go b/apis/namespaced/projects/v1alpha1/zz_generated.managedlist.go index 729c6f2a..10989170 100644 --- a/apis/namespaced/projects/v1alpha1/zz_generated.managedlist.go +++ b/apis/namespaced/projects/v1alpha1/zz_generated.managedlist.go @@ -65,6 +65,15 @@ func (l *DeployTokenList) GetItems() []resource.Managed { return items } +// GetItems of this EmailsOnPushList. +func (l *EmailsOnPushList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} + // GetItems of this HookList. func (l *HookList) GetItems() []resource.Managed { items := make([]resource.Managed, len(l.Items)) diff --git a/examples/projects/emailsonpush.yaml b/examples/projects/emailsonpush.yaml new file mode 100644 index 00000000..81c4c625 --- /dev/null +++ b/examples/projects/emailsonpush.yaml @@ -0,0 +1,15 @@ +apiVersion: projects.gitlab.m.crossplane.io/v1alpha1 +kind: EmailsOnPush +metadata: + name: example-emailsonpush +spec: + forProvider: + projectId: 123456 + recipients: example@example.com + pushEvents: true + tagPushEvents: true + disableDiffs: false + sendFromCommitterEmail: false + branchesToBeNotified: all + providerConfigRef: + name: gitlab-provider \ No newline at end of file diff --git a/package/crds/projects.gitlab.crossplane.io_emailsonpushes.yaml b/package/crds/projects.gitlab.crossplane.io_emailsonpushes.yaml new file mode 100644 index 00000000..5b94fd54 --- /dev/null +++ b/package/crds/projects.gitlab.crossplane.io_emailsonpushes.yaml @@ -0,0 +1,326 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: emailsonpushes.projects.gitlab.crossplane.io +spec: + group: projects.gitlab.crossplane.io + names: + categories: + - crossplane + - managed + - gitlab + kind: EmailsOnPush + listKind: EmailsOnPushList + plural: emailsonpushes + singular: emailsonpush + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec + properties: + deletionPolicy: + default: Delete + description: |- + DeletionPolicy specifies what will happen to the underlying external + when this managed resource is deleted - either "Delete" or "Orphan" the + external resource. + This field is planned to be deprecated in favor of the ManagementPolicies + field in a future release. Currently, both could be set independently and + non-default values would be honored if the feature flag is enabled. + See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223 + enum: + - Orphan + - Delete + type: string + forProvider: + description: EmailsOnPushParameters define desired state + properties: + branchesToBeNotified: + description: BranchesToBeNotified defines which branches trigger + notifications + type: string + disableDiffs: + description: DisableDiffs disables sending diffs in email + type: boolean + projectId: + description: ProjectID is the ID of the project. + format: int64 + type: integer + projectIdRef: + description: ProjectIDRef references a Project + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + projectIdSelector: + description: ProjectIDSelector selects a Project + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + policy: + description: Policies for selection. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + type: object + pushEvents: + description: PushEvents triggers emails on push events + type: boolean + recipients: + description: Recipients is a comma separated list of emails + type: string + sendFromCommitterEmail: + description: SendFromCommitterEmail sends email from committer's + email + type: boolean + tagPushEvents: + description: TagPushEvents triggers emails on tag push events + type: boolean + type: object + managementPolicies: + default: + - '*' + description: |- + THIS IS A BETA FIELD. It is on by default but can be opted out + through a Crossplane feature flag. + ManagementPolicies specify the array of actions Crossplane is allowed to + take on the managed and external resources. + This field is planned to replace the DeletionPolicy field in a future + release. Currently, both could be set independently and non-default + values would be honored if the feature flag is enabled. If both are + custom, the DeletionPolicy field will be ignored. + See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223 + and this one: https://github.com/crossplane/crossplane/blob/444267e84783136daa93568b364a5f01228cacbe/design/one-pager-ignore-changes.md + items: + description: |- + A ManagementAction represents an action that the Crossplane controllers + can take on an external resource. + enum: + - Observe + - Create + - Update + - Delete + - LateInitialize + - '*' + type: string + type: array + providerConfigRef: + default: + name: default + description: |- + ProviderConfigReference specifies how the provider that will be used to + create, observe, update, and delete this managed resource should be + configured. + properties: + name: + description: Name of the referenced object. + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + writeConnectionSecretToRef: + description: |- + WriteConnectionSecretToReference specifies the namespace and name of a + Secret to which any connection details for this managed resource should + be written. Connection details frequently include the endpoint, username, + and password required to connect to the managed resource. + properties: + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - name + - namespace + type: object + required: + - forProvider + type: object + status: + description: Status + properties: + atProvider: + description: EmailsOnPushObservation represents observed state + properties: + branchesToBeNotified: + type: string + disableDiffs: + type: boolean + pushEvents: + type: boolean + recipients: + type: string + sendFromCommitterEmail: + type: boolean + tagPushEvents: + type: boolean + type: object + conditions: + description: Conditions of the resource. + 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 + observedGeneration: + description: |- + ObservedGeneration is the latest metadata.generation + which resulted in either a ready state, or stalled due to error + it can not recover from without human intervention. + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/package/crds/projects.gitlab.m.crossplane.io_emailsonpushes.yaml b/package/crds/projects.gitlab.m.crossplane.io_emailsonpushes.yaml new file mode 100644 index 00000000..47393d55 --- /dev/null +++ b/package/crds/projects.gitlab.m.crossplane.io_emailsonpushes.yaml @@ -0,0 +1,290 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: emailsonpushes.projects.gitlab.m.crossplane.io +spec: + group: projects.gitlab.m.crossplane.io + names: + categories: + - crossplane + - managed + - gitlab + kind: EmailsOnPush + listKind: EmailsOnPushList + plural: emailsonpushes + singular: emailsonpush + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec + properties: + forProvider: + description: EmailsOnPushParameters define desired state + properties: + branchesToBeNotified: + description: BranchesToBeNotified defines which branches trigger + notifications + type: string + disableDiffs: + description: DisableDiffs disables sending diffs in email + type: boolean + projectId: + description: ProjectID is the ID of the project. + format: int64 + type: integer + projectIdRef: + description: ProjectIDRef references a Project + properties: + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace of the referenced object + type: string + policy: + description: Policies for referencing. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + required: + - name + type: object + projectIdSelector: + description: ProjectIDSelector selects a Project + properties: + matchControllerRef: + description: |- + MatchControllerRef ensures an object with the same controller reference + as the selecting object is selected. + type: boolean + matchLabels: + additionalProperties: + type: string + description: MatchLabels ensures an object with matching labels + is selected. + type: object + namespace: + description: Namespace for the selector + type: string + policy: + description: Policies for selection. + properties: + resolution: + default: Required + description: |- + Resolution specifies whether resolution of this reference is required. + The default is 'Required', which means the reconcile will fail if the + reference cannot be resolved. 'Optional' means this reference will be + a no-op if it cannot be resolved. + enum: + - Required + - Optional + type: string + resolve: + description: |- + Resolve specifies when this reference should be resolved. The default + is 'IfNotPresent', which will attempt to resolve the reference only when + the corresponding field is not present. Use 'Always' to resolve the + reference on every reconcile. + enum: + - Always + - IfNotPresent + type: string + type: object + type: object + pushEvents: + description: PushEvents triggers emails on push events + type: boolean + recipients: + description: Recipients is a comma separated list of emails + type: string + sendFromCommitterEmail: + description: SendFromCommitterEmail sends email from committer's + email + type: boolean + tagPushEvents: + description: TagPushEvents triggers emails on tag push events + type: boolean + type: object + managementPolicies: + default: + - '*' + description: |- + THIS IS A BETA FIELD. It is on by default but can be opted out + through a Crossplane feature flag. + ManagementPolicies specify the array of actions Crossplane is allowed to + take on the managed and external resources. + See the design doc for more information: https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223 + and this one: https://github.com/crossplane/crossplane/blob/444267e84783136daa93568b364a5f01228cacbe/design/one-pager-ignore-changes.md + items: + description: |- + A ManagementAction represents an action that the Crossplane controllers + can take on an external resource. + enum: + - Observe + - Create + - Update + - Delete + - LateInitialize + - '*' + type: string + type: array + providerConfigRef: + default: + kind: ClusterProviderConfig + name: default + description: |- + ProviderConfigReference specifies how the provider that will be used to + create, observe, update, and delete this managed resource should be + configured. + properties: + kind: + description: Kind of the referenced object. + type: string + name: + description: Name of the referenced object. + type: string + required: + - kind + - name + type: object + writeConnectionSecretToRef: + description: |- + WriteConnectionSecretToReference specifies the namespace and name of a + Secret to which any connection details for this managed resource should + be written. Connection details frequently include the endpoint, username, + and password required to connect to the managed resource. + properties: + name: + description: Name of the secret. + type: string + required: + - name + type: object + required: + - forProvider + type: object + status: + description: Status + properties: + atProvider: + description: EmailsOnPushObservation represents observed state + properties: + branchesToBeNotified: + type: string + disableDiffs: + type: boolean + pushEvents: + type: boolean + recipients: + type: string + sendFromCommitterEmail: + type: boolean + tagPushEvents: + type: boolean + type: object + conditions: + description: Conditions of the resource. + 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 + observedGeneration: + description: |- + ObservedGeneration is the latest metadata.generation + which resulted in either a ready state, or stalled due to error + it can not recover from without human intervention. + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/pkg/cluster/clients/projects/zz_emailsonpush.go b/pkg/cluster/clients/projects/zz_emailsonpush.go new file mode 100644 index 00000000..3479235a --- /dev/null +++ b/pkg/cluster/clients/projects/zz_emailsonpush.go @@ -0,0 +1,100 @@ +// Code generated by hack/generate-cluster-scope.go - DO NOT EDIT. + +package projects + +import ( + gitlab "gitlab.com/gitlab-org/api/client-go" + + "github.com/crossplane-contrib/provider-gitlab/apis/cluster/projects/v1alpha1" + "github.com/crossplane-contrib/provider-gitlab/pkg/cluster/clients" + "github.com/crossplane-contrib/provider-gitlab/pkg/common" +) + +// EmailsOnPushClient defines GitLab Emails on Push service operations. +type EmailsOnPushClient interface { + GetEmailsOnPushService(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) + SetEmailsOnPushService(pid interface{}, opt *gitlab.SetEmailsOnPushServiceOptions, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) + DeleteEmailsOnPushService(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) +} + +// NewEmailsOnPushClient returns a new GitLab Emails on Push service client. +func NewEmailsOnPushClient(cfg common.Config) EmailsOnPushClient { + git := common.NewClient(cfg) + return git.Services +} + +// LateInitializeEmailsOnPush fills empty spec fields using values from GitLab. +func LateInitializeEmailsOnPush(in *v1alpha1.EmailsOnPushParameters, svc *gitlab.EmailsOnPushService) { + if svc == nil || svc.Properties == nil { + return + } + + lateInitRecipients(in, svc) + lateInitBranches(in, svc) + lateInitBooleans(in, svc) +} + +func lateInitRecipients(in *v1alpha1.EmailsOnPushParameters, svc *gitlab.EmailsOnPushService) { + if in.Recipients == nil && svc.Properties.Recipients != "" { + in.Recipients = clients.StringToPtr(svc.Properties.Recipients) + } +} + +func lateInitBranches(in *v1alpha1.EmailsOnPushParameters, svc *gitlab.EmailsOnPushService) { + branches := svc.Properties.BranchesToBeNotified + if branches == "" { + branches = "all" + } + + if in.BranchesToBeNotified == nil { + in.BranchesToBeNotified = clients.StringToPtr(branches) + } +} + +func lateInitBooleans(in *v1alpha1.EmailsOnPushParameters, svc *gitlab.EmailsOnPushService) { + if in.DisableDiffs == nil { + in.DisableDiffs = &svc.Properties.DisableDiffs + } + if in.SendFromCommitterEmail == nil { + in.SendFromCommitterEmail = &svc.Properties.SendFromCommitterEmail + } + if in.PushEvents == nil { + in.PushEvents = &svc.Properties.PushEvents + } + if in.TagPushEvents == nil { + in.TagPushEvents = &svc.Properties.TagPushEvents + } +} + +// GenerateEmailsOnPushObservation converts GitLab response to status. +func GenerateEmailsOnPushObservation(svc *gitlab.EmailsOnPushService) v1alpha1.EmailsOnPushObservation { + if svc == nil || svc.Properties == nil { + return v1alpha1.EmailsOnPushObservation{} + } + + branches := svc.Properties.BranchesToBeNotified + if branches == "" { + branches = "all" + } + + return v1alpha1.EmailsOnPushObservation{ + Recipients: svc.Properties.Recipients, + DisableDiffs: svc.Properties.DisableDiffs, + SendFromCommitterEmail: svc.Properties.SendFromCommitterEmail, + PushEvents: svc.Properties.PushEvents, + TagPushEvents: svc.Properties.TagPushEvents, + BranchesToBeNotified: branches, + } +} + +// GenerateSetEmailsOnPushOptions builds GitLab update options from spec. +func GenerateSetEmailsOnPushOptions(p *v1alpha1.EmailsOnPushParameters) *gitlab.SetEmailsOnPushServiceOptions { + return &gitlab.SetEmailsOnPushServiceOptions{ + Recipients: p.Recipients, + DisableDiffs: p.DisableDiffs, + SendFromCommitterEmail: p.SendFromCommitterEmail, + PushEvents: p.PushEvents, + TagPushEvents: p.TagPushEvents, + BranchesToBeNotified: p.BranchesToBeNotified, + } +} diff --git a/pkg/cluster/controller/projects/emailsonpush/zz_controller.go b/pkg/cluster/controller/projects/emailsonpush/zz_controller.go new file mode 100644 index 00000000..8e6b6cd7 --- /dev/null +++ b/pkg/cluster/controller/projects/emailsonpush/zz_controller.go @@ -0,0 +1,199 @@ +// Code generated by hack/generate-cluster-scope.go - DO NOT EDIT. + +package emailsonpush + +import ( + "context" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + "github.com/crossplane/crossplane-runtime/v2/pkg/statemetrics" + "github.com/pkg/errors" + gitlab "gitlab.com/gitlab-org/api/client-go" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane-contrib/provider-gitlab/apis/cluster/projects/v1alpha1" + "github.com/crossplane-contrib/provider-gitlab/pkg/cluster/clients/projects" + "github.com/crossplane-contrib/provider-gitlab/pkg/common" +) + +const ( + errNotEmailsOnPush = "managed resource is not a EmailsOnPush custom resource" + errProjectIDMissing = "ProjectID is missing" + errGetFailed = "cannot get EmailsOnPush service" + errSetFailed = "cannot set EmailsOnPush service" +) + +// SetupEmailsOnPush adds a controller that reconciles EmailsOnPush. +func SetupEmailsOnPush(mgr ctrl.Manager, o controller.Options) error { + name := managed.ControllerName("cluster." + v1alpha1.EmailsOnPushGroupKind) + + reconcilerOpts := []managed.ReconcilerOption{ + managed.WithExternalConnecter(&connector{ + kube: mgr.GetClient(), + newGitlabClientFn: projects.NewEmailsOnPushClient, + }), + managed.WithInitializers(), + managed.WithPollInterval(o.PollInterval), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOpts = append(reconcilerOpts, managed.WithManagementPolicies()) + } + + r := managed.NewReconciler( + mgr, + resource.ManagedKind(v1alpha1.EmailsOnPushGroupVersionKind), + reconcilerOpts..., + ) + + if err := mgr.Add(statemetrics.NewMRStateRecorder( + mgr.GetClient(), + o.Logger, + o.MetricOptions.MRStateMetrics, + &v1alpha1.EmailsOnPushList{}, + o.MetricOptions.PollStateMetricInterval, + )); err != nil { + return err + } + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&v1alpha1.EmailsOnPush{}). + Complete(r) +} + +// SetupEmailsOnPushGated adds controller with CRD gate support. +func SetupEmailsOnPushGated(mgr ctrl.Manager, o controller.Options) error { + o.Gate.Register(func() { + if err := SetupEmailsOnPush(mgr, o); err != nil { + mgr.GetLogger().Error(err, "unable to setup reconciler", + "gvk", v1alpha1.EmailsOnPushGroupVersionKind.String()) + } + }, v1alpha1.EmailsOnPushGroupVersionKind) + + return nil +} + +type connector struct { + kube client.Client + newGitlabClientFn func(cfg common.Config) projects.EmailsOnPushClient +} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { + cr, ok := mg.(*v1alpha1.EmailsOnPush) + if !ok { + return nil, errors.New(errNotEmailsOnPush) + } + + cfg, err := common.GetConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + return &external{ + kube: c.kube, + client: c.newGitlabClientFn(*cfg), + }, nil +} + +type external struct { + kube client.Client + client projects.EmailsOnPushClient +} + +func (e *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*v1alpha1.EmailsOnPush) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotEmailsOnPush) + } + + if meta.WasDeleted(cr) { + return managed.ExternalObservation{ + ResourceExists: false, + }, nil + } + + if cr.Spec.ForProvider.ProjectID == nil { + return managed.ExternalObservation{}, errors.New(errProjectIDMissing) + } + + svc, resp, err := e.client.GetEmailsOnPushService( + *cr.Spec.ForProvider.ProjectID, + gitlab.WithContext(ctx), + ) + + if err != nil { + if resp != nil && resp.StatusCode == 404 { + return managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: false, + }, nil + } + + return managed.ExternalObservation{}, errors.Wrap(err, errGetFailed) + } + + before := cr.Spec.ForProvider + projects.LateInitializeEmailsOnPush(&cr.Spec.ForProvider, svc) + + if before != cr.Spec.ForProvider && e.kube != nil { + if err := e.kube.Update(ctx, cr); err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, "cannot update EmailsOnPush spec after late init") + } + } + + cr.Status.AtProvider = projects.GenerateEmailsOnPushObservation(svc) + cr.Status.SetConditions(xpv1.Available()) + + return managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, nil +} + +func (e *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + // GitLab integration endpoint uses PUT (idempotent), + // so Create does nothing — Update handles configuration. + return managed.ExternalCreation{}, nil +} + +func (e *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + cr, ok := mg.(*v1alpha1.EmailsOnPush) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotEmailsOnPush) + } + + if cr.Spec.ForProvider.ProjectID == nil { + return managed.ExternalUpdate{}, errors.New(errProjectIDMissing) + } + + options := projects.GenerateSetEmailsOnPushOptions(&cr.Spec.ForProvider) + + _, _, err := e.client.SetEmailsOnPushService( + *cr.Spec.ForProvider.ProjectID, + options, + gitlab.WithContext(ctx), + ) + if err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errSetFailed) + } + + return managed.ExternalUpdate{}, nil +} + +func (e *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + return managed.ExternalDelete{}, nil +} + +func (e *external) Disconnect(ctx context.Context) error { + return nil +} diff --git a/pkg/cluster/controller/projects/emailsonpush/zz_controller_test.go b/pkg/cluster/controller/projects/emailsonpush/zz_controller_test.go new file mode 100644 index 00000000..fcfc25bb --- /dev/null +++ b/pkg/cluster/controller/projects/emailsonpush/zz_controller_test.go @@ -0,0 +1,163 @@ +// Code generated by hack/generate-cluster-scope.go - DO NOT EDIT. + +package emailsonpush + +import ( + "context" + "net/http" + "testing" + + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + "github.com/crossplane/crossplane-runtime/v2/pkg/test" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + gitlab "gitlab.com/gitlab-org/api/client-go" + + "github.com/crossplane-contrib/provider-gitlab/apis/cluster/projects/v1alpha1" +) + +var ( + errBoom = errors.New("boom") + projectID = int64(123) +) + +type mockClient struct { + get func(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) + set func(pid interface{}, opt *gitlab.SetEmailsOnPushServiceOptions, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) + del func(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) +} + +func (m *mockClient) GetEmailsOnPushService(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) { + if m.get != nil { + return m.get(pid, options...) + } + return nil, nil, nil +} + +func (m *mockClient) SetEmailsOnPushService(pid interface{}, opt *gitlab.SetEmailsOnPushServiceOptions, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) { + if m.set != nil { + return m.set(pid, opt, options...) + } + return nil, nil, nil +} + +func (m *mockClient) DeleteEmailsOnPushService(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) { + if m.del != nil { + return m.del(pid, options...) + } + return nil, nil +} + +func emailOnPush() *v1alpha1.EmailsOnPush { + return &v1alpha1.EmailsOnPush{} +} + +type args struct { + client *mockClient + cr resource.Managed +} + +func TestObserve(t *testing.T) { + type want struct { + result managed.ExternalObservation + err error + } + + cases := map[string]struct { + args + want + }{ + "InvalidType": { + args: args{ + cr: &v1alpha1.Project{}, + }, + want: want{ + err: errors.New(errNotEmailsOnPush), + }, + }, + "MissingProjectID": { + args: args{ + cr: emailOnPush(), + }, + want: want{ + err: errors.New(errProjectIDMissing), + }, + }, + "GetError": { + args: args{ + client: &mockClient{ + get: func(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) { + return nil, nil, errBoom + }, + }, + cr: func() resource.Managed { + cr := emailOnPush() + cr.Spec.ForProvider.ProjectID = &projectID + return cr + }(), + }, + want: want{ + err: errors.Wrap(errBoom, errGetFailed), + }, + }, + "ObserveSuccess": { + args: args{ + client: &mockClient{ + get: func(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) { + return &gitlab.EmailsOnPushService{ + Properties: &gitlab.EmailsOnPushServiceProperties{}, + }, &gitlab.Response{}, nil + }, + }, + cr: func() resource.Managed { + cr := emailOnPush() + cr.Spec.ForProvider.ProjectID = &projectID + return cr + }(), + }, + want: want{ + result: managed.ExternalObservation{ + ResourceExists: true, + }, + }, + }, + "Observe404": { + args: args{ + client: &mockClient{ + get: func(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) { + return nil, &gitlab.Response{Response: &http.Response{StatusCode: 404}}, errBoom + }, + }, + cr: func() resource.Managed { + cr := emailOnPush() + cr.Spec.ForProvider.ProjectID = &projectID + return cr + }(), + }, + want: want{ + result: managed.ExternalObservation{ + ResourceExists: true, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &external{ + client: tc.client, + } + + res, err := e.Observe(context.Background(), tc.cr) + + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Fatalf("Observe error mismatch (-want +got):\n%s", diff) + } + + if diff := cmp.Diff(tc.want.result.ResourceExists, res.ResourceExists); diff != "" { + t.Fatalf("Observe result mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/pkg/cluster/controller/projects/zz_setup.go b/pkg/cluster/controller/projects/zz_setup.go index 811cc36e..07d8a1b9 100644 --- a/pkg/cluster/controller/projects/zz_setup.go +++ b/pkg/cluster/controller/projects/zz_setup.go @@ -27,8 +27,8 @@ import ( "github.com/crossplane-contrib/provider-gitlab/pkg/cluster/controller/projects/badges" "github.com/crossplane-contrib/provider-gitlab/pkg/cluster/controller/projects/deploykeys" "github.com/crossplane-contrib/provider-gitlab/pkg/cluster/controller/projects/deploytokens" + "github.com/crossplane-contrib/provider-gitlab/pkg/cluster/controller/projects/emailsonpush" "github.com/crossplane-contrib/provider-gitlab/pkg/cluster/controller/projects/hooks" - integrationmattermost "github.com/crossplane-contrib/provider-gitlab/pkg/cluster/controller/projects/integrationmattermost" "github.com/crossplane-contrib/provider-gitlab/pkg/cluster/controller/projects/members" "github.com/crossplane-contrib/provider-gitlab/pkg/cluster/controller/projects/pipelineschedules" "github.com/crossplane-contrib/provider-gitlab/pkg/cluster/controller/projects/projects" @@ -52,7 +52,7 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { runners.SetupRunner, protectedbranches.SetupProtectedBranch, badges.SetupBadge, - integrationmattermost.SetupIntegrationMattermost, + emailsonpush.SetupEmailsOnPush, } { if err := setup(mgr, o); err != nil { return err @@ -77,7 +77,7 @@ func SetupGated(mgr ctrl.Manager, o controller.Options) error { runners.SetupRunnerGated, protectedbranches.SetupProtectedBranchGated, badges.SetupBadgeGated, - integrationmattermost.SetupIntegrationMattermostGated, + emailsonpush.SetupEmailsOnPushGated, } { if err := setup(mgr, o); err != nil { return err diff --git a/pkg/namespaced/clients/projects/emailsonpush.go b/pkg/namespaced/clients/projects/emailsonpush.go new file mode 100644 index 00000000..b09e4593 --- /dev/null +++ b/pkg/namespaced/clients/projects/emailsonpush.go @@ -0,0 +1,98 @@ +package projects + +import ( + gitlab "gitlab.com/gitlab-org/api/client-go" + + "github.com/crossplane-contrib/provider-gitlab/apis/namespaced/projects/v1alpha1" + "github.com/crossplane-contrib/provider-gitlab/pkg/common" + "github.com/crossplane-contrib/provider-gitlab/pkg/namespaced/clients" +) + +// EmailsOnPushClient defines GitLab Emails on Push service operations. +type EmailsOnPushClient interface { + GetEmailsOnPushService(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) + SetEmailsOnPushService(pid interface{}, opt *gitlab.SetEmailsOnPushServiceOptions, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) + DeleteEmailsOnPushService(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) +} + +// NewEmailsOnPushClient returns a new GitLab Emails on Push service client. +func NewEmailsOnPushClient(cfg common.Config) EmailsOnPushClient { + git := common.NewClient(cfg) + return git.Services +} + +// LateInitializeEmailsOnPush fills empty spec fields using values from GitLab. +func LateInitializeEmailsOnPush(in *v1alpha1.EmailsOnPushParameters, svc *gitlab.EmailsOnPushService) { + if svc == nil || svc.Properties == nil { + return + } + + lateInitRecipients(in, svc) + lateInitBranches(in, svc) + lateInitBooleans(in, svc) +} + +func lateInitRecipients(in *v1alpha1.EmailsOnPushParameters, svc *gitlab.EmailsOnPushService) { + if in.Recipients == nil && svc.Properties.Recipients != "" { + in.Recipients = clients.StringToPtr(svc.Properties.Recipients) + } +} + +func lateInitBranches(in *v1alpha1.EmailsOnPushParameters, svc *gitlab.EmailsOnPushService) { + branches := svc.Properties.BranchesToBeNotified + if branches == "" { + branches = "all" + } + + if in.BranchesToBeNotified == nil { + in.BranchesToBeNotified = clients.StringToPtr(branches) + } +} + +func lateInitBooleans(in *v1alpha1.EmailsOnPushParameters, svc *gitlab.EmailsOnPushService) { + if in.DisableDiffs == nil { + in.DisableDiffs = &svc.Properties.DisableDiffs + } + if in.SendFromCommitterEmail == nil { + in.SendFromCommitterEmail = &svc.Properties.SendFromCommitterEmail + } + if in.PushEvents == nil { + in.PushEvents = &svc.Properties.PushEvents + } + if in.TagPushEvents == nil { + in.TagPushEvents = &svc.Properties.TagPushEvents + } +} + +// GenerateEmailsOnPushObservation converts GitLab response to status. +func GenerateEmailsOnPushObservation(svc *gitlab.EmailsOnPushService) v1alpha1.EmailsOnPushObservation { + if svc == nil || svc.Properties == nil { + return v1alpha1.EmailsOnPushObservation{} + } + + branches := svc.Properties.BranchesToBeNotified + if branches == "" { + branches = "all" + } + + return v1alpha1.EmailsOnPushObservation{ + Recipients: svc.Properties.Recipients, + DisableDiffs: svc.Properties.DisableDiffs, + SendFromCommitterEmail: svc.Properties.SendFromCommitterEmail, + PushEvents: svc.Properties.PushEvents, + TagPushEvents: svc.Properties.TagPushEvents, + BranchesToBeNotified: branches, + } +} + +// GenerateSetEmailsOnPushOptions builds GitLab update options from spec. +func GenerateSetEmailsOnPushOptions(p *v1alpha1.EmailsOnPushParameters) *gitlab.SetEmailsOnPushServiceOptions { + return &gitlab.SetEmailsOnPushServiceOptions{ + Recipients: p.Recipients, + DisableDiffs: p.DisableDiffs, + SendFromCommitterEmail: p.SendFromCommitterEmail, + PushEvents: p.PushEvents, + TagPushEvents: p.TagPushEvents, + BranchesToBeNotified: p.BranchesToBeNotified, + } +} diff --git a/pkg/namespaced/controller/projects/emailsonpush/controller.go b/pkg/namespaced/controller/projects/emailsonpush/controller.go new file mode 100644 index 00000000..67452fbc --- /dev/null +++ b/pkg/namespaced/controller/projects/emailsonpush/controller.go @@ -0,0 +1,197 @@ +package emailsonpush + +import ( + "context" + + xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1" + "github.com/crossplane/crossplane-runtime/v2/pkg/controller" + "github.com/crossplane/crossplane-runtime/v2/pkg/event" + "github.com/crossplane/crossplane-runtime/v2/pkg/feature" + "github.com/crossplane/crossplane-runtime/v2/pkg/meta" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + "github.com/crossplane/crossplane-runtime/v2/pkg/statemetrics" + "github.com/pkg/errors" + gitlab "gitlab.com/gitlab-org/api/client-go" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/crossplane-contrib/provider-gitlab/apis/namespaced/projects/v1alpha1" + "github.com/crossplane-contrib/provider-gitlab/pkg/common" + "github.com/crossplane-contrib/provider-gitlab/pkg/namespaced/clients/projects" +) + +const ( + errNotEmailsOnPush = "managed resource is not a EmailsOnPush custom resource" + errProjectIDMissing = "ProjectID is missing" + errGetFailed = "cannot get EmailsOnPush service" + errSetFailed = "cannot set EmailsOnPush service" +) + +// SetupEmailsOnPush adds a controller that reconciles EmailsOnPush. +func SetupEmailsOnPush(mgr ctrl.Manager, o controller.Options) error { + name := managed.ControllerName(v1alpha1.EmailsOnPushGroupKind) + + reconcilerOpts := []managed.ReconcilerOption{ + managed.WithExternalConnecter(&connector{ + kube: mgr.GetClient(), + newGitlabClientFn: projects.NewEmailsOnPushClient, + }), + managed.WithInitializers(), + managed.WithPollInterval(o.PollInterval), + managed.WithLogger(o.Logger.WithValues("controller", name)), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))), + } + + if o.Features.Enabled(feature.EnableBetaManagementPolicies) { + reconcilerOpts = append(reconcilerOpts, managed.WithManagementPolicies()) + } + + r := managed.NewReconciler( + mgr, + resource.ManagedKind(v1alpha1.EmailsOnPushGroupVersionKind), + reconcilerOpts..., + ) + + if err := mgr.Add(statemetrics.NewMRStateRecorder( + mgr.GetClient(), + o.Logger, + o.MetricOptions.MRStateMetrics, + &v1alpha1.EmailsOnPushList{}, + o.MetricOptions.PollStateMetricInterval, + )); err != nil { + return err + } + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&v1alpha1.EmailsOnPush{}). + Complete(r) +} + +// SetupEmailsOnPushGated adds controller with CRD gate support. +func SetupEmailsOnPushGated(mgr ctrl.Manager, o controller.Options) error { + o.Gate.Register(func() { + if err := SetupEmailsOnPush(mgr, o); err != nil { + mgr.GetLogger().Error(err, "unable to setup reconciler", + "gvk", v1alpha1.EmailsOnPushGroupVersionKind.String()) + } + }, v1alpha1.EmailsOnPushGroupVersionKind) + + return nil +} + +type connector struct { + kube client.Client + newGitlabClientFn func(cfg common.Config) projects.EmailsOnPushClient +} + +func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { + cr, ok := mg.(*v1alpha1.EmailsOnPush) + if !ok { + return nil, errors.New(errNotEmailsOnPush) + } + + cfg, err := common.GetConfig(ctx, c.kube, cr) + if err != nil { + return nil, err + } + + return &external{ + kube: c.kube, + client: c.newGitlabClientFn(*cfg), + }, nil +} + +type external struct { + kube client.Client + client projects.EmailsOnPushClient +} + +func (e *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*v1alpha1.EmailsOnPush) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotEmailsOnPush) + } + + if meta.WasDeleted(cr) { + return managed.ExternalObservation{ + ResourceExists: false, + }, nil + } + + if cr.Spec.ForProvider.ProjectID == nil { + return managed.ExternalObservation{}, errors.New(errProjectIDMissing) + } + + svc, resp, err := e.client.GetEmailsOnPushService( + *cr.Spec.ForProvider.ProjectID, + gitlab.WithContext(ctx), + ) + + if err != nil { + if resp != nil && resp.StatusCode == 404 { + return managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: false, + }, nil + } + + return managed.ExternalObservation{}, errors.Wrap(err, errGetFailed) + } + + before := cr.Spec.ForProvider + projects.LateInitializeEmailsOnPush(&cr.Spec.ForProvider, svc) + + if before != cr.Spec.ForProvider && e.kube != nil { + if err := e.kube.Update(ctx, cr); err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, "cannot update EmailsOnPush spec after late init") + } + } + + cr.Status.AtProvider = projects.GenerateEmailsOnPushObservation(svc) + cr.Status.SetConditions(xpv1.Available()) + + return managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, nil +} + +func (e *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + // GitLab integration endpoint uses PUT (idempotent), + // so Create does nothing — Update handles configuration. + return managed.ExternalCreation{}, nil +} + +func (e *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + cr, ok := mg.(*v1alpha1.EmailsOnPush) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotEmailsOnPush) + } + + if cr.Spec.ForProvider.ProjectID == nil { + return managed.ExternalUpdate{}, errors.New(errProjectIDMissing) + } + + options := projects.GenerateSetEmailsOnPushOptions(&cr.Spec.ForProvider) + + _, _, err := e.client.SetEmailsOnPushService( + *cr.Spec.ForProvider.ProjectID, + options, + gitlab.WithContext(ctx), + ) + if err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errSetFailed) + } + + return managed.ExternalUpdate{}, nil +} + +func (e *external) Delete(ctx context.Context, mg resource.Managed) (managed.ExternalDelete, error) { + return managed.ExternalDelete{}, nil +} + +func (e *external) Disconnect(ctx context.Context) error { + return nil +} diff --git a/pkg/namespaced/controller/projects/emailsonpush/controller_test.go b/pkg/namespaced/controller/projects/emailsonpush/controller_test.go new file mode 100644 index 00000000..e915935a --- /dev/null +++ b/pkg/namespaced/controller/projects/emailsonpush/controller_test.go @@ -0,0 +1,161 @@ +package emailsonpush + +import ( + "context" + "net/http" + "testing" + + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/v2/pkg/resource" + "github.com/crossplane/crossplane-runtime/v2/pkg/test" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + gitlab "gitlab.com/gitlab-org/api/client-go" + + "github.com/crossplane-contrib/provider-gitlab/apis/namespaced/projects/v1alpha1" +) + +var ( + errBoom = errors.New("boom") + projectID = int64(123) +) + +type mockClient struct { + get func(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) + set func(pid interface{}, opt *gitlab.SetEmailsOnPushServiceOptions, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) + del func(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) +} + +func (m *mockClient) GetEmailsOnPushService(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) { + if m.get != nil { + return m.get(pid, options...) + } + return nil, nil, nil +} + +func (m *mockClient) SetEmailsOnPushService(pid interface{}, opt *gitlab.SetEmailsOnPushServiceOptions, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) { + if m.set != nil { + return m.set(pid, opt, options...) + } + return nil, nil, nil +} + +func (m *mockClient) DeleteEmailsOnPushService(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.Response, error) { + if m.del != nil { + return m.del(pid, options...) + } + return nil, nil +} + +func emailOnPush() *v1alpha1.EmailsOnPush { + return &v1alpha1.EmailsOnPush{} +} + +type args struct { + client *mockClient + cr resource.Managed +} + +func TestObserve(t *testing.T) { + type want struct { + result managed.ExternalObservation + err error + } + + cases := map[string]struct { + args + want + }{ + "InvalidType": { + args: args{ + cr: &v1alpha1.Project{}, + }, + want: want{ + err: errors.New(errNotEmailsOnPush), + }, + }, + "MissingProjectID": { + args: args{ + cr: emailOnPush(), + }, + want: want{ + err: errors.New(errProjectIDMissing), + }, + }, + "GetError": { + args: args{ + client: &mockClient{ + get: func(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) { + return nil, nil, errBoom + }, + }, + cr: func() resource.Managed { + cr := emailOnPush() + cr.Spec.ForProvider.ProjectID = &projectID + return cr + }(), + }, + want: want{ + err: errors.Wrap(errBoom, errGetFailed), + }, + }, + "ObserveSuccess": { + args: args{ + client: &mockClient{ + get: func(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) { + return &gitlab.EmailsOnPushService{ + Properties: &gitlab.EmailsOnPushServiceProperties{}, + }, &gitlab.Response{}, nil + }, + }, + cr: func() resource.Managed { + cr := emailOnPush() + cr.Spec.ForProvider.ProjectID = &projectID + return cr + }(), + }, + want: want{ + result: managed.ExternalObservation{ + ResourceExists: true, + }, + }, + }, + "Observe404": { + args: args{ + client: &mockClient{ + get: func(pid interface{}, options ...gitlab.RequestOptionFunc) (*gitlab.EmailsOnPushService, *gitlab.Response, error) { + return nil, &gitlab.Response{Response: &http.Response{StatusCode: 404}}, errBoom + }, + }, + cr: func() resource.Managed { + cr := emailOnPush() + cr.Spec.ForProvider.ProjectID = &projectID + return cr + }(), + }, + want: want{ + result: managed.ExternalObservation{ + ResourceExists: true, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + e := &external{ + client: tc.client, + } + + res, err := e.Observe(context.Background(), tc.cr) + + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Fatalf("Observe error mismatch (-want +got):\n%s", diff) + } + + if diff := cmp.Diff(tc.want.result.ResourceExists, res.ResourceExists); diff != "" { + t.Fatalf("Observe result mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/pkg/namespaced/controller/projects/setup.go b/pkg/namespaced/controller/projects/setup.go index 7c3a4bfa..07015096 100644 --- a/pkg/namespaced/controller/projects/setup.go +++ b/pkg/namespaced/controller/projects/setup.go @@ -25,8 +25,8 @@ import ( "github.com/crossplane-contrib/provider-gitlab/pkg/namespaced/controller/projects/badges" "github.com/crossplane-contrib/provider-gitlab/pkg/namespaced/controller/projects/deploykeys" "github.com/crossplane-contrib/provider-gitlab/pkg/namespaced/controller/projects/deploytokens" + "github.com/crossplane-contrib/provider-gitlab/pkg/namespaced/controller/projects/emailsonpush" "github.com/crossplane-contrib/provider-gitlab/pkg/namespaced/controller/projects/hooks" - integrationmattermost "github.com/crossplane-contrib/provider-gitlab/pkg/namespaced/controller/projects/integrationmattermost" "github.com/crossplane-contrib/provider-gitlab/pkg/namespaced/controller/projects/members" "github.com/crossplane-contrib/provider-gitlab/pkg/namespaced/controller/projects/pipelineschedules" "github.com/crossplane-contrib/provider-gitlab/pkg/namespaced/controller/projects/projects" @@ -50,7 +50,7 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { runners.SetupRunner, protectedbranches.SetupProtectedBranch, badges.SetupBadge, - integrationmattermost.SetupIntegrationMattermost, + emailsonpush.SetupEmailsOnPush, } { if err := setup(mgr, o); err != nil { return err @@ -75,7 +75,7 @@ func SetupGated(mgr ctrl.Manager, o controller.Options) error { runners.SetupRunnerGated, protectedbranches.SetupProtectedBranchGated, badges.SetupBadgeGated, - integrationmattermost.SetupIntegrationMattermostGated, + emailsonpush.SetupEmailsOnPushGated, } { if err := setup(mgr, o); err != nil { return err