diff --git a/apis/kubernetes/v1alpha1/conversion.go b/apis/kubernetes/v1alpha1/conversion.go new file mode 100644 index 0000000000..70a6745f58 --- /dev/null +++ b/apis/kubernetes/v1alpha1/conversion.go @@ -0,0 +1,22 @@ +/* +Copyright 2023 The Crossplane Authors. + +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 + +// Hub marks this type as a conversion hub. +func (g *Object) Hub() { + +} diff --git a/apis/kubernetes/v1alpha1/doc.go b/apis/kubernetes/v1alpha1/doc.go new file mode 100644 index 0000000000..66721d9fa3 --- /dev/null +++ b/apis/kubernetes/v1alpha1/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2020 The Crossplane Authors. + +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 contains the v1alpha1 group Object resources of the Kubernetes provider. +// +kubebuilder:object:generate=true +// +groupName=kubernetes.crossplane.io +// +versionName=v1alpha1 +package v1alpha1 diff --git a/apis/kubernetes/v1alpha1/register.go b/apis/kubernetes/v1alpha1/register.go new file mode 100644 index 0000000000..9be51be8cd --- /dev/null +++ b/apis/kubernetes/v1alpha1/register.go @@ -0,0 +1,50 @@ +/* +Copyright 2020 The Crossplane Authors. + +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 ( + "reflect" + + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +// Package type metadata. +const ( + Group = "kubernetes.crossplane.io" + Version = "v1alpha1" +) + +var ( + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: Group, Version: Version} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} +) + +// Object type metadata. +var ( + ObjectKind = reflect.TypeOf(Object{}).Name() + ObjectGroupKind = schema.GroupKind{Group: Group, Kind: ObjectKind}.String() + ObjectKindAPIVersion = ObjectKind + "." + SchemeGroupVersion.String() + ObjectGroupVersionKind = SchemeGroupVersion.WithKind(ObjectKind) +) + +func init() { + SchemeBuilder.Register(&Object{}, &ObjectList{}) +} diff --git a/apis/kubernetes/v1alpha1/types.go b/apis/kubernetes/v1alpha1/types.go new file mode 100644 index 0000000000..d6a2b76c27 --- /dev/null +++ b/apis/kubernetes/v1alpha1/types.go @@ -0,0 +1,254 @@ +/* +Copyright 2020 The Crossplane Authors. + +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 ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" +) + +// ObjectAction defines actions applicable to Object +type ObjectAction string + +// A ManagementPolicy determines what should happen to the underlying external +// resource when a managed resource is created, updated, deleted, or observed. +// +kubebuilder:validation:Enum=Default;ObserveCreateUpdate;ObserveDelete;Observe +type ManagementPolicy string + +const ( + // Default means the provider can fully manage the resource. + Default ManagementPolicy = "Default" + // ObserveCreateUpdate means the provider can observe, create, or update + // the resource, but can not delete it. + ObserveCreateUpdate ManagementPolicy = "ObserveCreateUpdate" + // ObserveDelete means the provider can observe or delete the resource, but + // can not create and update it. + ObserveDelete ManagementPolicy = "ObserveDelete" + // Observe means the provider can only observe the resource. + Observe ManagementPolicy = "Observe" + + // ObjectActionCreate means to create an Object + ObjectActionCreate ObjectAction = "Create" + // ObjectActionUpdate means to update an Object + ObjectActionUpdate ObjectAction = "Update" + // ObjectActionDelete means to delete an Object + ObjectActionDelete ObjectAction = "Delete" +) + +// DependsOn refers to an object by Name, Kind, APIVersion, etc. It is used to +// reference other Object or arbitrary Kubernetes resource which is either +// cluster or namespace scoped. +type DependsOn struct { + // APIVersion of the referenced object. + // +kubebuilder:default=kubernetes.crossplane.io/v1alpha1 + // +optional + APIVersion string `json:"apiVersion,omitempty"` + // Kind of the referenced object. + // +kubebuilder:default=Object + // +optional + Kind string `json:"kind,omitempty"` + // Name of the referenced object. + Name string `json:"name"` + // Namespace of the referenced object. + // +optional + Namespace string `json:"namespace,omitempty"` +} + +// PatchesFrom refers to an object by Name, Kind, APIVersion, etc., and patch +// fields from this object. +type PatchesFrom struct { + DependsOn `json:",inline"` + // FieldPath is the path of the field on the resource whose value is to be + // used as input. + FieldPath *string `json:"fieldPath"` +} + +// Reference refers to an Object or arbitrary Kubernetes resource and optionally +// patch values from that resource to the current Object. +type Reference struct { + // DependsOn is used to declare dependency on other Object or arbitrary + // Kubernetes resource. + // +optional + *DependsOn `json:"dependsOn,omitempty"` + // PatchesFrom is used to declare dependency on other Object or arbitrary + // Kubernetes resource, and also patch fields from this object. + // +optional + *PatchesFrom `json:"patchesFrom,omitempty"` + // ToFieldPath is the path of the field on the resource whose value will + // be changed with the result of transforms. Leave empty if you'd like to + // propagate to the same path as patchesFrom.fieldPath. + // +optional + ToFieldPath *string `json:"toFieldPath,omitempty"` +} + +// ObjectParameters are the configurable fields of a Object. +type ObjectParameters struct { + // Raw JSON representation of the kubernetes object to be created. + // +kubebuilder:validation:EmbeddedResource + // +kubebuilder:pruning:PreserveUnknownFields + Manifest runtime.RawExtension `json:"manifest"` +} + +// ObjectObservation are the observable fields of a Object. +type ObjectObservation struct { + // Raw JSON representation of the remote object. + // +kubebuilder:validation:EmbeddedResource + // +kubebuilder:pruning:PreserveUnknownFields + Manifest runtime.RawExtension `json:"manifest,omitempty"` +} + +// A ObjectSpec defines the desired state of a Object. +type ObjectSpec struct { + xpv1.ResourceSpec `json:",inline"` + ConnectionDetails []ConnectionDetail `json:"connectionDetails,omitempty"` + ForProvider ObjectParameters `json:"forProvider"` + // +kubebuilder:default=Default + ManagementPolicy `json:"managementPolicy,omitempty"` + References []Reference `json:"references,omitempty"` + Readiness Readiness `json:"readiness,omitempty"` + // Watch enables watching the referenced or managed kubernetes resources. + // + // THIS IS AN ALPHA FIELD. Do not use it in production. It is not honored + // unless "watches" feature gate is enabled, and may be changed or removed + // without notice. + // +optional + // +kubebuilder:default=false + Watch bool `json:"watch,omitempty"` +} + +// ReadinessPolicy defines how the Object's readiness condition should be computed. +type ReadinessPolicy string + +const ( + // ReadinessPolicySuccessfulCreate means the object is marked as ready when the + // underlying external resource is successfully created. + ReadinessPolicySuccessfulCreate ReadinessPolicy = "SuccessfulCreate" + // ReadinessPolicyDeriveFromObject means the object is marked as ready if and only if the underlying + // external resource is considered ready. + ReadinessPolicyDeriveFromObject ReadinessPolicy = "DeriveFromObject" + + // ReadinessPolicyAllTrue means that all conditions have status true on the object. + // There must be at least one condition. + ReadinessPolicyAllTrue ReadinessPolicy = "AllTrue" +) + +// Readiness defines how the object's readiness condition should be computed, +// if not specified it will be considered ready as soon as the underlying external +// resource is considered up-to-date. +type Readiness struct { + // Policy defines how the Object's readiness condition should be computed. + // +optional + // +kubebuilder:validation:Enum=SuccessfulCreate;DeriveFromObject;AllTrue + // +kubebuilder:default=SuccessfulCreate + Policy ReadinessPolicy `json:"policy,omitempty"` +} + +// ConnectionDetail represents an entry in the connection secret for an Object +type ConnectionDetail struct { + v1.ObjectReference `json:",inline"` + ToConnectionSecretKey string `json:"toConnectionSecretKey,omitempty"` +} + +// A ObjectStatus represents the observed state of a Object. +type ObjectStatus struct { + xpv1.ResourceStatus `json:",inline"` + AtProvider ObjectObservation `json:"atProvider,omitempty"` +} + +// +kubebuilder:object:root=true + +// A Object is an provider Kubernetes API type +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="KIND",type="string",JSONPath=".spec.forProvider.manifest.kind" +// +kubebuilder:printcolumn:name="APIVERSION",type="string",JSONPath=".spec.forProvider.manifest.apiVersion",priority=1 +// +kubebuilder:printcolumn:name="METANAME",type="string",JSONPath=".spec.forProvider.manifest.metadata.name",priority=1 +// +kubebuilder:printcolumn:name="METANAMESPACE",type="string",JSONPath=".spec.forProvider.manifest.metadata.namespace",priority=1 +// +kubebuilder:printcolumn:name="PROVIDERCONFIG",type="string",JSONPath=".spec.providerConfigRef.name" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,kubernetes} +// +kubebuilder:deprecatedversion +// Deprecated: v1alpha1.Object is deprecated in favor of v1alpha2.Object +type Object struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ObjectSpec `json:"spec"` + Status ObjectStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ObjectList contains a list of Object +type ObjectList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Object `json:"items"` +} + +// ApplyFromFieldPathPatch patches the "to" resource, using a source field +// on the "from" resource. +func (r *Reference) ApplyFromFieldPathPatch(from, to runtime.Object) error { + // Default to patch the same field on the "to" resource. + if r.ToFieldPath == nil { + r.ToFieldPath = r.PatchesFrom.FieldPath + } + + paved, err := fieldpath.PaveObject(from) + if err != nil { + return err + } + + out, err := paved.GetValue(*r.PatchesFrom.FieldPath) + if err != nil { + return err + } + + return patchFieldValueToObject(*r.ToFieldPath, out, to) +} + +// patchFieldValueToObject, given a path, value and "to" object, will +// apply the value to the "to" object at the given path, returning +// any errors as they occur. +func patchFieldValueToObject(path string, value interface{}, to runtime.Object) error { + paved, err := fieldpath.PaveObject(to) + if err != nil { + return err + } + + err = paved.SetValue("spec.forProvider.manifest."+path, value) + if err != nil { + return err + } + + return runtime.DefaultUnstructuredConverter.FromUnstructured(paved.UnstructuredContent(), to) +} + +// IsActionAllowed determines if action is allowed to be performed on Object +func (p *ManagementPolicy) IsActionAllowed(action ObjectAction) bool { + if action == ObjectActionCreate || action == ObjectActionUpdate { + return *p == Default || *p == ObserveCreateUpdate + } + + // ObjectActionDelete + return *p == Default || *p == ObserveDelete +} diff --git a/apis/kubernetes/v1alpha1/zz_generated.deepcopy.go b/apis/kubernetes/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..c1caa976e7 --- /dev/null +++ b/apis/kubernetes/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,244 @@ +//go:build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectionDetail) DeepCopyInto(out *ConnectionDetail) { + *out = *in + out.ObjectReference = in.ObjectReference +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionDetail. +func (in *ConnectionDetail) DeepCopy() *ConnectionDetail { + if in == nil { + return nil + } + out := new(ConnectionDetail) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DependsOn) DeepCopyInto(out *DependsOn) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DependsOn. +func (in *DependsOn) DeepCopy() *DependsOn { + if in == nil { + return nil + } + out := new(DependsOn) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Object) DeepCopyInto(out *Object) { + *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 Object. +func (in *Object) DeepCopy() *Object { + if in == nil { + return nil + } + out := new(Object) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Object) 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 *ObjectList) DeepCopyInto(out *ObjectList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Object, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectList. +func (in *ObjectList) DeepCopy() *ObjectList { + if in == nil { + return nil + } + out := new(ObjectList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ObjectList) 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 *ObjectObservation) DeepCopyInto(out *ObjectObservation) { + *out = *in + in.Manifest.DeepCopyInto(&out.Manifest) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectObservation. +func (in *ObjectObservation) DeepCopy() *ObjectObservation { + if in == nil { + return nil + } + out := new(ObjectObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectParameters) DeepCopyInto(out *ObjectParameters) { + *out = *in + in.Manifest.DeepCopyInto(&out.Manifest) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectParameters. +func (in *ObjectParameters) DeepCopy() *ObjectParameters { + if in == nil { + return nil + } + out := new(ObjectParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectSpec) DeepCopyInto(out *ObjectSpec) { + *out = *in + in.ResourceSpec.DeepCopyInto(&out.ResourceSpec) + if in.ConnectionDetails != nil { + in, out := &in.ConnectionDetails, &out.ConnectionDetails + *out = make([]ConnectionDetail, len(*in)) + copy(*out, *in) + } + in.ForProvider.DeepCopyInto(&out.ForProvider) + if in.References != nil { + in, out := &in.References, &out.References + *out = make([]Reference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + out.Readiness = in.Readiness +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectSpec. +func (in *ObjectSpec) DeepCopy() *ObjectSpec { + if in == nil { + return nil + } + out := new(ObjectSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectStatus) DeepCopyInto(out *ObjectStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + in.AtProvider.DeepCopyInto(&out.AtProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStatus. +func (in *ObjectStatus) DeepCopy() *ObjectStatus { + if in == nil { + return nil + } + out := new(ObjectStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PatchesFrom) DeepCopyInto(out *PatchesFrom) { + *out = *in + out.DependsOn = in.DependsOn + if in.FieldPath != nil { + in, out := &in.FieldPath, &out.FieldPath + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchesFrom. +func (in *PatchesFrom) DeepCopy() *PatchesFrom { + if in == nil { + return nil + } + out := new(PatchesFrom) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Readiness) DeepCopyInto(out *Readiness) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Readiness. +func (in *Readiness) DeepCopy() *Readiness { + if in == nil { + return nil + } + out := new(Readiness) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Reference) DeepCopyInto(out *Reference) { + *out = *in + if in.DependsOn != nil { + in, out := &in.DependsOn, &out.DependsOn + *out = new(DependsOn) + **out = **in + } + if in.PatchesFrom != nil { + in, out := &in.PatchesFrom, &out.PatchesFrom + *out = new(PatchesFrom) + (*in).DeepCopyInto(*out) + } + if in.ToFieldPath != nil { + in, out := &in.ToFieldPath, &out.ToFieldPath + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Reference. +func (in *Reference) DeepCopy() *Reference { + if in == nil { + return nil + } + out := new(Reference) + in.DeepCopyInto(out) + return out +} diff --git a/apis/kubernetes/v1alpha1/zz_generated.managed.go b/apis/kubernetes/v1alpha1/zz_generated.managed.go new file mode 100644 index 0000000000..93e1fccab9 --- /dev/null +++ b/apis/kubernetes/v1alpha1/zz_generated.managed.go @@ -0,0 +1,65 @@ +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + +// GetCondition of this Object. +func (mg *Object) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetDeletionPolicy of this Object. +func (mg *Object) GetDeletionPolicy() xpv1.DeletionPolicy { + return mg.Spec.DeletionPolicy +} + +// GetManagementPolicies of this Object. +func (mg *Object) GetManagementPolicies() xpv1.ManagementPolicies { + return mg.Spec.ManagementPolicies +} + +// GetProviderConfigReference of this Object. +func (mg *Object) GetProviderConfigReference() *xpv1.Reference { + return mg.Spec.ProviderConfigReference +} + +// GetPublishConnectionDetailsTo of this Object. +func (mg *Object) GetPublishConnectionDetailsTo() *xpv1.PublishConnectionDetailsTo { + return mg.Spec.PublishConnectionDetailsTo +} + +// GetWriteConnectionSecretToReference of this Object. +func (mg *Object) GetWriteConnectionSecretToReference() *xpv1.SecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this Object. +func (mg *Object) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetDeletionPolicy of this Object. +func (mg *Object) SetDeletionPolicy(r xpv1.DeletionPolicy) { + mg.Spec.DeletionPolicy = r +} + +// SetManagementPolicies of this Object. +func (mg *Object) SetManagementPolicies(r xpv1.ManagementPolicies) { + mg.Spec.ManagementPolicies = r +} + +// SetProviderConfigReference of this Object. +func (mg *Object) SetProviderConfigReference(r *xpv1.Reference) { + mg.Spec.ProviderConfigReference = r +} + +// SetPublishConnectionDetailsTo of this Object. +func (mg *Object) SetPublishConnectionDetailsTo(r *xpv1.PublishConnectionDetailsTo) { + mg.Spec.PublishConnectionDetailsTo = r +} + +// SetWriteConnectionSecretToReference of this Object. +func (mg *Object) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} diff --git a/apis/kubernetes/v1alpha1/zz_generated.managedlist.go b/apis/kubernetes/v1alpha1/zz_generated.managedlist.go new file mode 100644 index 0000000000..01f3cf2d56 --- /dev/null +++ b/apis/kubernetes/v1alpha1/zz_generated.managedlist.go @@ -0,0 +1,14 @@ +// Code generated by angryjet. DO NOT EDIT. + +package v1alpha1 + +import resource "github.com/crossplane/crossplane-runtime/pkg/resource" + +// GetItems of this ObjectList. +func (l *ObjectList) GetItems() []resource.Managed { + items := make([]resource.Managed, len(l.Items)) + for i := range l.Items { + items[i] = &l.Items[i] + } + return items +} diff --git a/cmd/controller.go b/cmd/controller.go index 90989309bd..733119caa6 100644 --- a/cmd/controller.go +++ b/cmd/controller.go @@ -8,7 +8,6 @@ import ( "github.com/spf13/viper" "github.com/vshn/appcat/v4/pkg" "github.com/vshn/appcat/v4/pkg/controller/events" - "github.com/vshn/appcat/v4/pkg/controller/postgres" "github.com/vshn/appcat/v4/pkg/controller/webhooks" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -23,7 +22,9 @@ type controller struct { metricsAddr, healthAddr string leaderElect bool enableWebhooks bool + enableAppcatWebhooks bool enableQuotas bool + enableEventForwarding bool certDir string } @@ -44,8 +45,10 @@ func init() { ControllerCMD.Flags().BoolVar(&c.leaderElect, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") ControllerCMD.Flags().BoolVar(&c.enableWebhooks, "webhooks", true, "Disable the validation webhooks.") + ControllerCMD.Flags().BoolVar(&c.enableAppcatWebhooks, "appcat-webhooks", true, "Disable the appcat validation webhooks") ControllerCMD.Flags().StringVar(&c.certDir, "certdir", "/etc/webhook/certs", "Set the webhook certificate directory") ControllerCMD.Flags().BoolVar(&c.enableQuotas, "quotas", false, "Enable the quota webhooks, is only active if webhooks is also true") + ControllerCMD.Flags().BoolVar(&c.enableEventForwarding, "event-forwarding", true, "Disable event-forwarding") viper.AutomaticEnv() if !viper.IsSet("PLANS_NAMESPACE") { viper.Set("PLANS_NAMESPACE", "syn-appcat") @@ -74,25 +77,17 @@ func (c *controller) executeController(cmd *cobra.Command, _ []string) error { return err } - xpg := &postgres.XPostgreSQLReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - } - - err = xpg.SetupWithManager(mgr) - if err != nil { - return err - } - - events := &events.EventHandler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - } + if c.enableEventForwarding { + events := &events.EventHandler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } - err = events.SetupWithManager(mgr) + err = events.SetupWithManager(mgr) - if err != nil { - return err + if err != nil { + return err + } } if c.enableWebhooks { @@ -101,7 +96,7 @@ func (c *controller) executeController(cmd *cobra.Command, _ []string) error { return fmt.Errorf("PLANS_NAMEPSACE env variable needs to be set for quota support") } - err := setupWebhooks(mgr, c.enableQuotas) + err := setupWebhooks(mgr, c.enableQuotas, c.enableAppcatWebhooks) if err != nil { return err } @@ -117,45 +112,54 @@ func (c *controller) executeController(cmd *cobra.Command, _ []string) error { return mgr.Start(ctrl.SetupSignalHandler()) } -func setupWebhooks(mgr manager.Manager, withQuota bool) error { - err := webhooks.SetupPostgreSQLWebhookHandlerWithManager(mgr, withQuota) - if err != nil { - return err - } - err = webhooks.SetupRedisWebhookHandlerWithManager(mgr, withQuota) - if err != nil { - return err - } - err = webhooks.SetupMariaDBWebhookHandlerWithManager(mgr, withQuota) - if err != nil { - return err - } - err = webhooks.SetupMinioWebhookHandlerWithManager(mgr, withQuota) - if err != nil { - return err - } - err = webhooks.SetupNextcloudWebhookHandlerWithManager(mgr, withQuota) - if err != nil { - return err - } - err = webhooks.SetupKeycloakWebhookHandlerWithManager(mgr, withQuota) - if err != nil { - return err +func setupWebhooks(mgr manager.Manager, withQuota bool, withAppcatWebhooks bool) error { + if withAppcatWebhooks { + err := webhooks.SetupPostgreSQLWebhookHandlerWithManager(mgr, withQuota) + if err != nil { + return err + } + err = webhooks.SetupRedisWebhookHandlerWithManager(mgr, withQuota) + if err != nil { + return err + } + err = webhooks.SetupMariaDBWebhookHandlerWithManager(mgr, withQuota) + if err != nil { + return err + } + err = webhooks.SetupMinioWebhookHandlerWithManager(mgr, withQuota) + if err != nil { + return err + } + err = webhooks.SetupNextcloudWebhookHandlerWithManager(mgr, withQuota) + if err != nil { + return err + } + err = webhooks.SetupKeycloakWebhookHandlerWithManager(mgr, withQuota) + if err != nil { + return err + } + err = webhooks.SetupXObjectbucketCDeletionProtectionHandlerWithManager(mgr) + if err != nil { + return err + } + + err = webhooks.SetupObjectbucketDeletionProtectionHandlerWithManager(mgr) + if err != nil { + return err + } } - err = webhooks.SetupNamespaceDeletionProtectionHandlerWithManager(mgr) + + err := webhooks.SetupNamespaceDeletionProtectionHandlerWithManager(mgr) if err != nil { return err } - - err = webhooks.SetupXObjectbucketCDeletionProtectionHandlerWithManager(mgr) + err = webhooks.SetupReleaseDeletionProtectionHandlerWithManager(mgr) if err != nil { return err } - - err = webhooks.SetupObjectbucketDeletionProtectionHandlerWithManager(mgr) + err = webhooks.SetupObjectDeletionProtectionHandlerWithManager(mgr) if err != nil { return err } - return webhooks.SetupPVCDeletionProtectionHandlerWithManager(mgr) } diff --git a/config/controller/cluster-role.yaml b/config/controller/cluster-role.yaml index 4cf5fa78ed..ed214af81e 100644 --- a/config/controller/cluster-role.yaml +++ b/config/controller/cluster-role.yaml @@ -39,11 +39,45 @@ rules: - update - watch - apiGroups: - - kubernetes.crossplane.io + - syn.tools resources: - - objects + - compositemariadbinstances verbs: - - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - syn.tools + resources: + - compositemariadbinstances/status + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - syn.tools + resources: + - compositeredisinstances + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - syn.tools + resources: + - compositeredisinstances/status + verbs: + - get + - list + - patch + - update + - watch - apiGroups: - vshn.appcat.vshn.io resources: @@ -142,17 +176,6 @@ rules: - patch - update - watch -- apiGroups: - - vshn.appcat.vshn.io - resources: - - xvshnpostgresqls/finalizers - verbs: - - create - - get - - list - - patch - - update - - watch - apiGroups: - vshn.appcat.vshn.io resources: diff --git a/config/controller/webhooks.yaml b/config/controller/webhooks.yaml index 049497c9c3..f32efe7677 100644 --- a/config/controller/webhooks.yaml +++ b/config/controller/webhooks.yaml @@ -42,6 +42,25 @@ webhooks: resources: - objectbuckets sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-kubernetes-crossplane-io-v1alpha1-object + failurePolicy: Fail + name: objects.vshn.appcat.vshn.io + rules: + - apiGroups: + - kubernetes.crossplane.io + apiVersions: + - v1alpha1 + operations: + - DELETE + resources: + - objects + sideEffects: None - admissionReviewVersions: - v1 clientConfig: @@ -82,6 +101,25 @@ webhooks: resources: - persistentvolumeclaims sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-helm-crossplane-io-v1beta1-release + failurePolicy: Fail + name: releases.vshn.appcat.vshn.io + rules: + - apiGroups: + - helm.crossplane.io + apiVersions: + - v1beta1 + operations: + - DELETE + resources: + - releases + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/pkg/controller/postgres/deletion_protection.go b/pkg/controller/postgres/deletion_protection.go deleted file mode 100644 index 60f9025c01..0000000000 --- a/pkg/controller/postgres/deletion_protection.go +++ /dev/null @@ -1,97 +0,0 @@ -package postgres - -import ( - "context" - "encoding/json" - "sort" - "strconv" - - logging "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/crossplane/crossplane-runtime/pkg/errors" - "github.com/go-logr/logr" - "github.com/vshn/appcat/v4/pkg/common/jsonpatch" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" -) - -const ( - finalizerName = "appcat.io/deletionProtection" -) - -func handle(ctx context.Context, inst client.Object) (client.Patch, error) { - log := logging.FromContext(ctx, "namespace", inst.GetNamespace(), "instance", inst.GetName()) - op := jsonpatch.JSONopNone - - removed := controllerutil.RemoveFinalizer(inst, finalizerName) - - if removed { - log.Info("Ensuring deprecated finalizer is not set", "objectName", inst.GetName()) - op = jsonpatch.JSONopRemove - } - - return getPatchObjectFinalizer(log, inst, op) -} - -func getPatchObjectFinalizer(log logr.Logger, inst client.Object, op jsonpatch.JSONop) (client.Patch, error) { - // handle the case if crossplane or something else decides to add more finalizers, or if - // the finalizer is already there. - index := len(inst.GetFinalizers()) - dupes := []int{} - for i, finalizer := range inst.GetFinalizers() { - if finalizer == finalizerName { - index = i - dupes = append(dupes, i) - } - } - - // if this is a noop and we have exactly one or no dupe, then we're done here - if op == jsonpatch.JSONopNone && (len(dupes) == 1 || len(dupes) == 0) { - return nil, nil - } - - log.V(1).Info("Index size", "size", index, "found finalizers", inst.GetFinalizers()) - - strIndex := strconv.Itoa(index) - if op == jsonpatch.JSONopAdd { - strIndex = "-" - } - - patchOps := []jsonpatch.JSONpatch{} - if op != jsonpatch.JSONopNone { - patchOps = append(patchOps, jsonpatch.JSONpatch{ - Op: op, - Path: "/metadata/finalizers/" + strIndex, - Value: finalizerName, - }) - } - - // if we have more than one of our finalizers, we need to remove the excess ones - if len(dupes) > 1 { - // Jsonpatch doesn't specify how deletion of multiple indices in an array is handled. - // When starting with the lowest first, it could be that the indices all shift and not match anymore. - // We reverse sort, so that the patch contains the largest index first. - // This way we avoid any index shifting during the deletion and are on the safe side. - sort.Sort(sort.Reverse(sort.IntSlice(dupes))) - for _, v := range dupes { - // We skip the first one, so we don't remove all of them - if v == 0 { - continue - } - patchOps = append(patchOps, jsonpatch.JSONpatch{ - Op: jsonpatch.JSONopRemove, - Path: "/metadata/finalizers/" + strconv.Itoa(v), - }) - } - } - - patch, err := json.Marshal(patchOps) - if err != nil { - return nil, errors.Wrap(err, "can't marshal patch") - } - - log.V(1).Info("Patching object", "patch", string(patch)) - - return client.RawPatch(types.JSONPatchType, patch), nil -} diff --git a/pkg/controller/postgres/deletion_protection_test.go b/pkg/controller/postgres/deletion_protection_test.go deleted file mode 100644 index ef34262fc8..0000000000 --- a/pkg/controller/postgres/deletion_protection_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package postgres - -import ( - "context" - "encoding/json" - "strconv" - "testing" - - "github.com/go-logr/logr" - "github.com/stretchr/testify/assert" - xkube "github.com/vshn/appcat/v4/apis/kubernetes/v1alpha2" - vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" - "github.com/vshn/appcat/v4/pkg/common/jsonpatch" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func init() { - _ = vshnv1.AddToScheme(s) - _ = corev1.AddToScheme(s) - _ = xkube.SchemeBuilder.AddToScheme(s) -} - -func Test_Handle(t *testing.T) { - tests := map[string]struct { - ctx context.Context - obj vshnv1.XVSHNPostgreSQL - expectedPatch client.Patch - }{ - - "WhenFinalizer_ThenRemoveOpPatch": { - ctx: context.Background(), - obj: getXVSHNPostgreSQL(1), - expectedPatch: getPatch(jsonpatch.JSONopRemove), - }, - "WhenMultipleFinalzers_ThenOnlyKeepOne": { - ctx: context.Background(), - obj: getXVSHNPostgreSQL(2), - expectedPatch: getPatch(jsonpatch.JSONopRemove), - }, - "WhenMoreFinalzers_ThenOnlyKeepOne": { - ctx: context.Background(), - obj: getXVSHNPostgreSQL(3), - expectedPatch: getPatch(jsonpatch.JSONopRemove), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - - // WHEN - actualPatch, err := handle(tc.ctx, &tc.obj) - - // THEN - assert.NoError(t, err) - assert.Equal(t, tc.expectedPatch, actualPatch) - }) - } -} - -func Test_GetPatchObjectFinalizer(t *testing.T) { - tests := map[string]struct { - obj vshnv1.XVSHNPostgreSQL - op jsonpatch.JSONop - expectedPatch client.Patch - }{ - "WhenOpRemove_ThenReturnPatch": { - obj: getXVSHNPostgreSQL(1), - op: jsonpatch.JSONopRemove, - expectedPatch: getPatch(jsonpatch.JSONopRemove), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - - // GIVEN - log := logr.Discard() - - // WHEN - patch, err := getPatchObjectFinalizer(log, &tc.obj, tc.op) - - // THEN - assert.NoError(t, err) - assert.Equal(t, tc.expectedPatch, patch) - - }) - } -} - -func getXVSHNPostgreSQL(addFinalizer int) vshnv1.XVSHNPostgreSQL { - obj := vshnv1.XVSHNPostgreSQL{} - for i := 0; i < addFinalizer; i++ { - obj.Finalizers = append(obj.Finalizers, finalizerName) - } - return obj -} - -func getPatch(op jsonpatch.JSONop) client.Patch { - strIndex := strconv.Itoa(0) - if op == jsonpatch.JSONopAdd { - strIndex = "-" - } - patchOps := []jsonpatch.JSONpatch{ - { - Op: op, - Path: "/metadata/finalizers/" + strIndex, - Value: finalizerName, - }, - } - patch, _ := json.Marshal(patchOps) - return client.RawPatch(types.JSONPatchType, patch) -} diff --git a/pkg/controller/postgres/reconciler.go b/pkg/controller/postgres/reconciler.go deleted file mode 100644 index 2d0feb4b36..0000000000 --- a/pkg/controller/postgres/reconciler.go +++ /dev/null @@ -1,109 +0,0 @@ -package postgres - -import ( - "context" - - "github.com/crossplane/crossplane-runtime/pkg/errors" - xkube "github.com/vshn/appcat/v4/apis/kubernetes/v1alpha2" - - vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/util/retry" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - logging "sigs.k8s.io/controller-runtime/pkg/log" -) - -//+kubebuilder:rbac:groups=kubernetes.crossplane.io,resources=objects,verbs=delete - -// To run on newer OpenShift version, this RBAC permission is necessary. -//+kubebuilder:rbac:groups=vshn.appcat.vshn.io,resources=xvshnpostgresqls/finalizers,verbs=get;list;patch;update;watch;create - -type XPostgreSQLReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -func (p *XPostgreSQLReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := logging.FromContext(ctx, "namespace", req.Namespace, "instance", req.Name) - inst := &vshnv1.XVSHNPostgreSQL{} - err := p.Get(ctx, req.NamespacedName, inst) - - if apierrors.IsNotFound(err) { - log.Info("Instance deleted") - return ctrl.Result{}, nil - } - - err = p.handleDeletionProtection(ctx, inst) - if err != nil { - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil -} - -func (p *XPostgreSQLReconciler) handleDeletionProtection(ctx context.Context, inst *vshnv1.XVSHNPostgreSQL) error { - log := logging.FromContext(ctx, "namespace", inst.GetNamespace(), "instance", inst.GetName()) - - baseObj := &vshnv1.XVSHNPostgreSQL{ - ObjectMeta: metav1.ObjectMeta{ - Name: inst.Name, - Namespace: inst.Namespace, - }, - } - - patch, err := handle(ctx, inst) - - if err != nil { - return errors.Wrap(err, "cannot return patch operation object") - } - - if patch != nil { - - errorFunc := func(err error) bool { - return err != nil && !apierrors.IsNotFound(err) - } - - // Unfortunately patches just return generic errors if you patch something that has been modified. - // So we just retry a few times before actually logging and error. - err := retry.OnError(retry.DefaultBackoff, errorFunc, func() error { - log.V(1).Info("Trying to patch the object") - return p.Patch(ctx, baseObj, patch) - }) - - if err != nil { - return err - } - - } - - return nil -} - -func (p *XPostgreSQLReconciler) deletePostgresDB(ctx context.Context, inst *vshnv1.XVSHNPostgreSQL) error { - log := logging.FromContext(ctx, "namespace", inst.GetNamespace(), "instance", inst.GetName()) - - log.V(1).Info("Deleting sgcluster object") - o := &xkube.Object{ - ObjectMeta: metav1.ObjectMeta{ - Name: inst.Name + "-cluster", - }, - } - - err := p.Delete(ctx, o) - if err != nil && !apierrors.IsNotFound(err) { - return err - } - return nil -} - -// SetupWithManager sets up the controller with the Manager. -func (p *XPostgreSQLReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&vshnv1.XVSHNPostgreSQL{}). - Owns(&corev1.Namespace{}). - Complete(p) -} diff --git a/pkg/controller/postgres/reconciler_test.go b/pkg/controller/postgres/reconciler_test.go deleted file mode 100644 index 26d8ae9d4f..0000000000 --- a/pkg/controller/postgres/reconciler_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package postgres - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - xkube "github.com/vshn/appcat/v4/apis/kubernetes/v1alpha2" - v1 "github.com/vshn/appcat/v4/apis/vshn/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/ptr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" -) - -var ( - s = runtime.NewScheme() -) - -func init() { - _ = v1.AddToScheme(s) - _ = corev1.AddToScheme(s) - _ = xkube.SchemeBuilder.AddToScheme(s) -} - -func Test_Reconcile(t *testing.T) { - tests := []struct { - name string - req reconcile.Request - inst v1.XVSHNPostgreSQL - instanceNamespace corev1.Namespace - expectedResult ctrl.Result - expectedError error - expectInstanceNamespace bool - }{ - { - name: "WhenFinalizer_ThenPatchInstance", - req: reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: "instance-1", - }, - }, - inst: v1.XVSHNPostgreSQL{ - ObjectMeta: metav1.ObjectMeta{ - Name: "instance-1", - Finalizers: []string{finalizerName}, - }, - Spec: v1.XVSHNPostgreSQLSpec{ - Parameters: v1.VSHNPostgreSQLParameters{ - Backup: v1.VSHNPostgreSQLBackup{ - DeletionProtection: ptr.To(true), - }, - }, - }, - }, - instanceNamespace: corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "vshn-postgresql-instance-1", - }, - }, - expectedResult: ctrl.Result{}, - expectInstanceNamespace: true, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - - // GIVEN - fclient := fake.NewClientBuilder(). - WithScheme(s). - WithObjects(&tc.inst, &tc.instanceNamespace). - Build() - reconciler := XPostgreSQLReconciler{ - Client: fclient, - } - - // WHEN - result, err := reconciler.Reconcile(context.Background(), tc.req) - - // THEN - if tc.expectedError != nil { - assert.Error(t, tc.expectedError, err) - } else { - assert.NoError(t, err) - } - assert.Equal(t, tc.expectedResult, result) - - // Assert that the composite finalizers are as expected - resultComposite := &v1.XVSHNPostgreSQL{} - getObjectToAssert(t, resultComposite, fclient, client.ObjectKeyFromObject(&tc.inst)) - - // Assert that the namespace also has the finalizers - resultNs := &corev1.Namespace{} - if tc.expectInstanceNamespace { - getObjectToAssert(t, resultNs, fclient, client.ObjectKeyFromObject(&tc.instanceNamespace)) - } else { - assert.Error(t, fclient.Get(context.TODO(), client.ObjectKeyFromObject(&tc.instanceNamespace), resultNs)) - } - - assert.NotContains(t, resultComposite.GetFinalizers(), finalizerName) - }) - } -} - -func getObjectToAssert(t assert.TestingT, obj client.Object, fclient client.Client, key client.ObjectKey) { - err := fclient.Get(context.TODO(), key, obj) - assert.NoError(t, err) -} diff --git a/pkg/controller/webhooks/object.go b/pkg/controller/webhooks/object.go new file mode 100644 index 0000000000..603440e018 --- /dev/null +++ b/pkg/controller/webhooks/object.go @@ -0,0 +1,77 @@ +package webhooks + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + xkubev1alpha1 "github.com/vshn/appcat/v4/apis/kubernetes/v1alpha1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +//+kubebuilder:webhook:verbs=delete,path=/validate-kubernetes-crossplane-io-v1alpha1-object,mutating=false,failurePolicy=fail,groups="kubernetes.crossplane.io",resources=objects,versions=v1alpha1,name=objects.vshn.appcat.vshn.io,sideEffects=None,admissionReviewVersions=v1 + +//+kubebuilder:rbac:groups=syn.tools,resources=compositeredisinstances,verbs=get;list;watch;patch;update +//+kubebuilder:rbac:groups=syn.tools,resources=compositeredisinstances/status,verbs=get;list;watch;patch;update +//+kubebuilder:rbac:groups=syn.tools,resources=compositemariadbinstances,verbs=get;list;watch;patch;update +//+kubebuilder:rbac:groups=syn.tools,resources=compositemariadbinstances/status,verbs=get;list;watch;patch;update + +var _ webhook.CustomValidator = &ObjectDeletionProtectionHandler{} + +type ObjectDeletionProtectionHandler struct { + client client.Client + log logr.Logger +} + +// SetupNamespaceDeletionProtectionHandlerWithManager registers the validation webhook with the manager. +func SetupObjectDeletionProtectionHandlerWithManager(mgr ctrl.Manager) error { + + return ctrl.NewWebhookManagedBy(mgr). + For(&xkubev1alpha1.Object{}). + WithValidator(&ObjectDeletionProtectionHandler{ + client: mgr.GetClient(), + log: mgr.GetLogger().WithName("webhook").WithName("object"), + }). + Complete() +} + +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (p *ObjectDeletionProtectionHandler) ValidateCreate(_ context.Context, _ runtime.Object) (admission.Warnings, error) { + // NOOP for now + return nil, nil +} + +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (p *ObjectDeletionProtectionHandler) ValidateUpdate(_ context.Context, _, _ runtime.Object) (admission.Warnings, error) { + // NOOP for now + return nil, nil +} + +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (p *ObjectDeletionProtectionHandler) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + + crossplaneObject, ok := obj.(client.Object) + if !ok { + return nil, fmt.Errorf("object is not valid") + } + + l := p.log.WithValues("object", crossplaneObject.GetName(), "object", crossplaneObject.GetNamespace(), "GVK", crossplaneObject.GetObjectKind().GroupVersionKind().String()) + + compInfo, err := checkManagedObject(ctx, crossplaneObject, p.client, l) + if err != nil { + return nil, err + } + + if compInfo.Exists { + l.Info("Blocking deletion of object", "parent", compInfo.Name) + return nil, fmt.Errorf(protectedMessage, "object", compInfo.Name) + } + + l.Info("Allowing deletion of object", "parent", compInfo.Name) + + return nil, nil +} diff --git a/pkg/controller/webhooks/release.go b/pkg/controller/webhooks/release.go new file mode 100644 index 0000000000..fa9821086e --- /dev/null +++ b/pkg/controller/webhooks/release.go @@ -0,0 +1,77 @@ +package webhooks + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + helmv1beta1 "github.com/vshn/appcat/v4/apis/helm/release/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +//+kubebuilder:webhook:verbs=delete,path=/validate-helm-crossplane-io-v1beta1-release,mutating=false,failurePolicy=fail,groups="helm.crossplane.io",resources=releases,versions=v1beta1,name=releases.vshn.appcat.vshn.io,sideEffects=None,admissionReviewVersions=v1 + +//+kubebuilder:rbac:groups=syn.tools,resources=compositeredisinstances,verbs=get;list;watch;patch;update +//+kubebuilder:rbac:groups=syn.tools,resources=compositeredisinstances/status,verbs=get;list;watch;patch;update +//+kubebuilder:rbac:groups=syn.tools,resources=compositemariadbinstances,verbs=get;list;watch;patch;update +//+kubebuilder:rbac:groups=syn.tools,resources=compositemariadbinstances/status,verbs=get;list;watch;patch;update + +var _ webhook.CustomValidator = &ReleaseDeletionProtectionHandler{} + +type ReleaseDeletionProtectionHandler struct { + client client.Client + log logr.Logger +} + +// SetupNamespaceDeletionProtectionHandlerWithManager registers the validation webhook with the manager. +func SetupReleaseDeletionProtectionHandlerWithManager(mgr ctrl.Manager) error { + + return ctrl.NewWebhookManagedBy(mgr). + For(&helmv1beta1.Release{}). + WithValidator(&ReleaseDeletionProtectionHandler{ + client: mgr.GetClient(), + log: mgr.GetLogger().WithName("webhook").WithName("release"), + }). + Complete() +} + +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (p *ReleaseDeletionProtectionHandler) ValidateCreate(_ context.Context, _ runtime.Object) (admission.Warnings, error) { + // NOOP for now + return nil, nil +} + +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (p *ReleaseDeletionProtectionHandler) ValidateUpdate(_ context.Context, _, _ runtime.Object) (admission.Warnings, error) { + // NOOP for now + return nil, nil +} + +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (p *ReleaseDeletionProtectionHandler) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + + release, ok := obj.(client.Object) + if !ok { + return nil, fmt.Errorf("object is not valid") + } + + l := p.log.WithValues("object", release.GetName(), "release", release.GetNamespace(), "GVK", release.GetObjectKind().GroupVersionKind().String()) + + compInfo, err := checkManagedObject(ctx, release, p.client, l) + if err != nil { + return nil, err + } + + if compInfo.Exists { + l.Info("Blocking deletion of release", "parent", compInfo.Name) + return nil, fmt.Errorf(protectedMessage, "release", compInfo.Name) + } + + l.Info("Allowing deletion of release", "parent", compInfo.Name) + + return nil, nil +}