From f13b8f26a9205ba9844b2c794f4bd5a08e1b1716 Mon Sep 17 00:00:00 2001 From: William Mortl Date: Wed, 1 Apr 2020 17:10:28 -0700 Subject: [PATCH] Server runs --- pkg/resourcemanager/psql/server/client.go | 392 ------------------ pkg/resourcemanager/psql/server/manager.go | 24 -- pkg/resourcemanager/psql/server/server.go | 190 +++++++++ .../psql/server/server_manager.go | 49 +++ .../psql/server/server_reconcile.go | 205 +++++++++ 5 files changed, 444 insertions(+), 416 deletions(-) delete mode 100644 pkg/resourcemanager/psql/server/client.go delete mode 100644 pkg/resourcemanager/psql/server/manager.go create mode 100644 pkg/resourcemanager/psql/server/server.go create mode 100644 pkg/resourcemanager/psql/server/server_manager.go create mode 100644 pkg/resourcemanager/psql/server/server_reconcile.go diff --git a/pkg/resourcemanager/psql/server/client.go b/pkg/resourcemanager/psql/server/client.go deleted file mode 100644 index 6ef051c4c7c..00000000000 --- a/pkg/resourcemanager/psql/server/client.go +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -package server - -import ( - "context" - "fmt" - - psql "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" - "github.com/Azure/azure-service-operator/api/v1alpha1" - azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" - "github.com/Azure/azure-service-operator/pkg/errhelp" - "github.com/Azure/azure-service-operator/pkg/helpers" - "github.com/Azure/azure-service-operator/pkg/resourcemanager" - "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" - "github.com/Azure/azure-service-operator/pkg/resourcemanager/iam" - "github.com/Azure/azure-service-operator/pkg/secrets" - "github.com/Azure/go-autorest/autorest/to" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" -) - -type PSQLServerClient struct { - SecretClient secrets.SecretClient - Scheme *runtime.Scheme -} - -func NewPSQLServerClient(secretclient secrets.SecretClient, scheme *runtime.Scheme) *PSQLServerClient { - return &PSQLServerClient{ - SecretClient: secretclient, - Scheme: scheme, - } -} - -func getPSQLServersClient() psql.ServersClient { - serversClient := psql.NewServersClientWithBaseURI(config.BaseURI(), config.SubscriptionID()) - a, _ := iam.GetResourceManagementAuthorizer() - serversClient.Authorizer = a - serversClient.AddToUserAgent(config.UserAgent()) - return serversClient -} - -func getPSQLCheckNameAvailabilityClient() psql.CheckNameAvailabilityClient { - nameavailabilityClient := psql.NewCheckNameAvailabilityClientWithBaseURI(config.BaseURI(), config.SubscriptionID()) - a, _ := iam.GetResourceManagementAuthorizer() - nameavailabilityClient.Authorizer = a - nameavailabilityClient.AddToUserAgent(config.UserAgent()) - return nameavailabilityClient -} - -func (p *PSQLServerClient) CheckServerNameAvailability(ctx context.Context, servername string) (bool, error) { - - client := getPSQLCheckNameAvailabilityClient() - - resourceType := "server" - - nameAvailabilityRequest := psql.NameAvailabilityRequest{ - Name: &servername, - Type: &resourceType, - } - _, err := client.Execute(ctx, nameAvailabilityRequest) - if err == nil { // Name available - return true, nil - } - return false, err - -} -func (p *PSQLServerClient) Ensure(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { - options := &resourcemanager.Options{} - for _, opt := range opts { - opt(options) - } - - if options.SecretClient != nil { - p.SecretClient = options.SecretClient - } - - instance, err := p.convert(obj) - if err != nil { - return true, err - } - - // Check to see if secret exists and if yes retrieve the admin login and password - secret, err := p.GetOrPrepareSecret(ctx, instance) - if err != nil { - return false, err - } - // Update secret - err = p.AddServerCredsToSecrets(ctx, instance.Name, secret, instance) - if err != nil { - return false, err - } - - client := getPSQLServersClient() - - // convert kube labels to expected tag format - labels := map[string]*string{} - for k, v := range instance.GetLabels() { - labels[k] = &v - } - instance.Status.Provisioning = true - // Check if this server already exists and its state if it does. This is required - // to overcome the issue with the lack of idempotence of the Create call - - server, err := p.GetServer(ctx, instance.Spec.ResourceGroup, instance.Name) - if err == nil { - instance.Status.State = string(server.UserVisibleState) - if server.UserVisibleState == "Ready" { - instance.Status.Provisioned = true - instance.Status.Provisioning = false - instance.Status.Message = resourcemanager.SuccessMsg - return true, nil - } - return false, nil - } - - adminlogin := string(secret["username"]) - adminpassword := string(secret["password"]) - - skuInfo := psql.Sku{ - Name: to.StringPtr(instance.Spec.Sku.Name), - Tier: psql.SkuTier(instance.Spec.Sku.Tier), - Capacity: to.Int32Ptr(instance.Spec.Sku.Capacity), - Size: to.StringPtr(instance.Spec.Sku.Size), - Family: to.StringPtr(instance.Spec.Sku.Family), - } - future, err := p.CreateServerIfValid( - ctx, - instance.Name, - instance.Spec.ResourceGroup, - instance.Spec.Location, - labels, - psql.ServerVersion(instance.Spec.ServerVersion), - psql.SslEnforcementEnum(instance.Spec.SSLEnforcement), - skuInfo, - adminlogin, - adminpassword, - ) - - if err != nil { - // let the user know what happened - instance.Status.Message = err.Error() - instance.Status.Provisioning = false - // errors we expect might happen that we are ok with waiting for - catch := []string{ - errhelp.ResourceGroupNotFoundErrorCode, - errhelp.ParentNotFoundErrorCode, - errhelp.NotFoundErrorCode, - errhelp.AsyncOpIncompleteError, - } - - catchUnrecoverableErrors := []string{ - errhelp.ProvisioningDisabled, - errhelp.LocationNotAvailableForResourceType, - } - - azerr := errhelp.NewAzureErrorAzureError(err) - if helpers.ContainsString(catch, azerr.Type) { - // most of these error technically mean the resource is actually not provisioning - switch azerr.Type { - case errhelp.AsyncOpIncompleteError: - instance.Status.Provisioning = true - } - // reconciliation is not done but error is acceptable - return false, nil - } - if helpers.ContainsString(catchUnrecoverableErrors, azerr.Type) { - // Unrecoverable error, so stop reconcilation - instance.Status.Message = "Reconcilation hit unrecoverable error: " + err.Error() - return true, nil - } - // reconciliation not done and we don't know what happened - return false, err - } - - instance.Status.State = future.Status() - - server, err = future.Result(client) - if err != nil { - // let the user know what happened - instance.Status.Message = err.Error() - instance.Status.Provisioning = false - // errors we expect might happen that we are ok with waiting for - catch := []string{ - errhelp.ResourceGroupNotFoundErrorCode, - errhelp.ParentNotFoundErrorCode, - errhelp.NotFoundErrorCode, - errhelp.AsyncOpIncompleteError, - } - - azerr := errhelp.NewAzureErrorAzureError(err) - if helpers.ContainsString(catch, azerr.Type) { - // most of these error technically mean the resource is actually not provisioning - switch azerr.Type { - case errhelp.AsyncOpIncompleteError: - instance.Status.Provisioning = true - } - // reconciliation is not done but error is acceptable - return false, nil - } - // reconciliation not done and we don't know what happened - return false, err - } - - instance.Status.State = string(server.UserVisibleState) - - if instance.Status.Provisioning { - instance.Status.Provisioned = true - instance.Status.Provisioning = false - instance.Status.Message = resourcemanager.SuccessMsg - } else { - instance.Status.Provisioned = false - instance.Status.Provisioning = true - } - - return true, nil -} - -func (p *PSQLServerClient) Delete(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { - - options := &resourcemanager.Options{} - for _, opt := range opts { - opt(options) - } - - if options.SecretClient != nil { - p.SecretClient = options.SecretClient - } - - instance, err := p.convert(obj) - if err != nil { - return true, err - } - - status, err := p.DeleteServer(ctx, instance.Spec.ResourceGroup, instance.Name) - if err != nil { - if !errhelp.IsAsynchronousOperationNotComplete(err) { - return true, err - } - } - instance.Status.State = status - - if err == nil { - if status != "InProgress" { - // Best case deletion of secrets - key := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} - p.SecretClient.Delete(ctx, key) - return false, nil - } - } - - return true, nil -} - -func (p *PSQLServerClient) GetParents(obj runtime.Object) ([]resourcemanager.KubeParent, error) { - - instance, err := p.convert(obj) - if err != nil { - return nil, err - } - - return []resourcemanager.KubeParent{ - { - Key: types.NamespacedName{ - Namespace: instance.Namespace, - Name: instance.Spec.ResourceGroup, - }, - Target: &azurev1alpha1.ResourceGroup{}, - }, - }, nil -} - -func (g *PSQLServerClient) GetStatus(obj runtime.Object) (*v1alpha1.ASOStatus, error) { - instance, err := g.convert(obj) - if err != nil { - return nil, err - } - return &instance.Status, nil -} - -func (p *PSQLServerClient) convert(obj runtime.Object) (*v1alpha1.PostgreSQLServer, error) { - local, ok := obj.(*v1alpha1.PostgreSQLServer) - if !ok { - return nil, fmt.Errorf("failed type assertion on kind: %s", obj.GetObjectKind().GroupVersionKind().String()) - } - return local, nil -} - -func (p *PSQLServerClient) CreateServerIfValid(ctx context.Context, servername string, resourcegroup string, location string, tags map[string]*string, serverversion psql.ServerVersion, sslenforcement psql.SslEnforcementEnum, skuInfo psql.Sku, adminlogin string, adminpassword string) (future psql.ServersCreateFuture, err error) { - - client := getPSQLServersClient() - - // Check if name is valid if this is the first create call - valid, err := p.CheckServerNameAvailability(ctx, servername) - if valid == false { - return future, err - } - - future, err = client.Create( - ctx, - resourcegroup, - servername, - psql.ServerForCreate{ - Location: &location, - Tags: tags, - Properties: &psql.ServerPropertiesForDefaultCreate{ - AdministratorLogin: &adminlogin, - AdministratorLoginPassword: &adminpassword, - Version: serverversion, - SslEnforcement: sslenforcement, - //StorageProfile: &psql.StorageProfile{}, - CreateMode: psql.CreateModeServerPropertiesForCreate, - }, - Sku: &skuInfo, - }, - ) - - return future, err -} - -func (p *PSQLServerClient) DeleteServer(ctx context.Context, resourcegroup string, servername string) (status string, err error) { - - client := getPSQLServersClient() - - _, err = client.Get(ctx, resourcegroup, servername) - if err == nil { // Server present, so go ahead and delete - future, err := client.Delete(ctx, resourcegroup, servername) - return future.Status(), err - } - // Server not present so return success anyway - return "Server not present", nil - -} - -func (p *PSQLServerClient) GetServer(ctx context.Context, resourcegroup string, servername string) (server psql.Server, err error) { - - client := getPSQLServersClient() - return client.Get(ctx, resourcegroup, servername) -} - -func (p *PSQLServerClient) AddServerCredsToSecrets(ctx context.Context, secretName string, data map[string][]byte, instance *azurev1alpha1.PostgreSQLServer) error { - key := types.NamespacedName{ - Name: secretName, - Namespace: instance.Namespace, - } - - err := p.SecretClient.Upsert(ctx, - key, - data, - secrets.WithOwner(instance), - secrets.WithScheme(p.Scheme), - ) - if err != nil { - return err - } - - return nil -} - -func (p *PSQLServerClient) GetOrPrepareSecret(ctx context.Context, instance *azurev1alpha1.PostgreSQLServer) (map[string][]byte, error) { - name := instance.Name - - usernameLength := 8 - passwordLength := 16 - - secret := map[string][]byte{} - - key := types.NamespacedName{Name: name, Namespace: instance.Namespace} - if stored, err := p.SecretClient.Get(ctx, key); err == nil { - return stored, nil - } - - randomUsername, err := helpers.GenerateRandomUsername(usernameLength, 0) - if err != nil { - return secret, err - } - - randomPassword, err := helpers.GenerateRandomPassword(passwordLength) - if err != nil { - return secret, err - } - - secret["username"] = []byte(randomUsername) - secret["fullyQualifiedUsername"] = []byte(fmt.Sprintf("%s@%s", randomUsername, name)) - secret["password"] = []byte(randomPassword) - secret["postgreSqlServerName"] = []byte(name) - // TODO: The below may not be right for non Azure public cloud. - secret["fullyQualifiedServerName"] = []byte(name + ".postgres.database.azure.com") - - return secret, nil -} diff --git a/pkg/resourcemanager/psql/server/manager.go b/pkg/resourcemanager/psql/server/manager.go deleted file mode 100644 index 7843a62c882..00000000000 --- a/pkg/resourcemanager/psql/server/manager.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -package server - -import ( - "context" - - psql "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" - "github.com/Azure/azure-service-operator/pkg/resourcemanager" -) - -type PostgreSQLServerManager interface { - //convert(obj runtime.Object) (*v1alpha1.PostgreSQLServer, error) - - //CheckServerNameAvailability(ctx context.Context, servername string) (bool, error) - CreateServerIfValid(ctx context.Context, servername string, resourcegroup string, location string, tags map[string]*string, serverversion psql.ServerVersion, sslenforcement psql.SslEnforcementEnum, skuInfo psql.Sku, adminlogin string, adminpassword string) (psql.ServersCreateFuture, error) - DeleteServer(ctx context.Context, resourcegroup string, servername string) (string, error) - GetServer(ctx context.Context, resourcegroup string, servername string) (psql.Server, error) - //AddServerCredsToSecrets(ctx context.Context, secretName string, data map[string][]byte, instance *azurev1alpha1.PostgreSQLServer) error - - // also embed async client methods - resourcemanager.ARMClient -} diff --git a/pkg/resourcemanager/psql/server/server.go b/pkg/resourcemanager/psql/server/server.go new file mode 100644 index 00000000000..b998f1b74a2 --- /dev/null +++ b/pkg/resourcemanager/psql/server/server.go @@ -0,0 +1,190 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package server + +import ( + "context" + "fmt" + + psql "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/Azure/azure-service-operator/pkg/helpers" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/iam" + "github.com/Azure/azure-service-operator/pkg/secrets" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +) + +type PSQLServerClient struct { + SecretClient secrets.SecretClient + Scheme *runtime.Scheme +} + +func NewPSQLServerClient(secretclient secrets.SecretClient, scheme *runtime.Scheme) *PSQLServerClient { + return &PSQLServerClient{ + SecretClient: secretclient, + Scheme: scheme, + } +} + +func getPSQLServersClient() (psql.ServersClient, error) { + serversClient := psql.NewServersClientWithBaseURI(config.BaseURI(), config.SubscriptionID()) + a, err := iam.GetResourceManagementAuthorizer() + if err != nil { + return psql.ServersClient{}, err + } + serversClient.Authorizer = a + serversClient.AddToUserAgent(config.UserAgent()) + return serversClient, nil +} + +func getPSQLCheckNameAvailabilityClient() (psql.CheckNameAvailabilityClient, error) { + nameavailabilityClient := psql.NewCheckNameAvailabilityClientWithBaseURI(config.BaseURI(), config.SubscriptionID()) + a, err := iam.GetResourceManagementAuthorizer() + if err != nil { + return psql.CheckNameAvailabilityClient{}, err + } + nameavailabilityClient.Authorizer = a + nameavailabilityClient.AddToUserAgent(config.UserAgent()) + return nameavailabilityClient, nil +} + +func (p *PSQLServerClient) CheckServerNameAvailability(ctx context.Context, servername string) (bool, error) { + + client, err := getPSQLCheckNameAvailabilityClient() + if err != nil { + return false, err + } + + resourceType := "server" + + nameAvailabilityRequest := psql.NameAvailabilityRequest{ + Name: &servername, + Type: &resourceType, + } + _, err = client.Execute(ctx, nameAvailabilityRequest) + if err == nil { // Name available + return true, nil + } + return false, err + +} + +func (p *PSQLServerClient) CreateServerIfValid(ctx context.Context, servername string, resourcegroup string, location string, tags map[string]*string, serverversion psql.ServerVersion, sslenforcement psql.SslEnforcementEnum, skuInfo psql.Sku, adminlogin string, adminpassword string) (server psql.Server, err error) { + + client, err := getPSQLServersClient() + if err != nil { + return psql.Server{}, err + } + + // Check if name is valid if this is the first create call + valid, err := p.CheckServerNameAvailability(ctx, servername) + if valid == false { + return psql.Server{}, err + } + + future, err := client.Create( + ctx, + resourcegroup, + servername, + psql.ServerForCreate{ + Location: &location, + Tags: tags, + Properties: &psql.ServerPropertiesForDefaultCreate{ + AdministratorLogin: &adminlogin, + AdministratorLoginPassword: &adminpassword, + Version: serverversion, + SslEnforcement: sslenforcement, + //StorageProfile: &psql.StorageProfile{}, + CreateMode: psql.CreateModeServerPropertiesForCreate, + }, + Sku: &skuInfo, + }, + ) + if err != nil { + return psql.Server{}, err + } + + return future.Result(client) +} + +func (p *PSQLServerClient) DeleteServer(ctx context.Context, resourcegroup string, servername string) (status string, err error) { + + client, err := getPSQLServersClient() + if err != nil { + return "", err + } + + _, err = client.Get(ctx, resourcegroup, servername) + if err == nil { // Server present, so go ahead and delete + future, err := client.Delete(ctx, resourcegroup, servername) + return future.Status(), err + } + // Server not present so return success anyway + return "Server not present", nil + +} + +func (p *PSQLServerClient) GetServer(ctx context.Context, resourcegroup string, servername string) (server psql.Server, err error) { + + client, err := getPSQLServersClient() + if err != nil { + return psql.Server{}, err + } + + return client.Get(ctx, resourcegroup, servername) +} + +func (p *PSQLServerClient) AddServerCredsToSecrets(ctx context.Context, secretName string, data map[string][]byte, instance *azurev1alpha1.PostgreSQLServer) error { + key := types.NamespacedName{ + Name: secretName, + Namespace: instance.Namespace, + } + + err := p.SecretClient.Upsert(ctx, + key, + data, + secrets.WithOwner(instance), + secrets.WithScheme(p.Scheme), + ) + if err != nil { + return err + } + + return nil +} + +func (p *PSQLServerClient) GetOrPrepareSecret(ctx context.Context, instance *azurev1alpha1.PostgreSQLServer) (map[string][]byte, error) { + name := instance.Name + + usernameLength := 8 + passwordLength := 16 + + secret := map[string][]byte{} + + key := types.NamespacedName{Name: name, Namespace: instance.Namespace} + if stored, err := p.SecretClient.Get(ctx, key); err == nil { + return stored, nil + } + + randomUsername, err := helpers.GenerateRandomUsername(usernameLength, 0) + if err != nil { + return secret, err + } + + randomPassword, err := helpers.GenerateRandomPassword(passwordLength) + if err != nil { + return secret, err + } + + secret["username"] = []byte(randomUsername) + secret["fullyQualifiedUsername"] = []byte(fmt.Sprintf("%s@%s", randomUsername, name)) + secret["password"] = []byte(randomPassword) + secret["postgreSqlServerName"] = []byte(name) + // TODO: The below may not be right for non Azure public cloud. + secret["fullyQualifiedServerName"] = []byte(name + ".postgres.database.azure.com") + + return secret, nil +} diff --git a/pkg/resourcemanager/psql/server/server_manager.go b/pkg/resourcemanager/psql/server/server_manager.go new file mode 100644 index 00000000000..3fc3ad42f8c --- /dev/null +++ b/pkg/resourcemanager/psql/server/server_manager.go @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package server + +import ( + "context" + + psql "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/Azure/azure-service-operator/pkg/resourcemanager" +) + +type PostgreSQLServerManager interface { + //convert(obj runtime.Object) (*v1alpha1.PostgreSQLServer, error) + + CheckServerNameAvailability(ctx context.Context, + servername string) (bool, error) + + CreateServerIfValid(ctx context.Context, + servername string, + resourcegroup string, + location string, + tags map[string]*string, + serverversion psql.ServerVersion, + sslenforcement psql.SslEnforcementEnum, + skuInfo psql.Sku, + adminlogin string, + adminpassword string) (psql.Server, error) + + DeleteServer(ctx context.Context, + resourcegroup string, + servername string) (string, error) + + GetServer(ctx context.Context, + resourcegroup string, + servername string) (psql.Server, error) + + AddServerCredsToSecrets(ctx context.Context, + secretName string, + data map[string][]byte, + instance *azurev1alpha1.PostgreSQLServer) error + + GetOrPrepareSecret(ctx context.Context, + instance *azurev1alpha1.PostgreSQLServer) (map[string][]byte, error) + + // also embed async client methods + resourcemanager.ARMClient +} diff --git a/pkg/resourcemanager/psql/server/server_reconcile.go b/pkg/resourcemanager/psql/server/server_reconcile.go new file mode 100644 index 00000000000..6d03d12c769 --- /dev/null +++ b/pkg/resourcemanager/psql/server/server_reconcile.go @@ -0,0 +1,205 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package server + +import ( + "context" + "fmt" + + psql "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" + "github.com/Azure/azure-service-operator/api/v1alpha1" + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/Azure/azure-service-operator/pkg/errhelp" + "github.com/Azure/azure-service-operator/pkg/helpers" + "github.com/Azure/azure-service-operator/pkg/resourcemanager" + "github.com/Azure/go-autorest/autorest/to" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +) + +// Ensure creates the Postgres server +func (p *PSQLServerClient) Ensure(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { + options := &resourcemanager.Options{} + for _, opt := range opts { + opt(options) + } + + if options.SecretClient != nil { + p.SecretClient = options.SecretClient + } + + instance, err := p.convert(obj) + if err != nil { + return true, err + } + + // Check to see if secret exists and if yes retrieve the admin login and password + secret, err := p.GetOrPrepareSecret(ctx, instance) + if err != nil { + return false, err + } + // Update secret + err = p.AddServerCredsToSecrets(ctx, instance.Name, secret, instance) + if err != nil { + return false, err + } + + // if an error occurs thats ok as it means that it doesn't exist yet + getServer, err := p.GetServer(ctx, instance.Spec.ResourceGroup, instance.Name) + if err == nil { + instance.Status.State = string(getServer.UserVisibleState) + + // succeeded! so end reconcilliation successfully + if getServer.UserVisibleState == "Ready" { + instance.Status.Message = resourcemanager.SuccessMsg + instance.Status.ResourceId = *getServer.ID + instance.Status.Provisioned = true + instance.Status.Provisioning = false + return true, nil + } + + // the database exists but has not provisioned yet - so keep waiting + instance.Status.Message = "Postgres server exists but may not be ready" + instance.Status.State = string(getServer.UserVisibleState) + return false, nil + } + + // setup variables for create call + labels := map[string]*string{} + for k, v := range instance.GetLabels() { + labels[k] = &v + } + adminlogin := string(secret["username"]) + adminpassword := string(secret["password"]) + skuInfo := psql.Sku{ + Name: to.StringPtr(instance.Spec.Sku.Name), + Tier: psql.SkuTier(instance.Spec.Sku.Tier), + Capacity: to.Int32Ptr(instance.Spec.Sku.Capacity), + Size: to.StringPtr(instance.Spec.Sku.Size), + Family: to.StringPtr(instance.Spec.Sku.Family), + } + + // create the server + instance.Status.Provisioning = true + _, err = p.CreateServerIfValid( + ctx, + instance.Name, + instance.Spec.ResourceGroup, + instance.Spec.Location, + labels, + psql.ServerVersion(instance.Spec.ServerVersion), + psql.SslEnforcementEnum(instance.Spec.SSLEnforcement), + skuInfo, + adminlogin, + adminpassword, + ) + if err != nil { + instance.Status.Message = errhelp.StripErrorIDs(err) + azerr := errhelp.NewAzureErrorAzureError(err) + + instance.Status.Provisioning = false + + catchInProgress := []string{ + errhelp.AsyncOpIncompleteError, + errhelp.AlreadyExists, + } + catchKnownError := []string{ + errhelp.ResourceGroupNotFoundErrorCode, + errhelp.ParentNotFoundErrorCode, + errhelp.NotFoundErrorCode, + } + + // handle the errors + if helpers.ContainsString(catchInProgress, azerr.Type) { + instance.Status.Message = "Postgres server exists but may not be ready" + return false, nil + } else if helpers.ContainsString(catchKnownError, azerr.Type) { + instance.Status.Provisioning = false + return false, nil + } else { + + // serious error occured, end reconcilliation and mark it as failed + instance.Status.Message = fmt.Sprintf("Error occurred creating the Postgres server: %s", errhelp.StripErrorIDs(err)) + instance.Status.Provisioned = false + instance.Status.Provisioning = false + instance.Status.FailedProvisioning = true + return true, nil + } + } + + return false, nil +} + +// Delete deletes the Postgres server +func (p *PSQLServerClient) Delete(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { + + options := &resourcemanager.Options{} + for _, opt := range opts { + opt(options) + } + + if options.SecretClient != nil { + p.SecretClient = options.SecretClient + } + + instance, err := p.convert(obj) + if err != nil { + return true, err + } + + status, err := p.DeleteServer(ctx, instance.Spec.ResourceGroup, instance.Name) + if err != nil { + if !errhelp.IsAsynchronousOperationNotComplete(err) { + return true, err + } + } + instance.Status.State = status + + if err == nil { + if status != "InProgress" { + // Best case deletion of secrets + key := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} + p.SecretClient.Delete(ctx, key) + return false, nil + } + } + + return true, nil +} + +// GetParents gets the resource's parents +func (p *PSQLServerClient) GetParents(obj runtime.Object) ([]resourcemanager.KubeParent, error) { + + instance, err := p.convert(obj) + if err != nil { + return nil, err + } + + return []resourcemanager.KubeParent{ + { + Key: types.NamespacedName{ + Namespace: instance.Namespace, + Name: instance.Spec.ResourceGroup, + }, + Target: &azurev1alpha1.ResourceGroup{}, + }, + }, nil +} + +// GetStatus returns the status +func (p *PSQLServerClient) GetStatus(obj runtime.Object) (*v1alpha1.ASOStatus, error) { + instance, err := p.convert(obj) + if err != nil { + return nil, err + } + return &instance.Status, nil +} + +func (p *PSQLServerClient) convert(obj runtime.Object) (*v1alpha1.PostgreSQLServer, error) { + local, ok := obj.(*v1alpha1.PostgreSQLServer) + if !ok { + return nil, fmt.Errorf("failed type assertion on kind: %s", obj.GetObjectKind().GroupVersionKind().String()) + } + return local, nil +}