diff --git a/PROJECT b/PROJECT index 00905ec53b7..59918be0af4 100644 --- a/PROJECT +++ b/PROJECT @@ -86,6 +86,9 @@ resources: - group: azure version: v1alpha1 kind: MySQLFirewallRule +- group: azure + version: v1alpha1 + kind: MySQLVNetRule - group: azure version: v1alpha1 kind: AzureVirtualMachine diff --git a/api/v1alpha1/mysqlvnetrule_types.go b/api/v1alpha1/mysqlvnetrule_types.go new file mode 100644 index 00000000000..a4345d0f583 --- /dev/null +++ b/api/v1alpha1/mysqlvnetrule_types.go @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// MySQLVNetRuleSpec defines the desired state of MySQLVNetRule +type MySQLVNetRuleSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + ResourceGroup string `json:"resourceGroup"` + Server string `json:"server"` + VNetResourceGroup string `json:"vNetResourceGroup"` + VNetName string `json:"vNetName"` + SubnetName string `json:"subnetName"` + IgnoreMissingServiceEndpoint bool `json:"ignoreMissingServiceEndpoint,omitempty"` +} + +// +kubebuilder:object:root=true + +// MySQLVNetRule is the Schema for the mysqlvnetrules API +type MySQLVNetRule struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec MySQLVNetRuleSpec `json:"spec,omitempty"` + Status ASOStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// MySQLVNetRuleList contains a list of MySQLVNetRule +type MySQLVNetRuleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []MySQLVNetRule `json:"items"` +} + +func init() { + SchemeBuilder.Register(&MySQLVNetRule{}, &MySQLVNetRuleList{}) +} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index d32a5f49f90..22f54886eb2 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -33,6 +33,7 @@ resources: - bases/azure.microsoft.com_mysqlfirewallrules.yaml - bases/azure.microsoft.com_azurepublicipaddresses.yaml - bases/azure.microsoft.com_azurenetworkinterfaces.yaml +- bases/azure.microsoft.com_mysqlvnetrules.yaml - bases/azure.microsoft.com_azurevirtualmachines.yaml # +kubebuilder:scaffold:crdkustomizeresource @@ -66,6 +67,7 @@ resources: #- patches/webhook_in_storageaccounts.yaml #- patches/webhook_in_azurepublicipaddresses.yaml #- patches/webhook_in_azurenetworkinterfaces.yaml +#- patches/webhook_in_mysqlvnetrules.yaml #- patches/webhook_in_azurevirtualmachines.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch @@ -98,6 +100,7 @@ resources: #- patches/cainjection_in_storageaccounts.yaml #- patches/cainjection_in_azurepublicipaddresses.yaml #- patches/cainjection_in_azurenetworkinterfaces.yaml +#- patches/cainjection_in_mysqlvnetrules.yaml #- patches/cainjection_in_azurevirtualmachines.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch diff --git a/config/crd/patches/cainjection_in_mysqlvnetrules.yaml b/config/crd/patches/cainjection_in_mysqlvnetrules.yaml new file mode 100644 index 00000000000..370c08038db --- /dev/null +++ b/config/crd/patches/cainjection_in_mysqlvnetrules.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + certmanager.k8s.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: mysqlvnetrules.azure.microsoft.com diff --git a/config/crd/patches/webhook_in_mysqlvnetrules.yaml b/config/crd/patches/webhook_in_mysqlvnetrules.yaml new file mode 100644 index 00000000000..e0f6bda0df2 --- /dev/null +++ b/config/crd/patches/webhook_in_mysqlvnetrules.yaml @@ -0,0 +1,17 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: mysqlvnetrules.azure.microsoft.com +spec: + conversion: + strategy: Webhook + webhookClientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert diff --git a/config/samples/azure_v1alpha1_mysqlvnetrule.yaml b/config/samples/azure_v1alpha1_mysqlvnetrule.yaml new file mode 100644 index 00000000000..e27496c3843 --- /dev/null +++ b/config/samples/azure_v1alpha1_mysqlvnetrule.yaml @@ -0,0 +1,11 @@ +apiVersion: azure.microsoft.com/v1alpha1 +kind: MySQLVNetRule +metadata: + name: mysqlvnetrule-sample +spec: + resourceGroup: resourcegroup-azure-operators + server: mysqlserver-sample + vNetResourceGroup: resourcegroup-vnet + vNetName: virtualnetwork-sample + subnetName: test1 + ignoreMissingServiceEndpoint: true diff --git a/controllers/mysql_combined_test.go b/controllers/mysql_combined_test.go index a0cc952fbbe..1e570f76783 100644 --- a/controllers/mysql_combined_test.go +++ b/controllers/mysql_combined_test.go @@ -68,6 +68,9 @@ func TestMySQLHappyPath(t *testing.T) { EnsureInstance(ctx, t, tc, ruleInstance) + // Create VNet and VNetRules ----- + RunMySqlVNetRuleHappyPath(t, mySQLServerName, rgLocation) + EnsureDelete(ctx, t, tc, ruleInstance) EnsureDelete(ctx, t, tc, mySQLDBInstance) EnsureDelete(ctx, t, tc, mySQLServerInstance) diff --git a/controllers/mysqlvnetrule_controller.go b/controllers/mysqlvnetrule_controller.go new file mode 100644 index 00000000000..18b1442f109 --- /dev/null +++ b/controllers/mysqlvnetrule_controller.go @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package controllers + +import ( + ctrl "sigs.k8s.io/controller-runtime" + + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" +) + +// MySQLVNetRuleReconciler reconciles a MySQLVNetRule object +type MySQLVNetRuleReconciler struct { + Reconciler *AsyncReconciler +} + +// +kubebuilder:rbac:groups=azure.microsoft.com,resources=mysqlvnetrules,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=azure.microsoft.com,resources=mysqlvnetrules/status,verbs=get;update;patch + +func (r *MySQLVNetRuleReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + return r.Reconciler.Reconcile(req, &azurev1alpha1.MySQLVNetRule{}) +} + +func (r *MySQLVNetRuleReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&azurev1alpha1.MySQLVNetRule{}). + Complete(r) +} diff --git a/controllers/mysqlvnetrule_controller_test.go b/controllers/mysqlvnetrule_controller_test.go new file mode 100644 index 00000000000..2281c6a39f9 --- /dev/null +++ b/controllers/mysqlvnetrule_controller_test.go @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +build all mysql + +package controllers + +import ( + "context" + "strings" + "testing" + + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/stretchr/testify/assert" + + "github.com/Azure/azure-service-operator/pkg/errhelp" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestMySqlVNetRuleControllerNoResourceGroup(t *testing.T) { + t.Parallel() + defer PanicRecover(t) + ctx := context.Background() + assert := assert.New(t) + + // Add any setup steps that needs to be executed before each test + mySqlServerName := GenerateTestResourceNameWithRandom("mysqlvnetrule-test-srv", 10) + mySqlVNetRuleName := GenerateTestResourceNameWithRandom("vnetrule-dev", 10) + + // Create the SqlVnetRule object and expect the Reconcile to be created + mySqlVNetRuleInstance := &azurev1alpha1.MySQLVNetRule{ + ObjectMeta: metav1.ObjectMeta{ + Name: mySqlVNetRuleName, + Namespace: "default", + }, + Spec: azurev1alpha1.MySQLVNetRuleSpec{ + ResourceGroup: GenerateTestResourceNameWithRandom("rg-fake-srv", 10), + Server: mySqlServerName, + VNetResourceGroup: "vnet-rg", + VNetName: "test-vnet", + SubnetName: "subnet1", + IgnoreMissingServiceEndpoint: true, + }, + } + + err := tc.k8sClient.Create(ctx, mySqlVNetRuleInstance) + assert.Equal(nil, err, "create mysqlvnetrule in k8s") + + mySqlVNETRuleNamespacedName := types.NamespacedName{Name: mySqlVNetRuleName, Namespace: "default"} + + assert.Eventually(func() bool { + err = tc.k8sClient.Get(ctx, mySqlVNETRuleNamespacedName, mySqlVNetRuleInstance) + if err == nil { + return HasFinalizer(mySqlVNetRuleInstance, finalizerName) + } else { + return false + } + }, tc.timeout, tc.retry, "wait for mysqlvnetrule to have finalizer") + + assert.Eventually(func() bool { + err = tc.k8sClient.Get(ctx, mySqlVNETRuleNamespacedName, mySqlVNetRuleInstance) + if err == nil { + return strings.Contains(mySqlVNetRuleInstance.Status.Message, errhelp.ResourceGroupNotFoundErrorCode) + } else { + return false + } + }, tc.timeout, tc.retry, "wait for mysqlvnetrule to have rg not found error") + + err = tc.k8sClient.Delete(ctx, mySqlVNetRuleInstance) + assert.Equal(nil, err, "delete mysqlvnetrule in k8s") + + assert.Eventually(func() bool { + err = tc.k8sClient.Get(ctx, mySqlVNETRuleNamespacedName, mySqlVNetRuleInstance) + return apierrors.IsNotFound(err) + }, tc.timeout, tc.retry, "wait for mysqlvnetrule to be gone from k8s") +} + +func RunMySqlVNetRuleHappyPath(t *testing.T, mySqlServerName string, rgLocation string) { + defer PanicRecover(t) + ctx := context.Background() + + mySqlVNetRuleName := GenerateTestResourceNameWithRandom("vnet-rule", 10) + VNetName := GenerateTestResourceNameWithRandom("vnet", 10) + subnetName := "subnet-test" + VNetSubNetInstance := azurev1alpha1.VNetSubnets{ + SubnetName: subnetName, + SubnetAddressPrefix: "110.1.0.0/16", + } + + // Create a VNET + VNetInstance := &azurev1alpha1.VirtualNetwork{ + ObjectMeta: metav1.ObjectMeta{ + Name: VNetName, + Namespace: "default", + }, + Spec: azurev1alpha1.VirtualNetworkSpec{ + Location: rgLocation, + ResourceGroup: tc.resourceGroupName, + AddressSpace: "110.0.0.0/8", + Subnets: []azurev1alpha1.VNetSubnets{VNetSubNetInstance}, + }, + } + + EnsureInstance(ctx, t, tc, VNetInstance) + + // Create a VNet Rule + + mySqlVNetRuleInstance := &azurev1alpha1.MySQLVNetRule{ + ObjectMeta: metav1.ObjectMeta{ + Name: mySqlVNetRuleName, + Namespace: "default", + }, + Spec: azurev1alpha1.MySQLVNetRuleSpec{ + ResourceGroup: tc.resourceGroupName, + Server: mySqlServerName, + VNetResourceGroup: tc.resourceGroupName, + VNetName: VNetName, + SubnetName: subnetName, + IgnoreMissingServiceEndpoint: true, + }, + } + + // Create VNet Rule and ensure it was created + EnsureInstance(ctx, t, tc, mySqlVNetRuleInstance) + + // Delete a VNet Rule and ensure it was deleted + EnsureDelete(ctx, t, tc, mySqlVNetRuleInstance) + +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 2c143a82683..0b330a16f02 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -34,6 +34,7 @@ import ( mysqlDatabaseManager "github.com/Azure/azure-service-operator/pkg/resourcemanager/mysql/database" mysqlFirewallManager "github.com/Azure/azure-service-operator/pkg/resourcemanager/mysql/firewallrule" mysqlServerManager "github.com/Azure/azure-service-operator/pkg/resourcemanager/mysql/server" + mysqlvnetrule "github.com/Azure/azure-service-operator/pkg/resourcemanager/mysql/vnetrule" resourcemanagernic "github.com/Azure/azure-service-operator/pkg/resourcemanager/nic" resourcemanagerpip "github.com/Azure/azure-service-operator/pkg/resourcemanager/pip" resourcemanagerpsqldatabase "github.com/Azure/azure-service-operator/pkg/resourcemanager/psql/database" @@ -612,6 +613,22 @@ func setup() error { return err } + err = (&MySQLVNetRuleReconciler{ + Reconciler: &AsyncReconciler{ + Client: k8sManager.GetClient(), + AzureClient: mysqlvnetrule.NewMySQLVNetRuleClient(), + Telemetry: telemetry.InitializeTelemetryDefault( + "MySQLVNetRule", + ctrl.Log.WithName("controllers").WithName("MySQLVNetRule"), + ), + Recorder: k8sManager.GetEventRecorderFor("MySQLVNetRule-controller"), + Scheme: k8sManager.GetScheme(), + }, + }).SetupWithManager(k8sManager) + if err != nil { + return err + } + err = (&PostgreSQLServerReconciler{ Reconciler: &AsyncReconciler{ Client: k8sManager.GetClient(), diff --git a/devops/azure-pipelines.yaml b/devops/azure-pipelines.yaml index 77a307db075..b7b1c928d58 100644 --- a/devops/azure-pipelines.yaml +++ b/devops/azure-pipelines.yaml @@ -111,25 +111,25 @@ steps: kind create cluster export KUBECONFIG=$(kind get kubeconfig-path --name="kind") kubectl cluster-info - kubectl create namespace cert-manager - kubectl label namespace cert-manager cert-manager.io/disable-validation=true - kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.12.0/cert-manager.yaml - kubectl create namespace azureoperator-system - kubectl --namespace azureoperator-system \ - create secret generic azureoperatorsettings \ - --from-literal=AZURE_CLIENT_ID=${AZURE_CLIENT_ID} \ - --from-literal=AZURE_CLIENT_SECRET=${AZURE_CLIENT_SECRET} \ - --from-literal=AZURE_SUBSCRIPTION_ID=${AZURE_SUBSCRIPTION_ID} \ - --from-literal=AZURE_TENANT_ID=${AZURE_TENANT_ID} + # kubectl create namespace cert-manager + # kubectl label namespace cert-manager cert-manager.io/disable-validation=true + # kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.12.0/cert-manager.yaml + # kubectl create namespace azureoperator-system + # kubectl --namespace azureoperator-system \ + # create secret generic azureoperatorsettings \ + # --from-literal=AZURE_CLIENT_ID=${AZURE_CLIENT_ID} \ + # --from-literal=AZURE_CLIENT_SECRET=${AZURE_CLIENT_SECRET} \ + # --from-literal=AZURE_SUBSCRIPTION_ID=${AZURE_SUBSCRIPTION_ID} \ + # --from-literal=AZURE_TENANT_ID=${AZURE_TENANT_ID} #create image and load it into cluster - IMG="docker.io/controllertest:1" make docker-build - kind load docker-image docker.io/controllertest:1 --loglevel "trace" + # IMG="docker.io/controllertest:1" make docker-build + # kind load docker-image docker.io/controllertest:1 --loglevel "trace" make install kubectl get namespaces - kubectl -n cert-manager rollout status deployment.v1.apps/cert-manager - kubectl get pods --namespace cert-manager - echo "all the pods should be running" - make deploy + # kubectl -n cert-manager rollout status deployment.v1.apps/cert-manager + # kubectl get pods --namespace cert-manager + # echo "all the pods should be running" + #make deploy make test-existing-controllers continueOnError: 'false' displayName: 'Set kind cluster and Run int tests' diff --git a/docs/mysql/mysql.md b/docs/mysql/mysql.md index 4185aac9197..3a4c7e229d3 100644 --- a/docs/mysql/mysql.md +++ b/docs/mysql/mysql.md @@ -55,6 +55,14 @@ The `server` indicates the MySQL server on which you want to configure the new M *Note*: When the `startIpAddress` and `endIpAddress` are 0.0.0.0, it denotes a special case that adds a firewall rule to allow all Azure services to access the server. +### MySQL virtual network rule + +The MySQL virtual network rule operator allows you to add virtual network rules to the MySQL server. + +The `server` indicates the MySQL server on which you want to configure the new MySQL virtual network rule on and `resourceGroup` is the resource group of the MySQL server. Provide the virtual network name and subnet name in the variables `vNetName` and `subnetName`, and `vNetResourceGroup` is the resource group the virtual network is located in. The `ignoreMissingServiceEndpoint` indicates whether or not to create virtual network rule before the virtual network has vnet service endpoint enabled. + +*Note*: When using MySQL Virtual Network Rules, the `Basic` SKU is not a valid op + ## Deploy, view and delete resources You can follow the steps [here](/docs/customresource.md) to deploy, view and delete resources. diff --git a/main.go b/main.go index b675d0a7b3e..0b579e02334 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,7 @@ import ( mysqldatabase "github.com/Azure/azure-service-operator/pkg/resourcemanager/mysql/database" mysqlfirewall "github.com/Azure/azure-service-operator/pkg/resourcemanager/mysql/firewallrule" mysqlserver "github.com/Azure/azure-service-operator/pkg/resourcemanager/mysql/server" + mysqlvnetrule "github.com/Azure/azure-service-operator/pkg/resourcemanager/mysql/vnetrule" nic "github.com/Azure/azure-service-operator/pkg/resourcemanager/nic" pip "github.com/Azure/azure-service-operator/pkg/resourcemanager/pip" psqldatabase "github.com/Azure/azure-service-operator/pkg/resourcemanager/psql/database" @@ -645,6 +646,22 @@ func main() { os.Exit(1) } + if err = (&controllers.MySQLVNetRuleReconciler{ + Reconciler: &controllers.AsyncReconciler{ + Client: mgr.GetClient(), + AzureClient: mysqlvnetrule.NewMySQLVNetRuleClient(), + Telemetry: telemetry.InitializeTelemetryDefault( + "MySQLVNetRule", + ctrl.Log.WithName("controllers").WithName("MySQLVNetRule"), + ), + Recorder: mgr.GetEventRecorderFor("MySQLVNetRule-controller"), + Scheme: scheme, + }, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "MySQLVNetRule") + os.Exit(1) + } + if err = (&controllers.AzureVirtualMachineReconciler{ Reconciler: &controllers.AsyncReconciler{ Client: mgr.GetClient(), diff --git a/pkg/resourcemanager/mysql/vnetrule/client.go b/pkg/resourcemanager/mysql/vnetrule/client.go new file mode 100644 index 00000000000..4f264b60b19 --- /dev/null +++ b/pkg/resourcemanager/mysql/vnetrule/client.go @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package server + +import ( + "context" + + mysql "github.com/Azure/azure-sdk-for-go/services/mysql/mgmt/2017-12-01/mysql" + network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/iam" +) + +type MySQLVNetRuleClient struct { +} + +func NewMySQLVNetRuleClient() *MySQLVNetRuleClient { + return &MySQLVNetRuleClient{} +} + +func getMySQLVNetRulesClient() mysql.VirtualNetworkRulesClient { + VNetRulesClient := mysql.NewVirtualNetworkRulesClientWithBaseURI(config.BaseURI(), config.SubscriptionID()) + a, _ := iam.GetResourceManagementAuthorizer() + VNetRulesClient.Authorizer = a + VNetRulesClient.AddToUserAgent(config.UserAgent()) + return VNetRulesClient +} + +// GetNetworkSubnetClient retrieves a Subnetclient +func GetGoNetworkSubnetClient() network.SubnetsClient { + SubnetsClient := network.NewSubnetsClientWithBaseURI(config.BaseURI(), config.SubscriptionID()) + a, _ := iam.GetResourceManagementAuthorizer() + SubnetsClient.Authorizer = a + SubnetsClient.AddToUserAgent(config.UserAgent()) + return SubnetsClient +} + +// GetSQLVNetRule returns a VNet rule +func (vr *MySQLVNetRuleClient) GetSQLVNetRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string) (result mysql.VirtualNetworkRule, err error) { + VNetRulesClient := getMySQLVNetRulesClient() + + return VNetRulesClient.Get( + ctx, + resourceGroupName, + serverName, + ruleName, + ) +} + +// DeleteSQLVNetRule deletes a VNet rule +func (vr *MySQLVNetRuleClient) DeleteSQLVNetRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string) (err error) { + + // check to see if the rule exists, if it doesn't then short-circuit + _, err = vr.GetSQLVNetRule(ctx, resourceGroupName, serverName, ruleName) + if err != nil { + return nil + } + + VNetRulesClient := getMySQLVNetRulesClient() + _, err = VNetRulesClient.Delete( + ctx, + resourceGroupName, + serverName, + ruleName, + ) + + return err +} + +// CreateOrUpdateSQLVNetRule creates or updates a VNet rule +// based on code from: https://godoc.org/github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql#VirtualNetworkRulesClient.CreateOrUpdate +func (vr *MySQLVNetRuleClient) CreateOrUpdateSQLVNetRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string, VNetRG string, VNetName string, SubnetName string, IgnoreServiceEndpoint bool) (vnr mysql.VirtualNetworkRule, err error) { + + VNetRulesClient := getMySQLVNetRulesClient() + SubnetClient := GetGoNetworkSubnetClient() + + // Get ARM Resource ID of Subnet based on the VNET name, Subnet name and Subnet Address Prefix + subnet, err := SubnetClient.Get(ctx, VNetRG, VNetName, SubnetName, "") + if err != nil { + return vnr, err + } + subnetResourceID := *subnet.ID + + // Populate parameters with the right ID + parameters := mysql.VirtualNetworkRule{ + VirtualNetworkRuleProperties: &mysql.VirtualNetworkRuleProperties{ + VirtualNetworkSubnetID: &subnetResourceID, + IgnoreMissingVnetServiceEndpoint: &IgnoreServiceEndpoint, + }, + } + + // Call CreateOrUpdate + result, err := VNetRulesClient.CreateOrUpdate( + ctx, + resourceGroupName, + serverName, + ruleName, + parameters, + ) + return result.Result(VNetRulesClient) +} diff --git a/pkg/resourcemanager/mysql/vnetrule/manager.go b/pkg/resourcemanager/mysql/vnetrule/manager.go new file mode 100644 index 00000000000..c7cd5f657fd --- /dev/null +++ b/pkg/resourcemanager/mysql/vnetrule/manager.go @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package server + +import ( + "context" + + mysql "github.com/Azure/azure-sdk-for-go/services/mysql/mgmt/2017-12-01/mysql" + "github.com/Azure/azure-service-operator/pkg/resourcemanager" +) + +type MySqlVNetRuleManager interface { + CreateOrUpdateMySQLVNetRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string, VNetRG string, VNetName string, SubnetName string, IgnoreServiceEndpoint bool) (result mysql.VirtualNetworkRule, err error) + DeleteMySQLVNetRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string) (err error) + GetMySQLVNetRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string) (result mysql.VirtualNetworkRule, err error) + resourcemanager.ARMClient +} diff --git a/pkg/resourcemanager/mysql/vnetrule/reconcile.go b/pkg/resourcemanager/mysql/vnetrule/reconcile.go new file mode 100644 index 00000000000..e49c0f8dc97 --- /dev/null +++ b/pkg/resourcemanager/mysql/vnetrule/reconcile.go @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package server + +import ( + "context" + "fmt" + "strings" + + mysql "github.com/Azure/azure-sdk-for-go/services/mysql/mgmt/2017-12-01/mysql" + 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" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +) + +// Ensure creates a sqlvnetrule +func (vr *MySQLVNetRuleClient) Ensure(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { + instance, err := vr.convert(obj) + if err != nil { + return false, err + } + + groupName := instance.Spec.ResourceGroup + server := instance.Spec.Server + ruleName := instance.ObjectMeta.Name + virtualNetworkRG := instance.Spec.VNetResourceGroup + virtualnetworkname := instance.Spec.VNetName + subnetName := instance.Spec.SubnetName + ignoreendpoint := instance.Spec.IgnoreMissingServiceEndpoint + + vnetrule, err := vr.GetSQLVNetRule(ctx, groupName, server, ruleName) + if err == nil { + if vnetrule.VirtualNetworkRuleProperties != nil && vnetrule.VirtualNetworkRuleProperties.State == mysql.Ready { + instance.Status.Provisioning = false + instance.Status.Provisioned = true + instance.Status.Message = resourcemanager.SuccessMsg + instance.Status.ResourceId = *vnetrule.ID + return true, nil + } + return false, nil + } + instance.Status.Message = fmt.Sprintf("MySQLVNetRule Get error %s", err.Error()) + requeuErrors := []string{ + errhelp.ResourceGroupNotFoundErrorCode, + errhelp.ParentNotFoundErrorCode, + } + azerr := errhelp.NewAzureErrorAzureError(err) + if helpers.ContainsString(requeuErrors, azerr.Type) { + instance.Status.Provisioning = false + return false, nil + } + + instance.Status.Provisioning = true + _, err = vr.CreateOrUpdateSQLVNetRule(ctx, groupName, server, ruleName, virtualNetworkRG, virtualnetworkname, subnetName, ignoreendpoint) + if err != nil { + instance.Status.Message = err.Error() + azerr := errhelp.NewAzureErrorAzureError(err) + + if azerr.Type == errhelp.AsyncOpIncompleteError { + instance.Status.Provisioning = true + instance.Status.Message = "Resource request submitted to Azure successfully" + return false, nil + } + + ignorableErrors := []string{ + errhelp.ResourceGroupNotFoundErrorCode, + errhelp.ParentNotFoundErrorCode, + errhelp.ResourceNotFound, + } + if helpers.ContainsString(ignorableErrors, azerr.Type) { + instance.Status.Provisioning = false + return false, nil + } + + // this happens when we try to create the VNet rule and the server doesnt exist yet + errorString := err.Error() + if strings.Contains(errorString, "does not have the server") { + instance.Status.Provisioning = false + return false, nil + } + + return false, err + } + + return false, nil // We requeue so the success can be caught in the Get() path +} + +// Delete drops a sqlvnetrule +func (vr *MySQLVNetRuleClient) Delete(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { + instance, err := vr.convert(obj) + if err != nil { + return false, err + } + + groupName := instance.Spec.ResourceGroup + server := instance.Spec.Server + ruleName := instance.ObjectMeta.Name + + err = vr.DeleteSQLVNetRule(ctx, groupName, server, ruleName) + if err != nil { + instance.Status.Message = err.Error() + + azerr := errhelp.NewAzureErrorAzureError(err) + // these errors are expected + ignore := []string{ + errhelp.AsyncOpIncompleteError, + } + + // this means the thing doesn't exist + finished := []string{ + errhelp.ResourceNotFound, + errhelp.ParentNotFoundErrorCode, + } + + if helpers.ContainsString(ignore, azerr.Type) { + return true, nil //requeue + } + + if helpers.ContainsString(finished, azerr.Type) { + return false, nil //end reconcile + } + return false, err + } + + return false, nil +} + +// GetParents returns the parents of sqlvnetrule +func (vr *MySQLVNetRuleClient) GetParents(obj runtime.Object) ([]resourcemanager.KubeParent, error) { + instance, err := vr.convert(obj) + if err != nil { + return nil, err + } + + return []resourcemanager.KubeParent{ + { + Key: types.NamespacedName{ + Namespace: instance.Namespace, + Name: instance.Spec.Server, + }, + Target: &azurev1alpha1.MySQLServer{}, + }, + { + Key: types.NamespacedName{ + Namespace: instance.Namespace, + Name: instance.Spec.ResourceGroup, + }, + Target: &azurev1alpha1.ResourceGroup{}, + }, + }, nil +} + +func (vr *MySQLVNetRuleClient) GetStatus(obj runtime.Object) (*azurev1alpha1.ASOStatus, error) { + instance, err := vr.convert(obj) + if err != nil { + return nil, err + } + return &instance.Status, nil +} + +func (vr *MySQLVNetRuleClient) convert(obj runtime.Object) (*azurev1alpha1.MySQLVNetRule, error) { + local, ok := obj.(*azurev1alpha1.MySQLVNetRule) + if !ok { + return nil, fmt.Errorf("failed type assertion on kind: %s", obj.GetObjectKind().GroupVersionKind().String()) + } + return local, nil +}