From becb841aa1f770adce53449ab42493ea9f31b984 Mon Sep 17 00:00:00 2001 From: Mel Rush Date: Wed, 2 Oct 2019 10:47:53 -0600 Subject: [PATCH] Check Name Availability/Validation for SQL Resources (#245) * adding CheckNameAvailablity to sqlclient go sdk * adding checkname call in controller * cleaning, commments, etc * fixing instance and params * fixing gerserver to match rest of interface * fixing fcn comment * adding error check * Rearranging vars * adding msg and reconcile loop stop * making global var * making type of service const * removing unnecessary log * changing statement to check boolean instead of string contents * fixing log --- controllers/sqlserver_controller.go | 23 +++++++++- .../sqlclient/resourceclient.go | 5 ++- .../sqlclient/sqlclient_godsk.go | 42 +++++++++++++++++++ .../sqlclient/sqlproperties.go | 20 +++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) diff --git a/controllers/sqlserver_controller.go b/controllers/sqlserver_controller.go index c962ee81730..4a1e0361f5a 100644 --- a/controllers/sqlserver_controller.go +++ b/controllers/sqlserver_controller.go @@ -53,7 +53,6 @@ type SqlServerReconciler struct { func (r *SqlServerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { ctx := context.Background() log := r.Log.WithValues("sqlserver", req.NamespacedName) - var instance azurev1.SqlServer if err := r.Get(ctx, req.NamespacedName, &instance); err != nil { @@ -64,6 +63,17 @@ func (r *SqlServerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return ctrl.Result{}, client.IgnoreNotFound(err) } + location := instance.Spec.Location + name := instance.ObjectMeta.Name + groupName := instance.Spec.ResourceGroup + + sdkClient := sql.GoSDKClient{ + Ctx: ctx, + ResourceGroupName: groupName, + ServerName: name, + Location: location, + } + if helpers.IsBeingDeleted(&instance) { if helpers.HasFinalizer(&instance, SQLServerFinalizerName) { if err := r.deleteExternal(&instance); err != nil { @@ -96,6 +106,17 @@ func (r *SqlServerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { } } + availableResp, err := sdkClient.CheckNameAvailability() + if err != nil { + log.Info("error validating name") + return ctrl.Result{}, err + } + if !availableResp.Available { + log.Info("Servername is invalid or not available") + r.Recorder.Event(&instance, "Warning", "Failed", "Servername is invalid") + return ctrl.Result{Requeue: false}, fmt.Errorf("Servername invalid %s", availableResp.Name) + } + if !instance.IsSubmitted() { r.Recorder.Event(&instance, "Normal", "Submitting", "starting resource reconciliation") if err := r.reconcileExternal(&instance); err != nil { diff --git a/pkg/resourcemanager/sqlclient/resourceclient.go b/pkg/resourcemanager/sqlclient/resourceclient.go index 9d8eeb6a4b1..f8ef15e3e31 100644 --- a/pkg/resourcemanager/sqlclient/resourceclient.go +++ b/pkg/resourcemanager/sqlclient/resourceclient.go @@ -21,4 +21,7 @@ type ResourceClient interface { DeleteDB(databaseName string) (result autorest.Response, err error) DeleteSQLServer() (result autorest.Response, err error) DeleteSQLFirewallRule(ruleName string) (err error) -} + GetServer() (result sql.Server, err error) + IsAsyncNotCompleted(err error) (result bool) + CheckNameAvailability() (result AvailabilityResponse, err error) +} \ No newline at end of file diff --git a/pkg/resourcemanager/sqlclient/sqlclient_godsk.go b/pkg/resourcemanager/sqlclient/sqlclient_godsk.go index c9caa3d0164..80b8bce4da7 100644 --- a/pkg/resourcemanager/sqlclient/sqlclient_godsk.go +++ b/pkg/resourcemanager/sqlclient/sqlclient_godsk.go @@ -15,6 +15,8 @@ import ( "github.com/Azure/go-autorest/autorest/to" ) +const typeOfService = "Microsoft.Sql/servers" + // getGoServersClient retrieves a ServersClient func getGoServersClient() sql.ServersClient { serversClient := sql.NewServersClient(config.SubscriptionID()) @@ -239,3 +241,43 @@ func (sdk GoSDKClient) DeleteSQLServer() (result autorest.Response, err error) { return future.Result(serversClient) } + +// IsAsyncNotCompleted returns true if the error is due to async not completed +func (sdk GoSDKClient) IsAsyncNotCompleted(err error) (result bool) { + result = false + if err != nil && strings.Contains(err.Error(), "asynchronous operation has not completed") { + result = true + } else if strings.Contains(err.Error(), "is busy with another operation") { + result = true + } + return result +} + +// GetServer returns a server +func (sdk GoSDKClient) GetServer() (result sql.Server, err error) { + serversClient := getGoServersClient() + + return serversClient.Get( + sdk.Ctx, + sdk.ResourceGroupName, + sdk.ServerName, + ) +} + +// CheckNameAvailability determines whether a SQL resource can be created with the specified name +func (sdk GoSDKClient) CheckNameAvailability() (result AvailabilityResponse, err error) { + serversClient := getGoServersClient() + + response, err := serversClient.CheckNameAvailability( + sdk.Ctx, + sql.CheckNameAvailabilityRequest{ + Name: to.StringPtr(sdk.ServerName), + Type: to.StringPtr(typeOfService), + }, + ) + if err != nil { + return result, err + } + + return ToAvailabilityResponse(response), err +} \ No newline at end of file diff --git a/pkg/resourcemanager/sqlclient/sqlproperties.go b/pkg/resourcemanager/sqlclient/sqlproperties.go index 7d3131d7685..e469d6217fd 100644 --- a/pkg/resourcemanager/sqlclient/sqlproperties.go +++ b/pkg/resourcemanager/sqlclient/sqlproperties.go @@ -136,3 +136,23 @@ func translateDBEdition(in DBEdition) (result sql.DatabaseEdition) { return result } + +// AvailabilityResponse is the response for checking name validation +type AvailabilityResponse struct { + Available bool + Message string + Name string +} + +// ToAvailabilityResponse converts CheckNameAvailabilityResponse to AvailabilityResponse +func ToAvailabilityResponse(response sql.CheckNameAvailabilityResponse) (result AvailabilityResponse) { + result.Available = *response.Available + if response.Message != nil { + result.Message = *response.Message + } + if response.Name != nil { + result.Name = *response.Name + } + + return result +}