From 703c0c2f7bc3e41f1e55a70a30688175756f45f6 Mon Sep 17 00:00:00 2001 From: Alper Rifat Ulucinar Date: Wed, 25 Aug 2021 14:35:56 +0300 Subject: [PATCH 1/3] Add PostgreSQLServerConfiguration managed resource - Support for configuring PostgreSQLServer parameters - Fixes #281 Signed-off-by: Alper Rifat Ulucinar --- apis/database/v1beta1/configuration_types.go | 140 +++++++++++ apis/database/v1beta1/referencers.go | 35 +++ apis/database/v1beta1/register.go | 9 + .../database/v1beta1/zz_generated.deepcopy.go | 149 +++++++++++ apis/database/v1beta1/zz_generated.managed.go | 56 +++++ .../v1beta1/zz_generated.managedlist.go | 9 + .../postgresqlserverconfiguration.yaml | 14 ++ ...ane.io_postgresqlserverconfigurations.yaml | 236 ++++++++++++++++++ .../database/configuration/postgresql.go | 125 ++++++++++ pkg/controller/azure.go | 2 + .../postgresqlserverconfiguration/managed.go | 216 ++++++++++++++++ 11 files changed, 991 insertions(+) create mode 100644 apis/database/v1beta1/configuration_types.go create mode 100644 examples/database/postgresqlserverconfiguration.yaml create mode 100644 package/crds/database.azure.crossplane.io_postgresqlserverconfigurations.yaml create mode 100644 pkg/clients/database/configuration/postgresql.go create mode 100644 pkg/controller/database/postgresqlserverconfiguration/managed.go diff --git a/apis/database/v1beta1/configuration_types.go b/apis/database/v1beta1/configuration_types.go new file mode 100644 index 00000000..d05b37e5 --- /dev/null +++ b/apis/database/v1beta1/configuration_types.go @@ -0,0 +1,140 @@ +/* +Copyright 2021 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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + + apisv1alpha3 "github.com/crossplane/provider-azure/apis/v1alpha3" +) + +// +kubebuilder:object:root=true + +// A PostgreSQLServerConfiguration is a managed resource that represents an Azure +// PostgreSQL Server Configuration. +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="VERSION",type="string",JSONPath=".spec.forProvider.version" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,azure} +type PostgreSQLServerConfiguration struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SQLServerConfigurationSpec `json:"spec"` + Status SQLServerConfigurationStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// PostgreSQLServerConfigurationList contains a list of PostgreSQLServerConfiguration. +type PostgreSQLServerConfigurationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []PostgreSQLServerConfiguration `json:"items"` +} + +// SQLServerConfigurationParameters define the desired state of an Azure SQL +// Database Server Configuration, either PostgreSQL or MySQL Configuration. +type SQLServerConfigurationParameters struct { + // ResourceGroupName specifies the name of the resource group that should + // contain this SQLServer. + // +immutable + ResourceGroupName string `json:"resourceGroupName,omitempty"` + + // ResourceGroupNameRef - A reference to a ResourceGroup object to retrieve + // its name + // +immutable + ResourceGroupNameRef *xpv1.Reference `json:"resourceGroupNameRef,omitempty"` + + // ResourceGroupNameSelector - A selector for a ResourceGroup object to + // retrieve its name + // +immutable + ResourceGroupNameSelector *xpv1.Selector `json:"resourceGroupNameSelector,omitempty"` + + // ServerName specifies the name of the server that this + // configuration applies to. + // +immutable + ServerName string `json:"serverName,omitempty"` + + // ServerNameRef - A reference to a PostgreSQLServer object to retrieve + // its name + // +immutable + ServerNameRef *xpv1.Reference `json:"serverNameRef,omitempty"` + + // ServerNameSelector - A selector for a PostgreSQLServer object to + // retrieve its name + // +immutable + ServerNameSelector *xpv1.Selector `json:"serverNameSelector,omitempty"` + + // Name - Configuration name to be applied + // +kubebuilder:validation:Required + // +immutable + Name string `json:"name,omitempty"` + + // Value - Configuration value to be applied + // +kubebuilder:validation:Optional + Value *string `json:"value,omitempty"` +} + +// A SQLServerConfigurationSpec defines the desired state of a SQLServer +// Configuration. +type SQLServerConfigurationSpec struct { + xpv1.ResourceSpec `json:",inline"` + ForProvider SQLServerConfigurationParameters `json:"forProvider"` +} + +// SQLServerConfigurationObservation represents the current state of Azure SQL resource. +type SQLServerConfigurationObservation struct { + // ID - Resource ID + ID string `json:"id,omitempty"` + + // Name - Resource name. + Name string `json:"name,omitempty"` + + // Type - Resource type. + Type string `json:"type,omitempty"` + + // DataType - Data type for the configuration + DataType string `json:"dataType,omitempty"` + + // Value - Applied configuration value + Value string `json:"value,omitempty"` + + // DefaultValue - Default value for this configuration + DefaultValue string `json:"defaultValue,omitempty"` + + // Source - Applied configuration source + Source string `json:"source,omitempty"` + + // Description - Description for the configuration + Description string `json:"description,omitempty"` + + // LastOperation represents the state of the last operation started by the + // controller. + LastOperation apisv1alpha3.AsyncOperation `json:"lastOperation,omitempty"` +} + +// A SQLServerConfigurationStatus represents the observed state of a +// SQLServerConfiguration. +type SQLServerConfigurationStatus struct { + xpv1.ResourceStatus `json:",inline"` + AtProvider SQLServerConfigurationObservation `json:"atProvider,omitempty"` +} diff --git a/apis/database/v1beta1/referencers.go b/apis/database/v1beta1/referencers.go index 5846f843..ebc261d2 100644 --- a/apis/database/v1beta1/referencers.go +++ b/apis/database/v1beta1/referencers.go @@ -68,3 +68,38 @@ func (mg *PostgreSQLServer) ResolveReferences(ctx context.Context, c client.Read return nil } + +// ResolveReferences of this PostgreSQLServerConfiguration. +func (mg *PostgreSQLServerConfiguration) ResolveReferences(ctx context.Context, c client.Reader) error { + r := reference.NewAPIResolver(c, mg) + + // Resolve spec.forProvider.resourceGroupName + rsp, err := r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: mg.Spec.ForProvider.ResourceGroupName, + Reference: mg.Spec.ForProvider.ResourceGroupNameRef, + Selector: mg.Spec.ForProvider.ResourceGroupNameSelector, + To: reference.To{Managed: &v1alpha3.ResourceGroup{}, List: &v1alpha3.ResourceGroupList{}}, + Extract: reference.ExternalName(), + }) + if err != nil { + return errors.Wrap(err, "spec.forProvider.resourceGroupName") + } + mg.Spec.ForProvider.ResourceGroupName = rsp.ResolvedValue + mg.Spec.ForProvider.ResourceGroupNameRef = rsp.ResolvedReference + + // Resolve spec.forProvider.resourceGroupName + rsp, err = r.Resolve(ctx, reference.ResolutionRequest{ + CurrentValue: mg.Spec.ForProvider.ServerName, + Reference: mg.Spec.ForProvider.ServerNameRef, + Selector: mg.Spec.ForProvider.ServerNameSelector, + To: reference.To{Managed: &PostgreSQLServer{}, List: &PostgreSQLServerList{}}, + Extract: reference.ExternalName(), + }) + if err != nil { + return errors.Wrap(err, "spec.forProvider.serverName") + } + mg.Spec.ForProvider.ServerName = rsp.ResolvedValue + mg.Spec.ForProvider.ServerNameRef = rsp.ResolvedReference + + return nil +} diff --git a/apis/database/v1beta1/register.go b/apis/database/v1beta1/register.go index 640b4296..9ff2c848 100644 --- a/apis/database/v1beta1/register.go +++ b/apis/database/v1beta1/register.go @@ -53,7 +53,16 @@ var ( PostgreSQLServerGroupVersionKind = SchemeGroupVersion.WithKind(PostgreSQLServerKind) ) +// PostgreSQLServerConfiguration type metadata. +var ( + PostgreSQLServerConfigurationKind = reflect.TypeOf(PostgreSQLServerConfiguration{}).Name() + PostgreSQLServerConfigurationGroupKind = schema.GroupKind{Group: Group, Kind: PostgreSQLServerConfigurationKind}.String() + PostgreSQLServerConfigurationKindAPIVersion = PostgreSQLServerConfigurationKind + "." + SchemeGroupVersion.String() + PostgreSQLServerConfigurationGroupVersionKind = SchemeGroupVersion.WithKind(PostgreSQLServerConfigurationKind) +) + func init() { SchemeBuilder.Register(&MySQLServer{}, &MySQLServerList{}) SchemeBuilder.Register(&PostgreSQLServer{}, &PostgreSQLServerList{}) + SchemeBuilder.Register(&PostgreSQLServerConfiguration{}, &PostgreSQLServerConfigurationList{}) } diff --git a/apis/database/v1beta1/zz_generated.deepcopy.go b/apis/database/v1beta1/zz_generated.deepcopy.go index ebf0290d..2651cdcc 100644 --- a/apis/database/v1beta1/zz_generated.deepcopy.go +++ b/apis/database/v1beta1/zz_generated.deepcopy.go @@ -111,6 +111,65 @@ func (in *PostgreSQLServer) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PostgreSQLServerConfiguration) DeepCopyInto(out *PostgreSQLServerConfiguration) { + *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 PostgreSQLServerConfiguration. +func (in *PostgreSQLServerConfiguration) DeepCopy() *PostgreSQLServerConfiguration { + if in == nil { + return nil + } + out := new(PostgreSQLServerConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PostgreSQLServerConfiguration) 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 *PostgreSQLServerConfigurationList) DeepCopyInto(out *PostgreSQLServerConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PostgreSQLServerConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgreSQLServerConfigurationList. +func (in *PostgreSQLServerConfigurationList) DeepCopy() *PostgreSQLServerConfigurationList { + if in == nil { + return nil + } + out := new(PostgreSQLServerConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PostgreSQLServerConfigurationList) 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 *PostgreSQLServerList) DeepCopyInto(out *PostgreSQLServerList) { *out = *in @@ -163,6 +222,96 @@ func (in *SKU) DeepCopy() *SKU { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SQLServerConfigurationObservation) DeepCopyInto(out *SQLServerConfigurationObservation) { + *out = *in + out.LastOperation = in.LastOperation +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQLServerConfigurationObservation. +func (in *SQLServerConfigurationObservation) DeepCopy() *SQLServerConfigurationObservation { + if in == nil { + return nil + } + out := new(SQLServerConfigurationObservation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SQLServerConfigurationParameters) DeepCopyInto(out *SQLServerConfigurationParameters) { + *out = *in + if in.ResourceGroupNameRef != nil { + in, out := &in.ResourceGroupNameRef, &out.ResourceGroupNameRef + *out = new(v1.Reference) + **out = **in + } + if in.ResourceGroupNameSelector != nil { + in, out := &in.ResourceGroupNameSelector, &out.ResourceGroupNameSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } + if in.ServerNameRef != nil { + in, out := &in.ServerNameRef, &out.ServerNameRef + *out = new(v1.Reference) + **out = **in + } + if in.ServerNameSelector != nil { + in, out := &in.ServerNameSelector, &out.ServerNameSelector + *out = new(v1.Selector) + (*in).DeepCopyInto(*out) + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQLServerConfigurationParameters. +func (in *SQLServerConfigurationParameters) DeepCopy() *SQLServerConfigurationParameters { + if in == nil { + return nil + } + out := new(SQLServerConfigurationParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SQLServerConfigurationSpec) DeepCopyInto(out *SQLServerConfigurationSpec) { + *out = *in + in.ResourceSpec.DeepCopyInto(&out.ResourceSpec) + in.ForProvider.DeepCopyInto(&out.ForProvider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQLServerConfigurationSpec. +func (in *SQLServerConfigurationSpec) DeepCopy() *SQLServerConfigurationSpec { + if in == nil { + return nil + } + out := new(SQLServerConfigurationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SQLServerConfigurationStatus) DeepCopyInto(out *SQLServerConfigurationStatus) { + *out = *in + in.ResourceStatus.DeepCopyInto(&out.ResourceStatus) + out.AtProvider = in.AtProvider +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQLServerConfigurationStatus. +func (in *SQLServerConfigurationStatus) DeepCopy() *SQLServerConfigurationStatus { + if in == nil { + return nil + } + out := new(SQLServerConfigurationStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SQLServerObservation) DeepCopyInto(out *SQLServerObservation) { *out = *in diff --git a/apis/database/v1beta1/zz_generated.managed.go b/apis/database/v1beta1/zz_generated.managed.go index dc97c618..7b873d27 100644 --- a/apis/database/v1beta1/zz_generated.managed.go +++ b/apis/database/v1beta1/zz_generated.managed.go @@ -131,3 +131,59 @@ func (mg *PostgreSQLServer) SetProviderReference(r *xpv1.Reference) { func (mg *PostgreSQLServer) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { mg.Spec.WriteConnectionSecretToReference = r } + +// GetCondition of this PostgreSQLServerConfiguration. +func (mg *PostgreSQLServerConfiguration) GetCondition(ct xpv1.ConditionType) xpv1.Condition { + return mg.Status.GetCondition(ct) +} + +// GetDeletionPolicy of this PostgreSQLServerConfiguration. +func (mg *PostgreSQLServerConfiguration) GetDeletionPolicy() xpv1.DeletionPolicy { + return mg.Spec.DeletionPolicy +} + +// GetProviderConfigReference of this PostgreSQLServerConfiguration. +func (mg *PostgreSQLServerConfiguration) GetProviderConfigReference() *xpv1.Reference { + return mg.Spec.ProviderConfigReference +} + +/* +GetProviderReference of this PostgreSQLServerConfiguration. +Deprecated: Use GetProviderConfigReference. +*/ +func (mg *PostgreSQLServerConfiguration) GetProviderReference() *xpv1.Reference { + return mg.Spec.ProviderReference +} + +// GetWriteConnectionSecretToReference of this PostgreSQLServerConfiguration. +func (mg *PostgreSQLServerConfiguration) GetWriteConnectionSecretToReference() *xpv1.SecretReference { + return mg.Spec.WriteConnectionSecretToReference +} + +// SetConditions of this PostgreSQLServerConfiguration. +func (mg *PostgreSQLServerConfiguration) SetConditions(c ...xpv1.Condition) { + mg.Status.SetConditions(c...) +} + +// SetDeletionPolicy of this PostgreSQLServerConfiguration. +func (mg *PostgreSQLServerConfiguration) SetDeletionPolicy(r xpv1.DeletionPolicy) { + mg.Spec.DeletionPolicy = r +} + +// SetProviderConfigReference of this PostgreSQLServerConfiguration. +func (mg *PostgreSQLServerConfiguration) SetProviderConfigReference(r *xpv1.Reference) { + mg.Spec.ProviderConfigReference = r +} + +/* +SetProviderReference of this PostgreSQLServerConfiguration. +Deprecated: Use SetProviderConfigReference. +*/ +func (mg *PostgreSQLServerConfiguration) SetProviderReference(r *xpv1.Reference) { + mg.Spec.ProviderReference = r +} + +// SetWriteConnectionSecretToReference of this PostgreSQLServerConfiguration. +func (mg *PostgreSQLServerConfiguration) SetWriteConnectionSecretToReference(r *xpv1.SecretReference) { + mg.Spec.WriteConnectionSecretToReference = r +} diff --git a/apis/database/v1beta1/zz_generated.managedlist.go b/apis/database/v1beta1/zz_generated.managedlist.go index 11cfdb73..d4a29c97 100644 --- a/apis/database/v1beta1/zz_generated.managedlist.go +++ b/apis/database/v1beta1/zz_generated.managedlist.go @@ -29,6 +29,15 @@ func (l *MySQLServerList) GetItems() []resource.Managed { return items } +// GetItems of this PostgreSQLServerConfigurationList. +func (l *PostgreSQLServerConfigurationList) 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 PostgreSQLServerList. func (l *PostgreSQLServerList) GetItems() []resource.Managed { items := make([]resource.Managed, len(l.Items)) diff --git a/examples/database/postgresqlserverconfiguration.yaml b/examples/database/postgresqlserverconfiguration.yaml new file mode 100644 index 00000000..42505fc7 --- /dev/null +++ b/examples/database/postgresqlserverconfiguration.yaml @@ -0,0 +1,14 @@ +apiVersion: database.azure.crossplane.io/v1beta1 +kind: PostgreSQLServerConfiguration +metadata: + name: example-psql-configuration +spec: + providerConfigRef: + name: example + forProvider: + resourceGroupNameRef: + name: example-rg + serverNameRef: + name: example-psql + name: max_wal_senders + value: "12" diff --git a/package/crds/database.azure.crossplane.io_postgresqlserverconfigurations.yaml b/package/crds/database.azure.crossplane.io_postgresqlserverconfigurations.yaml new file mode 100644 index 00000000..60193a73 --- /dev/null +++ b/package/crds/database.azure.crossplane.io_postgresqlserverconfigurations.yaml @@ -0,0 +1,236 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.0 + creationTimestamp: null + name: postgresqlserverconfigurations.database.azure.crossplane.io +spec: + group: database.azure.crossplane.io + names: + categories: + - crossplane + - managed + - azure + kind: PostgreSQLServerConfiguration + listKind: PostgreSQLServerConfigurationList + plural: postgresqlserverconfigurations + singular: postgresqlserverconfiguration + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + - jsonPath: .status.conditions[?(@.type=='Synced')].status + name: SYNCED + type: string + - jsonPath: .spec.forProvider.version + name: VERSION + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: A PostgreSQLServerConfiguration is a managed resource that represents an Azure PostgreSQL Server Configuration. + 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: A SQLServerConfigurationSpec defines the desired state of a SQLServer Configuration. + properties: + deletionPolicy: + description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + enum: + - Orphan + - Delete + type: string + forProvider: + description: SQLServerConfigurationParameters define the desired state of an Azure SQL Database Server Configuration, either PostgreSQL or MySQL Configuration. + properties: + name: + description: Name - Configuration name to be applied + type: string + resourceGroupName: + description: ResourceGroupName specifies the name of the resource group that should contain this SQLServer. + type: string + resourceGroupNameRef: + description: ResourceGroupNameRef - A reference to a ResourceGroup object to retrieve its name + properties: + name: + description: Name of the referenced object. + type: string + required: + - name + type: object + resourceGroupNameSelector: + description: ResourceGroupNameSelector - A selector for a ResourceGroup object to retrieve its name + 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 + type: object + serverName: + description: ServerName specifies the name of the server that this configuration applies to. + type: string + serverNameRef: + description: ServerNameRef - A reference to a PostgreSQLServer object to retrieve its name + properties: + name: + description: Name of the referenced object. + type: string + required: + - name + type: object + serverNameSelector: + description: ServerNameSelector - A selector for a PostgreSQLServer object to retrieve its name + 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 + type: object + value: + description: Value - Configuration value to be applied + type: string + type: object + providerConfigRef: + 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 + required: + - name + type: object + providerRef: + description: 'ProviderReference specifies the provider that will be used to create, observe, update, and delete this managed resource. Deprecated: Please use ProviderConfigReference, i.e. `providerConfigRef`' + properties: + name: + description: Name of the referenced object. + type: string + 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: A SQLServerConfigurationStatus represents the observed state of a SQLServerConfiguration. + properties: + atProvider: + description: SQLServerConfigurationObservation represents the current state of Azure SQL resource. + properties: + dataType: + description: DataType - Data type for the configuration + type: string + defaultValue: + description: DefaultValue - Default value for this configuration + type: string + description: + description: Description - Description for the configuration + type: string + id: + description: ID - Resource ID + type: string + lastOperation: + description: LastOperation represents the state of the last operation started by the controller. + properties: + errorMessage: + description: ErrorMessage represents the error that occurred during the operation. + type: string + method: + description: Method is HTTP method that the initial request is made with. + type: string + pollingUrl: + description: PollingURL is used to fetch the status of the given operation. + type: string + status: + description: Status represents the status of the operation. + type: string + type: object + name: + description: Name - Resource name. + type: string + source: + description: Source - Applied configuration source + type: string + type: + description: Type - Resource type. + type: string + value: + description: Value - Applied configuration value + type: string + 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 + 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 + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/pkg/clients/database/configuration/postgresql.go b/pkg/clients/database/configuration/postgresql.go new file mode 100644 index 00000000..d0f25670 --- /dev/null +++ b/pkg/clients/database/configuration/postgresql.go @@ -0,0 +1,125 @@ +/* +Copyright 2021 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 configuration + +import ( + "context" + "net/http" + + "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" + "github.com/Azure/go-autorest/autorest" + + azuredbv1beta1 "github.com/crossplane/provider-azure/apis/database/v1beta1" + "github.com/crossplane/provider-azure/apis/v1alpha3" + azure "github.com/crossplane/provider-azure/pkg/clients" +) + +const ( + // SourceSystemManaged represents the source for system-managed configuration values + SourceSystemManaged = "system-default" +) + +// NOTE: postgresql and mysql structs and functions live in their respective +// packages even though they are exactly the same. However, Crossplane does not +// make that assumption and use the respective package for each type, although, +// they both share the same SQLServerParameters and SQLServerObservation objects. +// https://github.com/Azure/azure-sdk-for-go/blob/master/services/mysql/mgmt/2017-12-01/mysql/models.go +// https://github.com/Azure/azure-sdk-for-go/blob/master/services/postgresql/mgmt/2017-12-01/postgresql/models.go + +// PostgreSQLConfigurationAPI represents the API interface for a PostgreSQL Server Configuration client +type PostgreSQLConfigurationAPI interface { + Get(ctx context.Context, s *azuredbv1beta1.PostgreSQLServerConfiguration) (postgresql.Configuration, error) + CreateOrUpdate(ctx context.Context, s *azuredbv1beta1.PostgreSQLServerConfiguration) error + Delete(ctx context.Context, s *azuredbv1beta1.PostgreSQLServerConfiguration) error + GetRESTClient() autorest.Sender +} + +// PostgreSQLConfigurationClient is the concreate implementation of the PostgreSQLConfigurationAPI interface for PostgreSQL that calls Azure API. +type PostgreSQLConfigurationClient struct { + postgresql.ConfigurationsClient +} + +// NewPostgreSQLConfigurationClient creates and initializes a PostgreSQLConfigurationClient instance. +func NewPostgreSQLConfigurationClient(cl postgresql.ConfigurationsClient) *PostgreSQLConfigurationClient { + return &PostgreSQLConfigurationClient{ + ConfigurationsClient: cl, + } +} + +// GetRESTClient returns the underlying REST client that the client object uses. +func (c *PostgreSQLConfigurationClient) GetRESTClient() autorest.Sender { + return c.ConfigurationsClient.Client +} + +// Get retrieves the requested PostgreSQL Configuration +func (c *PostgreSQLConfigurationClient) Get(ctx context.Context, cr *azuredbv1beta1.PostgreSQLServerConfiguration) (postgresql.Configuration, error) { + return c.ConfigurationsClient.Get(ctx, cr.Spec.ForProvider.ResourceGroupName, cr.Spec.ForProvider.ServerName, cr.Spec.ForProvider.Name) +} + +// CreateOrUpdate creates or updates a PostgreSQL Server Configuration +func (c *PostgreSQLConfigurationClient) CreateOrUpdate(ctx context.Context, cr *azuredbv1beta1.PostgreSQLServerConfiguration) error { + return c.update(ctx, cr, cr.Spec.ForProvider.Value, nil) +} + +// Delete deletes the given PostgreSQL Server Configuration +func (c *PostgreSQLConfigurationClient) Delete(ctx context.Context, cr *azuredbv1beta1.PostgreSQLServerConfiguration) error { + source := SourceSystemManaged + return c.update(ctx, cr, &cr.Status.AtProvider.DefaultValue, &source) +} + +func (c *PostgreSQLConfigurationClient) update(ctx context.Context, cr *azuredbv1beta1.PostgreSQLServerConfiguration, value, source *string) error { + s := cr.Spec.ForProvider + config := postgresql.Configuration{ + ConfigurationProperties: &postgresql.ConfigurationProperties{ + Value: value, + Source: source, + }, + } + op, err := c.ConfigurationsClient.CreateOrUpdate(ctx, s.ResourceGroupName, s.ServerName, s.Name, config) + if err != nil { + return err + } + cr.Status.AtProvider.LastOperation = v1alpha3.AsyncOperation{ + PollingURL: op.PollingURL(), + Method: http.MethodPut, + } + return nil +} + +// UpdatePostgreSQLConfigurationObservation produces SQLServerConfigurationObservation from postgresql.Configuration. +func UpdatePostgreSQLConfigurationObservation(o *azuredbv1beta1.SQLServerConfigurationObservation, in postgresql.Configuration) { + o.ID = azure.ToString(in.ID) + o.Name = azure.ToString(in.Name) + o.Type = azure.ToString(in.Type) + o.DataType = azure.ToString(in.DataType) + o.Value = azure.ToString(in.Value) + o.DefaultValue = azure.ToString(in.DefaultValue) + o.Source = azure.ToString(in.Source) + o.Description = azure.ToString(in.Description) +} + +// LateInitializePostgreSQLConfiguration fills the empty values of SQLServerConfigurationParameters with the +// ones that are retrieved from the Azure API. +func LateInitializePostgreSQLConfiguration(p *azuredbv1beta1.SQLServerConfigurationParameters, in postgresql.Configuration) { + p.Value = azure.LateInitializeStringPtrFromPtr(p.Value, in.Value) +} + +// IsPostgreSQLConfigurationUpToDate is used to report whether given postgresql.Configuration is in +// sync with the SQLServerConfigurationParameters that user desires. +func IsPostgreSQLConfigurationUpToDate(p azuredbv1beta1.SQLServerConfigurationParameters, in postgresql.Configuration) bool { + return azure.ToString(p.Value) == azure.ToString(in.Value) +} diff --git a/pkg/controller/azure.go b/pkg/controller/azure.go index 6c05d5b0..9386fc82 100644 --- a/pkg/controller/azure.go +++ b/pkg/controller/azure.go @@ -32,6 +32,7 @@ import ( "github.com/crossplane/provider-azure/pkg/controller/database/mysqlserverfirewallrule" "github.com/crossplane/provider-azure/pkg/controller/database/mysqlservervirtualnetworkrule" "github.com/crossplane/provider-azure/pkg/controller/database/postgresqlserver" + "github.com/crossplane/provider-azure/pkg/controller/database/postgresqlserverconfiguration" "github.com/crossplane/provider-azure/pkg/controller/database/postgresqlserverfirewallrule" "github.com/crossplane/provider-azure/pkg/controller/database/postgresqlservervirtualnetworkrule" "github.com/crossplane/provider-azure/pkg/controller/network/subnet" @@ -52,6 +53,7 @@ func Setup(mgr ctrl.Manager, l logging.Logger, rl workqueue.RateLimiter, poll ti postgresqlserver.Setup, postgresqlserverfirewallrule.Setup, postgresqlservervirtualnetworkrule.Setup, + postgresqlserverconfiguration.Setup, cosmosdb.Setup, virtualnetwork.Setup, subnet.Setup, diff --git a/pkg/controller/database/postgresqlserverconfiguration/managed.go b/pkg/controller/database/postgresqlserverconfiguration/managed.go new file mode 100644 index 00000000..4444bcb1 --- /dev/null +++ b/pkg/controller/database/postgresqlserverconfiguration/managed.go @@ -0,0 +1,216 @@ +/* +Copyright 2021 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 postgresqlserverconfiguration + +import ( + "context" + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/pkg/meta" + "github.com/pkg/errors" + "k8s.io/client-go/util/workqueue" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + + "github.com/crossplane/crossplane-runtime/pkg/event" + "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/crossplane-runtime/pkg/ratelimiter" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/resource" + + "github.com/crossplane/provider-azure/apis/database/v1beta1" + azure "github.com/crossplane/provider-azure/pkg/clients" + "github.com/crossplane/provider-azure/pkg/clients/database/configuration" +) + +const ( + // error messages + errUpdateCR = "cannot update PostgreSQLServerConfiguration custom resource" + errNotPostgreSQLServerConfig = "managed resource is not a PostgreSQLServerConfiguration" + errCreatePostgreSQLServerConfig = "cannot create PostgreSQLServerConfiguration" + errUpdatePostgreSQLServerConfig = "cannot update PostgreSQLServerConfiguration" + errGetPostgreSQLServerConfig = "cannot get PostgreSQLServerConfiguration" + errDeletePostgreSQLServerConfig = "cannot delete PostgreSQLServerConfiguration" + errFetchLastOperation = "cannot fetch last operation" + + fmtExternalName = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.DBforPostgreSQL/servers/%s/configurations/%s" +) + +// Setup adds a controller that reconciles PostgreSQLInstances. +func Setup(mgr ctrl.Manager, l logging.Logger, rl workqueue.RateLimiter, poll time.Duration) error { + name := managed.ControllerName(v1beta1.PostgreSQLServerConfigurationGroupKind) + + return ctrl.NewControllerManagedBy(mgr). + Named(name). + WithOptions(controller.Options{ + RateLimiter: ratelimiter.NewDefaultManagedRateLimiter(rl), + }). + For(&v1beta1.PostgreSQLServerConfiguration{}). + Complete(managed.NewReconciler(mgr, + resource.ManagedKind(v1beta1.PostgreSQLServerConfigurationGroupVersionKind), + managed.WithExternalConnecter(&connecter{client: mgr.GetClient()}), + managed.WithReferenceResolver(managed.NewAPISimpleReferenceResolver(mgr.GetClient())), + managed.WithInitializers(managed.NewDefaultProviderConfig(mgr.GetClient())), + managed.WithPollInterval(poll), + managed.WithLogger(l.WithValues("controller", name)), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))))) +} + +type connecter struct { + client client.Client +} + +func (c *connecter) Connect(ctx context.Context, mg resource.Managed) (managed.ExternalClient, error) { + creds, auth, err := azure.GetAuthInfo(ctx, c.client, mg) + if err != nil { + return nil, err + } + cl := postgresql.NewConfigurationsClient(creds[azure.CredentialsKeySubscriptionID]) + cl.Authorizer = auth + return &external{ + kube: c.client, + client: configuration.NewPostgreSQLConfigurationClient(cl), + subscriptionID: creds[azure.CredentialsKeySubscriptionID], + }, nil +} + +type external struct { + kube client.Client + client configuration.PostgreSQLConfigurationAPI + subscriptionID string +} + +func (e external) getExternalName(resourceGroupName, serverName, configName string) string { + return fmt.Sprintf(fmtExternalName, e.subscriptionID, resourceGroupName, serverName, configName) +} + +func (e *external) Observe(ctx context.Context, mg resource.Managed) (managed.ExternalObservation, error) { // nolint:gocyclo + // cyclomatic complexity of this method (13) is slightly higher than our goal of 10. + cr, ok := mg.(*v1beta1.PostgreSQLServerConfiguration) + if !ok { + return managed.ExternalObservation{}, errors.New(errNotPostgreSQLServerConfig) + } + config, err := e.client.Get(ctx, cr) + if azure.IsNotFound(err) { + if err := azure.FetchAsyncOperation(ctx, e.client.GetRESTClient(), &cr.Status.AtProvider.LastOperation); err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errFetchLastOperation) + } + // Azure returns NotFound for GET calls until creation is completed + // successfully and we cannot return `ResourceExists: false` during creation + // since this will cause `Create` to be called again and it's not idempotent. + // So, we check whether a creation operation in fact is in motion. + creating := cr.Status.AtProvider.LastOperation.Method == "PUT" && + cr.Status.AtProvider.LastOperation.Status == azure.AsyncOperationStatusInProgress + return managed.ExternalObservation{ResourceExists: creating}, nil + } + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errGetPostgreSQLServerConfig) + } + // ARM does not return a 404 for the configuration resource even if we set its value to the server default + // and source to "system-default". Hence, we check those conditions here: + if meta.WasDeleted(cr) && cr.Status.AtProvider.Source == configuration.SourceSystemManaged && cr.Status.AtProvider.Value == cr.Status.AtProvider.DefaultValue { + return managed.ExternalObservation{ + ResourceExists: false, + }, nil + } + // it's possible that external.Create has never been called, thus set ext. name if not set + if meta.GetExternalName(cr) == "" { + meta.SetExternalName(cr, e.getExternalName(cr.Spec.ForProvider.ResourceGroupName, + cr.Spec.ForProvider.ServerName, cr.Spec.ForProvider.Name)) + } + + configuration.LateInitializePostgreSQLConfiguration(&cr.Spec.ForProvider, config) + if err := e.kube.Update(ctx, cr); err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errUpdateCR) + } + configuration.UpdatePostgreSQLConfigurationObservation(&cr.Status.AtProvider, config) + // We make this call after kube.Update since it doesn't update the + // status subresource but fetches the whole object after it's done. So, + // changes to status has to be done after kube.Update in order not to get them + // lost. + if err := azure.FetchAsyncOperation(ctx, e.client.GetRESTClient(), &cr.Status.AtProvider.LastOperation); err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errFetchLastOperation) + } + // if the configuration has been applied successfully, then mark MR as available + if cr.Status.AtProvider.Value == azure.ToString(cr.Spec.ForProvider.Value) { + cr.SetConditions(xpv1.Available()) + } else { + cr.SetConditions(xpv1.Unavailable()) + } + + o := managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: configuration.IsPostgreSQLConfigurationUpToDate(cr.Spec.ForProvider, config), + } + + return o, nil +} + +func (e *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { + cr, ok := mg.(*v1beta1.PostgreSQLServerConfiguration) + if !ok { + return managed.ExternalCreation{}, errors.New(errNotPostgreSQLServerConfig) + } + + if err := e.client.CreateOrUpdate(ctx, cr); err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, errCreatePostgreSQLServerConfig) + } + // no error if ext name does not match + meta.SetExternalName(cr, e.getExternalName(cr.Spec.ForProvider.ResourceGroupName, + cr.Spec.ForProvider.ServerName, cr.Spec.ForProvider.Name)) + + return managed.ExternalCreation{ + ExternalNameAssigned: true, + }, errors.Wrap( + azure.FetchAsyncOperation(ctx, e.client.GetRESTClient(), &cr.Status.AtProvider.LastOperation), + errFetchLastOperation) +} + +func (e *external) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) { + cr, ok := mg.(*v1beta1.PostgreSQLServerConfiguration) + if !ok { + return managed.ExternalUpdate{}, errors.New(errNotPostgreSQLServerConfig) + } + if cr.Status.AtProvider.LastOperation.Status == azure.AsyncOperationStatusInProgress { + return managed.ExternalUpdate{}, nil + } + if err := e.client.CreateOrUpdate(ctx, cr); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errUpdatePostgreSQLServerConfig) + } + + return managed.ExternalUpdate{}, errors.Wrap( + azure.FetchAsyncOperation(ctx, e.client.GetRESTClient(), &cr.Status.AtProvider.LastOperation), + errFetchLastOperation) +} + +func (e *external) Delete(ctx context.Context, mg resource.Managed) error { + cr, ok := mg.(*v1beta1.PostgreSQLServerConfiguration) + if !ok { + return errors.New(errNotPostgreSQLServerConfig) + } + + if err := e.client.Delete(ctx, cr); resource.Ignore(azure.IsNotFound, err) != nil { + return errors.Wrap(err, errDeletePostgreSQLServerConfig) + } + return errors.Wrap( + azure.FetchAsyncOperation(ctx, e.client.GetRESTClient(), &cr.Status.AtProvider.LastOperation), + errFetchLastOperation) +} From 7e6a8b4a4abae18a3da2c28cf3dfd8f97093a0f3 Mon Sep 17 00:00:00 2001 From: Alper Rifat Ulucinar Date: Fri, 27 Aug 2021 18:47:12 +0300 Subject: [PATCH 2/3] Rename external.getExternalName as external.generateExtName Signed-off-by: Alper Rifat Ulucinar --- apis/database/v1beta1/configuration_types.go | 4 +++- ....azure.crossplane.io_postgresqlserverconfigurations.yaml | 4 +++- pkg/clients/database/configuration/postgresql.go | 6 +++++- .../database/postgresqlserverconfiguration/managed.go | 6 +++--- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apis/database/v1beta1/configuration_types.go b/apis/database/v1beta1/configuration_types.go index d05b37e5..0526eb08 100644 --- a/apis/database/v1beta1/configuration_types.go +++ b/apis/database/v1beta1/configuration_types.go @@ -87,9 +87,11 @@ type SQLServerConfigurationParameters struct { // Name - Configuration name to be applied // +kubebuilder:validation:Required // +immutable - Name string `json:"name,omitempty"` + Name string `json:"name"` // Value - Configuration value to be applied + // Can be left unset to read the current value + // as a result of late-initialization. // +kubebuilder:validation:Optional Value *string `json:"value,omitempty"` } diff --git a/package/crds/database.azure.crossplane.io_postgresqlserverconfigurations.yaml b/package/crds/database.azure.crossplane.io_postgresqlserverconfigurations.yaml index 60193a73..a403951d 100644 --- a/package/crds/database.azure.crossplane.io_postgresqlserverconfigurations.yaml +++ b/package/crds/database.azure.crossplane.io_postgresqlserverconfigurations.yaml @@ -108,8 +108,10 @@ spec: type: object type: object value: - description: Value - Configuration value to be applied + description: Value - Configuration value to be applied Can be left unset to read the current value as a result of late-initialization. type: string + required: + - name type: object providerConfigRef: description: ProviderConfigReference specifies how the provider that will be used to create, observe, update, and delete this managed resource should be configured. diff --git a/pkg/clients/database/configuration/postgresql.go b/pkg/clients/database/configuration/postgresql.go index d0f25670..037b22ba 100644 --- a/pkg/clients/database/configuration/postgresql.go +++ b/pkg/clients/database/configuration/postgresql.go @@ -78,6 +78,10 @@ func (c *PostgreSQLConfigurationClient) CreateOrUpdate(ctx context.Context, cr * // Delete deletes the given PostgreSQL Server Configuration func (c *PostgreSQLConfigurationClient) Delete(ctx context.Context, cr *azuredbv1beta1.PostgreSQLServerConfiguration) error { source := SourceSystemManaged + // we are mimicking Terraform behavior here: when the configuration object + // is deleted, we are resetting its value to the system default, + // and updating its source to "system-default" to declare that + // we are no longer managing it. return c.update(ctx, cr, &cr.Status.AtProvider.DefaultValue, &source) } @@ -89,7 +93,7 @@ func (c *PostgreSQLConfigurationClient) update(ctx context.Context, cr *azuredbv Source: source, }, } - op, err := c.ConfigurationsClient.CreateOrUpdate(ctx, s.ResourceGroupName, s.ServerName, s.Name, config) + op, err := c.ConfigurationsClient.CreateOrUpdate(ctx, s.ResourceGroupName, cr.Spec.ForProvider.ServerName, cr.Spec.ForProvider.Name, config) if err != nil { return err } diff --git a/pkg/controller/database/postgresqlserverconfiguration/managed.go b/pkg/controller/database/postgresqlserverconfiguration/managed.go index 4444bcb1..18d67b86 100644 --- a/pkg/controller/database/postgresqlserverconfiguration/managed.go +++ b/pkg/controller/database/postgresqlserverconfiguration/managed.go @@ -98,7 +98,7 @@ type external struct { subscriptionID string } -func (e external) getExternalName(resourceGroupName, serverName, configName string) string { +func (e external) generateExtName(resourceGroupName, serverName, configName string) string { return fmt.Sprintf(fmtExternalName, e.subscriptionID, resourceGroupName, serverName, configName) } @@ -133,7 +133,7 @@ func (e *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex } // it's possible that external.Create has never been called, thus set ext. name if not set if meta.GetExternalName(cr) == "" { - meta.SetExternalName(cr, e.getExternalName(cr.Spec.ForProvider.ResourceGroupName, + meta.SetExternalName(cr, e.generateExtName(cr.Spec.ForProvider.ResourceGroupName, cr.Spec.ForProvider.ServerName, cr.Spec.ForProvider.Name)) } @@ -174,7 +174,7 @@ func (e *external) Create(ctx context.Context, mg resource.Managed) (managed.Ext return managed.ExternalCreation{}, errors.Wrap(err, errCreatePostgreSQLServerConfig) } // no error if ext name does not match - meta.SetExternalName(cr, e.getExternalName(cr.Spec.ForProvider.ResourceGroupName, + meta.SetExternalName(cr, e.generateExtName(cr.Spec.ForProvider.ResourceGroupName, cr.Spec.ForProvider.ServerName, cr.Spec.ForProvider.Name)) return managed.ExternalCreation{ From 84959cab9b4380f4b690e106957ebc8f7fdc4097 Mon Sep 17 00:00:00 2001 From: Alper Rifat Ulucinar Date: Fri, 27 Aug 2021 21:15:34 +0300 Subject: [PATCH 3/3] Use resource.LateInitializer - Add tests Signed-off-by: Alper Rifat Ulucinar --- go.mod | 2 +- go.sum | 4 +- .../azure.crossplane.io_resourcegroups.yaml | 5 +- .../crds/cache.azure.crossplane.io_redis.yaml | 5 +- ...mpute.azure.crossplane.io_aksclusters.yaml | 5 +- ....azure.crossplane.io_cosmosdbaccounts.yaml | 5 +- ...rossplane.io_mysqlserverfirewallrules.yaml | 5 +- ...base.azure.crossplane.io_mysqlservers.yaml | 5 +- ...ane.io_mysqlservervirtualnetworkrules.yaml | 5 +- ...ane.io_postgresqlserverconfigurations.yaml | 5 +- ...lane.io_postgresqlserverfirewallrules.yaml | 5 +- ...azure.crossplane.io_postgresqlservers.yaml | 5 +- ...o_postgresqlservervirtualnetworkrules.yaml | 5 +- .../network.azure.crossplane.io_subnets.yaml | 5 +- ...k.azure.crossplane.io_virtualnetworks.yaml | 5 +- .../storage.azure.crossplane.io_accounts.yaml | 5 +- ...torage.azure.crossplane.io_containers.yaml | 5 +- .../database/configuration/postgresql.go | 6 - .../database/configuration/postgresql_test.go | 103 +++++ .../postgresqlserverconfiguration/managed.go | 19 +- .../managed_test.go | 368 ++++++++++++++++++ 21 files changed, 542 insertions(+), 35 deletions(-) create mode 100644 pkg/clients/database/configuration/postgresql_test.go create mode 100644 pkg/controller/database/postgresqlserverconfiguration/managed_test.go diff --git a/go.mod b/go.mod index 578201f7..a98d8811 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 github.com/Azure/go-autorest/autorest/to v0.3.0 github.com/Azure/go-autorest/autorest/validation v0.2.0 // indirect - github.com/crossplane/crossplane-runtime v0.13.0 + github.com/crossplane/crossplane-runtime v0.14.0 github.com/crossplane/crossplane-tools v0.0.0-20210320162312-1baca298c527 github.com/google/go-cmp v0.5.2 github.com/google/uuid v1.1.2 diff --git a/go.sum b/go.sum index 691e796c..1e2e2650 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/crossplane/crossplane-runtime v0.13.0 h1:TFeItxtW32/fETB9be0AsEha/ur0bbrtQRocC+Jd6RI= -github.com/crossplane/crossplane-runtime v0.13.0/go.mod h1:Bc54/KBvV9ld/tvervcnhcSzk13FYguTqmYt72Mybps= +github.com/crossplane/crossplane-runtime v0.14.0 h1:alBvQAwg9wJ88wEBnzyzmlN0N/v1W3Jx4OvBX3Fmrkg= +github.com/crossplane/crossplane-runtime v0.14.0/go.mod h1:Bc54/KBvV9ld/tvervcnhcSzk13FYguTqmYt72Mybps= github.com/crossplane/crossplane-tools v0.0.0-20210320162312-1baca298c527 h1:9M6hMLKqjxtL9d9nwfcaAt59Ey0CPfSXQ3iIdYRUNaE= github.com/crossplane/crossplane-tools v0.0.0-20210320162312-1baca298c527/go.mod h1:C735A9X0x0lR8iGVOOxb49Mt70Ua4EM2b7PGaRPBLd4= github.com/dave/jennifer v1.3.0 h1:p3tl41zjjCZTNBytMwrUuiAnherNUZktlhPTKoF/sEk= diff --git a/package/crds/azure.crossplane.io_resourcegroups.yaml b/package/crds/azure.crossplane.io_resourcegroups.yaml index 2f050fe8..805cf5a4 100644 --- a/package/crds/azure.crossplane.io_resourcegroups.yaml +++ b/package/crds/azure.crossplane.io_resourcegroups.yaml @@ -42,7 +42,8 @@ spec: description: A ResourceGroupSpec defines the desired state of a ResourceGroup. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -51,6 +52,8 @@ spec: description: Location of the resource group. See the official list of valid regions - https://azure.microsoft.com/en-us/global-infrastructure/regions/ type: string 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: diff --git a/package/crds/cache.azure.crossplane.io_redis.yaml b/package/crds/cache.azure.crossplane.io_redis.yaml index f20df5e5..dc9a5c2f 100644 --- a/package/crds/cache.azure.crossplane.io_redis.yaml +++ b/package/crds/cache.azure.crossplane.io_redis.yaml @@ -51,7 +51,8 @@ spec: description: A RedisSpec defines the desired state of a Redis. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -152,6 +153,8 @@ spec: - sku type: object 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: diff --git a/package/crds/compute.azure.crossplane.io_aksclusters.yaml b/package/crds/compute.azure.crossplane.io_aksclusters.yaml index ac016db7..7bc7c1ca 100644 --- a/package/crds/compute.azure.crossplane.io_aksclusters.yaml +++ b/package/crds/compute.azure.crossplane.io_aksclusters.yaml @@ -51,7 +51,8 @@ spec: description: An AKSClusterSpec defines the desired state of a AKSCluster. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -74,6 +75,8 @@ spec: description: NodeVMSize is the name of the worker node VM size, e.g., Standard_B2s, Standard_F2s_v2, etc. type: string 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: diff --git a/package/crds/database.azure.crossplane.io_cosmosdbaccounts.yaml b/package/crds/database.azure.crossplane.io_cosmosdbaccounts.yaml index 0e2b1bdd..f4676495 100644 --- a/package/crds/database.azure.crossplane.io_cosmosdbaccounts.yaml +++ b/package/crds/database.azure.crossplane.io_cosmosdbaccounts.yaml @@ -51,7 +51,8 @@ spec: description: A CosmosDBAccountSpec defines the desired state of a CosmosDB Account. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -160,6 +161,8 @@ spec: - properties type: object 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: diff --git a/package/crds/database.azure.crossplane.io_mysqlserverfirewallrules.yaml b/package/crds/database.azure.crossplane.io_mysqlserverfirewallrules.yaml index 59e2c7c4..8d1977dd 100644 --- a/package/crds/database.azure.crossplane.io_mysqlserverfirewallrules.yaml +++ b/package/crds/database.azure.crossplane.io_mysqlserverfirewallrules.yaml @@ -48,7 +48,8 @@ spec: description: A FirewallRuleSpec defines the desired state of an Azure SQL firewall rule. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -121,6 +122,8 @@ spec: - properties type: object 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: diff --git a/package/crds/database.azure.crossplane.io_mysqlservers.yaml b/package/crds/database.azure.crossplane.io_mysqlservers.yaml index 25f5478c..7b93914e 100644 --- a/package/crds/database.azure.crossplane.io_mysqlservers.yaml +++ b/package/crds/database.azure.crossplane.io_mysqlservers.yaml @@ -48,7 +48,8 @@ spec: description: A SQLServerSpec defines the desired state of a SQLServer. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -175,6 +176,8 @@ spec: - version type: object 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: diff --git a/package/crds/database.azure.crossplane.io_mysqlservervirtualnetworkrules.yaml b/package/crds/database.azure.crossplane.io_mysqlservervirtualnetworkrules.yaml index 42815d28..769ce2ad 100644 --- a/package/crds/database.azure.crossplane.io_mysqlservervirtualnetworkrules.yaml +++ b/package/crds/database.azure.crossplane.io_mysqlservervirtualnetworkrules.yaml @@ -48,7 +48,8 @@ spec: description: A MySQLVirtualNetworkRuleSpec defines the desired state of a MySQLVirtualNetworkRule. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -85,6 +86,8 @@ spec: type: object type: object 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: diff --git a/package/crds/database.azure.crossplane.io_postgresqlserverconfigurations.yaml b/package/crds/database.azure.crossplane.io_postgresqlserverconfigurations.yaml index a403951d..e874da46 100644 --- a/package/crds/database.azure.crossplane.io_postgresqlserverconfigurations.yaml +++ b/package/crds/database.azure.crossplane.io_postgresqlserverconfigurations.yaml @@ -48,7 +48,8 @@ spec: description: A SQLServerConfigurationSpec defines the desired state of a SQLServer Configuration. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -114,6 +115,8 @@ spec: - name type: object 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: diff --git a/package/crds/database.azure.crossplane.io_postgresqlserverfirewallrules.yaml b/package/crds/database.azure.crossplane.io_postgresqlserverfirewallrules.yaml index 49c9c242..6ee002b1 100644 --- a/package/crds/database.azure.crossplane.io_postgresqlserverfirewallrules.yaml +++ b/package/crds/database.azure.crossplane.io_postgresqlserverfirewallrules.yaml @@ -48,7 +48,8 @@ spec: description: A FirewallRuleSpec defines the desired state of an Azure SQL firewall rule. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -121,6 +122,8 @@ spec: - properties type: object 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: diff --git a/package/crds/database.azure.crossplane.io_postgresqlservers.yaml b/package/crds/database.azure.crossplane.io_postgresqlservers.yaml index c4b2d418..35507528 100644 --- a/package/crds/database.azure.crossplane.io_postgresqlservers.yaml +++ b/package/crds/database.azure.crossplane.io_postgresqlservers.yaml @@ -48,7 +48,8 @@ spec: description: A SQLServerSpec defines the desired state of a SQLServer. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -175,6 +176,8 @@ spec: - version type: object 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: diff --git a/package/crds/database.azure.crossplane.io_postgresqlservervirtualnetworkrules.yaml b/package/crds/database.azure.crossplane.io_postgresqlservervirtualnetworkrules.yaml index a759a37d..ff9537f7 100644 --- a/package/crds/database.azure.crossplane.io_postgresqlservervirtualnetworkrules.yaml +++ b/package/crds/database.azure.crossplane.io_postgresqlservervirtualnetworkrules.yaml @@ -48,7 +48,8 @@ spec: description: A PostgreSQLVirtualNetworkRuleSpec defines the desired state of a PostgreSQLVirtualNetworkRule. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -85,6 +86,8 @@ spec: type: object type: object 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: diff --git a/package/crds/network.azure.crossplane.io_subnets.yaml b/package/crds/network.azure.crossplane.io_subnets.yaml index 3bc336af..a408c90c 100644 --- a/package/crds/network.azure.crossplane.io_subnets.yaml +++ b/package/crds/network.azure.crossplane.io_subnets.yaml @@ -48,7 +48,8 @@ spec: description: A SubnetSpec defines the desired state of a Subnet. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -81,6 +82,8 @@ spec: - addressPrefix type: object 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: diff --git a/package/crds/network.azure.crossplane.io_virtualnetworks.yaml b/package/crds/network.azure.crossplane.io_virtualnetworks.yaml index ea77e890..ef3dfb15 100644 --- a/package/crds/network.azure.crossplane.io_virtualnetworks.yaml +++ b/package/crds/network.azure.crossplane.io_virtualnetworks.yaml @@ -51,7 +51,8 @@ spec: description: A VirtualNetworkSpec defines the desired state of a VirtualNetwork. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -81,6 +82,8 @@ spec: type: boolean type: object 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: diff --git a/package/crds/storage.azure.crossplane.io_accounts.yaml b/package/crds/storage.azure.crossplane.io_accounts.yaml index bdce57a7..c56bb1e1 100644 --- a/package/crds/storage.azure.crossplane.io_accounts.yaml +++ b/package/crds/storage.azure.crossplane.io_accounts.yaml @@ -45,12 +45,15 @@ spec: description: An AccountSpec defines the desired state of an Account. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete type: string 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: diff --git a/package/crds/storage.azure.crossplane.io_containers.yaml b/package/crds/storage.azure.crossplane.io_containers.yaml index 4a26b866..a6fefeeb 100644 --- a/package/crds/storage.azure.crossplane.io_containers.yaml +++ b/package/crds/storage.azure.crossplane.io_containers.yaml @@ -51,7 +51,8 @@ spec: description: A ContainerSpec defines the desired state of a Container. properties: deletionPolicy: - description: DeletionPolicy specifies what will happen to the underlying external when this managed resource is deleted - either "Delete" or "Orphan" the external resource. The "Delete" policy is the default when no policy is specified. + 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. enum: - Orphan - Delete @@ -62,6 +63,8 @@ spec: description: Metadata for this Container. type: object 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: diff --git a/pkg/clients/database/configuration/postgresql.go b/pkg/clients/database/configuration/postgresql.go index 037b22ba..7c3b1d06 100644 --- a/pkg/clients/database/configuration/postgresql.go +++ b/pkg/clients/database/configuration/postgresql.go @@ -116,12 +116,6 @@ func UpdatePostgreSQLConfigurationObservation(o *azuredbv1beta1.SQLServerConfigu o.Description = azure.ToString(in.Description) } -// LateInitializePostgreSQLConfiguration fills the empty values of SQLServerConfigurationParameters with the -// ones that are retrieved from the Azure API. -func LateInitializePostgreSQLConfiguration(p *azuredbv1beta1.SQLServerConfigurationParameters, in postgresql.Configuration) { - p.Value = azure.LateInitializeStringPtrFromPtr(p.Value, in.Value) -} - // IsPostgreSQLConfigurationUpToDate is used to report whether given postgresql.Configuration is in // sync with the SQLServerConfigurationParameters that user desires. func IsPostgreSQLConfigurationUpToDate(p azuredbv1beta1.SQLServerConfigurationParameters, in postgresql.Configuration) bool { diff --git a/pkg/clients/database/configuration/postgresql_test.go b/pkg/clients/database/configuration/postgresql_test.go new file mode 100644 index 00000000..0b17a8d1 --- /dev/null +++ b/pkg/clients/database/configuration/postgresql_test.go @@ -0,0 +1,103 @@ +/* +Copyright 2021 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 configuration + +import ( + "testing" + + "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" + "github.com/google/go-cmp/cmp" + + "github.com/crossplane/provider-azure/apis/database/v1beta1" + azuredbv1beta1 "github.com/crossplane/provider-azure/apis/database/v1beta1" +) + +const ( + testValue1 = "testValue1" + testValue2 = "testValue2" +) + +type sqlServerConfigurationParametersModifier func(*azuredbv1beta1.SQLServerConfigurationParameters) + +func sqlServerConfigurationParametersWithValue(v *string) sqlServerConfigurationParametersModifier { + return func(cm *azuredbv1beta1.SQLServerConfigurationParameters) { + cm.Value = v + } +} + +func sqlServerConfigurationParameters(sm ...sqlServerConfigurationParametersModifier) *azuredbv1beta1.SQLServerConfigurationParameters { + cm := &azuredbv1beta1.SQLServerConfigurationParameters{} + for _, m := range sm { + m(cm) + } + return cm +} + +type configurationModifier func(configuration *postgresql.Configuration) + +func configurationWithValue(v *string) configurationModifier { + return func(configuration *postgresql.Configuration) { + configuration.Value = v + } +} + +func configuration(cm ...configurationModifier) *postgresql.Configuration { + c := &postgresql.Configuration{ + ConfigurationProperties: &postgresql.ConfigurationProperties{}, + } + for _, m := range cm { + m(c) + } + return c +} + +func TestIsPostgreSQLConfigurationUpToDate(t *testing.T) { + val1, val2 := testValue1, testValue2 + type args struct { + p v1beta1.SQLServerConfigurationParameters + in postgresql.Configuration + } + tests := map[string]struct { + args args + want bool + }{ + "UpToDate": { + args: args{ + p: *sqlServerConfigurationParameters( + sqlServerConfigurationParametersWithValue(&val1)), + in: *configuration(configurationWithValue(&val1)), + }, + want: true, + }, + "NeedsUpdate": { + args: args{ + p: *sqlServerConfigurationParameters( + sqlServerConfigurationParametersWithValue(&val1)), + in: *configuration(configurationWithValue(&val2)), + }, + want: false, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + got := IsPostgreSQLConfigurationUpToDate(tt.args.p, tt.args.in) + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("IsPostgreSQLConfigurationUpToDate(...): -want, +got\n%s", diff) + } + }) + } +} diff --git a/pkg/controller/database/postgresqlserverconfiguration/managed.go b/pkg/controller/database/postgresqlserverconfiguration/managed.go index 18d67b86..a5ec2aa6 100644 --- a/pkg/controller/database/postgresqlserverconfiguration/managed.go +++ b/pkg/controller/database/postgresqlserverconfiguration/managed.go @@ -43,7 +43,6 @@ import ( const ( // error messages - errUpdateCR = "cannot update PostgreSQLServerConfiguration custom resource" errNotPostgreSQLServerConfig = "managed resource is not a PostgreSQLServerConfiguration" errCreatePostgreSQLServerConfig = "cannot create PostgreSQLServerConfiguration" errUpdatePostgreSQLServerConfig = "cannot update PostgreSQLServerConfiguration" @@ -137,10 +136,9 @@ func (e *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex cr.Spec.ForProvider.ServerName, cr.Spec.ForProvider.Name)) } - configuration.LateInitializePostgreSQLConfiguration(&cr.Spec.ForProvider, config) - if err := e.kube.Update(ctx, cr); err != nil { - return managed.ExternalObservation{}, errors.Wrap(err, errUpdateCR) - } + l := resource.NewLateInitializer() + cr.Spec.ForProvider.Value = l.LateInitializeStringPtr(cr.Spec.ForProvider.Value, config.Value) + configuration.UpdatePostgreSQLConfigurationObservation(&cr.Status.AtProvider, config) // We make this call after kube.Update since it doesn't update the // status subresource but fetches the whole object after it's done. So, @@ -156,12 +154,11 @@ func (e *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex cr.SetConditions(xpv1.Unavailable()) } - o := managed.ExternalObservation{ - ResourceExists: true, - ResourceUpToDate: configuration.IsPostgreSQLConfigurationUpToDate(cr.Spec.ForProvider, config), - } - - return o, nil + return managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: configuration.IsPostgreSQLConfigurationUpToDate(cr.Spec.ForProvider, config), + ResourceLateInitialized: l.IsChanged(), + }, nil } func (e *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) { diff --git a/pkg/controller/database/postgresqlserverconfiguration/managed_test.go b/pkg/controller/database/postgresqlserverconfiguration/managed_test.go new file mode 100644 index 00000000..537e34ae --- /dev/null +++ b/pkg/controller/database/postgresqlserverconfiguration/managed_test.go @@ -0,0 +1,368 @@ +/* +Copyright 2021 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 postgresqlserverconfiguration + +import ( + "context" + "io/ioutil" + "net/http" + "strings" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" + "github.com/Azure/go-autorest/autorest" + "github.com/crossplane/crossplane-runtime/pkg/meta" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/resource" + "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/google/go-cmp/cmp" + "github.com/pkg/errors" + + "github.com/crossplane/provider-azure/apis/database/v1beta1" + azurev1alpha3 "github.com/crossplane/provider-azure/apis/v1alpha3" +) + +const ( + inProgressResponse = `{"status": "InProgress"}` + subscriptID = "subscription-id" +) + +type MockPostgreSQLConfigurationAPI struct { + MockGet func(ctx context.Context, s *v1beta1.PostgreSQLServerConfiguration) (postgresql.Configuration, error) + MockCreateOrUpdate func(ctx context.Context, s *v1beta1.PostgreSQLServerConfiguration) error + MockDelete func(ctx context.Context, s *v1beta1.PostgreSQLServerConfiguration) error + MockGetRESTClient func() autorest.Sender +} + +func (m *MockPostgreSQLConfigurationAPI) Get(ctx context.Context, s *v1beta1.PostgreSQLServerConfiguration) (postgresql.Configuration, error) { + return m.MockGet(ctx, s) +} + +func (m *MockPostgreSQLConfigurationAPI) CreateOrUpdate(ctx context.Context, s *v1beta1.PostgreSQLServerConfiguration) error { + return m.MockCreateOrUpdate(ctx, s) +} + +func (m *MockPostgreSQLConfigurationAPI) Delete(ctx context.Context, s *v1beta1.PostgreSQLServerConfiguration) error { + return m.MockDelete(ctx, s) +} + +func (m *MockPostgreSQLConfigurationAPI) GetRESTClient() autorest.Sender { + return m.MockGetRESTClient() +} + +type modifier func(configuration *v1beta1.PostgreSQLServerConfiguration) + +func withLastOperation(op azurev1alpha3.AsyncOperation) modifier { + return func(p *v1beta1.PostgreSQLServerConfiguration) { + p.Status.AtProvider.LastOperation = op + } +} + +func withExternalName(name string) modifier { + return func(p *v1beta1.PostgreSQLServerConfiguration) { + meta.SetExternalName(p, name) + } +} + +func postgresqlserverconfiguration(m ...modifier) *v1beta1.PostgreSQLServerConfiguration { + p := &v1beta1.PostgreSQLServerConfiguration{} + + for _, mod := range m { + mod(p) + } + return p +} + +func TestObserve(t *testing.T) { + errBoom := errors.New("boom") + name := "coolserver" + + type args struct { + ctx context.Context + mg resource.Managed + } + type want struct { + eo managed.ExternalObservation + err error + } + + cases := map[string]struct { + e managed.ExternalClient + args args + want want + }{ + "ErrNotAPostgreSQLServerConfiguration": { + e: &external{}, + args: args{ + ctx: context.Background(), + }, + want: want{ + err: errors.New(errNotPostgreSQLServerConfig), + }, + }, + "ErrGetServer": { + e: &external{ + client: &MockPostgreSQLConfigurationAPI{ + MockGet: func(_ context.Context, _ *v1beta1.PostgreSQLServerConfiguration) (postgresql.Configuration, error) { + return postgresql.Configuration{}, errBoom + }, + }, + }, + args: args{ + ctx: context.Background(), + mg: postgresqlserverconfiguration(), + }, + want: want{ + err: errors.Wrap(errBoom, errGetPostgreSQLServerConfig), + }, + }, + "ServerCreating": { + e: &external{ + client: &MockPostgreSQLConfigurationAPI{ + MockGet: func(_ context.Context, _ *v1beta1.PostgreSQLServerConfiguration) (postgresql.Configuration, error) { + return postgresql.Configuration{}, autorest.DetailedError{StatusCode: http.StatusNotFound} + }, + MockGetRESTClient: func() autorest.Sender { + return autorest.SenderFunc(func(req *http.Request) (*http.Response, error) { + return &http.Response{ + Request: req, + StatusCode: http.StatusAccepted, + Body: ioutil.NopCloser(strings.NewReader(inProgressResponse)), + ContentLength: int64(len([]byte(inProgressResponse))), + }, nil + }) + }, + }, + }, + args: args{ + ctx: context.Background(), + mg: postgresqlserverconfiguration(withLastOperation(azurev1alpha3.AsyncOperation{Method: http.MethodPut, PollingURL: "crossplane.io"})), + }, + want: want{ + eo: managed.ExternalObservation{ + ResourceExists: true, + }, + }, + }, + "ServerNotFound": { + e: &external{ + client: &MockPostgreSQLConfigurationAPI{ + MockGet: func(_ context.Context, _ *v1beta1.PostgreSQLServerConfiguration) (postgresql.Configuration, error) { + return postgresql.Configuration{}, autorest.DetailedError{StatusCode: http.StatusNotFound} + }, + MockGetRESTClient: func() autorest.Sender { + return nil + }, + }, + }, + args: args{ + ctx: context.Background(), + mg: postgresqlserverconfiguration(), + }, + want: want{ + eo: managed.ExternalObservation{ + ResourceExists: false, + }, + }, + }, + "ServerAvailable": { + e: &external{ + kube: &test.MockClient{ + MockUpdate: test.NewMockUpdateFn(nil), + }, + client: &MockPostgreSQLConfigurationAPI{ + MockGet: func(_ context.Context, _ *v1beta1.PostgreSQLServerConfiguration) (postgresql.Configuration, error) { + return postgresql.Configuration{ + ConfigurationProperties: &postgresql.ConfigurationProperties{}, + }, nil + }, + MockGetRESTClient: func() autorest.Sender { + return autorest.SenderFunc(func(*http.Request) (*http.Response, error) { + return nil, nil + }) + }, + }, + }, + args: args{ + ctx: context.Background(), + mg: postgresqlserverconfiguration( + withExternalName(name), + ), + }, + want: want{ + eo: managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: true, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + eo, err := tc.e.Observe(tc.args.ctx, tc.args.mg) + + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("tc.e.Observe(...): -want error, +got error:\n%s", diff) + } + if diff := cmp.Diff(tc.want.eo, eo); diff != "" { + t.Errorf("tc.e.Observe(...): -want, +got:\n%s", diff) + } + }) + } +} + +func TestCreate(t *testing.T) { + errBoom := errors.New("boom") + + type args struct { + ctx context.Context + mg resource.Managed + } + type want struct { + ec managed.ExternalCreation + err error + } + + cases := map[string]struct { + e managed.ExternalClient + args args + want want + }{ + "ErrNotAPostgreSQLServerConfiguration": { + e: &external{}, + args: args{ + ctx: context.Background(), + }, + want: want{ + err: errors.New(errNotPostgreSQLServerConfig), + }, + }, + "ErrCreateServer": { + e: &external{ + client: &MockPostgreSQLConfigurationAPI{ + MockCreateOrUpdate: func(_ context.Context, _ *v1beta1.PostgreSQLServerConfiguration) error { return errBoom }, + }, + subscriptionID: subscriptID, + }, + args: args{ + ctx: context.Background(), + mg: postgresqlserverconfiguration(), + }, + want: want{ + err: errors.Wrap(errBoom, errCreatePostgreSQLServerConfig), + }, + }, + "Successful": { + e: &external{ + client: &MockPostgreSQLConfigurationAPI{ + MockCreateOrUpdate: func(_ context.Context, _ *v1beta1.PostgreSQLServerConfiguration) error { return nil }, + MockGetRESTClient: func() autorest.Sender { + return autorest.SenderFunc(func(*http.Request) (*http.Response, error) { + return nil, nil + }) + }, + }, + subscriptionID: subscriptID, + }, + args: args{ + ctx: context.Background(), + mg: postgresqlserverconfiguration(), + }, + want: want{ + ec: managed.ExternalCreation{ + ExternalNameAssigned: true, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + ec, err := tc.e.Create(tc.args.ctx, tc.args.mg) + + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("tc.e.Create(...): -want error, +got error:\n%s", diff) + } + if diff := cmp.Diff(tc.want.ec, ec); diff != "" { + t.Errorf("tc.e.Create(...): -want, +got:\n%s", diff) + } + }) + } +} + +func TestDelete(t *testing.T) { + errBoom := errors.New("boom") + + type args struct { + ctx context.Context + mg resource.Managed + } + + cases := map[string]struct { + e managed.ExternalClient + args args + want error + }{ + "ErrNotAPostgreSQLServerConfiguration": { + e: &external{}, + args: args{ + ctx: context.Background(), + }, + want: errors.New(errNotPostgreSQLServerConfig), + }, + "ErrDeleteServer": { + e: &external{ + client: &MockPostgreSQLConfigurationAPI{ + MockDelete: func(_ context.Context, _ *v1beta1.PostgreSQLServerConfiguration) error { return errBoom }, + }, + }, + args: args{ + ctx: context.Background(), + mg: postgresqlserverconfiguration(), + }, + want: errors.Wrap(errBoom, errDeletePostgreSQLServerConfig), + }, + "Successful": { + e: &external{ + client: &MockPostgreSQLConfigurationAPI{ + MockDelete: func(_ context.Context, _ *v1beta1.PostgreSQLServerConfiguration) error { return nil }, + MockGetRESTClient: func() autorest.Sender { + return autorest.SenderFunc(func(*http.Request) (*http.Response, error) { + return nil, nil + }) + }, + }, + }, + args: args{ + ctx: context.Background(), + mg: postgresqlserverconfiguration(), + }, + want: nil, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + got := tc.e.Delete(tc.args.ctx, tc.args.mg) + + if diff := cmp.Diff(tc.want, got, test.EquateErrors()); diff != "" { + t.Errorf("tc.e.Delete(...): -want error, +got error:\n%s", diff) + } + }) + } +}