diff --git a/Makefile b/Makefile index 9cd662efef0..889450e47a8 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ GOBIN=$(shell go env GOBIN) endif # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) -CRD_OPTIONS ?= "crd" +CRD_OPTIONS ?= "crd:crdVersions=v1beta1" BUILD_ID ?= $(shell git rev-parse --short HEAD) timestamp := $(shell /bin/date "+%Y%m%d-%H%M%S") @@ -55,9 +55,17 @@ generate-test-certs: .PHONY: test-integration-controllers test-integration-controllers: generate fmt vet manifests TEST_RESOURCE_PREFIX=$(TEST_RESOURCE_PREFIX) TEST_USE_EXISTING_CLUSTER=false REQUEUE_AFTER=20 \ - go test -v -tags "$(BUILD_TAGS)" -coverprofile=reports/integration-controllers-coverage-output.txt -coverpkg=./... -covermode count -parallel 4 -timeout 45m ./controllers/... - #2>&1 | tee reports/integration-controllers-output.txt - #go-junit-report < reports/integration-controllers-output.txt > reports/integration-controllers-report.xml + go test -v -tags "$(BUILD_TAGS)" -coverprofile=reports/integration-controllers-coverage-output.txt -coverpkg=./... -covermode count -parallel 4 -timeout 45m \ + ./controllers/... \ + ./pkg/secrets/... + # TODO: Note that the above test (secrets/keyvault) is not an integration-controller test... but it's not a unit test either and unfortunately the test-integration-managers target isn't run in CI either? + +# Run subset of tests with v1 secret naming enabled to ensure no regression in old secret naming +.PHONY: test-v1-secret-naming +test-v1-secret-naming: generate fmt vet manifests + TEST_RESOURCE_PREFIX=$(TEST_RESOURCE_PREFIX) TEST_USE_EXISTING_CLUSTER=false REQUEUE_AFTER=20 AZURE_SECRET_NAMING_VERSION=1 \ + go test -v -run "^.*_SecretNamedCorrectly$$" -tags "$(BUILD_TAGS)" -coverprofile=reports/v1-secret-naming-coverage-output.txt -coverpkg=./... -covermode count -parallel 4 -timeout 10m \ + ./controllers/... # Run Resource Manager tests against the configured cluster .PHONY: test-integration-managers @@ -73,10 +81,8 @@ test-integration-managers: generate fmt vet manifests ./pkg/resourcemanager/psql/firewallrule/... \ ./pkg/resourcemanager/appinsights/... \ ./pkg/resourcemanager/vnet/... - #2>&1 | tee reports/integration-managers-output.txt - #go-junit-report < reports/integration-managers-output.txt > reports/integration-managers-report.xml -# Run all available tests. Note that Controllers are not unit-testable. +# Run all available unit tests. .PHONY: test-unit test-unit: generate fmt vet manifests TEST_USE_EXISTING_CLUSTER=false REQUEUE_AFTER=20 \ @@ -85,8 +91,6 @@ test-unit: generate fmt vet manifests ./pkg/resourcemanager/azuresql/azuresqlfailovergroup # The below folders are commented out because the tests in them fail... # ./api/... \ - # ./pkg/secrets/... \ - #2>&1 | tee testlogs.txt # Merge all the available test coverage results and publish a single report .PHONY: test-process-coverage @@ -223,12 +227,9 @@ generate: manifests # download controller-gen if necessary .PHONY: controller-gen controller-gen: -ifeq (, $(shell which controller-gen)) - go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.5 -CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen -else -CONTROLLER_GEN=$(shell which controller-gen) -endif + go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.0 + CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen + .PHONY: install-bindata install-bindata: @@ -316,13 +317,12 @@ install-aad-pod-identity: kubectl apply -f https://raw.githubusercontent.com/Azure/aad-pod-identity/master/deploy/infra/deployment-rbac.yaml .PHONY: install-test-dependencies -install-test-dependencies: +install-test-dependencies: controller-gen go get github.com/jstemmer/go-junit-report \ && go get github.com/axw/gocov/gocov \ && go get github.com/AlekSi/gocov-xml \ && go get github.com/wadey/gocovmerge \ && go get k8s.io/code-generator/cmd/conversion-gen@v0.18.2 \ - && go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.0 \ && go get sigs.k8s.io/kind@v0.9.0 \ && go get sigs.k8s.io/kustomize/kustomize/v3@v3.8.6 diff --git a/README.md b/README.md index 6eefd530468..60a67f06854 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ Ready to quickly deploy the latest version of Azure Service Operator on your Kub --set azureClientSecret=$AZURE_CLIENT_SECRET ``` - If you would like to install an older version you can list the avialable versions: + If you would like to install an older version you can list the available versions: ```sh helm search repo aso --versions ``` diff --git a/api/v1alpha1/azuresqluser_types.go b/api/v1alpha1/azuresqluser_types.go index 26c369d0e59..b22f4ab10ad 100644 --- a/api/v1alpha1/azuresqluser_types.go +++ b/api/v1alpha1/azuresqluser_types.go @@ -36,7 +36,7 @@ type AzureSQLUserSpec struct { AdminSecretKeyVault string `json:"adminSecretKeyVault,omitempty"` Username string `json:"username,omitempty"` KeyVaultToStoreSecrets string `json:"keyVaultToStoreSecrets,omitempty"` - KeyVaultSecretPrefix string `json:"keyVaultSecretPrefix,omitempty"` + KeyVaultSecretPrefix string `json:"keyVaultSecretPrefix,omitempty"` // TODO: Remove this in a future version? KeyVaultSecretFormats []string `json:"keyVaultSecretFormats,omitempty"` } diff --git a/api/v1alpha1/storageaccount_types.go b/api/v1alpha1/storageaccount_types.go index 2eb8e366ffe..48474acacec 100644 --- a/api/v1alpha1/storageaccount_types.go +++ b/api/v1alpha1/storageaccount_types.go @@ -39,7 +39,8 @@ type StorageAccountSpec struct { // StorageAccountSku the SKU of the storage account. type StorageAccountSku struct { // Name - The SKU name. Required for account creation; optional for update. - // Possible values include: 'StandardLRS', 'StandardGRS', 'StandardRAGRS', 'StandardZRS', 'PremiumLRS', 'PremiumZRS', 'StandardGZRS', 'StandardRAGZRS' + // Possible values include: 'Standard_LRS', 'Standard_GRS', 'Standard_RAGRS', 'Standard_ZRS', 'Premium_LRS', 'Premium_ZRS', 'Standard_GZRS', 'Standard_RAGZRS'. + // For the full list of allowed options, see: https://docs.microsoft.com/en-us/rest/api/storagerp/storageaccounts/create#skuname Name StorageAccountSkuName `json:"name,omitempty"` } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index de0e9964407..3babd76e2d4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -135,6 +135,26 @@ steps: BUILD_ID: $(Build.BuildId) workingDirectory: '$(System.DefaultWorkingDirectory)' + # TODO: There is no way to run steps in parallel in Azure pipelines but ideally this step would run in parallel + # TODO: with the above testing step to reduce overall runtime + - script: | + set -e + export PATH=$PATH:$(go env GOPATH)/bin:$(go env GOPATH)/kubebuilder/bin + export KUBEBUILDER_ASSETS=$(go env GOPATH)/kubebuilder/bin + make test-v1-secret-naming + displayName: Run legacy v1 secret naming tests + condition: eq(variables['check_changes.SOURCE_CODE_CHANGED'], 'true') + continueOnError: 'false' + env: + GO111MODULE: on + AZURE_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID) + AZURE_TENANT_ID: $(AZURE_TENANT_ID) + AZURE_CLIENT_ID: $(AZURE_CLIENT_ID) + AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET) + REQUEUE_AFTER: $(REQUEUE_AFTER) + BUILD_ID: $(Build.BuildId) + workingDirectory: '$(System.DefaultWorkingDirectory)' + - script: | set -e export PATH=$PATH:$(go env GOPATH)/bin diff --git a/charts/azure-service-operator/templates/secret.yaml b/charts/azure-service-operator/templates/secret.yaml index cf6259bbedb..658f13da387 100644 --- a/charts/azure-service-operator/templates/secret.yaml +++ b/charts/azure-service-operator/templates/secret.yaml @@ -20,4 +20,7 @@ data: {{- end }} {{- if .Values.azureOperatorKeyvault }} AZURE_OPERATOR_KEYVAULT: {{ .Values.azureOperatorKeyvault | b64enc | quote }} - {{- end }} \ No newline at end of file + {{- end }} + {{- if .Values.azureSecretNamingVersion }} + AZURE_SECRET_NAMING_VERSION: {{ .Values.azureSecretNamingVersion | b64enc | quote }} + {{- end }} diff --git a/charts/azure-service-operator/values.yaml b/charts/azure-service-operator/values.yaml index cdafff202c4..691b34265ca 100644 --- a/charts/azure-service-operator/values.yaml +++ b/charts/azure-service-operator/values.yaml @@ -19,6 +19,9 @@ azureClientSecret: "" # azureUseMI determines if ASO will use a Managed Identity to authenticate. azureUseMI: False +# azureSecretNamingVersion allows choosing the algorithm used to derive secret names. Version 2 is recommended. +azureSecretNamingVersion: "2" + # image defines the container image the ASO pod should run # Note: This should use the latest released tag number explicitly. If # it's ':latest' and someone deploys the chart after a new version has diff --git a/config/default/manager_image_patch.yaml b/config/default/manager_image_patch.yaml index edebac22717..684ba263e6f 100644 --- a/config/default/manager_image_patch.yaml +++ b/config/default/manager_image_patch.yaml @@ -51,6 +51,12 @@ spec: key: AZURE_CLOUD_ENV name: azureoperatorsettings optional: true + - name: AZURE_SECRET_NAMING_VERSION + valueFrom: + secretKeyRef: + name: azureoperatorsettings + key: AZURE_SECRET_NAMING_VERSION + optional: true # Used along with aad-pod-identity integration, but set always # because it doesn't hurt - name: POD_NAMESPACE diff --git a/controllers/appinsights_controller_test.go b/controllers/appinsights_controller_test.go index 6d9498fd571..0eb1298a7bc 100644 --- a/controllers/appinsights_controller_test.go +++ b/controllers/appinsights_controller_test.go @@ -9,8 +9,9 @@ import ( "context" "testing" - azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" ) func TestAppInsightsController(t *testing.T) { @@ -23,7 +24,7 @@ func TestAppInsightsController(t *testing.T) { appInsightsName := GenerateTestResourceName("appinsights") // Create an instance of Azure AppInsights - appInsightsInstance := &azurev1alpha1.AppInsights{ + instance := &azurev1alpha1.AppInsights{ ObjectMeta: metav1.ObjectMeta{ Name: appInsightsName, Namespace: "default", @@ -36,7 +37,7 @@ func TestAppInsightsController(t *testing.T) { }, } - EnsureInstance(ctx, t, tc, appInsightsInstance) + EnsureInstance(ctx, t, tc, instance) - EnsureDelete(ctx, t, tc, appInsightsInstance) + EnsureDelete(ctx, t, tc, instance) } diff --git a/controllers/async_controller.go b/controllers/async_controller.go index ea0f2d7256d..fbb84b86f15 100644 --- a/controllers/async_controller.go +++ b/controllers/async_controller.go @@ -26,9 +26,9 @@ import ( ) const ( - finalizerName string = "azure.microsoft.com/finalizer" - requeDuration time.Duration = time.Second * 20 - successMsg string = "successfully provisioned" + finalizerName string = "azure.microsoft.com/finalizer" + requeueDuration time.Duration = time.Second * 20 + successMsg string = "successfully provisioned" ) // AsyncReconciler is a generic reconciler for Azure resources. @@ -72,20 +72,21 @@ func (r *AsyncReconciler) Reconcile(req ctrl.Request, obj runtime.Object) (resul var keyvaultSecretClient secrets.SecretClient // Determine if we need to check KeyVault for secrets - KeyVaultName := keyvaultsecretlib.GetKeyVaultName(obj) + keyVaultName := keyvaultsecretlib.GetKeyVaultName(obj) - if len(KeyVaultName) != 0 { + if len(keyVaultName) != 0 { // Instantiate the KeyVault Secret Client - keyvaultSecretClient = keyvaultsecretlib.New(KeyVaultName, config.GlobalCredentials()) + keyvaultSecretClient = keyvaultsecretlib.New(keyVaultName, config.GlobalCredentials(), config.SecretNamingVersion()) r.Telemetry.LogInfoByInstance("status", "ensuring vault", req.String()) + // TODO: It's really awkward that we do this so often? if !keyvaultsecretlib.IsKeyVaultAccessible(keyvaultSecretClient) { r.Telemetry.LogInfoByInstance("requeuing", "awaiting vault verification", req.String()) // update the status of the resource in kubernetes status.Message = "Waiting for secretclient keyvault to be available" - return ctrl.Result{RequeueAfter: requeDuration}, r.Status().Update(ctx, obj) + return ctrl.Result{RequeueAfter: requeueDuration}, r.Status().Update(ctx, obj) } } @@ -118,7 +119,7 @@ func (r *AsyncReconciler) Reconcile(req ctrl.Request, obj runtime.Object) (resul } } else { if HasFinalizer(res, finalizerName) { - if len(KeyVaultName) != 0 { //KeyVault was specified in Spec, so use that for secrets + if len(keyVaultName) != 0 { // keyVault was specified in Spec, so use that for secrets configOptions = append(configOptions, resourcemanager.WithSecretClient(keyvaultSecretClient)) } found, deleteErr := r.AzureClient.Delete(ctx, obj, configOptions...) @@ -134,7 +135,7 @@ func (r *AsyncReconciler) Reconcile(req ctrl.Request, obj runtime.Object) (resul return ctrl.Result{}, r.Update(ctx, obj) } r.Telemetry.LogInfoByInstance("requeuing", "deletion unfinished", req.String()) - return ctrl.Result{RequeueAfter: requeDuration}, r.Status().Update(ctx, obj) + return ctrl.Result{RequeueAfter: requeueDuration}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } @@ -158,7 +159,7 @@ func (r *AsyncReconciler) Reconcile(req ctrl.Request, obj runtime.Object) (resul r.Telemetry.LogInfoByInstance("status", "reconciling object", req.String()) - if len(KeyVaultName) != 0 { //KeyVault was specified in Spec, so use that for secrets + if len(keyVaultName) != 0 { //KeyVault was specified in Spec, so use that for secrets configOptions = append(configOptions, resourcemanager.WithSecretClient(keyvaultSecretClient)) } @@ -191,7 +192,7 @@ func (r *AsyncReconciler) Reconcile(req ctrl.Request, obj runtime.Object) (resul result = ctrl.Result{} if !done { r.Telemetry.LogInfoByInstance("status", "reconciling object not finished", req.String()) - result.RequeueAfter = requeDuration + result.RequeueAfter = requeueDuration } else { r.Telemetry.LogInfoByInstance("reconciling", "success", req.String()) diff --git a/controllers/azuresql_combined_test.go b/controllers/azuresql_combined_test.go index de576db037d..10b447cf3db 100644 --- a/controllers/azuresql_combined_test.go +++ b/controllers/azuresql_combined_test.go @@ -13,18 +13,15 @@ import ( sql "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/v3.0/sql" "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" "github.com/Azure/azure-service-operator/api/v1beta1" "github.com/Azure/azure-service-operator/pkg/errhelp" - helpers "github.com/Azure/azure-service-operator/pkg/helpers" "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlshared" "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" - kvsecrets "github.com/Azure/azure-service-operator/pkg/secrets/keyvault" + "github.com/Azure/azure-service-operator/pkg/secrets" ) func TestAzureSqlServerCombinedHappyPath(t *testing.T) { @@ -54,19 +51,6 @@ func TestAzureSqlServerCombinedHappyPath(t *testing.T) { RequireInstance(ctx, t, tc, sqlServerInstance) RequireInstance(ctx, t, tc, sqlServerInstance2) - //verify secret exists in k8s for server 1 --------------------------------- - secret := &v1.Secret{} - assert.Eventually(func() bool { - err = tc.k8sClient.Get(ctx, types.NamespacedName{Name: sqlServerName, Namespace: sqlServerInstance.Namespace}, secret) - - if err == nil { - if (secret.ObjectMeta.Name == sqlServerName) && (secret.ObjectMeta.Namespace == sqlServerInstance.Namespace) { - return true - } - } - return false - }, tc.timeoutFast, tc.retry, "wait for server to have secret") - sqlDatabaseName1 := GenerateTestResourceNameWithRandom("sqldatabase", 10) sqlDatabaseName2 := GenerateTestResourceNameWithRandom("sqldatabase", 10) sqlDatabaseName3 := GenerateTestResourceNameWithRandom("sqldatabase", 10) @@ -104,11 +88,6 @@ func TestAzureSqlServerCombinedHappyPath(t *testing.T) { // run sub tests that require 1 sql server ---------------------------------- t.Run("group1", func(t *testing.T) { - t.Run("sub test for actions", func(t *testing.T) { - t.Parallel() - RunSQLActionHappy(t, sqlServerName) - }) - t.Run("set up second database in primary server using sku with maxsizebytes, then update it to use a different SKU", func(t *testing.T) { t.Parallel() @@ -263,175 +242,6 @@ func TestAzureSqlServerCombinedHappyPath(t *testing.T) { t.Parallel() RunAzureSqlVNetRuleHappyPath(t, sqlServerName, rgLocation) }) - - }) - - var sqlUser *azurev1alpha1.AzureSQLUser - var kvSqlUser1 *azurev1alpha1.AzureSQLUser - var kvSqlUser2 *azurev1alpha1.AzureSQLUser - - // run sub tests that require 2 servers or have to be run after rolladmincreds test ------------------ - t.Run("group2", func(t *testing.T) { - - t.Run("set up user in first db", func(t *testing.T) { - t.Parallel() - - // create a sql user and verify it provisions - username := "sql-test-user" + helpers.RandomString(10) - roles := []string{"db_owner"} - keyVaultSecretFormats := []string{"adonet"} - - sqlUser = &azurev1alpha1.AzureSQLUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: username, - Namespace: "default", - }, - Spec: azurev1alpha1.AzureSQLUserSpec{ - Server: sqlServerName, - DbName: sqlDatabaseName1, - ResourceGroup: rgName, - Roles: roles, - KeyVaultSecretFormats: keyVaultSecretFormats, - }, - } - - EnsureInstance(ctx, t, tc, sqlUser) - - // verify user's secret has been created - // this test suite defaults to Kube Secrets. They do not support keyvault-specific config but the spec is passed anyway - // to verify that passing them does not break the service - assert.Eventually(func() bool { - key := types.NamespacedName{Name: sqlUser.ObjectMeta.Name, Namespace: sqlUser.ObjectMeta.Namespace} - var secrets, _ = tc.secretClient.Get(ctx, key) - - return strings.Contains(string(secrets["azureSqlDatabaseName"]), sqlDatabaseName1) - }, tc.timeoutFast, tc.retry, "wait for secret store to show azure sql user credentials") - - t.Log(sqlUser.Status) - }) - - t.Run("set up user in first db with custom keyvault", func(t *testing.T) { - t.Parallel() - - // create a sql user and verify it provisions - username := "sql-test-user" + helpers.RandomString(10) - roles := []string{"db_owner"} - - // This test will attempt to persist secrets to the KV that was instantiated as part of the test suite - keyVaultName := tc.keyvaultName - - kvSqlUser1 = &azurev1alpha1.AzureSQLUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: username, - Namespace: "default", - }, - Spec: azurev1alpha1.AzureSQLUserSpec{ - Server: sqlServerName, - DbName: sqlDatabaseName1, - ResourceGroup: rgName, - Roles: roles, - KeyVaultToStoreSecrets: keyVaultName, - }, - } - - EnsureInstance(ctx, t, tc, kvSqlUser1) - - // Check that the user's secret is in the keyvault - keyVaultSecretClient := kvsecrets.New(keyVaultName, config.GlobalCredentials()) - - assert.Eventually(func() bool { - keyNamespace := "azuresqluser-" + sqlServerName + "-" + sqlDatabaseName1 - key := types.NamespacedName{Name: kvSqlUser1.ObjectMeta.Name, Namespace: keyNamespace} - var secrets, _ = keyVaultSecretClient.Get(ctx, key) - - return strings.Contains(string(secrets["azureSqlDatabaseName"]), sqlDatabaseName1) - }, tc.timeoutFast, tc.retry, "wait for keyvault to show azure sql user credentials") - - t.Log(kvSqlUser1.Status) - }) - - t.Run("set up user in first db with custom keyvault and custom formatting", func(t *testing.T) { - t.Parallel() - - // create a sql user and verify it provisions - username := "sql-test-user" + helpers.RandomString(10) - roles := []string{"db_owner"} - formats := []string{"adonet"} - - // This test will attempt to persist secrets to the KV that was instantiated as part of the test suite - keyVaultName := tc.keyvaultName - - kvSqlUser2 = &azurev1alpha1.AzureSQLUser{ - ObjectMeta: metav1.ObjectMeta{ - Name: username, - Namespace: "default", - }, - Spec: azurev1alpha1.AzureSQLUserSpec{ - Server: sqlServerName, - DbName: sqlDatabaseName1, - ResourceGroup: rgName, - Roles: roles, - KeyVaultToStoreSecrets: keyVaultName, - KeyVaultSecretFormats: formats, - }, - } - - EnsureInstance(ctx, t, tc, kvSqlUser2) - - // Check that the user's secret is in the keyvault - keyVaultSecretClient := kvsecrets.New(keyVaultName, config.GlobalCredentials()) - - assert.Eventually(func() bool { - keyNamespace := "azuresqluser-" + sqlServerName + "-" + sqlDatabaseName1 - keyName := kvSqlUser2.ObjectMeta.Name + "-adonet" - key := types.NamespacedName{Name: keyName, Namespace: keyNamespace} - var secrets, _ = keyVaultSecretClient.Get(ctx, key) - - return len(string(secrets[keyNamespace+"-"+keyName])) > 0 - }, tc.timeoutFast, tc.retry, "wait for keyvault to show azure sql user credentials with custom formats") - - t.Log(kvSqlUser2.Status) - }) - }) - - t.Run("deploy sql action and roll user credentials", func(t *testing.T) { - keyNamespace := "azuresqluser-" + sqlServerName + "-" + sqlDatabaseName1 - key := types.NamespacedName{Name: kvSqlUser1.ObjectMeta.Name, Namespace: keyNamespace} - - keyVaultName := tc.keyvaultName - keyVaultSecretClient := kvsecrets.New(keyVaultName, config.GlobalCredentials()) - var oldSecret, _ = keyVaultSecretClient.Get(ctx, key) - - sqlActionName := GenerateTestResourceNameWithRandom("azuresqlaction-dev", 10) - sqlActionInstance := &azurev1alpha1.AzureSqlAction{ - ObjectMeta: metav1.ObjectMeta{ - Name: sqlActionName, - Namespace: "default", - }, - Spec: azurev1alpha1.AzureSqlActionSpec{ - ResourceGroup: rgName, - ServerName: sqlServerName, - ActionName: "rollusercreds", - DbName: sqlDatabaseName1, - DbUser: kvSqlUser1.ObjectMeta.Name, - UserSecretKeyVault: keyVaultName, - }, - } - - err := tc.k8sClient.Create(ctx, sqlActionInstance) - assert.Equal(nil, err, "create sqlaction in k8s") - - sqlActionInstanceNamespacedName := types.NamespacedName{Name: sqlActionName, Namespace: "default"} - - assert.Eventually(func() bool { - _ = tc.k8sClient.Get(ctx, sqlActionInstanceNamespacedName, sqlActionInstance) - return sqlActionInstance.Status.Provisioned - }, tc.timeout, tc.retry, "wait for sql action to be submitted") - - var newSecret, _ = keyVaultSecretClient.Get(ctx, key) - - assert.NotEqual(oldSecret["password"], newSecret["password"], "password should have been updated") - assert.Equal(oldSecret["username"], newSecret["username"], "usernames should be the same") }) var sqlFailoverGroupInstance *v1beta1.AzureSqlFailoverGroup @@ -440,42 +250,6 @@ func TestAzureSqlServerCombinedHappyPath(t *testing.T) { sqlFailoverGroupNamespacedName := types.NamespacedName{Name: sqlFailoverGroupName, Namespace: "default"} t.Run("group3", func(t *testing.T) { - t.Run("delete db users and ensure that their secrets have been cleaned up", func(t *testing.T) { - EnsureDelete(ctx, t, tc, sqlUser) - EnsureDelete(ctx, t, tc, kvSqlUser1) - EnsureDelete(ctx, t, tc, kvSqlUser2) - - // Check that the user's secret is in the keyvault - keyVaultSecretClient := kvsecrets.New(tc.keyvaultName, config.GlobalCredentials()) - - assert.Eventually(func() bool { - key := types.NamespacedName{Name: sqlUser.ObjectMeta.Name, Namespace: sqlUser.ObjectMeta.Namespace} - var _, err = tc.secretClient.Get(ctx, key) - - // Once the secret is gone, the Kube secret client will return an error - return err != nil && strings.Contains(err.Error(), "not found") - }, tc.timeoutFast, tc.retry, "wait for the azuresqluser kube secret to be deleted") - - assert.Eventually(func() bool { - keyNamespace := "azuresqluser-" + sqlServerName + "-" + sqlDatabaseName1 - key := types.NamespacedName{Name: kvSqlUser1.ObjectMeta.Name, Namespace: keyNamespace} - var _, err = keyVaultSecretClient.Get(ctx, key) - - // Once the secret is gone, the KV secret client will return an err - return err != nil && strings.Contains(err.Error(), "secret does not exist") - }, tc.timeoutFast, tc.retry, "wait for the azuresqluser keyvault secret to be deleted") - - assert.Eventually(func() bool { - keyNamespace := "azuresqluser-" + sqlServerName + "-" + sqlDatabaseName1 - keyName := kvSqlUser2.ObjectMeta.Name + "-adonet" - key := types.NamespacedName{Name: keyName, Namespace: keyNamespace} - var _, err = keyVaultSecretClient.Get(ctx, key) - - // Once the secret is gone, the KV secret client will return an err - return err != nil && strings.Contains(err.Error(), "secret does not exist") - }, tc.timeoutFast, tc.retry, "wait for the azuresqluser custom formatted keyvault secret to be deleted") - }) - t.Run("delete local firewallrule", func(t *testing.T) { t.Parallel() EnsureDelete(ctx, t, tc, sqlFirewallRuleInstanceLocal) @@ -511,9 +285,10 @@ func TestAzureSqlServerCombinedHappyPath(t *testing.T) { // verify secret has been created assert.Eventually(func() bool { - var secrets, _ = tc.secretClient.Get(ctx, sqlFailoverGroupNamespacedName) + key := secrets.SecretKey{Name: sqlFailoverGroupInstance.Name, Namespace: sqlFailoverGroupInstance.Namespace, Kind: "AzureSqlFailoverGroup"} + var secrets, err = tc.secretClient.Get(ctx, key) - return strings.Contains(string(secrets["azureSqlPrimaryServer"]), sqlServerName) + return err == nil && strings.Contains(string(secrets["azureSqlPrimaryServer"]), sqlServerName) }, tc.timeout, tc.retry, "wait for secret store to show failovergroup server names ") sqlFailoverGroupInstance.Spec.FailoverPolicy = v1beta1.FailoverPolicyManual diff --git a/controllers/azuresqlaction_controller_test.go b/controllers/azuresqlaction_controller_test.go index efaa06c3a77..f42ae50008d 100644 --- a/controllers/azuresqlaction_controller_test.go +++ b/controllers/azuresqlaction_controller_test.go @@ -13,6 +13,7 @@ import ( v1 "k8s.io/api/core/v1" azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/Azure/azure-service-operator/pkg/secrets" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -29,7 +30,9 @@ func RunSQLActionHappy(t *testing.T, server string) { //Get SQL credentials to compare after rollover secret := &v1.Secret{} assert.Eventually(func() bool { - err := tc.k8sClient.Get(ctx, types.NamespacedName{Name: server, Namespace: "default"}, secret) + secretName := getSecretName(server) + + err := tc.k8sClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: "default"}, secret) if err != nil { return false } @@ -56,7 +59,13 @@ func RunSQLActionHappy(t *testing.T, server string) { // makre sure credentials are not the same as previous secretAfter := &v1.Secret{} assert.Eventually(func() bool { - err := tc.k8sClient.Get(ctx, types.NamespacedName{Name: server, Namespace: "default"}, secretAfter) + var secretName string + if tc.secretClient.GetSecretNamingVersion() == secrets.SecretNamingV1 { + secretName = server + } else { + secretName = "azuresqlserver-" + server + } + err := tc.k8sClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: "default"}, secretAfter) if err != nil { return false } @@ -68,3 +77,13 @@ func RunSQLActionHappy(t *testing.T, server string) { EnsureDelete(ctx, t, tc, sqlActionInstance) } + +func getSecretName(server string) string { + var secretName string + if tc.secretClient.GetSecretNamingVersion() == secrets.SecretNamingV1 { + secretName = server + } else { + secretName = "azuresqlserver-" + server + } + return secretName +} diff --git a/controllers/azuresqluser_controller_test.go b/controllers/azuresqluser_controller_test.go index 7fdf3f6dde2..1e23e08ff33 100644 --- a/controllers/azuresqluser_controller_test.go +++ b/controllers/azuresqluser_controller_test.go @@ -12,8 +12,9 @@ import ( 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/secrets" + "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -64,22 +65,13 @@ func TestAzureSQLUserControllerNoResourceGroup(t *testing.T) { username := "sql-test-user" + helpers.RandomString(10) roles := []string{"db_owner"} - secret := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: sqlServerName, - Namespace: "default", - }, - // Needed to avoid nil map error - Data: map[string][]byte{ - "username": []byte("username"), - "password": []byte("password"), - }, - Type: "Opaque", + adminSecretKey := secrets.SecretKey{Name: sqlServerName, Namespace: "default", Kind: "AzureSQLServer"} + data := map[string][]byte{ + "username": []byte("username"), + "password": []byte("password"), } - - // Create the sqlUser - err = tc.k8sClient.Create(ctx, secret) - assert.Equal(nil, err, "create admin secret in k8s") + err = tc.secretClient.Upsert(ctx, adminSecretKey, data) + assert.NoError(err) sqlUser = &azurev1alpha1.AzureSQLUser{ ObjectMeta: metav1.ObjectMeta{ diff --git a/controllers/cosmosdb_controller_test.go b/controllers/cosmosdb_controller_test.go index 31ad06cb5e7..33957e469e6 100644 --- a/controllers/cosmosdb_controller_test.go +++ b/controllers/cosmosdb_controller_test.go @@ -11,11 +11,11 @@ import ( "github.com/Azure/azure-service-operator/api/v1alpha1" "github.com/Azure/azure-service-operator/pkg/errhelp" + "github.com/Azure/azure-service-operator/pkg/secrets" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" ) func TestCosmosDBHappyPath(t *testing.T) { @@ -42,10 +42,9 @@ func TestCosmosDBHappyPath(t *testing.T) { }, } - key := types.NamespacedName{Name: name, Namespace: namespace} - EnsureInstance(ctx, t, tc, dbInstance) + key := secrets.SecretKey{Name: name, Namespace: namespace, Kind: "CosmosDB"} assert.Eventually(func() bool { secret, err := tc.secretClient.Get(ctx, key) return err == nil && len(secret) > 0 diff --git a/controllers/eventhub_storageaccount_controller_test.go b/controllers/eventhub_storageaccount_controller_test.go index 21757a4da96..c075290ceb8 100644 --- a/controllers/eventhub_storageaccount_controller_test.go +++ b/controllers/eventhub_storageaccount_controller_test.go @@ -12,6 +12,7 @@ import ( s "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-04-01/storage" azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" "github.com/Azure/azure-service-operator/api/v1alpha2" + "github.com/Azure/azure-service-operator/pkg/secrets" "github.com/Azure/azure-service-operator/pkg/errhelp" @@ -172,9 +173,10 @@ func TestEventHubControllerCreateAndDeleteCustomKeyVault(t *testing.T) { EnsureInstance(ctx, t, tc, eventhubInstance) // Check that the secret is added to KeyVault - keyvaultSecretClient := kvsecrets.New(keyVaultNameForSecrets, config.GlobalCredentials()) + keyvaultSecretClient := kvsecrets.New(keyVaultNameForSecrets, config.GlobalCredentials(), config.SecretNamingVersion()) + key := secrets.SecretKey{Name: eventhubInstance.Name, Namespace: eventhubInstance.Namespace, Kind: "EventHub"} - EnsureSecrets(ctx, t, tc, eventhubInstance, keyvaultSecretClient, eventhubName, eventhubInstance.Namespace) + EnsureSecrets(ctx, t, tc, eventhubInstance, keyvaultSecretClient, key) EnsureDelete(ctx, t, tc, eventhubInstance) EnsureDelete(ctx, t, tc, eventhubNamespaceInstance) diff --git a/controllers/helpers.go b/controllers/helpers.go index 734534e4fd8..cb4cfc3d224 100644 --- a/controllers/helpers.go +++ b/controllers/helpers.go @@ -8,16 +8,16 @@ import ( "crypto/rand" "crypto/rsa" "encoding/json" - "fmt" "strings" "testing" "time" - "github.com/Azure/azure-service-operator/api/v1alpha1" - "github.com/Azure/azure-service-operator/pkg/helpers" - "github.com/Azure/azure-service-operator/pkg/secrets" + "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2018-02-14/keyvault" + "github.com/Azure/go-autorest/autorest/to" "github.com/go-logr/logr" + "github.com/pkg/errors" + uuid "github.com/satori/go.uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" @@ -29,11 +29,14 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/Azure/azure-service-operator/pkg/helpers" resourcemanagersqldb "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqldb" resourcemanagersqlfailovergroup "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlfailovergroup" resourcemanagersqlfirewallrule "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlfirewallrule" resourcemanagersqlserver "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlserver" resourcemanagersqluser "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqluser" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" resourcemanagerconfig "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" resourcemanagereventhub "github.com/Azure/azure-service-operator/pkg/resourcemanager/eventhubs" resourcemanagerkeyvaults "github.com/Azure/azure-service-operator/pkg/resourcemanager/keyvaults" @@ -43,8 +46,11 @@ import ( resourcemanagerrediscaches "github.com/Azure/azure-service-operator/pkg/resourcemanager/rediscaches/redis" resourcegroupsresourcemanager "github.com/Azure/azure-service-operator/pkg/resourcemanager/resourcegroups" resourcemanagerstorages "github.com/Azure/azure-service-operator/pkg/resourcemanager/storages" + "github.com/Azure/azure-service-operator/pkg/secrets" ) +var TestResourceGroupPrefix = "rg-prime" + type TestContext struct { k8sClient client.Client secretClient secrets.SecretClient @@ -302,45 +308,37 @@ func EnsureDelete(ctx context.Context, t *testing.T, tc TestContext, instance ru } -func EnsureSecrets(ctx context.Context, t *testing.T, tc TestContext, instance runtime.Object, secretclient secrets.SecretClient, secretname string, secretnamespace string) { +func EnsureSecrets(ctx context.Context, t *testing.T, tc TestContext, instance runtime.Object, secretClient secrets.SecretClient, secretKey secrets.SecretKey) { assert := assert.New(t) typeOf := fmt.Sprintf("%T", instance) - key := types.NamespacedName{Name: secretname, Namespace: secretnamespace} - // Wait for secret err := helpers.Retry(tc.timeoutFast, tc.retry, func() error { - _, err := secretclient.Get(ctx, key) - if err != nil { - return fmt.Errorf("secret with name %s does not exist", key.String()) - } - return nil + _, err := secretClient.Get(ctx, secretKey) + return err }) assert.Nil(err, "error waiting for %s to have secret", typeOf) } -func EnsureSecretsWithValue(ctx context.Context, t *testing.T, tc TestContext, instance runtime.Object, secretclient secrets.SecretClient, secretname string, secretnamespace string, secretkey string, secretvalue string) { +func EnsureSecretsWithValue(ctx context.Context, t *testing.T, tc TestContext, instance runtime.Object, secretclient secrets.SecretClient, secretKey secrets.SecretKey, secretSubKey string, secretvalue string) { assert := assert.New(t) typeOf := fmt.Sprintf("%T", instance) - key := types.NamespacedName{Name: secretname, Namespace: secretnamespace} - // Wait for secret err := helpers.Retry(tc.timeoutFast, tc.retry, func() error { - secrets, err := secretclient.Get(ctx, key) + secrets, err := secretclient.Get(ctx, secretKey) if err != nil { return err } - if !strings.Contains(string(secrets[secretkey]), secretvalue) { - return fmt.Errorf("secret with key %s not equal to %s", secretname, secretvalue) + if !strings.Contains(string(secrets[secretSubKey]), secretvalue) { + return fmt.Errorf("secret with key %+v not equal to %s", secretKey, secretvalue) } return nil }) assert.Nil(err, "error waiting for %s to have correct secret", typeOf) - } func RequireInstance(ctx context.Context, t *testing.T, tc TestContext, instance runtime.Object) { @@ -424,7 +422,7 @@ func GenerateTestResourceName(id string) string { // GenerateTestResourceNameWithRandom returns a resource name with a random string appended func GenerateTestResourceNameWithRandom(id string, rc int) string { - return GenerateTestResourceName(id) + "-" + helpers.RandomString(rc) + return resourcemanagerconfig.TestResourcePrefix() + "-" + helpers.RandomString(rc) + "-" + id } // GenerateAlphaNumTestResourceName returns an alpha-numeric resource name @@ -444,3 +442,61 @@ func GenerateRandomSshPublicKeyString() string { sshPublicKeyData := string(ssh.MarshalAuthorizedKey(publicRsaKey)) return sshPublicKeyData } + +//CreateVaultWithAccessPolicies creates a new key vault and provides access policies to the specified user - used in test +func CreateVaultWithAccessPolicies(ctx context.Context, creds config.Credentials, groupName string, vaultName string, location string, clientID string) error { + vaultsClient, err := resourcemanagerkeyvaults.GetKeyVaultClient(creds) + if err != nil { + return errors.Wrapf(err, "couldn't get vaults client") + } + id, err := uuid.FromString(creds.TenantID()) + if err != nil { + return errors.Wrapf(err, "couldn't convert tenantID to UUID") + } + + apList := []keyvault.AccessPolicyEntry{} + ap := keyvault.AccessPolicyEntry{ + TenantID: &id, + Permissions: &keyvault.Permissions{ + Keys: &[]keyvault.KeyPermissions{ + keyvault.KeyPermissionsCreate, + }, + Secrets: &[]keyvault.SecretPermissions{ + keyvault.SecretPermissionsSet, + keyvault.SecretPermissionsGet, + keyvault.SecretPermissionsDelete, + keyvault.SecretPermissionsList, + }, + }, + } + if clientID != "" { + objID, err := resourcemanagerkeyvaults.GetObjectID(ctx, creds, creds.TenantID(), clientID) + if err != nil { + return err + } + if objID != nil { + ap.ObjectID = objID + apList = append(apList, ap) + } + + } + + params := keyvault.VaultCreateOrUpdateParameters{ + Properties: &keyvault.VaultProperties{ + TenantID: &id, + AccessPolicies: &apList, + Sku: &keyvault.Sku{ + Family: to.StringPtr("A"), + Name: keyvault.Standard, + }, + }, + Location: to.StringPtr(location), + } + + future, err := vaultsClient.CreateOrUpdate(ctx, groupName, vaultName, params) + if err != nil { + return err + } + + return future.WaitForCompletionRef(ctx, vaultsClient.Client) +} diff --git a/controllers/keyvault_controller_test.go b/controllers/keyvault_controller_test.go index e7ea31c4553..c154bd94d0d 100644 --- a/controllers/keyvault_controller_test.go +++ b/controllers/keyvault_controller_test.go @@ -18,6 +18,7 @@ import ( "github.com/Azure/azure-service-operator/pkg/errhelp" "github.com/Azure/azure-service-operator/pkg/helpers" "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" + "github.com/Azure/azure-service-operator/pkg/secrets" kvsecrets "github.com/Azure/azure-service-operator/pkg/secrets/keyvault" "github.com/stretchr/testify/assert" @@ -125,9 +126,9 @@ func TestKeyvaultControllerWithAccessPolicies(t *testing.T) { //Add code to set secret and get secret from this keyvault using secretclient - keyvaultSecretClient := kvsecrets.New(keyVaultName, config.GlobalCredentials()) + keyvaultSecretClient := kvsecrets.New(keyVaultName, config.GlobalCredentials(), config.SecretNamingVersion()) secretName := "test-key" - key := types.NamespacedName{Name: secretName, Namespace: "default"} + key := secrets.SecretKey{Name: secretName, Namespace: "default", Kind: "test"} datanew := map[string][]byte{ "test1": []byte("test2"), "test2": []byte("test3"), @@ -185,8 +186,8 @@ func TestKeyvaultControllerWithLimitedAccessPoliciesAndUpdate(t *testing.T) { }, tc.timeout, tc.retry, "wait for keyVaultInstance to be ready in azure") //Add code to set secret and get secret from this keyvault using secretclient - keyvaultSecretClient := kvsecrets.New(keyVaultName, config.GlobalCredentials()) - key := types.NamespacedName{Name: "test-key", Namespace: "default"} + keyvaultSecretClient := kvsecrets.New(keyVaultName, config.GlobalCredentials(), config.SecretNamingVersion()) + key := secrets.SecretKey{Name: "test-key", Namespace: "default", Kind: "test"} datanew := map[string][]byte{ "test1": []byte("test2"), "test2": []byte("test3"), @@ -332,9 +333,9 @@ func TestKeyvaultControllerWithVirtualNetworkRulesAndUpdate(t *testing.T) { return result.Response.StatusCode == http.StatusOK }, tc.timeout, tc.retry, "wait for keyVaultInstance to be ready in azure") - keyvaultSecretClient := kvsecrets.New(keyVaultName, config.GlobalCredentials()) + keyvaultSecretClient := kvsecrets.New(keyVaultName, config.GlobalCredentials(), config.SecretNamingVersion()) secretName := "test-key" - key := types.NamespacedName{Name: secretName, Namespace: "default"} + key := secrets.SecretKey{Name: secretName, Namespace: "default", Kind: "test"} datanew := map[string][]byte{ "test1": []byte("test2"), "test2": []byte("test3"), diff --git a/controllers/mysql_combined_test.go b/controllers/mysql_combined_test.go index da3fa18cdc9..5364931ecec 100644 --- a/controllers/mysql_combined_test.go +++ b/controllers/mysql_combined_test.go @@ -18,6 +18,7 @@ import ( "github.com/Azure/azure-service-operator/api/v1alpha2" "github.com/Azure/azure-service-operator/pkg/resourcemanager/mysql" "github.com/Azure/azure-service-operator/pkg/resourcemanager/mysql/mysqluser" + "github.com/Azure/azure-service-operator/pkg/secrets" ) func TestMySQLHappyPath(t *testing.T) { @@ -147,8 +148,10 @@ func RunMySQLUserHappyPath(ctx context.Context, t *testing.T, mySQLServerName st // TODO: Ugh this is fragile, the path to the secret should probably be set on the status? // See issue here: https://github.com/Azure/azure-service-operator/issues/1318 - secretNamespacedName := types.NamespacedName{Name: mySQLServerName, Namespace: "default"} - adminSecret, err := tc.secretClient.Get(ctx, secretNamespacedName) + adminSecretKey := secrets.SecretKey{Name: mySQLServerName, Namespace: "default", Kind: "mysqlserver"} + adminSecret, err := tc.secretClient.Get(ctx, adminSecretKey) + assert.NoError(err) + adminUser := string(adminSecret["fullyQualifiedUsername"]) adminPassword := string(adminSecret[mysqluser.MSecretPasswordKey]) fullServerName := string(adminSecret["fullyQualifiedServerName"]) @@ -163,6 +166,13 @@ func RunMySQLUserHappyPath(ctx context.Context, t *testing.T, mySQLServerName st adminPassword) assert.NoError(err) + // Ensure the user secret was created + secretKey := secrets.SecretKey{Name: username, Namespace: "default", Kind: "mysqluser"} + userSecret, err := tc.secretClient.Get(ctx, secretKey) + assert.NoError(err) + assert.True(len(userSecret[mysqluser.MSecretUsernameKey]) > 0) + assert.True(len(userSecret[mysqluser.MSecretPasswordKey]) > 0) + expectedRoles := mysql.SliceToSet(updatedRoles) expectedDbRoles := make(map[string]mysql.StringSet) for db, roles := range user.Spec.DatabaseRoles { diff --git a/controllers/mysqluser_controller_test.go b/controllers/mysqluser_controller_test.go index 3069afc6e9a..f42aed1a4b1 100644 --- a/controllers/mysqluser_controller_test.go +++ b/controllers/mysqluser_controller_test.go @@ -9,12 +9,13 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/Azure/azure-service-operator/api/v1alpha2" "github.com/Azure/azure-service-operator/pkg/errhelp" "github.com/Azure/azure-service-operator/pkg/helpers" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/Azure/azure-service-operator/pkg/secrets" ) func TestMySQLUserControllerNoAdminSecret(t *testing.T) { @@ -67,22 +68,13 @@ func TestMySQLUserControllerNoResourceGroup(t *testing.T) { mysqlUsername := "mysql-test-user" + helpers.RandomString(10) roles := []string{"select on *.*"} - secret := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: mysqlServerName, - Namespace: "default", - }, - // Needed to avoid nil map error - Data: map[string][]byte{ - "username": []byte("username"), - "password": []byte("password"), - }, - Type: "Opaque", + adminSecretKey := secrets.SecretKey{Name: mysqlServerName, Namespace: "default", Kind: "MySQLServer"} + data := map[string][]byte{ + "username": []byte("username"), + "password": []byte("password"), } - - // Create the sqlUser - err = tc.k8sClient.Create(ctx, secret) - assert.Equal(nil, err, "create admin secret in k8s") + err = tc.secretClient.Upsert(ctx, adminSecretKey, data) + assert.NoError(err) mysqlUser = &v1alpha2.MySQLUser{ ObjectMeta: metav1.ObjectMeta{ diff --git a/controllers/postgresqluser_controller_test.go b/controllers/postgresqluser_controller_test.go index 24a4590753a..4679be6d555 100644 --- a/controllers/postgresqluser_controller_test.go +++ b/controllers/postgresqluser_controller_test.go @@ -9,12 +9,13 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + 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/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/Azure/azure-service-operator/pkg/secrets" ) func TestPostgreSQLUserControllerNoAdminSecret(t *testing.T) { @@ -66,22 +67,13 @@ func TestPostgreSQLUserControllerNoResourceGroup(t *testing.T) { pusername := "psql-test-user" + helpers.RandomString(10) roles := []string{"azure_pg_admin"} - secret := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: psqlServerName, - Namespace: "default", - }, - // Needed to avoid nil map error - Data: map[string][]byte{ - "username": []byte("username"), - "password": []byte("password"), - }, - Type: "Opaque", + adminSecretKey := secrets.SecretKey{Name: psqlServerName, Namespace: "default", Kind: "PostgreSQLServer"} + data := map[string][]byte{ + "username": []byte("username"), + "password": []byte("password"), } - - // Create the sqlUser - err = tc.k8sClient.Create(ctx, secret) - assert.Equal(nil, err, "create admin secret in k8s") + err = tc.secretClient.Upsert(ctx, adminSecretKey, data) + assert.NoError(err) psqlUser = &azurev1alpha1.PostgreSQLUser{ ObjectMeta: metav1.ObjectMeta{ diff --git a/controllers/secret_naming_version_test.go b/controllers/secret_naming_version_test.go new file mode 100644 index 00000000000..46e14ce321d --- /dev/null +++ b/controllers/secret_naming_version_test.go @@ -0,0 +1,427 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +build all + +package controllers + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/Azure/go-autorest/autorest/to" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/Azure/azure-service-operator/api/v1beta1" + "github.com/Azure/azure-service-operator/pkg/helpers" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" + "github.com/Azure/azure-service-operator/pkg/secrets" + kvsecrets "github.com/Azure/azure-service-operator/pkg/secrets/keyvault" +) + +// NOTE: Tests in this file are intended to be run twice, once in the v1 secret naming mode +// and once in the v2 secret naming mode. In some instances they parallel tests +// which don't assert on secret name or shape. +// The naming of the tests is important! It's how the makefile knows to run just these tests +// in the old V1 mode. +// Not every resource is included here, only resources which for legacy reasons in the old v1 +// secret naming mode had secret naming schemes which were different than the standard. + +// This test intentionally mirrors TestAppInsightsController in appinsights_controller_test.go but is +// focused on ensuring the shape of the saved secret is correct +func TestAppInsights_SecretNamedCorrectly(t *testing.T) { + t.Parallel() + defer PanicRecover(t) + ctx := context.Background() + + assert := assert.New(t) + + rgName := tc.resourceGroupName + rgLocation := tc.resourceGroupLocation + appInsightsName := GenerateTestResourceNameWithRandom("appinsights", 6) + + // Create an instance of Azure AppInsights + instance := &v1alpha1.AppInsights{ + ObjectMeta: metav1.ObjectMeta{ + Name: appInsightsName, + Namespace: "default", + }, + Spec: v1alpha1.AppInsightsSpec{ + Kind: "web", + Location: rgLocation, + ResourceGroup: rgName, + ApplicationType: "other", + }, + } + + EnsureInstance(ctx, t, tc, instance) + + // Make sure the secret is created + var keyName string + if tc.secretClient.GetSecretNamingVersion() == secrets.SecretNamingV1 { + keyName = fmt.Sprintf("appinsights-%s-%s", instance.Spec.ResourceGroup, instance.Name) + } else { + keyName = instance.Name + } + + secretKey := secrets.SecretKey{Name: keyName, Namespace: instance.Namespace, Kind: "appinsights"} + + // Secret is created after reconciliation is in provisioned state + var secret map[string][]byte + assert.Eventually(func() bool { + var err error + secret, err = tc.secretClient.Get(ctx, secretKey) + return err == nil + }, tc.timeoutFast, tc.retry, "should be able to get secret") + + assert.NotEmpty(string(secret["instrumentationKey"])) + + EnsureDelete(ctx, t, tc, instance) +} + +// This test intentionally mirrors TestStorageAccountController in storage_controller_test.go but is +// focused on ensuring the shape of the saved secret is correct +func TestStorageAccount_SecretNamedCorrectly(t *testing.T) { + t.Parallel() + defer PanicRecover(t) + ctx := context.Background() + + assert := assert.New(t) + + rgName := tc.resourceGroupName + rgLocation := tc.resourceGroupLocation + storageName := "storageacct" + helpers.RandomString(6) + + instance := &v1alpha1.StorageAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: storageName, + Namespace: "default", + }, + Spec: v1alpha1.StorageAccountSpec{ + Kind: "BlobStorage", + Location: rgLocation, + ResourceGroup: rgName, + Sku: v1alpha1.StorageAccountSku{ + Name: "Standard_LRS", + }, + AccessTier: "Hot", + EnableHTTPSTrafficOnly: to.BoolPtr(true), + }, + } + + EnsureInstance(ctx, t, tc, instance) + + // Make sure the secret is created + var keyName string + if tc.secretClient.GetSecretNamingVersion() == secrets.SecretNamingV1 { + keyName = fmt.Sprintf("storageaccount-%s-%s", instance.Spec.ResourceGroup, instance.Name) + } else { + keyName = instance.Name + } + secretKey := secrets.SecretKey{Name: keyName, Namespace: instance.Namespace, Kind: "storageaccount"} + secret, err := tc.secretClient.Get(ctx, secretKey) + assert.NoError(err) + + assert.NotEmpty(string(secret["StorageAccountName"])) + assert.NotEmpty(string(secret["connectionString0"])) + assert.NotEmpty(string(secret["key0"])) + assert.NotEmpty(string(secret["connectionString1"])) + assert.NotEmpty(string(secret["key1"])) + + EnsureDelete(ctx, t, tc, instance) +} + +func assertSQLServerAdminSecretCreated(ctx context.Context, t *testing.T, sqlServerInstance *v1beta1.AzureSqlServer) { + secret := &v1.Secret{} + assert.Eventually(t, func() bool { + var expectedServerSecretName string + if tc.secretClient.GetSecretNamingVersion() == secrets.SecretNamingV1 { + expectedServerSecretName = sqlServerInstance.Name + } else { + expectedServerSecretName = fmt.Sprintf("%s-%s", "azuresqlserver", sqlServerInstance.Name) + } + err := tc.k8sClient.Get(ctx, types.NamespacedName{Namespace: sqlServerInstance.Namespace, Name: expectedServerSecretName}, secret) + if err != nil { + return false + } + return secret.Name == expectedServerSecretName && secret.Namespace == sqlServerInstance.Namespace + }, tc.timeoutFast, tc.retry, "wait for server to have secret") +} + +func TestAzureSqlServerAndUser_SecretNamedCorrectly(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 + rgName := tc.resourceGroupName + sqlServerName := GenerateTestResourceNameWithRandom("sqlserver", 10) + rgLocation := "westus2" + + sqlServerNamespacedName := types.NamespacedName{Name: sqlServerName, Namespace: "default"} + sqlServerInstance := v1beta1.NewAzureSQLServer(sqlServerNamespacedName, rgName, rgLocation) + + // create and wait + RequireInstance(ctx, t, tc, sqlServerInstance) + + //verify secret exists in k8s for server 1 + assertSQLServerAdminSecretCreated(ctx, t, sqlServerInstance) + + sqlDatabaseName := GenerateTestResourceNameWithRandom("sqldatabase", 10) + // Create the SqlDatabase object and expect the Reconcile to be created + sqlDatabaseInstance := &v1beta1.AzureSqlDatabase{ + ObjectMeta: metav1.ObjectMeta{ + Name: sqlDatabaseName, + Namespace: "default", + }, + Spec: v1beta1.AzureSqlDatabaseSpec{ + Location: rgLocation, + ResourceGroup: rgName, + Server: sqlServerName, + Edition: 0, + }, + } + + EnsureInstance(ctx, t, tc, sqlDatabaseInstance) + + // Create firewall rule + sqlFirewallRuleNamespacedName := types.NamespacedName{ + Name: GenerateTestResourceNameWithRandom("sqlfwr-local", 10), + Namespace: "default", + } + sqlFirewallRule := v1beta1.NewAzureSQLFirewallRule( + sqlFirewallRuleNamespacedName, + rgName, + sqlServerName, + "1.1.1.1", + "255.255.255.255", + ) + EnsureInstance(ctx, t, tc, sqlFirewallRule) + + t.Run("sub test for actions", func(t *testing.T) { + RunSQLActionHappy(t, sqlServerName) + }) + + var sqlUser *v1alpha1.AzureSQLUser + var kvSqlUser1 *v1alpha1.AzureSQLUser + var kvSqlUser2 *v1alpha1.AzureSQLUser + + // Run user subtests + t.Run("group2", func(t *testing.T) { + + t.Run("set up user in first db", func(t *testing.T) { + t.Parallel() + + // create a sql user and verify it provisions + username := "sql-test-user" + helpers.RandomString(10) + roles := []string{"db_owner"} + keyVaultSecretFormats := []string{"adonet"} + + sqlUser = &v1alpha1.AzureSQLUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: username, + Namespace: "default", + }, + Spec: v1alpha1.AzureSQLUserSpec{ + Server: sqlServerName, + DbName: sqlDatabaseName, + ResourceGroup: rgName, + Roles: roles, + KeyVaultSecretFormats: keyVaultSecretFormats, + }, + } + + EnsureInstance(ctx, t, tc, sqlUser) + + // verify user's secret has been created + // this test suite defaults to Kube Secrets. They do not support keyvault-specific config but the spec is passed anyway + // to verify that passing them does not break the service + assert.Eventually(func() bool { + key := secrets.SecretKey{Name: sqlUser.Name, Namespace: sqlUser.Namespace, Kind: "azuresqluser"} + var secrets, err = tc.secretClient.Get(ctx, key) + + return err == nil && strings.Contains(string(secrets["azureSqlDatabaseName"]), sqlDatabaseName) + }, tc.timeoutFast, tc.retry, "wait for secret store to show azure sql user credentials") + + t.Log(sqlUser.Status) + }) + + t.Run("set up user in db with custom keyvault", func(t *testing.T) { + t.Parallel() + + // create a sql user and verify it provisions + username := "sql-test-user" + helpers.RandomString(10) + roles := []string{"db_owner"} + + // This test will attempt to persist secrets to the KV that was instantiated as part of the test suite + keyVaultName := tc.keyvaultName + + kvSqlUser1 = &v1alpha1.AzureSQLUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: username, + Namespace: "default", + }, + Spec: v1alpha1.AzureSQLUserSpec{ + Server: sqlServerName, + DbName: sqlDatabaseName, + ResourceGroup: rgName, + Roles: roles, + KeyVaultToStoreSecrets: keyVaultName, + }, + } + + EnsureInstance(ctx, t, tc, kvSqlUser1) + + // Check that the user's secret is in the keyvault + keyVaultSecretClient := kvsecrets.New(keyVaultName, config.GlobalCredentials(), config.SecretNamingVersion()) + + assert.Eventually(func() bool { + key := makeSQLUserSecretKey(keyVaultSecretClient, kvSqlUser1) + var secrets, _ = keyVaultSecretClient.Get(ctx, key) + + return strings.Contains(string(secrets["azureSqlDatabaseName"]), sqlDatabaseName) + }, tc.timeoutFast, tc.retry, "wait for keyvault to show azure sql user credentials") + + t.Log(kvSqlUser1.Status) + }) + + t.Run("set up user in db with custom keyvault and custom formatting", func(t *testing.T) { + t.Parallel() + + // create a sql user and verify it provisions + username := "sql-test-user" + helpers.RandomString(10) + roles := []string{"db_owner"} + formats := []string{"adonet"} + + // This test will attempt to persist secrets to the KV that was instantiated as part of the test suite + keyVaultName := tc.keyvaultName + + kvSqlUser2 = &v1alpha1.AzureSQLUser{ + ObjectMeta: metav1.ObjectMeta{ + Name: username, + Namespace: "default", + }, + Spec: v1alpha1.AzureSQLUserSpec{ + Server: sqlServerName, + DbName: sqlDatabaseName, + ResourceGroup: rgName, + Roles: roles, + KeyVaultToStoreSecrets: keyVaultName, + KeyVaultSecretFormats: formats, + }, + } + + EnsureInstance(ctx, t, tc, kvSqlUser2) + + // Check that the user's secret is in the keyvault + keyVaultSecretClient := kvsecrets.New(keyVaultName, config.GlobalCredentials(), config.SecretNamingVersion()) + + assert.Eventually(func() bool { + key := makeSQLUserSecretKey(keyVaultSecretClient, kvSqlUser2) + key.Name = key.Name + "-adonet" + var secrets, err = keyVaultSecretClient.Get(ctx, key, secrets.Flatten(true)) + assert.NoError(err) + + return len(string(secrets["secret"])) > 0 + }, tc.timeoutFast, tc.retry, "wait for keyvault to show azure sql user credentials with custom formats") + + t.Log(kvSqlUser2.Status) + }) + }) + + t.Run("deploy sql action and roll user credentials", func(t *testing.T) { + keyVaultName := tc.keyvaultName + keyVaultSecretClient := kvsecrets.New(keyVaultName, config.GlobalCredentials(), config.SecretNamingVersion()) + + key := makeSQLUserSecretKey(keyVaultSecretClient, kvSqlUser1) + oldSecret, err := keyVaultSecretClient.Get(ctx, key) + assert.NoError(err) + + sqlActionName := GenerateTestResourceNameWithRandom("azuresqlaction-dev", 10) + sqlActionInstance := &v1alpha1.AzureSqlAction{ + ObjectMeta: metav1.ObjectMeta{ + Name: sqlActionName, + Namespace: "default", + }, + Spec: v1alpha1.AzureSqlActionSpec{ + ResourceGroup: rgName, + ServerName: sqlServerName, + ActionName: "rollusercreds", + DbName: sqlDatabaseName, + DbUser: kvSqlUser1.ObjectMeta.Name, + UserSecretKeyVault: keyVaultName, + }, + } + + err = tc.k8sClient.Create(ctx, sqlActionInstance) + assert.Equal(nil, err, "create sqlaction in k8s") + + sqlActionInstanceNamespacedName := types.NamespacedName{Name: sqlActionName, Namespace: "default"} + + assert.Eventually(func() bool { + _ = tc.k8sClient.Get(ctx, sqlActionInstanceNamespacedName, sqlActionInstance) + return sqlActionInstance.Status.Provisioned + }, tc.timeout, tc.retry, "wait for sql action to be submitted") + + newSecret, err := keyVaultSecretClient.Get(ctx, key) + assert.NoError(err) + + assert.NotEqual(oldSecret["password"], newSecret["password"], "password should have been updated") + assert.Equal(oldSecret["username"], newSecret["username"], "usernames should be the same") + }) + + t.Run("delete db users and ensure that their secrets have been cleaned up", func(t *testing.T) { + EnsureDelete(ctx, t, tc, sqlUser) + EnsureDelete(ctx, t, tc, kvSqlUser1) + EnsureDelete(ctx, t, tc, kvSqlUser2) + + // Check that the user's secret is in the keyvault + keyVaultSecretClient := kvsecrets.New(tc.keyvaultName, config.GlobalCredentials(), config.SecretNamingVersion()) + + assert.Eventually(func() bool { + key := secrets.SecretKey{Name: sqlUser.ObjectMeta.Name, Namespace: sqlUser.ObjectMeta.Namespace, Kind: "azuresqluser"} + var _, err = tc.secretClient.Get(ctx, key) + + // Once the secret is gone, the Kube secret client will return an error + return err != nil && strings.Contains(err.Error(), "not found") + }, tc.timeoutFast, tc.retry, "wait for the azuresqluser kube secret to be deleted") + + assert.Eventually(func() bool { + key := makeSQLUserSecretKey(keyVaultSecretClient, kvSqlUser1) + + var _, err = keyVaultSecretClient.Get(ctx, key) + + // Once the secret is gone, the KV secret client will return an err + return err != nil && strings.Contains(err.Error(), "could not be found") + }, tc.timeoutFast, tc.retry, "wait for the azuresqluser keyvault secret to be deleted") + + assert.Eventually(func() bool { + key := makeSQLUserSecretKey(keyVaultSecretClient, kvSqlUser2) + key.Name = key.Name + "-adonet" + var _, err = keyVaultSecretClient.Get(ctx, key, secrets.Flatten(true)) + + // Once the secret is gone, the KV secret client will return an err + return err != nil && strings.Contains(err.Error(), "could not be found") + }, tc.timeoutFast, tc.retry, "wait for the azuresqluser custom formatted keyvault secret to be deleted") + }) + + // Delete the SQL server + EnsureDelete(ctx, t, tc, sqlServerInstance) +} + +func makeSQLUserSecretKey(secretClient *kvsecrets.SecretClient, instance *v1alpha1.AzureSQLUser) secrets.SecretKey { + var keyNamespace string + if secretClient.GetSecretNamingVersion() == secrets.SecretNamingV1 { + keyNamespace = "azuresqluser-" + instance.Spec.Server + "-" + instance.Spec.DbName + } else { + keyNamespace = instance.Namespace + } + return secrets.SecretKey{Name: instance.Name, Namespace: keyNamespace, Kind: "azuresqluser"} +} diff --git a/controllers/storage_controller_test.go b/controllers/storage_controller_test.go new file mode 100644 index 00000000000..b2d683fa239 --- /dev/null +++ b/controllers/storage_controller_test.go @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +build all appinsights + +package controllers + +import ( + "context" + "testing" + + "github.com/Azure/go-autorest/autorest/to" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + v1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/Azure/azure-service-operator/pkg/helpers" +) + +func TestStorageAccountController(t *testing.T) { + t.Parallel() + defer PanicRecover(t) + ctx := context.Background() + + rgName := tc.resourceGroupName + rgLocation := tc.resourceGroupLocation + storageName := "storageacct" + helpers.RandomString(6) + + instance := &v1alpha1.StorageAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: storageName, + Namespace: "default", + }, + Spec: v1alpha1.StorageAccountSpec{ + Kind: "BlobStorage", + Location: rgLocation, + ResourceGroup: rgName, + Sku: v1alpha1.StorageAccountSku{ + Name: "Standard_LRS", + }, + AccessTier: "Hot", + EnableHTTPSTrafficOnly: to.BoolPtr(true), + }, + } + + EnsureInstance(ctx, t, tc, instance) + + EnsureDelete(ctx, t, tc, instance) +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 5f4f52ef8b5..61bf5b71d57 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "log" - "net/http" "os" "path/filepath" "runtime/debug" @@ -91,13 +90,16 @@ func setup() error { // the purpose of these tests envy.Set("POD_NAMESPACE", "azureoperator-system") + // Uncomment the below to run the tests in the old v1 naming mode + // envy.Set("AZURE_SECRET_NAMING_VERSION", "1") + err := resourcemanagerconfig.ParseEnvironment() if err != nil { return err } - resourceGroupName := GenerateTestResourceName("rg-prime") - resourcegroupLocation := resourcemanagerconfig.DefaultLocation() + resourceGroupName := GenerateTestResourceNameWithRandom(TestResourceGroupPrefix, 6) + resourceGroupLocation := resourcemanagerconfig.DefaultLocation() keyvaultName := GenerateAlphaNumTestResourceName("kv-prime") @@ -161,7 +163,7 @@ func setup() error { return err } - secretClient := k8sSecrets.New(k8sManager.GetClient()) + secretClient := k8sSecrets.New(k8sManager.GetClient(), config.SecretNamingVersion()) resourceGroupManager := resourcegroupsresourcemanager.NewAzureResourceGroupManager(config.GlobalCredentials()) keyVaultManager := resourcemanagerkeyvaults.NewAzureKeyVaultManager(config.GlobalCredentials(), k8sManager.GetScheme()) eventhubClient := resourcemanagereventhub.NewEventhubClient(config.GlobalCredentials(), secretClient, scheme.Scheme) @@ -914,7 +916,7 @@ func setup() error { // Create the ResourceGroup resource result, _ := resourceGroupManager.CheckExistence(context.Background(), resourceGroupName) if result.Response.StatusCode != 204 { - _, err = resourceGroupManager.CreateGroup(context.Background(), resourceGroupName, resourcegroupLocation) + _, err = resourceGroupManager.CreateGroup(context.Background(), resourceGroupName, resourceGroupLocation) if err != nil { return fmt.Errorf("ResourceGroup creation failed") } @@ -924,7 +926,7 @@ func setup() error { k8sClient: k8sClient, secretClient: secretClient, resourceGroupName: resourceGroupName, - resourceGroupLocation: resourcegroupLocation, + resourceGroupLocation: resourceGroupLocation, keyvaultName: keyvaultName, eventhubClient: eventhubClient, resourceGroupManager: resourceGroupManager, @@ -937,24 +939,16 @@ func setup() error { } log.Println("Creating KV:", keyvaultName) - kvManager := resourcemanagerkeyvaults.NewAzureKeyVaultManager(config.GlobalCredentials(), nil) - _, err = kvManager.CreateVaultWithAccessPolicies( + err = CreateVaultWithAccessPolicies( context.Background(), + resourcemanagerconfig.GlobalCredentials(), resourceGroupName, keyvaultName, - resourcegroupLocation, + resourceGroupLocation, resourcemanagerconfig.GlobalCredentials().ClientID(), ) - // Key Vault needs to be in "Suceeded" state - finish := time.Now().Add(tc.timeout) - for { - if finish.Before(time.Now()) { - return fmt.Errorf("time out waiting for keyvault") - } - result, _ := tc.keyVaultManager.GetVault(context.Background(), resourceGroupName, keyvaultName) - if result.Response.StatusCode == http.StatusOK { - break - } + if err != nil { + return err } log.Println(fmt.Sprintf("finished common controller test setup")) diff --git a/docs/howto/secrets.md b/docs/howto/secrets.md index 765953afceb..b42b20b0ef8 100644 --- a/docs/howto/secrets.md +++ b/docs/howto/secrets.md @@ -1,28 +1,76 @@ # Information about the Resource post deployment -Many of the Azure resources have access information like connection strings, access keys, admin user password etc. that is required by applications consuming the resource. This information is stored as secrets after resource creation. +Many of the Azure resources have access information like connection strings, access keys, admin user password etc. that is required by applications consuming the resource. +This information is stored by the operator as secrets after resource creation. The operator provides two options to store these secrets: -1. **Kubernetes secrets**: This is the default option. If the secretname is not specified in the Spec, a secret with the same name as ObjectMeta.name is created. +1. **Kubernetes secrets**: A Kubernetes secret will be created alongside the resource. This is the default option. For details about how the secrets are named, see [Secret naming](#secret-naming). -2. **Azure Keyvault secrets**: You can specify the name of the Azure Keyvault to use to store secrets through the environment variable `AZURE_OPERATOR_KEYVAULT`. +2. **Azure Key Vault secrets**: You can specify the default Azure Key Vault to store secrets in through via the `AZURE_OPERATOR_KEYVAULT` field in the `azureoperatorsettings` secret. +This can be set by exporting it as an environment variable and then including it in the secret creation. + ``` + export AZURE_OPERATOR_KEYVAULT=mykeyvault + kubectl create secret generic azureoperatorsettings \ + ... (other settings) \ + --from-literal=AZURE_OPERATOR_KEYVAULT=${AZURE_OPERATOR_KEYVAULT} + ``` + Or if you're using Helm: + ``` + helm upgrade --install aso aso/azure-service-operator \ + --create-namespace \ + --namespace=azureoperator-system \ + ... \ + --set azureOperatorKeyvault=$AZURE_OPERATOR_KEYVAULT + ``` -If the secretname is not specified in the Spec, the secret in Keyvault is normally created with the name `-`. The namespace is preprended to the name to avoid name collisions across Kubernetes namespaces. +For details about how the secrets are named, see [Secret naming](#secret-naming). +Some things to note about the Key Vault you use with the operator: +1. The KeyVault should have an access policy added for the identity under which the Operator runs as. + This access policy should include at least `get`, `set`, `list` and `delete` Secret permissions. +2. You can use a Key Vault with "Soft delete" enabled. However, you cannot use a Key Vault with "Purge Protection" enabled, as this prevents the + secrets from being deleted and causes issues if a resource with the same name is re-deployed. -``` -export AZURE_OPERATOR_KEYVAULT=OperatorSecretKeyVault -``` +## Secret naming + +There are two versions of secret naming used by the Azure Service Operator. The secret naming version is controlled by the `AZURE_SECRET_NAMING_VERSION` field of the `azureoperatorsettings` secret. +Valid values are `"1"` and `"2"`. Version `2` is the default. + +**We strongly recommend that you use version `2` as it is more consistent in how secrets are named and does a better job of avoiding naming conflicts.** -Some things to note about this Keyvault: -(i) The Keyvault should have an access policy added for the identity under which the Operator runs as. This access policy should include at least `get`, `set`, `list` and `delete` Secret permissions. +| AZURE_SECRET_NAMING_VERSION | Destination | Default secret name | Secret name if `secretName` overridden in spec | +|-----------------------------|-------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| 1 | Kubernetes | Namespace: ``, Name: `` for most resources. Some resources have a different naming scheme specific to that resource. | Namespace: ``, Name: `` for most resources. Some resources have a different naming scheme specific to that resource. | +| 1 | Key Vault | `-` for most resources. Some resources have a different naming scheme specific to that resource. | `-` for most resources. Some resources have a different naming scheme specific to that resource. | +| 2 | Kubernetes | Namespace ``, Name: `-` | Namespace ``, Name: `-` | +| 2 | Key Vault | `--` | `--` | -(ii) You can use a Keyvault with "Soft delete" enabled. However, you cannot use a Keyvault with "Purge Protection" enabled, as this prevents the secrets from being deleted and causes issues if a resource with the same name is re-deployed. +In the above table, `kind` is the Kind of the resource as specified in the YAML - for example `azuresqldatabase`. +## Per resource Key Vault -## Per resource Keyvault +In addition to being able to specify an Azure Key Vault to store secrets, you also have the option to specify a different Key Vault per resource. -In addition to being able to specify an Azure Keyvault to store secrets, you also have the option to specify a different Keyvault per resource. +Some situations may require that you use a different Key Vault to store the admin password for the Azure SQL server from the Key Vault used to store the connection string for eventhubs. +You can specify the Key Vault name in the Spec field `keyVaultToStoreSecrets`. When this is specified, the secrets produced when provisioning the resource in question will be stored +in the specified Key Vault instead of the global one configured for the operator. + +## Format of secrets in Key Vault +In some scenarios it may be helpful to understand the format of the secrets stored in Key Vault. One such scenario is if you created a parent resource with a tool other than the operator, +but you now would like to manage child resources with the operator. A common example of this is a single persistent SQL server whose administrator account will be used to provision other databases +and users. + +The secrets are all stored in Key Vault as a `secret` entity whose value is JSON serialized key-value pairs where the values have been base64 encoded. +For example, an Azure SQL Server created in namespace `default` with name `my-sql-server` will have a secret named `default-my-sql-server` that looks like: +``` +{ + "username": "aGFzMTFzMnU=", + "password": "XTdpMmQqNsd7YlpFdEApMw==", + "fullyqualifiedusername": "aGFzMTUzMnVAc3Fsc2VydmVyLXNhbXBsZS04ODg=", + "sqlservername": "c3Fsc2VyfmVyLXNhbXBsZS04ODg=", + "fullyqualifiedservername": "c3Fsc2VydmVyLXNhbXBsZS04ODguZGF0YWJhc2Uud2luZG93cy5uZXQ=" +} +``` -Some situations may require that you use a different Keyvault to store the admin password for the Azure SQL server from the Keyvault used to store the connection string for eventhubs. You can specify the Keyvault name in the Spec field `keyVaultToStoreSecrets`. When this is specified, the secrets from provisioning of that resource will be stored in this Keyvault instead of the global one configured for the operator. \ No newline at end of file +For more details about what fields are in each secret, see the documentation for the resource in question, for example: [azuresql](../services/azuresql.md) and [mysql](../services/mysql.md) diff --git a/go.mod b/go.mod index 9da0b9f420c..653df7182df 100644 --- a/go.mod +++ b/go.mod @@ -13,18 +13,19 @@ require ( github.com/Azure/go-autorest/autorest/to v0.4.0 github.com/Azure/go-autorest/autorest/validation v0.3.0 github.com/Azure/go-autorest/tracing v0.6.0 + github.com/beorn7/perks v1.0.1 // indirect github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/go-logr/logr v0.1.0 github.com/go-sql-driver/mysql v1.5.0 github.com/gobuffalo/envy v1.7.0 - github.com/google/go-cmp v0.4.0 - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 + github.com/google/go-cmp v0.5.2 + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.1.1 github.com/hashicorp/go-multierror v1.0.0 github.com/lib/pq v1.6.0 github.com/marstr/randname v0.0.0-20181206212954-d5b0f288ab8c - github.com/mitchellh/go-homedir v1.1.0 + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.3.0 // indirect github.com/onsi/ginkgo v1.12.1 github.com/onsi/gomega v1.10.1 @@ -34,17 +35,19 @@ require ( github.com/satori/go.uuid v1.2.0 github.com/sethvargo/go-password v0.1.2 github.com/spf13/cast v1.3.1 // indirect - github.com/spf13/cobra v1.0.0 + github.com/spf13/cobra v1.0.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/viper v1.6.3 + github.com/spf13/viper v1.6.3 // indirect github.com/stretchr/testify v1.5.1 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 // indirect + gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e // indirect gopkg.in/ini.v1 v1.55.0 // indirect k8s.io/api v0.18.6 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.18.6 - sigs.k8s.io/controller-runtime v0.6.2 - sigs.k8s.io/controller-tools v0.2.5 // indirect + sigs.k8s.io/controller-runtime v0.6.5 + sigs.k8s.io/controller-tools v0.4.0 // indirect + sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 // indirect ) diff --git a/go.sum b/go.sum index 4072f6a72fa..345ed271556 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,9 @@ cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZJ6mgB+PgBcCIa79kEKR8YCW+A= +github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= +github.com/AlekSi/gocov-xml v0.0.0-20190121064608-3a14fb1c4737 h1:JZHBkt0GhM+ARQykshqpI49yaWCHQbJonH3XpDTwMZQ= +github.com/AlekSi/gocov-xml v0.0.0-20190121064608-3a14fb1c4737/go.mod h1:w1KSuh2JgIL3nyRiZijboSUwbbxOrTzWwyWVFUHtXBQ= github.com/Azure/aad-pod-identity v1.6.3 h1:S9ucEc0Fjlj3nLJhEItjxhHySgYzU5i+mer9fo+Fu4M= github.com/Azure/aad-pod-identity v1.6.3/go.mod h1:wFUg5YGthk9OLfwg0vImAf6i4vsw17xMgQ8j3MbyvrM= github.com/Azure/azure-sdk-for-go v40.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -51,36 +54,55 @@ github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvd github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= +github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alessio/shellescape v1.2.2 h1:8LnL+ncxhWT2TR00dfJRT25JWWrhkMZXneHVWnetDZg= +github.com/alessio/shellescape v1.2.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEKQDklVPmzs71WM56RTTRqV4OrDC//Y4= github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/axw/gocov v1.0.0 h1:YsqYR66hUmilVr23tu8USgnJIJvnwh3n7j5zRn7x4LU= +github.com/axw/gocov v1.0.0/go.mod h1:LvQpEYiwwIb2nYkXY2fDWhg9/AsYqkhmrCshjlUJECE= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bombsimon/wsl v1.2.5/go.mod h1:43lEF/i0kpXbLCeDXL9LMT8c92HyBywXb0AsgMHYngM= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -99,6 +121,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 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/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -117,18 +140,26 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.1.0 h1:B0aXl1o/1cP8NbviYiBMkcHBtUjIJ1/Ccg6b+SwCLQg= +github.com/evanphx/json-patch/v5 v5.1.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= @@ -137,62 +168,97 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5 h1:8b2ZgKfKIUTVQpTb77MoRDIMEIwvDVw40o3aOXdfYzI= github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.4 h1:5I4CCSqoWzT+82bBkNIvmLc0UOsoKKQ4Fz+3VxOB7SY= github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4 h1:csnOgcgAiuGoM/Po7PEpKDoNulCcF3FGbSnbHfxgjMI= github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw= +github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-openapi/validate v0.19.8 h1:YFzsdWIDfVuLvIOF+ZmKjVg1MbPJ1QgY9PihMwei1ys= +github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.2.0 h1:EWCvMGGxOjsgwlWaP+f4+Hh6yrrte7JeFL2S6b+0hdM= github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -222,6 +288,21 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.21.0/go.mod h1:phxpHK52q7SE+5KpPnti4oZTdFCEsn/tKN+nFvCKXfk= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -230,6 +311,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -237,6 +320,7 @@ github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -247,14 +331,18 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -264,10 +352,16 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -282,6 +376,7 @@ github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= @@ -295,6 +390,7 @@ github.com/jcmturner/gokrb5/v8 v8.2.0 h1:lzPl/30ZLkTveYsYZPKMcgXc8MbnE6RsTd4F9Kg github.com/jcmturner/gokrb5/v8 v8.2.0/go.mod h1:T1hnNppQsBtxW0tCHMHTkAt8n/sABdzZgZdoFrZaZNM= github.com/jcmturner/rpc/v2 v2.0.2 h1:gMB4IwRXYsWw4Bc6o/az2HJgFUA1ffSh90i26ZJ6Xl0= github.com/jcmturner/rpc/v2 v2.0.2/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -307,21 +403,31 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.6.0 h1:I5DPxhYJChW9KYc66se+oKFFQX6VuQrKiprsX6ivRZc= github.com/lib/pq v1.6.0/go.mod h1:4vXEAYvW1fRQ2/FhZ78H73A60MHw1geSm145z2mdY1g= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -330,20 +436,35 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/marstr/collection v1.0.1 h1:j61osRfyny7zxBlLRtoCvOZ2VX7HEyybkZcsLNLJ0z0= github.com/marstr/collection v1.0.1/go.mod h1:HHDXVxjLO3UYCBXJWY+J/ZrxCUOYqrO66ob1AzIsmYA= github.com/marstr/randname v0.0.0-20181206212954-d5b0f288ab8c h1:JE+MDz5rhFN5EC9Dj/N8dLYKboTWm6FXeWhnyKVj0vA= github.com/marstr/randname v0.0.0-20181206212954-d5b0f288ab8c/go.mod h1:Xc224oUXd7/sKMjWKl17cfkYOKQ1S+HSOW7YHEGXauI= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.0 h1:iDwIio/3gk2QtLLEsqU5lInaMzos0hDTz8a6lazSFVw= github.com/mitchellh/mapstructure v1.3.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -354,14 +475,22 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -379,15 +508,20 @@ github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -420,24 +554,36 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d h1:K6eOUihrFLdZjZnA4XlRp864fmWXv9YTIk7VPLhRacA= +github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.1.0 h1:g0fH8RicVgNl+zVZDCDfbdWxAWoAEJyI7I3TZYXFiig= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sethvargo/go-password v0.1.2 h1:fhBF4thiPVKEZ7R6+CX46GWJiPyCyXshbeqZ7lqEeYo= github.com/sethvargo/go-password v0.1.2/go.mod h1:qKHfdSjT26DpHQWHWWR5+X4BI45jT31dg6j4RI2TEb0= +github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= @@ -466,31 +612,55 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= +github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/uudashr/gocognit v0.0.0-20190926065955-1655d0de0517/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad h1:W0LEBv82YCGEtcmPA3uNZBI33/qF//HAAs3MawDjRa0= +github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yujunz/go-getter v1.4.1-lite h1:FhvNc94AXMZkfqUwfMKhnQEC9phkphSGdPTL7tIdhOM= +github.com/yujunz/go-getter v1.4.1-lite/go.mod h1:sbmqxXjyLunH1PkF3n7zSlnVeMvmYUuIl9ZVs/7NyCc= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -505,9 +675,12 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -523,10 +696,15 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -543,11 +721,15 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -578,13 +760,18 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w= golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -597,26 +784,44 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72 h1:bw9doJza/SFBEweII/rHQh338oozWyiFsBRHtrflcws= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200616195046-dc31b401abb5 h1:UaoXseXAWUJUcuJ2E2oczJdLxAJXL0lOmVaBl7kuk+I= +golang.org/x/tools v0.0.0-20200616195046-dc31b401abb5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= @@ -653,7 +858,9 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= @@ -681,43 +888,113 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 h1:B0J02caTR6tpSJozBJyiAzT6CtBzjclw4pgm9gg8Ys0= gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= k8s.io/api v0.17.2 h1:NF1UFXcKN7/OOv1uxdRz3qfra8AHsPav5M93hlV9+Dc= k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4= +k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0= +k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= +k8s.io/api v0.18.3-beta.0/go.mod h1:ss0kLYOZJc/0IkQJRi4KutkuOa4a2w91yFcZj/DV22g= +k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA= +k8s.io/api v0.18.4-rc.0/go.mod h1:k05vFzymTGPDymkDoL4/FALcKTydjJmijhvliMpK6JU= +k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4= +k8s.io/api v0.18.5-rc.0/go.mod h1:Sen2GNymkPj7QZoPmUNns4ElqshVZynpXpPtDUeSBuM= +k8s.io/api v0.18.5-rc.1/go.mod h1:hRmX4oMFIKdJ8869BD7Hge7SzwFWXcUVC64i1JRD68I= +k8s.io/api v0.18.5/go.mod h1:tN+e/2nbdGKOAH55NMV8oGrMG+3uRlA9GaRfvnCCSNk= +k8s.io/api v0.18.6-rc.0/go.mod h1:uLQmuEFg2FndhM1SpSgW1gG8m0Z6jM1D/7Tg83cYX+8= k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE= k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/apiextensions-apiserver v0.17.0/go.mod h1:XiIFUakZywkUl54fVXa7QTEHcqQz9HG55nHd1DCoHj8= k8s.io/apiextensions-apiserver v0.17.2 h1:cP579D2hSZNuO/rZj9XFRzwJNYb41DbNANJb6Kolpss= k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs= +k8s.io/apiextensions-apiserver v0.18.2 h1:I4v3/jAuQC+89L3Z7dDgAiN4EOjN6sbm6iBqQwHTah8= +k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY= +k8s.io/apiextensions-apiserver v0.18.3-beta.0/go.mod h1:f1gtsQAUDzYS//fJFvSUKFgdC+NpzGfjOF/rdLR9tSQ= +k8s.io/apiextensions-apiserver v0.18.3/go.mod h1:TMsNGs7DYpMXd+8MOCX8KzPOCx8fnZMoIGB24m03+JE= +k8s.io/apiextensions-apiserver v0.18.4-rc.0/go.mod h1:7DHvEB3F/7GlRGcguTJrnrttgduc0hR7AgubheNubso= +k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio= +k8s.io/apiextensions-apiserver v0.18.5-rc.0/go.mod h1:m8Za6wsOS0xMWZjeADBlVI0rUE2+NUQmmgrDyMQY2ZI= +k8s.io/apiextensions-apiserver v0.18.5-rc.1/go.mod h1:AuLim3Q7dEPlyce++CaXUsc/NtdKHt+AhJ0y3YThB3Y= +k8s.io/apiextensions-apiserver v0.18.5/go.mod h1:woZ7PkEIMHjhHIyApvOwkGOkBLUYKuet0VWVkPTQ/Fs= +k8s.io/apiextensions-apiserver v0.18.6-rc.0/go.mod h1:zFJcSY89Lc7C0smuPseW+L5ZoC42YQSgAMEPPgdRNzU= k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo= k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.17.2 h1:hwDQQFbdRlpnnsR64Asdi55GyCaIP/3WQpMmbNBeWr4= k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= +k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/apimachinery v0.18.3-beta.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.4-rc.0/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.5-rc.0/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.5-rc.1/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.5/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.6-rc.0/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag= k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0= +k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg= k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo= +k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw= +k8s.io/apiserver v0.18.3-beta.0/go.mod h1:qbewHvUHj1k++gfQXEXI4/nWJgJyXoxyWE2MBCcmPJA= +k8s.io/apiserver v0.18.3/go.mod h1:tHQRmthRPLUtwqsOnJJMoI8SW3lnoReZeE861lH8vUw= +k8s.io/apiserver v0.18.4-rc.0/go.mod h1:Rbj1TWn9CxhGaZlOv5gsgNo+GENWrSbHRjX6MDw6z5Q= +k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8= +k8s.io/apiserver v0.18.5-rc.0/go.mod h1:CwCKg3lGX1yp4cDtUaY5eBBc3FFJq9nIFJYixZFTxmY= +k8s.io/apiserver v0.18.5-rc.1/go.mod h1:kJgwJ1BbLZG36i62lzhr0SGbJibJEQMuNB6keen1oAg= +k8s.io/apiserver v0.18.5/go.mod h1:+1XgOMq7YJ3OyqPNSJ54EveHwCoBWcJT9CaPycYI5ps= +k8s.io/apiserver v0.18.6-rc.0/go.mod h1:U8TdM3XQ4O8b6akOiA/EBsRQpVKep891RkjB8aFBTS8= k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= k8s.io/client-go v0.17.2 h1:ndIfkfXEGrNhLIgkr0+qhRguSD3u6DCmonepn1O6NYc= k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI= +k8s.io/client-go v0.17.3/go.mod h1:cLXlTMtWHkuK4tD360KpWz2gG2KtdWEr/OT02i3emRQ= +k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= +k8s.io/client-go v0.18.3-beta.0/go.mod h1:d6/8fR+mroX7RqejFSCwnPHIuCH7ztN/gZOWn3G3Urg= +k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw= +k8s.io/client-go v0.18.4-rc.0/go.mod h1:Ue536xBUYkD/afKIRXbs76v1aGvah8vfP1Wxm8y/VZM= +k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g= +k8s.io/client-go v0.18.5-rc.0/go.mod h1:iwpkbIi1dqKIHkSqFJvbjGADTdcJ1A+0x1yCyxzgvxI= +k8s.io/client-go v0.18.5-rc.1/go.mod h1:iextYxIaRGitB63LCMQrbSTvW5HuLZz/fLueCSQoWWU= +k8s.io/client-go v0.18.5/go.mod h1:EsiD+7Fx+bRckKWZXnAXRKKetm1WuzPagH4iOSC8x58= +k8s.io/client-go v0.18.6-rc.0/go.mod h1:xaNbMd3ry+nF+NENtuddNIsi3bkaX+dsXKdse065Rxc= k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw= k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= k8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= +k8s.io/code-generator v0.18.2 h1:C1Nn2JiMf244CvBDKVPX0W2mZFJkVBg54T8OV7/Imso= +k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= +k8s.io/code-generator v0.18.6 h1:QdfvGfs4gUCS1dru+rLbCKIFxYEV0IRfF8MXwY/ozLk= k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs= +k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM= +k8s.io/component-base v0.18.3-beta.0/go.mod h1:OGjWpevK+HnigavXbachXuwkhC+sfzDz0JbTtZlS/eI= +k8s.io/component-base v0.18.3/go.mod h1:bp5GzGR0aGkYEfTj+eTY0AN/vXTgkJdQXjNTTVUaa3k= +k8s.io/component-base v0.18.4-rc.0/go.mod h1:f5zfBfUZXbFTMegHDl8DZ6KJQYQZMhqWAq92cKuilU4= +k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk= +k8s.io/component-base v0.18.5-rc.0/go.mod h1:HVwE+ELVzz88sYXmZLOY20R1gZ4oXYZIgJy4BSHJ4ME= +k8s.io/component-base v0.18.5-rc.1/go.mod h1:lYT7l/Mn2DaJA7NG/DLmCJ0zI6hrbw/aTc0Ef7hMwXg= +k8s.io/component-base v0.18.5/go.mod h1:RSbcboNk4B+S8Acs2JaBOVW3XNz1+A637s2jL+QQrlU= +k8s.io/component-base v0.18.6-rc.0/go.mod h1:2ukzfbzuuPZrLZsW8NFT5DjQIzeuljMobfJzT7auXBE= k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200114144118-36b2048a9120 h1:RPscN6KhmG54S33L+lr3GS+oD1jmchIU0ll519K6FA4= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -727,6 +1004,7 @@ k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= @@ -739,12 +1017,34 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/controller-runtime v0.5.0 h1:CbqIy5fbUX+4E9bpnBFd204YAzRYlM9SWW77BbrcDQo= sigs.k8s.io/controller-runtime v0.5.0/go.mod h1:REiJzC7Y00U+2YkMbT8wxgrsX5USpXKGhb2sCtAXiT8= +sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvOk9NM= +sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo= +sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A= sigs.k8s.io/controller-runtime v0.6.2 h1:jkAnfdTYBpFwlmBn3pS5HFO06SfxvnTZ1p5PeEF/zAA= sigs.k8s.io/controller-runtime v0.6.2/go.mod h1:vhcq/rlnENJ09SIRp3EveTaZ0yqH526hjf9iJdbUJ/E= +sigs.k8s.io/controller-runtime v0.6.5 h1:DSRu6E4FBeVwd/p8niskCVWnX5TSC6ZT9L/OIWOBK7s= +sigs.k8s.io/controller-runtime v0.6.5/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY= +sigs.k8s.io/controller-tools v0.2.5 h1:kH7HKWed9XO42OTxyhUtqyImiefdZV2Q9Jbrytvhf18= sigs.k8s.io/controller-tools v0.2.5/go.mod h1:+t0Hz6tOhJQCdd7IYO0mNzimmiM9sqMU0021u6UCF2o= +sigs.k8s.io/controller-tools v0.4.0 h1:9zIdrc6q9RKke8+DnVPVBVZ+cfF9L0TwM01cxNnklYo= +sigs.k8s.io/controller-tools v0.4.0/go.mod h1:G9rHdZMVlBDocIxGkK3jHLWqcTMNvveypYJwrvYKjWU= +sigs.k8s.io/controller-tools v0.4.1 h1:VkuV0MxlRPmRu5iTgBZU4UxUX2LiR99n3sdQGRxZF4w= +sigs.k8s.io/kind v0.9.0 h1:SoDlXq6pEc7dGagHULNRCCBYrLH6xOi7lqXTRXeAlg4= +sigs.k8s.io/kind v0.9.0/go.mod h1:cxKQWwmbtRDzQ+RNKnR6gZG6fjbeTtItp5cGf+ww+1Y= +sigs.k8s.io/kustomize/api v0.6.4 h1:YIVJnBH+yngw1/1CcLH6GvJ2aEoX9jDlqVuWhfjWmYc= +sigs.k8s.io/kustomize/api v0.6.4/go.mod h1:BX11EGGdQTCYHLMaz7oT4TKdQ8owypNlYIrUj1i5HwA= +sigs.k8s.io/kustomize/cmd/config v0.8.4 h1:Lo26VjENBVfAOJu0v6w6C1cv5Zp9K8XRs39/0cb87+k= +sigs.k8s.io/kustomize/cmd/config v0.8.4/go.mod h1:1+URiyIJrjdxCdCNDuh2SLyf+I36txRgubFI3PIg2Gc= +sigs.k8s.io/kustomize/kustomize/v3 v3.8.6 h1:QmnslC85Qvi7onieAL5OF2gWu6BZXns+N3gWJAFkpys= +sigs.k8s.io/kustomize/kustomize/v3 v3.8.6/go.mod h1:xdsZQdh9dxB0xI+gv6K0nJuPYt1S+9O6K4Su1HXJTHM= +sigs.k8s.io/kustomize/kyaml v0.9.3 h1:kZ5HnNmmnbndSXFivrAVM6u3Ik1dwu4kbpaV8KNwy8I= +sigs.k8s.io/kustomize/kyaml v0.9.3/go.mod h1:UTm64bSWVdBUA8EQoYCxVOaBQxUdIOr5LKWxA4GNbkw= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= @@ -755,3 +1055,4 @@ sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/main.go b/main.go index ac463339d3f..68a885e8d4d 100644 --- a/main.go +++ b/main.go @@ -146,10 +146,10 @@ func main() { if keyvaultName == "" { setupLog.Info("Keyvault name is empty") - secretClient = k8sSecrets.New(mgr.GetClient()) + secretClient = k8sSecrets.New(mgr.GetClient(), config.SecretNamingVersion()) } else { setupLog.Info("Instantiating secrets client for keyvault " + keyvaultName) - secretClient = keyvaultSecrets.New(keyvaultName, config.GlobalCredentials()) + secretClient = keyvaultSecrets.New(keyvaultName, config.GlobalCredentials(), config.SecretNamingVersion()) } // TODO(creds-refactor): construction of these managers will need diff --git a/pkg/resourcemanager/appinsights/api_keys_reconcile.go b/pkg/resourcemanager/appinsights/api_keys_reconcile.go index 4ff65efbae4..baf8df557d5 100644 --- a/pkg/resourcemanager/appinsights/api_keys_reconcile.go +++ b/pkg/resourcemanager/appinsights/api_keys_reconcile.go @@ -63,6 +63,8 @@ func (c *InsightsAPIKeysClient) Ensure(ctx context.Context, obj runtime.Object, instance.Spec.WriteAnnotations, instance.Spec.AuthSDKControlChannel, ) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + if err != nil { instance.Status.Message = err.Error() azerr := errhelp.NewAzureError(err) @@ -72,8 +74,7 @@ func (c *InsightsAPIKeysClient) Ensure(ctx context.Context, obj runtime.Object, case http.StatusBadRequest: // if the key already exists it is fine only if the secret exists if strings.Contains(azerr.Type, "already exists") { - sKey := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} - if _, err := c.SecretClient.Get(ctx, sKey); err != nil { + if _, err := c.SecretClient.Get(ctx, secretKey); err != nil { instance.Status.Message = "api key exists but no key could be recovered" instance.Status.FailedProvisioning = true } @@ -89,8 +90,9 @@ func (c *InsightsAPIKeysClient) Ensure(ctx context.Context, obj runtime.Object, } // when create is successful we have to store the apikey somewhere - err = c.SecretClient.Upsert(ctx, - types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, + err = c.SecretClient.Upsert( + ctx, + secretKey, map[string][]byte{"apiKey": []byte(*apiKey.APIKey)}, secrets.WithOwner(instance), secrets.WithScheme(c.Scheme), @@ -148,8 +150,8 @@ func (c *InsightsAPIKeysClient) Delete(ctx context.Context, obj runtime.Object, } - sKey := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} - err = c.SecretClient.Delete(ctx, sKey) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + err = c.SecretClient.Delete(ctx, secretKey) if err != nil { return true, err } diff --git a/pkg/resourcemanager/appinsights/appinsights.go b/pkg/resourcemanager/appinsights/appinsights.go index 90d2b7e6ce6..4b6a7897287 100644 --- a/pkg/resourcemanager/appinsights/appinsights.go +++ b/pkg/resourcemanager/appinsights/appinsights.go @@ -11,16 +11,17 @@ import ( "github.com/Azure/azure-service-operator/pkg/secrets" "github.com/Azure/azure-sdk-for-go/services/appinsights/mgmt/2015-05-01/insights" + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/to" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "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/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/to" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" ) // Manager manages Azure Application Insights services @@ -30,6 +31,8 @@ type Manager struct { Scheme *runtime.Scheme } +var _ ApplicationInsightsManager = &Manager{} + // NewManager creates a new AppInsights Manager func NewManager(creds config.Credentials, secretClient secrets.SecretClient, scheme *runtime.Scheme) *Manager { return &Manager{ @@ -107,21 +110,18 @@ func (m *Manager) CreateAppInsights( } // StoreSecrets upserts the secret information for this app insight -func (m *Manager) StoreSecrets(ctx context.Context, resourceGroupName string, appInsightsName string, instrumentationKey string, instance *v1alpha1.AppInsights) error { +func (m *Manager) StoreSecrets(ctx context.Context, instrumentationKey string, instance *v1alpha1.AppInsights) error { // build the connection string data := map[string][]byte{ - "AppInsightsName": []byte(appInsightsName), + "AppInsightsName": []byte(instance.Name), } data["instrumentationKey"] = []byte(instrumentationKey) // upsert - key := types.NamespacedName{ - Name: fmt.Sprintf("appinsights-%s-%s", resourceGroupName, appInsightsName), - Namespace: instance.Namespace, - } + secretKey := m.makeSecretKey(instance) return m.SecretClient.Upsert(ctx, - key, + secretKey, data, secrets.WithOwner(instance), secrets.WithScheme(m.Scheme), @@ -129,12 +129,9 @@ func (m *Manager) StoreSecrets(ctx context.Context, resourceGroupName string, ap } // DeleteSecret deletes the secret information for this app insight -func (m *Manager) DeleteSecret(ctx context.Context, resourceGroupName string, appInsightsName string, instance *v1alpha1.AppInsights) error { - key := types.NamespacedName{ - Name: fmt.Sprintf("appinsights-%s-%s", resourceGroupName, appInsightsName), - Namespace: instance.Namespace, - } - return m.SecretClient.Delete(ctx, key) +func (m *Manager) DeleteSecret(ctx context.Context, instance *v1alpha1.AppInsights) error { + secretKey := m.makeSecretKey(instance) + return m.SecretClient.Delete(ctx, secretKey) } // Ensure checks the desired state of the operator @@ -166,8 +163,6 @@ func (m *Manager) Ensure(ctx context.Context, obj runtime.Object, opts ...resour if comp.ApplicationInsightsComponentProperties != nil { properties := *comp.ApplicationInsightsComponentProperties err = m.StoreSecrets(ctx, - instance.Spec.ResourceGroup, - instance.Name, *properties.InstrumentationKey, instance, ) @@ -233,12 +228,12 @@ func (m *Manager) Delete(ctx context.Context, obj runtime.Object, opts ...resour m.SecretClient = options.SecretClient } - i, err := m.convert(obj) + instance, err := m.convert(obj) if err != nil { return false, err } - response, err := m.DeleteAppInsights(ctx, i.Spec.ResourceGroup, i.Name) + response, err := m.DeleteAppInsights(ctx, instance.Spec.ResourceGroup, instance.Name) if err != nil { catch := []string{ errhelp.AsyncOpIncompleteError, @@ -253,22 +248,16 @@ func (m *Manager) Delete(ctx context.Context, obj runtime.Object, opts ...resour if helpers.ContainsString(catch, azerr.Type) { return true, nil } else if helpers.ContainsString(gone, azerr.Type) { - m.DeleteSecret(ctx, - i.Spec.ResourceGroup, - i.Name, - i) + m.DeleteSecret(ctx, instance) return false, nil } return true, err } - i.Status.State = response.Status + instance.Status.State = response.Status if err == nil { if response.Status != "InProgress" { - m.DeleteSecret(ctx, - i.Spec.ResourceGroup, - i.Name, - i) + m.DeleteSecret(ctx, instance) return false, nil } } @@ -318,3 +307,10 @@ func getComponentsClient(creds config.Credentials) (insights.ComponentsClient, e } return insightsClient, err } + +func (m *Manager) makeSecretKey(instance *v1alpha1.AppInsights) secrets.SecretKey { + if m.SecretClient.GetSecretNamingVersion() == secrets.SecretNamingV1 { + return secrets.SecretKey{Name: fmt.Sprintf("appinsights-%s-%s", instance.Spec.ResourceGroup, instance.Name), Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + } + return secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} +} diff --git a/pkg/resourcemanager/appinsights/manager.go b/pkg/resourcemanager/appinsights/manager.go index 1321755b74f..1f1ffe121da 100644 --- a/pkg/resourcemanager/appinsights/manager.go +++ b/pkg/resourcemanager/appinsights/manager.go @@ -24,16 +24,9 @@ type ApplicationInsightsManager interface { DeleteAppInsights(ctx context.Context, resourceGroupName string, resourceName string) (autorest.Response, error) GetAppInsights(ctx context.Context, resourceGroupName string, resourceName string) (insights.ApplicationInsightsComponent, error) - StoreSecrets(ctx context.Context, - resourceGroupName string, - appInsightsName string, - instrumentationKey string, - instance *v1alpha1.AppInsights) error + StoreSecrets(ctx context.Context, instrumentationKey string, instance *v1alpha1.AppInsights) error - DeleteSecret(ctx context.Context, - resourceGroupName string, - appInsightsName string, - instance *v1alpha1.AppInsights) error + DeleteSecret(ctx context.Context, instance *v1alpha1.AppInsights) error // ARM Client resourcemanager.ARMClient diff --git a/pkg/resourcemanager/azuresql/azuresqlaction/azuresqlaction.go b/pkg/resourcemanager/azuresql/azuresqlaction/azuresqlaction.go index 05e21946a6e..cba6b23ed94 100644 --- a/pkg/resourcemanager/azuresql/azuresqlaction/azuresqlaction.go +++ b/pkg/resourcemanager/azuresql/azuresqlaction/azuresqlaction.go @@ -6,20 +6,21 @@ package azuresqlaction import ( "context" "fmt" + "reflect" "strings" + "github.com/Azure/go-autorest/autorest/to" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + 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" - azuresqlserver "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlserver" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlserver" "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlshared" - azuresqluser "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqluser" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqluser" "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" "github.com/Azure/azure-service-operator/pkg/secrets" - "github.com/Azure/go-autorest/autorest/to" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" ) type AzureSqlActionManager struct { @@ -36,20 +37,31 @@ func NewAzureSqlActionManager(creds config.Credentials, secretClient secrets.Sec } } -func (s *AzureSqlActionManager) UpdateUserPassword(ctx context.Context, groupName string, serverName string, dbUser string, dbName string, - adminSecretKey types.NamespacedName, adminSecretClient secrets.SecretClient, userSecretClient secrets.SecretClient) error { - data, err := adminSecretClient.Get(ctx, adminSecretKey) +func (s *AzureSqlActionManager) UpdateUserPassword( + ctx context.Context, + groupName string, + serverName string, + dbUser string, + dbName string, + adminSecretKey secrets.SecretKey, + adminSecretClient secrets.SecretClient, + userSecretClient secrets.SecretClient) error { + + adminSecret, err := adminSecretClient.Get(ctx, adminSecretKey) if err != nil { return err } azuresqluserManager := azuresqluser.NewAzureSqlUserManager(s.Creds, userSecretClient, s.Scheme) - db, err := azuresqluserManager.ConnectToSqlDb(ctx, "sqlserver", serverName, dbName, 1433, string(data["username"]), string(data["password"])) + db, err := azuresqluserManager.ConnectToSqlDb(ctx, "sqlserver", serverName, dbName, 1433, string(adminSecret["username"]), string(adminSecret["password"])) if err != nil { return err } instance := &azurev1alpha1.AzureSQLUser{ + TypeMeta: metav1.TypeMeta{ + Kind: reflect.TypeOf(azurev1alpha1.AzureSQLUser{}).Name(), + }, ObjectMeta: metav1.ObjectMeta{ Name: dbUser, Namespace: adminSecretKey.Namespace, @@ -60,9 +72,9 @@ func (s *AzureSqlActionManager) UpdateUserPassword(ctx context.Context, groupNam }, } - DBSecret := azuresqluserManager.GetOrPrepareSecret(ctx, instance, userSecretClient) + userSecret := azuresqluserManager.GetOrPrepareSecret(ctx, instance, userSecretClient) // reset user from secret in case it was loaded - userExists, err := azuresqluserManager.UserExists(ctx, db, string(DBSecret["username"])) + userExists, err := azuresqluserManager.UserExists(ctx, db, string(userSecret["username"])) if err != nil { return fmt.Errorf("failed checking for user, err: %v", err) } @@ -72,19 +84,18 @@ func (s *AzureSqlActionManager) UpdateUserPassword(ctx context.Context, groupNam } password := helpers.NewPassword() - DBSecret["password"] = []byte(password) + userSecret["password"] = []byte(password) - err = azuresqluserManager.UpdateUser(ctx, DBSecret, db) + err = azuresqluserManager.UpdateUser(ctx, userSecret, db) if err != nil { return fmt.Errorf("error updating user credentials: %v", err) } - secretKey := azuresqluser.GetNamespacedName(instance, userSecretClient) - key := types.NamespacedName{Namespace: secretKey.Namespace, Name: dbUser} + secretKey := azuresqluser.MakeSecretKey(userSecretClient, instance) err = userSecretClient.Upsert( ctx, - key, - DBSecret, + secretKey, + userSecret, secrets.WithOwner(instance), secrets.WithScheme(s.Scheme), ) @@ -97,7 +108,7 @@ func (s *AzureSqlActionManager) UpdateUserPassword(ctx context.Context, groupNam // UpdateAdminPassword gets the server instance from Azure, updates the admin password // for the server and stores the new password in the secret -func (s *AzureSqlActionManager) UpdateAdminPassword(ctx context.Context, groupName string, serverName string, secretKey types.NamespacedName, secretClient secrets.SecretClient) error { +func (s *AzureSqlActionManager) UpdateAdminPassword(ctx context.Context, groupName string, serverName string, secretKey secrets.SecretKey, secretClient secrets.SecretClient) error { azuresqlserverManager := azuresqlserver.NewAzureSqlServerManager(s.Creds, secretClient, s.Scheme) // Get the SQL server instance diff --git a/pkg/resourcemanager/azuresql/azuresqlaction/azuresqlaction_reconcile.go b/pkg/resourcemanager/azuresql/azuresqlaction/azuresqlaction_reconcile.go index 2005d8adbe7..f23911ca9f8 100644 --- a/pkg/resourcemanager/azuresql/azuresqlaction/azuresqlaction_reconcile.go +++ b/pkg/resourcemanager/azuresql/azuresqlaction/azuresqlaction_reconcile.go @@ -8,14 +8,16 @@ import ( "fmt" "strings" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "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/azuresql/azuresqluser" "github.com/Azure/azure-service-operator/pkg/secrets" keyvaultsecretlib "github.com/Azure/azure-service-operator/pkg/secrets/keyvault" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" ) const usernameLength = 8 @@ -27,7 +29,7 @@ func (s *AzureSqlActionManager) Ensure(ctx context.Context, obj runtime.Object, if err != nil { return false, err } - var adminKey types.NamespacedName + var adminSecretKey secrets.SecretKey var adminSecretClient secrets.SecretClient var userSecretClient secrets.SecretClient serverName := instance.Spec.ServerName @@ -38,9 +40,9 @@ func (s *AzureSqlActionManager) Ensure(ctx context.Context, obj runtime.Object, // Determine the secret key based on the spec if len(instance.Spec.ServerAdminSecretName) == 0 { - adminKey = types.NamespacedName{Name: instance.Spec.ServerName, Namespace: instance.Namespace} + adminSecretKey = azuresqluser.GetAdminSecretKey(instance.Spec.ServerName, instance.Namespace) } else { - adminKey = types.NamespacedName{Name: instance.Spec.ServerAdminSecretName} + adminSecretKey = azuresqluser.GetAdminSecretKey(instance.Spec.ServerAdminSecretName, instance.Namespace) } // Determine secretclient based on Spec. If Keyvault name isn't specified, fall back to @@ -48,7 +50,7 @@ func (s *AzureSqlActionManager) Ensure(ctx context.Context, obj runtime.Object, if len(instance.Spec.ServerSecretKeyVault) == 0 { adminSecretClient = s.SecretClient } else { - adminSecretClient = keyvaultsecretlib.New(instance.Spec.ServerSecretKeyVault, s.Creds) + adminSecretClient = keyvaultsecretlib.New(instance.Spec.ServerSecretKeyVault, s.Creds, s.SecretClient.GetSecretNamingVersion()) if !keyvaultsecretlib.IsKeyVaultAccessible(adminSecretClient) { instance.Status.Message = "InvalidKeyVaultAccess: Keyvault not accessible yet" return false, nil @@ -56,7 +58,7 @@ func (s *AzureSqlActionManager) Ensure(ctx context.Context, obj runtime.Object, } // Roll SQL server's admin password - err := s.UpdateAdminPassword(ctx, groupName, serverName, adminKey, adminSecretClient) + err := s.UpdateAdminPassword(ctx, groupName, serverName, adminSecretKey, adminSecretClient) if err != nil { instance.Status.Message = err.Error() catch := []string{ @@ -86,9 +88,9 @@ func (s *AzureSqlActionManager) Ensure(ctx context.Context, obj runtime.Object, // Determine the admin secret key based on the spec if len(instance.Spec.ServerAdminSecretName) == 0 { - adminKey = types.NamespacedName{Name: instance.Spec.ServerName, Namespace: instance.Namespace} + adminSecretKey = azuresqluser.GetAdminSecretKey(instance.Spec.ServerName, instance.Namespace) } else { - adminKey = types.NamespacedName{Name: instance.Spec.ServerAdminSecretName} + adminSecretKey = azuresqluser.GetAdminSecretKey(instance.Spec.ServerAdminSecretName, instance.Namespace) } // Determine secretclient based on Spec. If Keyvault name isn't specified, fall back to @@ -96,7 +98,7 @@ func (s *AzureSqlActionManager) Ensure(ctx context.Context, obj runtime.Object, if len(instance.Spec.ServerSecretKeyVault) == 0 { adminSecretClient = s.SecretClient } else { - adminSecretClient = keyvaultsecretlib.New(instance.Spec.ServerSecretKeyVault, s.Creds) + adminSecretClient = keyvaultsecretlib.New(instance.Spec.ServerSecretKeyVault, s.Creds, s.SecretClient.GetSecretNamingVersion()) if !keyvaultsecretlib.IsKeyVaultAccessible(adminSecretClient) { instance.Status.Message = "InvalidKeyVaultAccess: Keyvault not accessible yet" return false, nil @@ -108,14 +110,14 @@ func (s *AzureSqlActionManager) Ensure(ctx context.Context, obj runtime.Object, if len(instance.Spec.UserSecretKeyVault) == 0 { userSecretClient = s.SecretClient } else { - userSecretClient = keyvaultsecretlib.New(instance.Spec.UserSecretKeyVault, s.Creds) + userSecretClient = keyvaultsecretlib.New(instance.Spec.UserSecretKeyVault, s.Creds, s.SecretClient.GetSecretNamingVersion()) if !keyvaultsecretlib.IsKeyVaultAccessible(userSecretClient) { instance.Status.Message = "InvalidKeyVaultAccess: Keyvault not accessible yet" return false, nil } } - err := s.UpdateUserPassword(ctx, groupName, serverName, instance.Spec.DbUser, instance.Spec.DbName, adminKey, adminSecretClient, userSecretClient) + err := s.UpdateUserPassword(ctx, groupName, serverName, instance.Spec.DbUser, instance.Spec.DbName, adminSecretKey, adminSecretClient, userSecretClient) if err != nil { instance.Status.Message = errhelp.StripErrorIDs(err) diff --git a/pkg/resourcemanager/azuresql/azuresqlfailovergroup/azuresqlfailovergroup_reconcile.go b/pkg/resourcemanager/azuresql/azuresqlfailovergroup/azuresqlfailovergroup_reconcile.go index a21d5eb739e..3e1539dedb5 100644 --- a/pkg/resourcemanager/azuresql/azuresqlfailovergroup/azuresqlfailovergroup_reconcile.go +++ b/pkg/resourcemanager/azuresql/azuresqlfailovergroup/azuresqlfailovergroup_reconcile.go @@ -85,8 +85,8 @@ func (fg *AzureSqlFailoverGroupManager) Ensure(ctx context.Context, obj runtime. return false, nil } - key := types.NamespacedName{Name: instance.ObjectMeta.Name, Namespace: instance.Namespace} - _, err := fg.SecretClient.Get(ctx, key) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + _, err := fg.SecretClient.Get(ctx, secretKey) // We make the same assumption many other places in the code make which is that if we cannot // get the secret it must not exist. if err != nil { @@ -96,7 +96,7 @@ func (fg *AzureSqlFailoverGroupManager) Ensure(ctx context.Context, obj runtime. // create or update the secret err = fg.SecretClient.Upsert( ctx, - key, + secretKey, secret, secrets.WithOwner(instance), secrets.WithScheme(fg.Scheme), @@ -156,7 +156,7 @@ func (fg *AzureSqlFailoverGroupManager) Delete(ctx context.Context, obj runtime. failoverGroupName := instance.ObjectMeta.Name // key for Secret to delete on successful provision - key := types.NamespacedName{Name: instance.ObjectMeta.Name, Namespace: instance.Namespace} + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} _, err = fg.DeleteFailoverGroup(ctx, groupName, serverName, failoverGroupName) if err != nil { @@ -180,7 +180,7 @@ func (fg *AzureSqlFailoverGroupManager) Delete(ctx context.Context, obj runtime. if helpers.ContainsString(finished, azerr.Type) { // Best case deletion of secret - fg.SecretClient.Delete(ctx, key) + fg.SecretClient.Delete(ctx, secretKey) return false, nil } instance.Status.Message = fmt.Sprintf("AzureSqlFailoverGroup Delete failed with: %s", err.Error()) @@ -189,7 +189,7 @@ func (fg *AzureSqlFailoverGroupManager) Delete(ctx context.Context, obj runtime. instance.Status.Message = fmt.Sprintf("Delete AzureSqlFailoverGroup succeeded") // Best case deletion of secret - fg.SecretClient.Delete(ctx, key) + fg.SecretClient.Delete(ctx, secretKey) return false, nil } diff --git a/pkg/resourcemanager/azuresql/azuresqlmanageduser/azuresqlmanageduser.go b/pkg/resourcemanager/azuresql/azuresqlmanageduser/azuresqlmanageduser.go index bd3626b3d8f..fbe50e412a9 100644 --- a/pkg/resourcemanager/azuresql/azuresqlmanageduser/azuresqlmanageduser.go +++ b/pkg/resourcemanager/azuresql/azuresqlmanageduser/azuresqlmanageduser.go @@ -10,17 +10,15 @@ import ( "strings" azuresql "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/v3.0/sql" + mssql "github.com/denisenkom/go-mssqldb" uuid "github.com/satori/go.uuid" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "github.com/Azure/azure-service-operator/api/v1alpha1" - azuresqlshared "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlshared" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlshared" "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" - - mssql "github.com/denisenkom/go-mssqldb" ) type AzureSqlManagedUserManager struct { @@ -143,62 +141,33 @@ func (s *AzureSqlManagedUserManager) DropUser(ctx context.Context, db *sql.DB, u // UpdateSecret gets or creates a secret func (s *AzureSqlManagedUserManager) UpdateSecret(ctx context.Context, instance *v1alpha1.AzureSQLManagedUser, secretClient secrets.SecretClient) error { - - secretprefix := instance.Name - secretnamespace := instance.Namespace - - if len(instance.Spec.KeyVaultSecretPrefix) != 0 { // If KeyVaultSecretPrefix is specified, use that for secrets - secretprefix = instance.Spec.KeyVaultSecretPrefix - secretnamespace = "" - } - secret := map[string][]byte{ "clientid": []byte(instance.Spec.ManagedIdentityClientId), "server": []byte(instance.Spec.Server), "dbName": []byte(instance.Spec.DbName), } - key := types.NamespacedName{Name: secretprefix, Namespace: secretnamespace} - // We store the different secret fields as different secrets - instance.Status.FlattenedSecrets = true - err := secretClient.Upsert(ctx, key, secret, secrets.Flatten(true)) - if err != nil { - if strings.Contains(err.Error(), "FlattenedSecretsNotSupported") { // kube client does not support Flatten - err = secretClient.Upsert(ctx, key, secret) - if err != nil { - return fmt.Errorf("Upsert into KubeClient without flatten failed") - } - instance.Status.FlattenedSecrets = false - } + var err error + secretKey := makeSecretKey(secretClient, instance) + if secretClient.IsKeyVault() { // TODO: Maybe should be SupportsFlatten() at least for this case? + instance.Status.FlattenedSecrets = true + err = secretClient.Upsert(ctx, secretKey, secret, secrets.Flatten(true)) + } else { + err = secretClient.Upsert(ctx, secretKey, secret) } + return err } // DeleteSecret deletes a secret func (s *AzureSqlManagedUserManager) DeleteSecrets(ctx context.Context, instance *v1alpha1.AzureSQLManagedUser, secretClient secrets.SecretClient) error { - secretprefix := instance.Name - secretnamespace := instance.Namespace - - if len(instance.Spec.KeyVaultSecretPrefix) != 0 { // If KeyVaultSecretPrefix is specified, use that for secrets - secretprefix = instance.Spec.KeyVaultSecretPrefix - secretnamespace = "" - } - suffixes := []string{"clientid", "server", "dbName"} - if instance.Status.FlattenedSecrets == false { - key := types.NamespacedName{Name: secretprefix, Namespace: secretnamespace} - return secretClient.Delete(ctx, key) + secretKey := makeSecretKey(secretClient, instance) + if secretClient.IsKeyVault() { // TODO: Maybe should be SupportsFlatten() at least for this case? + return secretClient.Delete(ctx, secretKey, secrets.Flatten(instance.Status.FlattenedSecrets, suffixes...)) } else { - // Delete the secrets one by one - for _, suffix := range suffixes { - key := types.NamespacedName{Name: secretprefix + "-" + suffix, Namespace: secretnamespace} - err := secretClient.Delete(ctx, key) - if err != nil { - return err - } - } + return secretClient.Delete(ctx, secretKey) } - return nil } func convertToSid(msiClientId string) string { @@ -243,3 +212,15 @@ func findBadChars(stack string) error { } return nil } + +func makeSecretKey(secretClient secrets.SecretClient, instance *v1alpha1.AzureSQLManagedUser) secrets.SecretKey { + if secretClient.GetSecretNamingVersion() == secrets.SecretNamingV1 { + if len(instance.Spec.KeyVaultSecretPrefix) != 0 { // If KeyVaultSecretPrefix is specified, use that for secrets + return secrets.SecretKey{Name: instance.Spec.KeyVaultSecretPrefix, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + } + return secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + } + + // TODO: Ignoring prefix here... we ok with that? + return secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} +} diff --git a/pkg/resourcemanager/azuresql/azuresqlserver/azuresqlserver.go b/pkg/resourcemanager/azuresql/azuresqlserver/azuresqlserver.go index 8aa90dc603b..796a30f6d05 100644 --- a/pkg/resourcemanager/azuresql/azuresqlserver/azuresqlserver.go +++ b/pkg/resourcemanager/azuresql/azuresqlserver/azuresqlserver.go @@ -9,12 +9,13 @@ import ( "net/http" "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/v3.0/sql" - azuresqlshared "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlshared" - "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" - "github.com/Azure/azure-service-operator/pkg/secrets" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/to" "k8s.io/apimachinery/pkg/runtime" + + "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlshared" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" + "github.com/Azure/azure-service-operator/pkg/secrets" ) const typeOfService = "Microsoft.Sql/servers" diff --git a/pkg/resourcemanager/azuresql/azuresqlserver/azuresqlserver_reconcile.go b/pkg/resourcemanager/azuresql/azuresqlserver/azuresqlserver_reconcile.go index 2a01623e775..925a406c12e 100644 --- a/pkg/resourcemanager/azuresql/azuresqlserver/azuresqlserver_reconcile.go +++ b/pkg/resourcemanager/azuresql/azuresqlserver/azuresqlserver_reconcile.go @@ -8,18 +8,20 @@ import ( "fmt" "strings" + "github.com/Azure/go-autorest/autorest/to" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "github.com/Azure/azure-service-operator/api/v1alpha1" "github.com/Azure/azure-service-operator/api/v1beta1" "github.com/Azure/azure-service-operator/pkg/errhelp" "github.com/Azure/azure-service-operator/pkg/helpers" "github.com/Azure/azure-service-operator/pkg/resourcemanager" - azuresqlshared "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlshared" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlshared" "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" "github.com/Azure/azure-service-operator/pkg/resourcemanager/pollclient" "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" ) const usernameLength = 8 @@ -46,12 +48,12 @@ func (s *AzureSqlServerManager) Ensure(ctx context.Context, obj runtime.Object, // Check to see if secret already exists for admin username/password // create or update the secret - key := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} - secret, err := s.SecretClient.Get(ctx, key) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + secret, err := s.SecretClient.Get(ctx, secretKey) if err != nil { if instance.Status.Provisioned { instance.Status.Message = err.Error() - return false, fmt.Errorf("Secret missing for provisioned server: %s", key.String()) + return false, fmt.Errorf("secret missing for provisioned server: %+v", secretKey) } // Assure that the requested name is available and assume the secret exists @@ -68,12 +70,8 @@ func (s *AzureSqlServerManager) Ensure(ctx context.Context, obj runtime.Object, return true, nil } - instance.Status.Message = fmt.Sprintf( - `SQL server already exists and the credentials could not be found. - If using kube secrets a secret should exist at '%s' for keyvault it should be '%s'`, - key.String(), - fmt.Sprintf("%s-%s", key.Namespace, key.Name), - ) + err = errors.Wrapf(err, "SQL server already exists and the credentials could not be found") + instance.Status.Message = err.Error() return false, nil } @@ -83,7 +81,7 @@ func (s *AzureSqlServerManager) Ensure(ctx context.Context, obj runtime.Object, } err = s.SecretClient.Upsert( ctx, - key, + secretKey, secret, secrets.WithOwner(instance), secrets.WithScheme(s.Scheme), @@ -264,7 +262,7 @@ func (s *AzureSqlServerManager) Delete(ctx context.Context, obj runtime.Object, name := instance.ObjectMeta.Name groupName := instance.Spec.ResourceGroup - key := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} // if the resource is in a failed state it was never created or could never be verified // so we skip attempting to delete the resrouce from Azure @@ -294,7 +292,7 @@ func (s *AzureSqlServerManager) Delete(ctx context.Context, obj runtime.Object, if helpers.ContainsString(finished, azerr.Type) { //Best effort deletion of secrets - s.SecretClient.Delete(ctx, key) + s.SecretClient.Delete(ctx, secretKey) return false, nil } @@ -302,7 +300,7 @@ func (s *AzureSqlServerManager) Delete(ctx context.Context, obj runtime.Object, } //Best effort deletion of secrets - s.SecretClient.Delete(ctx, key) + s.SecretClient.Delete(ctx, secretKey) return false, nil } diff --git a/pkg/resourcemanager/azuresql/azuresqluser/azuresqluser.go b/pkg/resourcemanager/azuresql/azuresqluser/azuresqluser.go index 122a135b4e7..ae8b74dbf98 100644 --- a/pkg/resourcemanager/azuresql/azuresqluser/azuresqluser.go +++ b/pkg/resourcemanager/azuresql/azuresqluser/azuresqluser.go @@ -7,20 +7,17 @@ import ( "context" "database/sql" "fmt" - "reflect" "strings" azuresql "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/v3.0/sql" + _ "github.com/denisenkom/go-mssqldb" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/Azure/azure-service-operator/api/v1alpha1" "github.com/Azure/azure-service-operator/pkg/helpers" - azuresqlshared "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlshared" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/azuresql/azuresqlshared" "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" "github.com/Azure/azure-service-operator/pkg/secrets" - - "github.com/Azure/azure-service-operator/api/v1alpha1" - "k8s.io/apimachinery/pkg/runtime" - - _ "github.com/denisenkom/go-mssqldb" - "k8s.io/apimachinery/pkg/types" ) // SqlServerPort is the default server port for sql server @@ -173,9 +170,7 @@ func (m *AzureSqlUserManager) DropUser(ctx context.Context, db *sql.DB, user str // DeleteSecrets deletes the secrets associated with a SQLUser func (m *AzureSqlUserManager) DeleteSecrets(ctx context.Context, instance *v1alpha1.AzureSQLUser, secretClient secrets.SecretClient) (bool, error) { - // determine our key namespace - if we're persisting to kube, we should use the actual instance namespace. - // In keyvault we have some creative freedom to allow more flexibility - secretKey := GetNamespacedName(instance, secretClient) + secretKey := MakeSecretKey(secretClient, instance) // delete standard user secret err := secretClient.Delete( @@ -188,8 +183,7 @@ func (m *AzureSqlUserManager) DeleteSecrets(ctx context.Context, instance *v1alp } // delete all the custom formatted secrets if keyvault is in use - keyVaultEnabled := reflect.TypeOf(secretClient).Elem().Name() == "KeyvaultSecretClient" - if keyVaultEnabled { + if secretClient.IsKeyVault() { customFormatNames := []string{ "adonet", "adonet-urlonly", @@ -203,17 +197,14 @@ func (m *AzureSqlUserManager) DeleteSecrets(ctx context.Context, instance *v1alp "password", } - for _, formatName := range customFormatNames { - key := types.NamespacedName{Namespace: secretKey.Namespace, Name: instance.Name + "-" + formatName} - - err = secretClient.Delete( - ctx, - key, - ) - if err != nil { - instance.Status.Message = "failed to delete secret, err: " + err.Error() - return false, err - } + err = secretClient.Delete( + ctx, + secretKey, + secrets.Flatten(true, customFormatNames...), + ) + if err != nil { + instance.Status.Message = "failed to delete secret, err: " + err.Error() + return false, err } } @@ -222,9 +213,9 @@ func (m *AzureSqlUserManager) DeleteSecrets(ctx context.Context, instance *v1alp // GetOrPrepareSecret gets or creates a secret func (m *AzureSqlUserManager) GetOrPrepareSecret(ctx context.Context, instance *v1alpha1.AzureSQLUser, secretClient secrets.SecretClient) map[string][]byte { - key := GetNamespacedName(instance, secretClient) + secretKey := MakeSecretKey(secretClient, instance) - secret, err := secretClient.Get(ctx, key) + secret, err := secretClient.Get(ctx, secretKey) if err != nil { // @todo: find out whether this is an error due to non existing key or failed conn pw := helpers.NewPassword() @@ -240,24 +231,24 @@ func (m *AzureSqlUserManager) GetOrPrepareSecret(ctx context.Context, instance * return secret } -// GetNamespacedName gets the namespaced-name -func GetNamespacedName(instance *v1alpha1.AzureSQLUser, secretClient secrets.SecretClient) types.NamespacedName { - var namespacedName types.NamespacedName - keyVaultEnabled := reflect.TypeOf(secretClient).Elem().Name() == "KeyvaultSecretClient" +func MakeSecretKey(secretClient secrets.SecretClient, instance *v1alpha1.AzureSQLUser) secrets.SecretKey { + if secretClient.GetSecretNamingVersion() == secrets.SecretNamingV1 { + // TODO: It's awkward that we have to have IsKeyVault on our interface which should be abstracting + // TODO: away KeyVault vs Kubernetes... but we need it for legacy reasons (ick). + if !secretClient.IsKeyVault() { + // The fact that this path doesn't take into account secret prefix is for legacy reasons... (although it feels like a bug) + return secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + } - if keyVaultEnabled { - // For a keyvault secret store, check for supplied namespace parameters - var dbUserCustomNamespace string + var namespace string if instance.Spec.KeyVaultSecretPrefix != "" { - dbUserCustomNamespace = instance.Spec.KeyVaultSecretPrefix + namespace = instance.Spec.KeyVaultSecretPrefix } else { - dbUserCustomNamespace = "azuresqluser-" + instance.Spec.Server + "-" + instance.Spec.DbName + namespace = "azuresqluser-" + instance.Spec.Server + "-" + instance.Spec.DbName } - - namespacedName = types.NamespacedName{Namespace: dbUserCustomNamespace, Name: instance.Name} - } else { - namespacedName = types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} + return secrets.SecretKey{Name: instance.Name, Namespace: namespace, Kind: instance.TypeMeta.Kind} } - return namespacedName + // TODO: Ignoring prefix here... we ok with that? + return secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} } diff --git a/pkg/resourcemanager/azuresql/azuresqluser/azuresqluser_reconcile.go b/pkg/resourcemanager/azuresql/azuresqluser/azuresqluser_reconcile.go index e8fe0596103..b0f2abc94da 100644 --- a/pkg/resourcemanager/azuresql/azuresqluser/azuresqluser_reconcile.go +++ b/pkg/resourcemanager/azuresql/azuresqluser/azuresqluser_reconcile.go @@ -9,22 +9,49 @@ import ( "reflect" "strings" - "github.com/Azure/azure-service-operator/pkg/helpers" - "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" - "github.com/Azure/azure-service-operator/pkg/secrets" - + _ "github.com/denisenkom/go-mssqldb" "github.com/google/uuid" + "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/Azure/azure-service-operator/api/v1beta1" "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/secrets" keyvaultSecrets "github.com/Azure/azure-service-operator/pkg/secrets/keyvault" - - _ "github.com/denisenkom/go-mssqldb" - "k8s.io/apimachinery/pkg/types" ) +func GetAdminSecretKey(adminSecretName string, namespace string) secrets.SecretKey { + // TODO: Is there a better way to get TypeMeta here? + return secrets.SecretKey{Name: adminSecretName, Namespace: namespace, Kind: reflect.TypeOf(v1beta1.AzureSqlServer{}).Name()} +} + +func (s *AzureSqlUserManager) getAdminSecret(ctx context.Context, instance *v1alpha1.AzureSQLUser) (map[string][]byte, error) { + adminSecretClient := s.SecretClient + adminSecretName := instance.Spec.AdminSecret + if len(adminSecretName) == 0 { + adminSecretName = instance.Spec.Server + } + adminSecretKey := GetAdminSecretKey(adminSecretName, instance.Namespace) + + // if the admin secret keyvault is not specified, fall back to global secretclient + if len(instance.Spec.AdminSecretKeyVault) != 0 { + adminSecretClient = keyvaultSecrets.New(instance.Spec.AdminSecretKeyVault, s.Creds, s.SecretClient.GetSecretNamingVersion()) + } + + // get admin creds for server + secret, err := adminSecretClient.Get(ctx, adminSecretKey) + if err != nil { + return nil, errors.Wrap(err, "AzureSqlServer admin secret not found") + } + + return secret, nil +} + // Ensure that user exists func (s *AzureSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { instance, err := s.convert(obj) @@ -42,53 +69,34 @@ func (s *AzureSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, op opt(options) } - adminSecretClient := s.SecretClient - - adminsecretName := instance.Spec.AdminSecret - if len(instance.Spec.AdminSecret) == 0 { - adminsecretName = instance.Spec.Server - } - - key := types.NamespacedName{Name: adminsecretName, Namespace: instance.Namespace} - - var sqlUserSecretClient secrets.SecretClient - if options.SecretClient != nil { - sqlUserSecretClient = options.SecretClient - } else { - sqlUserSecretClient = s.SecretClient - } - - // if the admin secret keyvault is not specified, fall back to global secretclient - if len(instance.Spec.AdminSecretKeyVault) != 0 { - adminSecretClient = keyvaultSecrets.New(instance.Spec.AdminSecretKeyVault, s.Creds) - if len(instance.Spec.AdminSecret) != 0 { - key = types.NamespacedName{Name: instance.Spec.AdminSecret} - } - } - - // get admin creds for server - adminSecret, err := adminSecretClient.Get(ctx, key) + adminSecret, err := s.getAdminSecret(ctx, instance) if err != nil { - instance.Status.Provisioning = false - instance.Status.Message = fmt.Sprintf("admin secret : %s, not found in %s", key.String(), reflect.TypeOf(adminSecretClient).Elem().Name()) + instance.Status.Message = err.Error() return false, nil } adminUser := string(adminSecret[SecretUsernameKey]) adminPassword := string(adminSecret[SecretPasswordKey]) + var userSecretClient secrets.SecretClient + if options.SecretClient != nil { + userSecretClient = options.SecretClient + } else { + userSecretClient = s.SecretClient + } + _, err = s.GetDB(ctx, instance.Spec.ResourceGroup, instance.Spec.Server, instance.Spec.DbName) if err != nil { instance.Status.Message = errhelp.StripErrorIDs(err) instance.Status.Provisioning = false - requeuErrors := []string{ + requeueErrors := []string{ errhelp.ResourceNotFound, errhelp.ParentNotFoundErrorCode, errhelp.ResourceGroupNotFoundErrorCode, } azerr := errhelp.NewAzureError(err) - if helpers.ContainsString(requeuErrors, azerr.Type) { + if helpers.ContainsString(requeueErrors, azerr.Type) { return false, nil } @@ -98,7 +106,7 @@ func (s *AzureSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, op return false, nil } - // if this is an unmarshall error - igmore and continue, otherwise report error and requeue + // if this is an unmarshall error - ignore and continue, otherwise report error and requeue if !strings.Contains(errorString, "cannot unmarshal array into Go struct field serviceError2.details") { return false, err } @@ -123,25 +131,23 @@ func (s *AzureSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, op return false, err } - // determine our key namespace - if we're persisting to kube, we should use the actual instance namespace. - // In keyvault we have to avoid collisions with other secrets so we create a custom namespace with the user's parameters - key = GetNamespacedName(instance, sqlUserSecretClient) + userSecretKey := MakeSecretKey(userSecretClient, instance) // create or get new user secret - DBSecret := s.GetOrPrepareSecret(ctx, instance, sqlUserSecretClient) + userSecret := s.GetOrPrepareSecret(ctx, instance, userSecretClient) // reset user from secret in case it was loaded - user := string(DBSecret[SecretUsernameKey]) + user := string(userSecret[SecretUsernameKey]) if user == "" { user = fmt.Sprintf("%s-%s", requestedUsername, uuid.New()) - DBSecret[SecretUsernameKey] = []byte(user) + userSecret[SecretUsernameKey] = []byte(user) } // Publishing the user secret: // We do this first so if the keyvault does not have right permissions we will not proceed to creating the user - err = sqlUserSecretClient.Upsert( + err = userSecretClient.Upsert( ctx, - key, - DBSecret, + userSecretKey, + userSecret, secrets.WithOwner(instance), secrets.WithScheme(s.Scheme), ) @@ -151,8 +157,7 @@ func (s *AzureSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, op } // Preformatted special formats are only available through keyvault as they require separated secrets - keyVaultEnabled := reflect.TypeOf(sqlUserSecretClient).Elem().Name() == "KeyvaultSecretClient" - if keyVaultEnabled { + if userSecretClient.IsKeyVault() { // Instantiate a map of all formats and flip the bool to true for any that have been requested in the spec. // Formats that were not requested will be explicitly deleted. requestedFormats := map[string]bool{ @@ -173,6 +178,7 @@ func (s *AzureSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, op // Deleted items will be processed immediately but secrets that need to be added will be created in this array and persisted in one pass at the end formattedSecrets := make(map[string][]byte) + var toDelete []string for formatName, requested := range requestedFormats { // Add the format to the output map if it has been requested otherwise call for its deletion from the secret store @@ -181,51 +187,51 @@ func (s *AzureSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, op case "adonet": formattedSecrets["adonet"] = []byte(fmt.Sprintf( "Server=tcp:%v,1433;Initial Catalog=%v;Persist Security Info=False;User ID=%v;Password=%v;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;", - string(DBSecret["fullyQualifiedServerName"]), + string(userSecret["fullyQualifiedServerName"]), instance.Spec.DbName, user, - string(DBSecret["password"]), + string(userSecret["password"]), )) case "adonet-urlonly": formattedSecrets["adonet-urlonly"] = []byte(fmt.Sprintf( "Server=tcp:%v,1433;Initial Catalog=%v;Persist Security Info=False; MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout", - string(DBSecret["fullyQualifiedServerName"]), + string(userSecret["fullyQualifiedServerName"]), instance.Spec.DbName, )) case "jdbc": formattedSecrets["jdbc"] = []byte(fmt.Sprintf( "jdbc:sqlserver://%v:1433;database=%v;user=%v@%v;password=%v;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*."+config.Environment().SQLDatabaseDNSSuffix+";loginTimeout=30;", - string(DBSecret["fullyQualifiedServerName"]), + string(userSecret["fullyQualifiedServerName"]), instance.Spec.DbName, user, instance.Spec.Server, - string(DBSecret["password"]), + string(userSecret["password"]), )) case "jdbc-urlonly": formattedSecrets["jdbc-urlonly"] = []byte(fmt.Sprintf( "jdbc:sqlserver://%v:1433;database=%v;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*."+config.Environment().SQLDatabaseDNSSuffix+";loginTimeout=30;", - string(DBSecret["fullyQualifiedServerName"]), + string(userSecret["fullyQualifiedServerName"]), instance.Spec.DbName, )) case "odbc": formattedSecrets["odbc"] = []byte(fmt.Sprintf( "Server=tcp:%v,1433;Initial Catalog=%v;Persist Security Info=False;User ID=%v;Password=%v;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;", - string(DBSecret["fullyQualifiedServerName"]), + string(userSecret["fullyQualifiedServerName"]), instance.Spec.DbName, user, - string(DBSecret["password"]), + string(userSecret["password"]), )) case "odbc-urlonly": formattedSecrets["odbc-urlonly"] = []byte(fmt.Sprintf( "Driver={ODBC Driver 13 for SQL Server};Server=tcp:%v,1433;Database=%v; Encrypt=yes;TrustServerCertificate=no;Connection Timeout=30;", - string(DBSecret["fullyQualifiedServerName"]), + string(userSecret["fullyQualifiedServerName"]), instance.Spec.DbName, )) case "server": - formattedSecrets["server"] = DBSecret["fullyQualifiedServerName"] + formattedSecrets["server"] = userSecret["fullyQualifiedServerName"] case "database": formattedSecrets["database"] = []byte(instance.Spec.DbName) @@ -234,19 +240,24 @@ func (s *AzureSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, op formattedSecrets["username"] = []byte(user) case "password": - formattedSecrets["password"] = DBSecret["password"] + formattedSecrets["password"] = userSecret["password"] } } else { - err = sqlUserSecretClient.Delete( - ctx, - types.NamespacedName{Namespace: key.Namespace, Name: instance.Name + "-" + formatName}, - ) + toDelete = append(toDelete, formatName) } } - err = sqlUserSecretClient.Upsert( + err = userSecretClient.Delete( + ctx, + userSecretKey, + secrets.Flatten(true, toDelete...)) + if err != nil { + return false, err + } + + err = userSecretClient.Upsert( ctx, - types.NamespacedName{Namespace: key.Namespace, Name: instance.Name}, + userSecretKey, formattedSecrets, secrets.WithOwner(instance), secrets.WithScheme(s.Scheme), @@ -257,14 +268,14 @@ func (s *AzureSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, op } } - userExists, err := s.UserExists(ctx, db, string(DBSecret[SecretUsernameKey])) + userExists, err := s.UserExists(ctx, db, string(userSecret[SecretUsernameKey])) if err != nil { instance.Status.Message = fmt.Sprintf("failed checking for user, err: %v", err) return false, nil } if !userExists { - user, err = s.CreateUser(ctx, DBSecret, db) + user, err = s.CreateUser(ctx, userSecret, db) if err != nil { instance.Status.Message = "failed creating user, err: " + err.Error() return false, err @@ -303,23 +314,7 @@ func (s *AzureSqlUserManager) Delete(ctx context.Context, obj runtime.Object, op return false, err } - adminSecretClient := s.SecretClient - - adminsecretName := instance.Spec.AdminSecret - if len(instance.Spec.AdminSecret) == 0 { - adminsecretName = instance.Spec.Server - } - key := types.NamespacedName{Name: adminsecretName, Namespace: instance.Namespace} - - // if the admin secret keyvault is not specified, fall back to global secretclient - if len(instance.Spec.AdminSecretKeyVault) != 0 { - adminSecretClient = keyvaultSecrets.New(instance.Spec.AdminSecretKeyVault, s.Creds) - if len(instance.Spec.AdminSecret) != 0 { - key = types.NamespacedName{Name: instance.Spec.AdminSecret} - } - } - - adminSecret, err := adminSecretClient.Get(ctx, key) + adminSecret, err := s.getAdminSecret(ctx, instance) if err != nil { // assuming if the admin secret is gone the sql server is too return false, nil @@ -342,10 +337,10 @@ func (s *AzureSqlUserManager) Delete(ctx context.Context, obj runtime.Object, op return false, err } - var adminuser = string(adminSecret[SecretUsernameKey]) - var adminpassword = string(adminSecret[SecretPasswordKey]) + var adminUser = string(adminSecret[SecretUsernameKey]) + var adminPassword = string(adminSecret[SecretPasswordKey]) - db, err := s.ConnectToSqlDb(ctx, DriverName, instance.Spec.Server, instance.Spec.DbName, SqlServerPort, adminuser, adminpassword) + db, err := s.ConnectToSqlDb(ctx, DriverName, instance.Spec.Server, instance.Spec.DbName, SqlServerPort, adminUser, adminPassword) if err != nil { instance.Status.Message = errhelp.StripErrorIDs(err) if strings.Contains(err.Error(), "create a firewall rule for this IP address") { @@ -363,8 +358,8 @@ func (s *AzureSqlUserManager) Delete(ctx context.Context, obj runtime.Object, op sqlUserSecretClient = s.SecretClient } - userkey := GetNamespacedName(instance, sqlUserSecretClient) - userSecret, err := sqlUserSecretClient.Get(ctx, userkey) + userSecretKey := MakeSecretKey(sqlUserSecretClient, instance) + userSecret, err := sqlUserSecretClient.Get(ctx, userSecretKey) if err != nil { //user secret is gone return false, nil diff --git a/pkg/resourcemanager/config/config.go b/pkg/resourcemanager/config/config.go index 0cf9609cedf..9f5f5d0d94e 100644 --- a/pkg/resourcemanager/config/config.go +++ b/pkg/resourcemanager/config/config.go @@ -9,6 +9,8 @@ import ( "github.com/Azure/go-autorest/autorest/azure" "github.com/marstr/randname" + + "github.com/Azure/azure-service-operator/pkg/secrets" ) var ( @@ -29,6 +31,7 @@ var ( baseURI string environment *azure.Environment podNamespace string + secretNamingVersion secrets.SecretNamingVersion testResourcePrefix string // used to generate resource names in tests, should probably exist in a test only package ) @@ -113,16 +116,22 @@ func BaseURI() string { return baseURI } +// SecretNamingVersion is the version of secret naming that the operator uses +func SecretNamingVersion() secrets.SecretNamingVersion { + return secretNamingVersion +} + // ConfigString returns the parts of the configuration file with are not secrets as a string for easy logging func ConfigString() string { creds := GlobalCredentials() return fmt.Sprintf( - "clientID: %q, tenantID: %q, subscriptionID: %q, cloudName: %q, useDeviceFlow: %v, useManagedIdentity: %v, podNamespace: %q", + "clientID: %q, tenantID: %q, subscriptionID: %q, cloudName: %q, useDeviceFlow: %v, useManagedIdentity: %v, podNamespace: %q, secretNamingVersion: %q", creds.ClientID(), creds.TenantID(), creds.SubscriptionID(), cloudName, UseDeviceFlow(), creds.UseManagedIdentity(), - podNamespace) + podNamespace, + SecretNamingVersion()) } diff --git a/pkg/resourcemanager/config/env.go b/pkg/resourcemanager/config/env.go index 00c208ab4e9..704ed7b3443 100644 --- a/pkg/resourcemanager/config/env.go +++ b/pkg/resourcemanager/config/env.go @@ -14,6 +14,7 @@ import ( "github.com/pkg/errors" "github.com/Azure/azure-service-operator/pkg/helpers" + "github.com/Azure/azure-service-operator/pkg/secrets" ) type ConfigRequirementType int @@ -65,6 +66,20 @@ func ParseEnvironment() error { return errors.Wrapf(err, "couldn't get POD_NAMESPACE env variable") } + secretNamingVersionInt, err := ParseIntFromEnvironment("AZURE_SECRET_NAMING_VERSION") + if err != nil { + return errors.Wrapf(err, "couldn't get AZURE_SECRET_NAMING_VERSION env variable") + } + + // If this isn't set, default to version 2 + if secretNamingVersionInt == 1 { + secretNamingVersion = secrets.SecretNamingV1 + } else if secretNamingVersionInt == 0 || secretNamingVersionInt == 2 { + secretNamingVersion = secrets.SecretNamingV2 + } else { + return errors.Errorf("secret naming version must be one of 0, 1, or 2 but was %d", secretNamingVersionInt) + } + for _, requirement := range GetExpectedConfigurationVariables() { switch requirement { case RequireClientID: @@ -110,10 +125,20 @@ func GetExpectedConfigurationVariables() []ConfigRequirementType { } func ParseBoolFromEnvironment(variable string) bool { - value, err := strconv.ParseBool(envy.Get(variable, "0")) + env := envy.Get(variable, "0") + value, err := strconv.ParseBool(env) if err != nil { - log.Printf("WARNING: invalid input value specified for bool %v: \"%v\", disabling\n", variable, value) + log.Printf("WARNING: invalid input value specified for %q, expected bool, actual: %q. Disabling\n", variable, env) value = false } return value } + +func ParseIntFromEnvironment(variable string) (int, error) { + env := envy.Get(variable, "0") + value, err := strconv.Atoi(env) + if err != nil { + return 0, errors.Wrapf(err, "invalid input value specified for %q, expected int, actual: %q", variable, env) + } + return value, nil +} diff --git a/pkg/resourcemanager/cosmosdbs/cosmosdb_reconcile.go b/pkg/resourcemanager/cosmosdbs/cosmosdb_reconcile.go index 5c814c51ce1..500bf3ab5be 100644 --- a/pkg/resourcemanager/cosmosdbs/cosmosdb_reconcile.go +++ b/pkg/resourcemanager/cosmosdbs/cosmosdb_reconcile.go @@ -14,6 +14,7 @@ import ( "github.com/Azure/azure-service-operator/pkg/helpers" "github.com/Azure/azure-service-operator/pkg/resourcemanager" "github.com/Azure/azure-service-operator/pkg/resourcemanager/pollclient" + "github.com/Azure/azure-service-operator/pkg/secrets" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -258,10 +259,7 @@ func (m *AzureCosmosDBManager) createOrUpdateSecret(ctx context.Context, instanc return err } - secretKey := types.NamespacedName{ - Name: instance.Name, - Namespace: instance.Namespace, - } + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} secretData := map[string][]byte{ "primaryEndpoint": []byte(*db.DocumentEndpoint), "primaryMasterKey": []byte(*keysResult.PrimaryMasterKey), @@ -289,9 +287,6 @@ func (m *AzureCosmosDBManager) createOrUpdateSecret(ctx context.Context, instanc } func (m *AzureCosmosDBManager) deleteSecret(ctx context.Context, instance *v1alpha1.CosmosDB) error { - secretKey := types.NamespacedName{ - Name: instance.Name, - Namespace: instance.Namespace, - } + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} return m.SecretClient.Delete(ctx, secretKey) } diff --git a/pkg/resourcemanager/eventhubs/hub.go b/pkg/resourcemanager/eventhubs/hub.go index bd2defe18d1..a5b31002489 100644 --- a/pkg/resourcemanager/eventhubs/hub.go +++ b/pkg/resourcemanager/eventhubs/hub.go @@ -152,13 +152,9 @@ func (e *azureEventHubManager) createOrUpdateAccessPolicyEventHub(resourcegroup } func (e *azureEventHubManager) createEventhubSecrets(ctx context.Context, secretName string, instance *azurev1alpha1.Eventhub, data map[string][]byte) error { - key := types.NamespacedName{ - Name: secretName, - Namespace: instance.Namespace, - } - + secretKey := secrets.SecretKey{Name: secretName, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} return e.SecretClient.Upsert(ctx, - key, + secretKey, data, secrets.WithOwner(instance), secrets.WithScheme(e.Scheme), @@ -166,14 +162,9 @@ func (e *azureEventHubManager) createEventhubSecrets(ctx context.Context, secret } func (e *azureEventHubManager) deleteEventhubSecrets(ctx context.Context, secretName string, instance *azurev1alpha1.Eventhub) error { - key := types.NamespacedName{ - Name: secretName, - Namespace: instance.Namespace, - } + secretKey := secrets.SecretKey{Name: secretName, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} - err := e.SecretClient.Delete(ctx, - key, - ) + err := e.SecretClient.Delete(ctx, secretKey) if err != nil { return err } @@ -241,7 +232,7 @@ func (m *azureEventHubManager) Ensure(ctx context.Context, obj runtime.Object, o if len(secretName) == 0 { secretName = eventhubName - instance.Spec.SecretName = eventhubName + instance.Spec.SecretName = eventhubName // TODO: Ideally this would be done in a mutating admission webhook } // write information back to instance diff --git a/pkg/resourcemanager/keyvaults/keyops.go b/pkg/resourcemanager/keyvaults/keyops.go index 0fb2e582655..acf72417773 100644 --- a/pkg/resourcemanager/keyvaults/keyops.go +++ b/pkg/resourcemanager/keyvaults/keyops.go @@ -23,10 +23,10 @@ import ( // KeyvaultKeyClient emcompasses the methods needed for the keyops client to fulfill the ARMClient interface type KeyvaultKeyClient struct { Creds config.Credentials - KeyvaultClient *azureKeyVaultManager + KeyvaultClient *AzureKeyVaultManager } -func NewKeyvaultKeyClient(creds config.Credentials, client *azureKeyVaultManager) *KeyvaultKeyClient { +func NewKeyvaultKeyClient(creds config.Credentials, client *AzureKeyVaultManager) *KeyvaultKeyClient { return &KeyvaultKeyClient{ Creds: creds, KeyvaultClient: client, diff --git a/pkg/resourcemanager/keyvaults/keyvault.go b/pkg/resourcemanager/keyvaults/keyvault.go index 3e2a33deaf0..d71ae4a9be5 100644 --- a/pkg/resourcemanager/keyvaults/keyvault.go +++ b/pkg/resourcemanager/keyvaults/keyvault.go @@ -30,19 +30,19 @@ import ( "github.com/Azure/azure-service-operator/pkg/resourcemanager/iam" ) -type azureKeyVaultManager struct { +type AzureKeyVaultManager struct { Creds config.Credentials Scheme *runtime.Scheme } -func NewAzureKeyVaultManager(creds config.Credentials, scheme *runtime.Scheme) *azureKeyVaultManager { - return &azureKeyVaultManager{ +func NewAzureKeyVaultManager(creds config.Credentials, scheme *runtime.Scheme) *AzureKeyVaultManager { + return &AzureKeyVaultManager{ Creds: creds, Scheme: scheme, } } -func getVaultsClient(creds config.Credentials) (keyvault.VaultsClient, error) { +func GetKeyVaultClient(creds config.Credentials) (keyvault.VaultsClient, error) { vaultsClient := keyvault.NewVaultsClientWithBaseURI(config.BaseURI(), creds.SubscriptionID()) a, err := iam.GetResourceManagementAuthorizer(creds) if err != nil { @@ -53,7 +53,7 @@ func getVaultsClient(creds config.Credentials) (keyvault.VaultsClient, error) { return vaultsClient, nil } -func getObjectID(ctx context.Context, creds config.Credentials, tenantID string, clientID string) (*string, error) { +func GetObjectID(ctx context.Context, creds config.Credentials, tenantID string, clientID string) (*string, error) { appclient := auth.NewApplicationsClient(tenantID) a, err := iam.GetGraphAuthorizer(creds) if err != nil { @@ -215,7 +215,7 @@ func ParseAccessPolicy(ctx context.Context, creds config.Credentials, policy *v1 } if policy.ClientID != "" { - objID, err := getObjectID(ctx, creds, policy.TenantID, policy.ClientID) + objID, err := GetObjectID(ctx, creds, policy.TenantID, policy.ClientID) if err != nil { return keyvault.AccessPolicyEntry{}, err } @@ -228,13 +228,13 @@ func ParseAccessPolicy(ctx context.Context, creds config.Credentials, policy *v1 } // CreateVault creates a new key vault -func (m *azureKeyVaultManager) CreateVault(ctx context.Context, instance *v1alpha1.KeyVault, sku azurev1alpha1.KeyVaultSku, tags map[string]*string) (keyvault.Vault, error) { +func (m *AzureKeyVaultManager) CreateVault(ctx context.Context, instance *v1alpha1.KeyVault, sku azurev1alpha1.KeyVaultSku, tags map[string]*string) (keyvault.Vault, error) { vaultName := instance.Name location := instance.Spec.Location groupName := instance.Spec.ResourceGroup enableSoftDelete := instance.Spec.EnableSoftDelete - vaultsClient, err := getVaultsClient(m.Creds) + vaultsClient, err := GetKeyVaultClient(m.Creds) if err != nil { return keyvault.Vault{}, errors.Wrapf(err, "couldn't get vaults client") } @@ -294,68 +294,9 @@ func (m *azureKeyVaultManager) CreateVault(ctx context.Context, instance *v1alph return future.Result(vaultsClient) } -//CreateVaultWithAccessPolicies creates a new key vault and provides access policies to the specified user -// TODO: Nuke all of this because its only for the tests -func (m *azureKeyVaultManager) CreateVaultWithAccessPolicies(ctx context.Context, groupName string, vaultName string, location string, clientID string) (keyvault.Vault, error) { - vaultsClient, err := getVaultsClient(m.Creds) - if err != nil { - return keyvault.Vault{}, errors.Wrapf(err, "couldn't get vaults client") - } - id, err := uuid.FromString(m.Creds.TenantID()) - if err != nil { - return keyvault.Vault{}, errors.Wrapf(err, "couldn't convert tenantID to UUID") - } - - apList := []keyvault.AccessPolicyEntry{} - ap := keyvault.AccessPolicyEntry{ - TenantID: &id, - Permissions: &keyvault.Permissions{ - Keys: &[]keyvault.KeyPermissions{ - keyvault.KeyPermissionsCreate, - }, - Secrets: &[]keyvault.SecretPermissions{ - keyvault.SecretPermissionsSet, - keyvault.SecretPermissionsGet, - keyvault.SecretPermissionsDelete, - keyvault.SecretPermissionsList, - }, - }, - } - if clientID != "" { - objID, err := getObjectID(ctx, m.Creds, m.Creds.TenantID(), clientID) - if err != nil { - return keyvault.Vault{}, err - } - if objID != nil { - ap.ObjectID = objID - apList = append(apList, ap) - } - - } - - params := keyvault.VaultCreateOrUpdateParameters{ - Properties: &keyvault.VaultProperties{ - TenantID: &id, - AccessPolicies: &apList, - Sku: &keyvault.Sku{ - Family: to.StringPtr("A"), - Name: keyvault.Standard, - }, - }, - Location: to.StringPtr(location), - } - - future, err := vaultsClient.CreateOrUpdate(ctx, groupName, vaultName, params) - if err != nil { - return keyvault.Vault{}, err - } - - return future.Result(vaultsClient) -} - // DeleteVault removes the resource group named by env var -func (m *azureKeyVaultManager) DeleteVault(ctx context.Context, groupName string, vaultName string) (result autorest.Response, err error) { - vaultsClient, err := getVaultsClient(m.Creds) +func (m *AzureKeyVaultManager) DeleteVault(ctx context.Context, groupName string, vaultName string) (result autorest.Response, err error) { + vaultsClient, err := GetKeyVaultClient(m.Creds) if err != nil { return autorest.Response{}, err } @@ -363,8 +304,8 @@ func (m *azureKeyVaultManager) DeleteVault(ctx context.Context, groupName string } // CheckExistence checks for the presence of a keyvault instance on Azure -func (m *azureKeyVaultManager) GetVault(ctx context.Context, groupName string, vaultName string) (result keyvault.Vault, err error) { - vaultsClient, err := getVaultsClient(m.Creds) +func (m *AzureKeyVaultManager) GetVault(ctx context.Context, groupName string, vaultName string) (result keyvault.Vault, err error) { + vaultsClient, err := GetKeyVaultClient(m.Creds) if err != nil { return keyvault.Vault{}, err } @@ -372,7 +313,7 @@ func (m *azureKeyVaultManager) GetVault(ctx context.Context, groupName string, v } -func (m *azureKeyVaultManager) Ensure(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { +func (m *AzureKeyVaultManager) Ensure(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { instance, err := m.convert(obj) if err != nil { return true, err @@ -473,7 +414,7 @@ func HandleCreationError(instance *v1alpha1.KeyVault, err error) (bool, error) { return false, err } -func (m *azureKeyVaultManager) Delete(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { +func (m *AzureKeyVaultManager) Delete(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { instance, err := m.convert(obj) if err != nil { return true, err @@ -506,7 +447,7 @@ func (m *azureKeyVaultManager) Delete(ctx context.Context, obj runtime.Object, o return false, nil } -func (m *azureKeyVaultManager) GetParents(obj runtime.Object) ([]resourcemanager.KubeParent, error) { +func (m *AzureKeyVaultManager) GetParents(obj runtime.Object) ([]resourcemanager.KubeParent, error) { instance, err := m.convert(obj) if err != nil { @@ -524,7 +465,7 @@ func (m *azureKeyVaultManager) GetParents(obj runtime.Object) ([]resourcemanager }, nil } -func (g *azureKeyVaultManager) GetStatus(obj runtime.Object) (*v1alpha1.ASOStatus, error) { +func (g *AzureKeyVaultManager) GetStatus(obj runtime.Object) (*v1alpha1.ASOStatus, error) { instance, err := g.convert(obj) if err != nil { return nil, err @@ -532,7 +473,7 @@ func (g *azureKeyVaultManager) GetStatus(obj runtime.Object) (*v1alpha1.ASOStatu return &instance.Status, nil } -func (m *azureKeyVaultManager) convert(obj runtime.Object) (*v1alpha1.KeyVault, error) { +func (m *AzureKeyVaultManager) convert(obj runtime.Object) (*v1alpha1.KeyVault, error) { local, ok := obj.(*v1alpha1.KeyVault) if !ok { return nil, fmt.Errorf("failed type assertion on kind: %s", obj.GetObjectKind().GroupVersionKind().String()) diff --git a/pkg/resourcemanager/keyvaults/keyvault_manager.go b/pkg/resourcemanager/keyvaults/keyvault_manager.go index b98e82a1e24..974d80d66b6 100644 --- a/pkg/resourcemanager/keyvaults/keyvault_manager.go +++ b/pkg/resourcemanager/keyvaults/keyvault_manager.go @@ -6,9 +6,9 @@ package keyvaults import ( "context" + "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2018-02-14/keyvault" "github.com/Azure/go-autorest/autorest" - "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2018-02-14/keyvault" azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" "github.com/Azure/azure-service-operator/pkg/resourcemanager" ) @@ -16,9 +16,6 @@ import ( type KeyVaultManager interface { CreateVault(ctx context.Context, instance *azurev1alpha1.KeyVault, sku azurev1alpha1.KeyVaultSku, tags map[string]*string) (keyvault.Vault, error) - // CreateVault and grant access to the specific user ID - CreateVaultWithAccessPolicies(ctx context.Context, groupName string, vaultName string, location string, userID string) (keyvault.Vault, error) - // DeleteVault removes the resource group named by env var DeleteVault(ctx context.Context, groupName string, vaultName string) (result autorest.Response, err error) @@ -29,4 +26,4 @@ type KeyVaultManager interface { resourcemanager.ARMClient } -var _ KeyVaultManager = &azureKeyVaultManager{} +var _ KeyVaultManager = &AzureKeyVaultManager{} diff --git a/pkg/resourcemanager/keyvaults/suite_test.go b/pkg/resourcemanager/keyvaults/suite_test.go index 11919520d03..2a859a8ba94 100644 --- a/pkg/resourcemanager/keyvaults/suite_test.go +++ b/pkg/resourcemanager/keyvaults/suite_test.go @@ -69,7 +69,7 @@ var _ = BeforeSuite(func() { tc = TestContext{ ResourceGroupName: resourceGroupName, ResourceGroupLocation: resourceGroupLocation, - keyvaultManager: &azureKeyVaultManager{ + keyvaultManager: &AzureKeyVaultManager{ Scheme: scheme.Scheme, }, ResourceGroupManager: resourceGroupManager, diff --git a/pkg/resourcemanager/mysql/mysqluser/mysqluser.go b/pkg/resourcemanager/mysql/mysqluser/mysqluser.go index 8f2d3436c0f..1c7e3dd30fb 100644 --- a/pkg/resourcemanager/mysql/mysqluser/mysqluser.go +++ b/pkg/resourcemanager/mysql/mysqluser/mysqluser.go @@ -11,7 +11,6 @@ import ( mysqlmgmt "github.com/Azure/azure-sdk-for-go/services/mysql/mgmt/2017-12-01/mysql" _ "github.com/go-sql-driver/mysql" //mysql drive link "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "github.com/Azure/azure-service-operator/api/v1alpha2" "github.com/Azure/azure-service-operator/pkg/helpers" @@ -86,7 +85,7 @@ func (m *MySqlUserManager) CreateUser(ctx context.Context, secret map[string][]b func (m *MySqlUserManager) DeleteSecrets(ctx context.Context, instance *v1alpha2.MySQLUser, secretClient secrets.SecretClient) (bool, error) { // determine our key namespace - if we're persisting to kube, we should use the actual instance namespace. // In keyvault we have some creative freedom to allow more flexibility - secretKey := GetNamespacedName(instance, secretClient) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} // delete standard user secret err := secretClient.Delete( @@ -103,9 +102,9 @@ func (m *MySqlUserManager) DeleteSecrets(ctx context.Context, instance *v1alpha2 // GetOrPrepareSecret gets or creates a secret func (m *MySqlUserManager) GetOrPrepareSecret(ctx context.Context, instance *v1alpha2.MySQLUser, secretClient secrets.SecretClient) map[string][]byte { - key := GetNamespacedName(instance, secretClient) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} - secret, err := secretClient.Get(ctx, key) + secret, err := secretClient.Get(ctx, secretKey) if err != nil { pw := helpers.NewPassword() return map[string][]byte{ @@ -118,9 +117,3 @@ func (m *MySqlUserManager) GetOrPrepareSecret(ctx context.Context, instance *v1a } return secret } - -// GetNamespacedName gets the namespaced-name -func GetNamespacedName(instance *v1alpha2.MySQLUser, secretClient secrets.SecretClient) types.NamespacedName { - - return types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} -} diff --git a/pkg/resourcemanager/mysql/mysqluser/mysqluser_reconcile.go b/pkg/resourcemanager/mysql/mysqluser/mysqluser_reconcile.go index 82f43a0b0c4..679350a5dc7 100644 --- a/pkg/resourcemanager/mysql/mysqluser/mysqluser_reconcile.go +++ b/pkg/resourcemanager/mysql/mysqluser/mysqluser_reconcile.go @@ -47,7 +47,7 @@ func (s *MySqlUserManager) Ensure(ctx context.Context, obj runtime.Object, opts adminSecretName = instance.Spec.Server } - key := types.NamespacedName{Name: adminSecretName, Namespace: instance.Namespace} + adminSecretKey := secrets.SecretKey{Name: adminSecretName, Namespace: instance.Namespace, Kind: reflect.TypeOf(v1alpha2.MySQLServer{}).Name()} var mysqlUserSecretClient secrets.SecretClient if options.SecretClient != nil { @@ -58,17 +58,15 @@ func (s *MySqlUserManager) Ensure(ctx context.Context, obj runtime.Object, opts // if the admin secret keyvault is not specified, fall back to configured secretclient if len(instance.Spec.AdminSecretKeyVault) != 0 { - adminSecretClient = keyvaultSecrets.New(instance.Spec.AdminSecretKeyVault, s.Creds) - if len(instance.Spec.AdminSecret) != 0 { - key = types.NamespacedName{Name: instance.Spec.AdminSecret} - } + adminSecretClient = keyvaultSecrets.New(instance.Spec.AdminSecretKeyVault, s.Creds, s.SecretClient.GetSecretNamingVersion()) } // get admin creds for server - adminSecret, err := adminSecretClient.Get(ctx, key) + adminSecret, err := adminSecretClient.Get(ctx, adminSecretKey) if err != nil { + err = errors.Wrap(err, "MySQLServer admin secret not found") instance.Status.Provisioning = false - instance.Status.Message = fmt.Sprintf("admin secret : %s, not found in %s", key.String(), reflect.TypeOf(adminSecretClient).Elem().Name()) + instance.Status.Message = err.Error() return false, nil } @@ -114,25 +112,22 @@ func (s *MySqlUserManager) Ensure(ctx context.Context, obj runtime.Object, opts return false, err } - // determine our key namespace - if we're persisting to kube, we should use the actual instance namespace. - // In keyvault we have to avoid collisions with other secrets so we create a custom namespace with the user's parameters - key = GetNamespacedName(instance, mysqlUserSecretClient) - + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} // create or get new user secret - dbSecret := s.GetOrPrepareSecret(ctx, instance, mysqlUserSecretClient) + userSecret := s.GetOrPrepareSecret(ctx, instance, mysqlUserSecretClient) // reset user from secret in case it was loaded - user := string(dbSecret[MSecretUsernameKey]) + user := string(userSecret[MSecretUsernameKey]) if user == "" { user = fmt.Sprintf(requestedUsername) - dbSecret[MSecretUsernameKey] = []byte(user) + userSecret[MSecretUsernameKey] = []byte(user) } // Publishing the user secret: // We do this first so if the keyvault does not have right permissions we will not proceed to creating the user err = mysqlUserSecretClient.Upsert( ctx, - key, - dbSecret, + secretKey, + userSecret, secrets.WithOwner(instance), secrets.WithScheme(s.Scheme), ) @@ -141,7 +136,7 @@ func (s *MySqlUserManager) Ensure(ctx context.Context, obj runtime.Object, opts return false, err } - user, err = s.CreateUser(ctx, dbSecret, db) + user, err = s.CreateUser(ctx, userSecret, db) if err != nil { instance.Status.Message = "failed creating user, err: " + err.Error() return false, err @@ -182,12 +177,12 @@ func (s *MySqlUserManager) Delete(ctx context.Context, obj runtime.Object, opts adminSecretClient := s.SecretClient - adminsecretName := instance.Spec.AdminSecret + adminSecretName := instance.Spec.AdminSecret if len(instance.Spec.AdminSecret) == 0 { - adminsecretName = instance.Spec.Server + adminSecretName = instance.Spec.Server } - key := types.NamespacedName{Name: adminsecretName, Namespace: instance.Namespace} + adminSecretKey := secrets.SecretKey{Name: adminSecretName, Namespace: instance.Namespace, Kind: reflect.TypeOf(v1alpha2.MySQLServer{}).Name()} var mysqlUserSecretClient secrets.SecretClient if options.SecretClient != nil { @@ -198,13 +193,10 @@ func (s *MySqlUserManager) Delete(ctx context.Context, obj runtime.Object, opts // if the admin secret keyvault is not specified, fall back to configured secretclient if len(instance.Spec.AdminSecretKeyVault) != 0 { - adminSecretClient = keyvaultSecrets.New(instance.Spec.AdminSecretKeyVault, s.Creds) - if len(instance.Spec.AdminSecret) != 0 { - key = types.NamespacedName{Name: instance.Spec.AdminSecret} - } + adminSecretClient = keyvaultSecrets.New(instance.Spec.AdminSecretKeyVault, s.Creds, s.SecretClient.GetSecretNamingVersion()) } - adminSecret, err := adminSecretClient.Get(ctx, key) + adminSecret, err := adminSecretClient.Get(ctx, adminSecretKey) if err != nil { // assuming if the admin secret is gone the sql server is too return false, nil @@ -251,8 +243,8 @@ func (s *MySqlUserManager) Delete(ctx context.Context, obj runtime.Object, opts userSecretClient = s.SecretClient } - userkey := GetNamespacedName(instance, userSecretClient) - userSecret, err := userSecretClient.Get(ctx, userkey) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + userSecret, err := userSecretClient.Get(ctx, secretKey) if err != nil { return false, nil } diff --git a/pkg/resourcemanager/mysql/server/reconcile.go b/pkg/resourcemanager/mysql/server/reconcile.go index 8018d0d4bc3..c42a858b8d6 100644 --- a/pkg/resourcemanager/mysql/server/reconcile.go +++ b/pkg/resourcemanager/mysql/server/reconcile.go @@ -9,6 +9,10 @@ import ( "strings" mysql "github.com/Azure/azure-sdk-for-go/services/mysql/mgmt/2017-12-01/mysql" + "github.com/Azure/go-autorest/autorest/to" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "github.com/Azure/azure-service-operator/api/v1alpha1" azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" "github.com/Azure/azure-service-operator/api/v1alpha2" @@ -18,9 +22,6 @@ import ( "github.com/Azure/azure-service-operator/pkg/resourcemanager" "github.com/Azure/azure-service-operator/pkg/resourcemanager/pollclient" "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" ) // Ensure idempotently instantiates the requested server (if possible) in Azure @@ -58,7 +59,7 @@ func (m *MySQLServerClient) Ensure(ctx context.Context, obj runtime.Object, opts return false, err } - err = m.AddServerCredsToSecrets(ctx, instance.Name, secret, instance) + err = m.AddServerCredsToSecrets(ctx, secret, instance) if err != nil { return false, err } @@ -112,7 +113,7 @@ func (m *MySQLServerClient) Ensure(ctx context.Context, obj runtime.Object, opts } if server.UserVisibleState == mysql.ServerStateReady { // Update secret with FQ name of the server. We ignore the error. - m.UpdateServerNameInSecret(ctx, instance.Name, secret, *server.FullyQualifiedDomainName, instance) + m.UpdateServerNameInSecret(ctx, secret, *server.FullyQualifiedDomainName, instance) instance.Status.Provisioned = true instance.Status.Provisioning = false @@ -236,8 +237,8 @@ func (m *MySQLServerClient) Delete(ctx context.Context, obj runtime.Object, opts if err == nil { if status != "InProgress" { // Best case deletion of secrets - key := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} - m.SecretClient.Delete(ctx, key) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + m.SecretClient.Delete(ctx, secretKey) return false, nil } } @@ -284,14 +285,11 @@ func (m *MySQLServerClient) convert(obj runtime.Object) (*v1alpha2.MySQLServer, } // AddServerCredsToSecrets saves the server's admin credentials in the secret store -func (m *MySQLServerClient) AddServerCredsToSecrets(ctx context.Context, secretName string, data map[string][]byte, instance *azurev1alpha2.MySQLServer) error { - key := types.NamespacedName{ - Name: secretName, - Namespace: instance.Namespace, - } +func (m *MySQLServerClient) AddServerCredsToSecrets(ctx context.Context, data map[string][]byte, instance *azurev1alpha2.MySQLServer) error { + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} err := m.SecretClient.Upsert(ctx, - key, + secretKey, data, secrets.WithOwner(instance), secrets.WithScheme(m.Scheme), @@ -304,16 +302,13 @@ func (m *MySQLServerClient) AddServerCredsToSecrets(ctx context.Context, secretN } // UpdateSecretWithFullServerName updates the secret with the fully qualified server name -func (m *MySQLServerClient) UpdateServerNameInSecret(ctx context.Context, secretName string, data map[string][]byte, fullservername string, instance *azurev1alpha2.MySQLServer) error { - key := types.NamespacedName{ - Name: secretName, - Namespace: instance.Namespace, - } +func (m *MySQLServerClient) UpdateServerNameInSecret(ctx context.Context, data map[string][]byte, fullservername string, instance *azurev1alpha2.MySQLServer) error { + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} data["fullyQualifiedServerName"] = []byte(fullservername) err := m.SecretClient.Upsert(ctx, - key, + secretKey, data, secrets.WithOwner(instance), secrets.WithScheme(m.Scheme), @@ -327,46 +322,45 @@ func (m *MySQLServerClient) UpdateServerNameInSecret(ctx context.Context, secret // GetOrPrepareSecret gets tje admin credentials if they are stored or generates some if not func (m *MySQLServerClient) GetOrPrepareSecret(ctx context.Context, instance *azurev1alpha2.MySQLServer) (map[string][]byte, error) { - name := instance.Name createmode := instance.Spec.CreateMode // If createmode == default, then this is a new server creation, so generate username/password // If createmode == replica, then get the credentials from the source server secret and use that secret := map[string][]byte{} - var key types.NamespacedName - var Username string - var Password string + var key secrets.SecretKey + var username string + var password string if strings.EqualFold(createmode, "default") { // new Mysql server creation // See if secret already exists and return if it does - key = types.NamespacedName{Name: name, Namespace: instance.Namespace} + key = secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} if stored, err := m.SecretClient.Get(ctx, key); err == nil { return stored, nil } // Generate random username password if secret does not exist already - Username = helpers.GenerateRandomUsername(10) - Password = helpers.NewPassword() + username = helpers.GenerateRandomUsername(10) + password = helpers.NewPassword() } else { // replica sourceServerId := instance.Spec.ReplicaProperties.SourceServerId if len(sourceServerId) != 0 { // Parse to get source server name sourceServerIdSplit := strings.Split(sourceServerId, "/") - sourceserver := sourceServerIdSplit[len(sourceServerIdSplit)-1] + sourceServer := sourceServerIdSplit[len(sourceServerIdSplit)-1] // Get the username and password from the source server's secret - key = types.NamespacedName{Name: sourceserver, Namespace: instance.Namespace} - if sourcesecret, err := m.SecretClient.Get(ctx, key); err == nil { - Username = string(sourcesecret["username"]) - Password = string(sourcesecret["password"]) + key = secrets.SecretKey{Name: sourceServer, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + if sourceSecret, err := m.SecretClient.Get(ctx, key); err == nil { + username = string(sourceSecret["username"]) + password = string(sourceSecret["password"]) } } } // Populate secret fields - secret["username"] = []byte(Username) - secret["fullyQualifiedUsername"] = []byte(fmt.Sprintf("%s@%s", Username, name)) - secret["password"] = []byte(Password) - secret["mySqlServerName"] = []byte(name) + secret["username"] = []byte(username) + secret["fullyQualifiedUsername"] = []byte(fmt.Sprintf("%s@%s", username, instance.Name)) + secret["password"] = []byte(password) + secret["mySqlServerName"] = []byte(instance.Name) return secret, nil } diff --git a/pkg/resourcemanager/psql/psqluser/psqluser.go b/pkg/resourcemanager/psql/psqluser/psqluser.go index 491b2172a42..412e7aceb76 100644 --- a/pkg/resourcemanager/psql/psqluser/psqluser.go +++ b/pkg/resourcemanager/psql/psqluser/psqluser.go @@ -10,16 +10,14 @@ import ( "strings" psql "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" + _ "github.com/lib/pq" //the pg lib + "k8s.io/apimachinery/pkg/runtime" + + "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" psdatabase "github.com/Azure/azure-service-operator/pkg/resourcemanager/psql/database" "github.com/Azure/azure-service-operator/pkg/secrets" - - "github.com/Azure/azure-service-operator/api/v1alpha1" - "k8s.io/apimachinery/pkg/runtime" - - _ "github.com/lib/pq" //the pg lib - "k8s.io/apimachinery/pkg/types" ) // PSqlServerPort is the default server port for sql server @@ -177,7 +175,7 @@ func (m *PostgreSqlUserManager) DropUser(ctx context.Context, db *sql.DB, user s // DeleteSecrets deletes the secrets associated with a SQLUser func (m *PostgreSqlUserManager) DeleteSecrets(ctx context.Context, instance *v1alpha1.PostgreSQLUser, secretClient secrets.SecretClient) (bool, error) { - secretKey := GetNamespacedName(instance, secretClient) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} // delete standard user secret err := secretClient.Delete( @@ -194,9 +192,9 @@ func (m *PostgreSqlUserManager) DeleteSecrets(ctx context.Context, instance *v1a // GetOrPrepareSecret gets or creates a secret func (m *PostgreSqlUserManager) GetOrPrepareSecret(ctx context.Context, instance *v1alpha1.PostgreSQLUser, secretClient secrets.SecretClient) map[string][]byte { - key := GetNamespacedName(instance, secretClient) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} - secret, err := secretClient.Get(ctx, key) + secret, err := secretClient.Get(ctx, secretKey) psqldbdnssuffix := "postgres.database.azure.com" if config.Environment().Name != "AzurePublicCloud" { @@ -218,8 +216,3 @@ func (m *PostgreSqlUserManager) GetOrPrepareSecret(ctx context.Context, instance return secret } - -// GetNamespacedName gets the namespaced-name -func GetNamespacedName(instance *v1alpha1.PostgreSQLUser, secretClient secrets.SecretClient) types.NamespacedName { - return types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} -} diff --git a/pkg/resourcemanager/psql/psqluser/psqluser_reconcile.go b/pkg/resourcemanager/psql/psqluser/psqluser_reconcile.go index 466c87e3cee..63aaebd68e8 100644 --- a/pkg/resourcemanager/psql/psqluser/psqluser_reconcile.go +++ b/pkg/resourcemanager/psql/psqluser/psqluser_reconcile.go @@ -10,6 +10,8 @@ import ( "reflect" "strings" + "github.com/pkg/errors" + "github.com/Azure/azure-service-operator/pkg/helpers" "github.com/Azure/azure-service-operator/pkg/secrets" @@ -43,12 +45,13 @@ func (m *PostgreSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, adminSecretClient := m.SecretClient - adminsecretName := instance.Spec.AdminSecret + adminSecretName := instance.Spec.AdminSecret if len(instance.Spec.AdminSecret) == 0 { - adminsecretName = instance.Spec.Server + adminSecretName = instance.Spec.Server } - key := types.NamespacedName{Name: adminsecretName, Namespace: instance.Namespace} + // TODO: Is there a better way to get TypeMeta here? + adminSecretKey := secrets.SecretKey{Name: adminSecretName, Namespace: instance.Namespace, Kind: reflect.TypeOf(v1alpha2.PostgreSQLServer{}).Name()} var sqlUserSecretClient secrets.SecretClient if options.SecretClient != nil { @@ -59,17 +62,15 @@ func (m *PostgreSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, // if the admin secret keyvault is not specified, fall back to configured secretclient if len(instance.Spec.AdminSecretKeyVault) != 0 { - adminSecretClient = keyvaultSecrets.New(instance.Spec.AdminSecretKeyVault, m.Creds) - if len(instance.Spec.AdminSecret) != 0 { - key = types.NamespacedName{Name: instance.Spec.AdminSecret} - } + adminSecretClient = keyvaultSecrets.New(instance.Spec.AdminSecretKeyVault, m.Creds, m.SecretClient.GetSecretNamingVersion()) } // get admin creds for server - adminSecret, err := adminSecretClient.Get(ctx, key) + adminSecret, err := adminSecretClient.Get(ctx, adminSecretKey) if err != nil { + err = errors.Wrap(err, "PostgreSQLServer admin secret not found") instance.Status.Provisioning = false - instance.Status.Message = fmt.Sprintf("admin secret : %s, not found in %s", key.String(), reflect.TypeOf(adminSecretClient).Elem().Name()) + instance.Status.Message = err.Error() return false, nil } @@ -124,25 +125,23 @@ func (m *PostgreSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, return false, err } - // determine our key namespace - if we're persisting to kube, we should use the actual instance namespace. - // In keyvault we have to avoid collisions with other secrets so we create a custom namespace with the user's parameters - key = GetNamespacedName(instance, sqlUserSecretClient) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} // create or get new user secret - DBSecret := m.GetOrPrepareSecret(ctx, instance, sqlUserSecretClient) + userSecret := m.GetOrPrepareSecret(ctx, instance, sqlUserSecretClient) // reset user from secret in case it was loaded - user := string(DBSecret[PSecretUsernameKey]) + user := string(userSecret[PSecretUsernameKey]) if user == "" { user = fmt.Sprintf(requestedUsername) - DBSecret[PSecretUsernameKey] = []byte(user) + userSecret[PSecretUsernameKey] = []byte(user) } // Publishing the user secret: // We do this first so if the keyvault does not have right permissions we will not proceed to creating the user err = sqlUserSecretClient.Upsert( ctx, - key, - DBSecret, + secretKey, + userSecret, secrets.WithOwner(instance), secrets.WithScheme(m.Scheme), ) @@ -151,14 +150,14 @@ func (m *PostgreSqlUserManager) Ensure(ctx context.Context, obj runtime.Object, return false, err } - userExists, err := m.UserExists(ctx, db, string(DBSecret[PSecretUsernameKey])) + userExists, err := m.UserExists(ctx, db, string(userSecret[PSecretUsernameKey])) if err != nil { instance.Status.Message = fmt.Sprintf("failed checking for user, err: %v", err) return false, nil } if !userExists { - user, err = m.CreateUser(ctx, DBSecret, db) + user, err = m.CreateUser(ctx, userSecret, db) if err != nil { instance.Status.Message = "failed creating user, err: " + err.Error() return false, err @@ -199,22 +198,20 @@ func (m *PostgreSqlUserManager) Delete(ctx context.Context, obj runtime.Object, adminSecretClient := m.SecretClient - adminsecretName := instance.Spec.AdminSecret + adminSecretName := instance.Spec.AdminSecret if len(instance.Spec.AdminSecret) == 0 { - adminsecretName = instance.Spec.Server + adminSecretName = instance.Spec.Server } - key := types.NamespacedName{Name: adminsecretName, Namespace: instance.Namespace} + + adminSecretKey := secrets.SecretKey{Name: adminSecretName, Namespace: instance.Namespace, Kind: reflect.TypeOf(v1alpha2.PostgreSQLServer{}).Name()} // if the admin secret keyvault is not specified, fall back to configured secretclient if len(instance.Spec.AdminSecretKeyVault) != 0 { - adminSecretClient = keyvaultSecrets.New(instance.Spec.AdminSecretKeyVault, m.Creds) - if len(instance.Spec.AdminSecret) != 0 { - key = types.NamespacedName{Name: instance.Spec.AdminSecret} - } + adminSecretClient = keyvaultSecrets.New(instance.Spec.AdminSecretKeyVault, m.Creds, m.SecretClient.GetSecretNamingVersion()) } - adminSecret, err := adminSecretClient.Get(ctx, key) + adminSecret, err := adminSecretClient.Get(ctx, adminSecretKey) if err != nil { // assuming if the admin secret is gone the sql server is too return false, nil @@ -237,11 +234,11 @@ func (m *PostgreSqlUserManager) Delete(ctx context.Context, obj runtime.Object, return false, err } - adminuser := string(adminSecret["fullyQualifiedUsername"]) - adminpassword := string(adminSecret[PSecretPasswordKey]) - fullservername := string(adminSecret["fullyQualifiedServerName"]) + adminUser := string(adminSecret["fullyQualifiedUsername"]) + adminPassword := string(adminSecret[PSecretPasswordKey]) + fullServerName := string(adminSecret["fullyQualifiedServerName"]) - db, err := m.ConnectToSqlDb(ctx, PDriverName, fullservername, instance.Spec.DbName, PSqlServerPort, adminuser, adminpassword) + db, err := m.ConnectToSqlDb(ctx, PDriverName, fullServerName, instance.Spec.DbName, PSqlServerPort, adminUser, adminPassword) if err != nil { instance.Status.Message = errhelp.StripErrorIDs(err) if strings.Contains(err.Error(), "no pg_hba.conf entry for host") { @@ -261,8 +258,8 @@ func (m *PostgreSqlUserManager) Delete(ctx context.Context, obj runtime.Object, psqlUserSecretClient = m.SecretClient } - userkey := GetNamespacedName(instance, psqlUserSecretClient) - userSecret, err := psqlUserSecretClient.Get(ctx, userkey) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + userSecret, err := psqlUserSecretClient.Get(ctx, secretKey) if err != nil { return false, nil diff --git a/pkg/resourcemanager/psql/server/server.go b/pkg/resourcemanager/psql/server/server.go index a982ef1eec8..57a74c82d6a 100644 --- a/pkg/resourcemanager/psql/server/server.go +++ b/pkg/resourcemanager/psql/server/server.go @@ -8,14 +8,14 @@ import ( "fmt" psql "github.com/Azure/azure-sdk-for-go/services/postgresql/mgmt/2017-12-01/postgresql" + "github.com/Azure/go-autorest/autorest/to" + "k8s.io/apimachinery/pkg/runtime" + "github.com/Azure/azure-service-operator/api/v1alpha2" "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" - "github.com/Azure/go-autorest/autorest/to" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" ) type PSQLServerClient struct { @@ -193,14 +193,11 @@ func (c *PSQLServerClient) GetServer(ctx context.Context, resourcegroup string, return client.Get(ctx, resourcegroup, servername) } -func (c *PSQLServerClient) AddServerCredsToSecrets(ctx context.Context, secretName string, data map[string][]byte, instance *v1alpha2.PostgreSQLServer) error { - key := types.NamespacedName{ - Name: secretName, - Namespace: instance.Namespace, - } +func (c *PSQLServerClient) AddServerCredsToSecrets(ctx context.Context, data map[string][]byte, instance *v1alpha2.PostgreSQLServer) error { + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} err := c.SecretClient.Upsert(ctx, - key, + secretKey, data, secrets.WithOwner(instance), secrets.WithScheme(c.Scheme), @@ -212,16 +209,13 @@ func (c *PSQLServerClient) AddServerCredsToSecrets(ctx context.Context, secretNa return nil } -func (c *PSQLServerClient) UpdateSecretWithFullServerName(ctx context.Context, secretName string, data map[string][]byte, instance *v1alpha2.PostgreSQLServer, fullservername string) error { - key := types.NamespacedName{ - Name: secretName, - Namespace: instance.Namespace, - } +func (c *PSQLServerClient) UpdateSecretWithFullServerName(ctx context.Context, data map[string][]byte, instance *v1alpha2.PostgreSQLServer, fullServerName string) error { + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} - data["fullyQualifiedServerName"] = []byte(fullservername) + data["fullyQualifiedServerName"] = []byte(fullServerName) err := c.SecretClient.Upsert(ctx, - key, + secretKey, data, secrets.WithOwner(instance), secrets.WithScheme(c.Scheme), @@ -234,14 +228,12 @@ func (c *PSQLServerClient) UpdateSecretWithFullServerName(ctx context.Context, s } func (c *PSQLServerClient) GetOrPrepareSecret(ctx context.Context, instance *v1alpha2.PostgreSQLServer) (map[string][]byte, error) { - name := instance.Name - usernameLength := 8 secret := map[string][]byte{} - key := types.NamespacedName{Name: name, Namespace: instance.Namespace} - if stored, err := c.SecretClient.Get(ctx, key); err == nil { + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + if stored, err := c.SecretClient.Get(ctx, secretKey); err == nil { return stored, nil } @@ -249,9 +241,9 @@ func (c *PSQLServerClient) GetOrPrepareSecret(ctx context.Context, instance *v1a randomPassword := helpers.NewPassword() secret["username"] = []byte(randomUsername) - secret["fullyQualifiedUsername"] = []byte(fmt.Sprintf("%s@%s", randomUsername, name)) + secret["fullyQualifiedUsername"] = []byte(fmt.Sprintf("%s@%s", randomUsername, instance.Name)) secret["password"] = []byte(randomPassword) - secret["postgreSqlServerName"] = []byte(name) + secret["postgreSqlServerName"] = []byte(instance.Name) return secret, nil } diff --git a/pkg/resourcemanager/psql/server/server_reconcile.go b/pkg/resourcemanager/psql/server/server_reconcile.go index 1d85f31bb52..d978dc93a00 100644 --- a/pkg/resourcemanager/psql/server/server_reconcile.go +++ b/pkg/resourcemanager/psql/server/server_reconcile.go @@ -15,6 +15,8 @@ import ( "github.com/Azure/azure-service-operator/pkg/helpers" "github.com/Azure/azure-service-operator/pkg/resourcemanager" "github.com/Azure/azure-service-operator/pkg/resourcemanager/pollclient" + "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" @@ -56,7 +58,7 @@ func (c *PSQLServerClient) Ensure(ctx context.Context, obj runtime.Object, opts } // Update secret with the fully qualified server name - err = c.AddServerCredsToSecrets(ctx, instance.Name, secret, instance) + err = c.AddServerCredsToSecrets(ctx, secret, instance) if err != nil { return false, err } @@ -79,7 +81,7 @@ func (c *PSQLServerClient) Ensure(ctx context.Context, obj runtime.Object, opts if getServer.UserVisibleState == psql.ServerStateReady { // Update the secret with fully qualified server name. Ignore error as we have the admin creds which is critical. - c.UpdateSecretWithFullServerName(ctx, instance.Name, secret, instance, *getServer.FullyQualifiedDomainName) + c.UpdateSecretWithFullServerName(ctx, secret, instance, *getServer.FullyQualifiedDomainName) instance.Status.Message = resourcemanager.SuccessMsg instance.Status.ResourceId = *getServer.ID @@ -234,8 +236,8 @@ func (c *PSQLServerClient) Delete(ctx context.Context, obj runtime.Object, opts if err == nil { if status != "InProgress" { // Best case deletion of secrets - key := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} - c.SecretClient.Delete(ctx, key) + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + c.SecretClient.Delete(ctx, secretKey) return false, nil } } diff --git a/pkg/resourcemanager/rediscaches/redis/rediscache_reconcile.go b/pkg/resourcemanager/rediscaches/redis/rediscache_reconcile.go index 869444f6848..0c1e6d3c0df 100644 --- a/pkg/resourcemanager/rediscaches/redis/rediscache_reconcile.go +++ b/pkg/resourcemanager/rediscaches/redis/rediscache_reconcile.go @@ -13,6 +13,8 @@ import ( "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/secrets" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ) @@ -130,13 +132,13 @@ func (rc *AzureRedisCacheManager) Delete(ctx context.Context, obj runtime.Object } // key for SecretClient to delete secrets on successful deletion - key := types.NamespacedName{Name: instance.Spec.SecretName, Namespace: instance.Namespace} + secretKey := secrets.SecretKey{Name: instance.Spec.SecretName, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} resp, err := rc.GetRedisCache(ctx, groupName, name) if err != nil { if resp.StatusCode == http.StatusNotFound { // Best case deletion of secrets - rc.SecretClient.Delete(ctx, key) + rc.SecretClient.Delete(ctx, secretKey) return false, nil } return false, err @@ -164,7 +166,7 @@ func (rc *AzureRedisCacheManager) Delete(ctx context.Context, obj runtime.Object } if helpers.ContainsString(finished, azerr.Type) { // Best case deletion of secrets - rc.SecretClient.Delete(ctx, key) + rc.SecretClient.Delete(ctx, secretKey) return false, nil } return true, err diff --git a/pkg/resourcemanager/rediscaches/shared.go b/pkg/resourcemanager/rediscaches/shared.go index 10273d6d7ac..d0e30d28caa 100644 --- a/pkg/resourcemanager/rediscaches/shared.go +++ b/pkg/resourcemanager/rediscaches/shared.go @@ -15,7 +15,6 @@ import ( "github.com/Azure/azure-sdk-for-go/services/redis/mgmt/2018-03-01/redis" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" ) // AzureRedisManager @@ -53,11 +52,10 @@ func (m *AzureRedisManager) CreateSecrets(ctx context.Context, instance *v1alpha secretName = instance.Name } - key := types.NamespacedName{Name: secretName, Namespace: instance.Namespace} - + secretKey := secrets.SecretKey{Name: secretName, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} err := m.SecretClient.Upsert( ctx, - key, + secretKey, data, secrets.WithOwner(instance), secrets.WithScheme(m.Scheme), diff --git a/pkg/resourcemanager/storages/storageaccount/storageaccount.go b/pkg/resourcemanager/storages/storageaccount/storageaccount.go index cd108625236..995bcb0a8ce 100644 --- a/pkg/resourcemanager/storages/storageaccount/storageaccount.go +++ b/pkg/resourcemanager/storages/storageaccount/storageaccount.go @@ -11,16 +11,16 @@ import ( "strings" "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-04-01/storage" + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/to" + "k8s.io/apimachinery/pkg/runtime" + "github.com/Azure/azure-service-operator/api/v1alpha1" azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" resourcemgrconfig "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" - "github.com/Azure/go-autorest/autorest/to" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" ) const templateForConnectionString = "DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s;EndpointSuffix=%s" @@ -219,14 +219,21 @@ func (m *azureStorageManager) StoreSecrets(ctx context.Context, resourceGroupNam } // upsert - key := types.NamespacedName{ - Name: fmt.Sprintf("storageaccount-%s-%s", resourceGroupName, accountName), - Namespace: instance.Namespace, - } + secretKey := m.makeSecretKey(instance) return m.SecretClient.Upsert(ctx, - key, + secretKey, data, secrets.WithOwner(instance), secrets.WithScheme(m.Scheme), ) } + +func (m *azureStorageManager) makeSecretKey(instance *v1alpha1.StorageAccount) secrets.SecretKey { + if m.SecretClient.GetSecretNamingVersion() == secrets.SecretNamingV1 { + return secrets.SecretKey{ + Name: fmt.Sprintf("storageaccount-%s-%s", instance.Spec.ResourceGroup, instance.Name), + Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind, + } + } + return secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} +} diff --git a/pkg/resourcemanager/storages/storageaccount/storageaccount_reconcile.go b/pkg/resourcemanager/storages/storageaccount/storageaccount_reconcile.go index 74ac2ba0606..3aed9004c4b 100644 --- a/pkg/resourcemanager/storages/storageaccount/storageaccount_reconcile.go +++ b/pkg/resourcemanager/storages/storageaccount/storageaccount_reconcile.go @@ -174,12 +174,7 @@ func (sa *azureStorageManager) Delete(ctx context.Context, obj runtime.Object, o name := instance.ObjectMeta.Name groupName := instance.Spec.ResourceGroup - key := types.NamespacedName{ - Name: fmt.Sprintf("storageaccount-%s-%s", - instance.Spec.ResourceGroup, - instance.Name), - Namespace: instance.Namespace, - } + secretKey := sa.makeSecretKey(instance) _, err = sa.DeleteStorage(ctx, groupName, name) if err != nil { @@ -191,7 +186,7 @@ func (sa *azureStorageManager) Delete(ctx context.Context, obj runtime.Object, o azerr := errhelp.NewAzureError(err) if helpers.ContainsString(catch, azerr.Type) { // Best case deletion of secrets - sa.SecretClient.Delete(ctx, key) + sa.SecretClient.Delete(ctx, secretKey) return false, nil } return true, err diff --git a/pkg/resourcemanager/vm/client.go b/pkg/resourcemanager/vm/client.go index c3508e979de..bf69b43c79a 100644 --- a/pkg/resourcemanager/vm/client.go +++ b/pkg/resourcemanager/vm/client.go @@ -9,13 +9,13 @@ import ( "strings" compute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-10-01/compute" + "k8s.io/apimachinery/pkg/runtime" + 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 AzureVirtualMachineClient struct { @@ -157,14 +157,11 @@ func (c *AzureVirtualMachineClient) GetVirtualMachine(ctx context.Context, resou return client.Get(ctx, resourcegroup, vmName, "") } -func (c *AzureVirtualMachineClient) AddVirtualMachineCredsToSecrets(ctx context.Context, secretName string, data map[string][]byte, instance *azurev1alpha1.AzureVirtualMachine) error { - key := types.NamespacedName{ - Name: secretName, - Namespace: instance.Namespace, - } +func (c *AzureVirtualMachineClient) AddVirtualMachineCredsToSecrets(ctx context.Context, data map[string][]byte, instance *azurev1alpha1.AzureVirtualMachine) error { + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} err := c.SecretClient.Upsert(ctx, - key, + secretKey, data, secrets.WithOwner(instance), secrets.WithScheme(c.Scheme), @@ -177,12 +174,10 @@ func (c *AzureVirtualMachineClient) AddVirtualMachineCredsToSecrets(ctx context. } func (c *AzureVirtualMachineClient) GetOrPrepareSecret(ctx context.Context, instance *azurev1alpha1.AzureVirtualMachine) (map[string][]byte, error) { - name := instance.Name - secret := map[string][]byte{} - key := types.NamespacedName{Name: name, Namespace: instance.Namespace} - if stored, err := c.SecretClient.Get(ctx, key); err == nil { + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + if stored, err := c.SecretClient.Get(ctx, secretKey); err == nil { return stored, nil } diff --git a/pkg/resourcemanager/vm/reconcile.go b/pkg/resourcemanager/vm/reconcile.go index 615dabfe87f..6f776be621b 100644 --- a/pkg/resourcemanager/vm/reconcile.go +++ b/pkg/resourcemanager/vm/reconcile.go @@ -42,7 +42,7 @@ func (c *AzureVirtualMachineClient) Ensure(ctx context.Context, obj runtime.Obje return false, err } // Update secret - err = c.AddVirtualMachineCredsToSecrets(ctx, instance.Name, secret, instance) + err = c.AddVirtualMachineCredsToSecrets(ctx, secret, instance) if err != nil { return false, err } diff --git a/pkg/resourcemanager/vmext/client.go b/pkg/resourcemanager/vmext/client.go index f735622bce9..c743ddf3ce6 100644 --- a/pkg/resourcemanager/vmext/client.go +++ b/pkg/resourcemanager/vmext/client.go @@ -7,13 +7,13 @@ import ( "context" "encoding/json" - compute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-10-01/compute" + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-10-01/compute" + "k8s.io/apimachinery/pkg/runtime" + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" "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 AzureVirtualMachineExtensionClient struct { @@ -99,13 +99,10 @@ func (c *AzureVirtualMachineExtensionClient) GetVirtualMachineExtension(ctx cont } func (p *AzureVirtualMachineExtensionClient) AddVirtualMachineExtensionCredsToSecrets(ctx context.Context, secretName string, data map[string][]byte, instance *azurev1alpha1.AzureVirtualMachineExtension) error { - key := types.NamespacedName{ - Name: secretName, - Namespace: instance.Namespace, - } + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} err := p.SecretClient.Upsert(ctx, - key, + secretKey, data, secrets.WithOwner(instance), secrets.WithScheme(p.Scheme), @@ -118,12 +115,10 @@ func (p *AzureVirtualMachineExtensionClient) AddVirtualMachineExtensionCredsToSe } func (p *AzureVirtualMachineExtensionClient) GetOrPrepareSecret(ctx context.Context, instance *azurev1alpha1.AzureVirtualMachineExtension) (map[string][]byte, error) { - name := instance.Name - secret := map[string][]byte{} - key := types.NamespacedName{Name: name, Namespace: instance.Namespace} - if stored, err := p.SecretClient.Get(ctx, key); err == nil { + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + if stored, err := p.SecretClient.Get(ctx, secretKey); err == nil { return stored, nil } diff --git a/pkg/resourcemanager/vmss/client.go b/pkg/resourcemanager/vmss/client.go index e481b05e0a4..e18d68c6aed 100644 --- a/pkg/resourcemanager/vmss/client.go +++ b/pkg/resourcemanager/vmss/client.go @@ -9,13 +9,13 @@ import ( "strings" compute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-10-01/compute" + "k8s.io/apimachinery/pkg/runtime" + 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 AzureVMScaleSetClient struct { @@ -215,14 +215,11 @@ func (c *AzureVMScaleSetClient) GetVMScaleSet(ctx context.Context, resourcegroup return client.Get(ctx, resourcegroup, vmssName) } -func (p *AzureVMScaleSetClient) AddVMScaleSetCredsToSecrets(ctx context.Context, secretName string, data map[string][]byte, instance *azurev1alpha1.AzureVMScaleSet) error { - key := types.NamespacedName{ - Name: secretName, - Namespace: instance.Namespace, - } +func (p *AzureVMScaleSetClient) AddVMScaleSetCredsToSecrets(ctx context.Context, data map[string][]byte, instance *azurev1alpha1.AzureVMScaleSet) error { + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} err := p.SecretClient.Upsert(ctx, - key, + secretKey, data, secrets.WithOwner(instance), secrets.WithScheme(p.Scheme), @@ -235,12 +232,10 @@ func (p *AzureVMScaleSetClient) AddVMScaleSetCredsToSecrets(ctx context.Context, } func (p *AzureVMScaleSetClient) GetOrPrepareSecret(ctx context.Context, instance *azurev1alpha1.AzureVMScaleSet) (map[string][]byte, error) { - name := instance.Name - secret := map[string][]byte{} - key := types.NamespacedName{Name: name, Namespace: instance.Namespace} - if stored, err := p.SecretClient.Get(ctx, key); err == nil { + secretKey := secrets.SecretKey{Name: instance.Name, Namespace: instance.Namespace, Kind: instance.TypeMeta.Kind} + if stored, err := p.SecretClient.Get(ctx, secretKey); err == nil { return stored, nil } diff --git a/pkg/resourcemanager/vmss/reconcile.go b/pkg/resourcemanager/vmss/reconcile.go index ff5f270109d..05bdc4ce267 100644 --- a/pkg/resourcemanager/vmss/reconcile.go +++ b/pkg/resourcemanager/vmss/reconcile.go @@ -45,7 +45,7 @@ func (c *AzureVMScaleSetClient) Ensure(ctx context.Context, obj runtime.Object, return false, err } // Update secret - err = c.AddVMScaleSetCredsToSecrets(ctx, instance.Name, secret, instance) + err = c.AddVMScaleSetCredsToSecrets(ctx, secret, instance) if err != nil { return false, err } diff --git a/pkg/secrets/interface.go b/pkg/secrets/interface.go index fce3cea915e..1ce3699986d 100644 --- a/pkg/secrets/interface.go +++ b/pkg/secrets/interface.go @@ -5,18 +5,29 @@ package secrets import ( "context" + "fmt" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" +) + +type SecretNamingVersion string + +const ( + SecretNamingV1 = SecretNamingVersion("secretnamingv1") + SecretNamingV2 = SecretNamingVersion("secretnamingv2") ) type SecretClient interface { - Create(ctx context.Context, key types.NamespacedName, data map[string][]byte, opts ...SecretOption) error - Upsert(ctx context.Context, key types.NamespacedName, data map[string][]byte, opts ...SecretOption) error - Delete(ctx context.Context, key types.NamespacedName) error - Get(ctx context.Context, key types.NamespacedName) (map[string][]byte, error) + Upsert(ctx context.Context, key SecretKey, data map[string][]byte, opts ...SecretOption) error + Delete(ctx context.Context, key SecretKey, opts ...SecretOption) error + Get(ctx context.Context, key SecretKey, opts ...SecretOption) (map[string][]byte, error) + GetSecretNamingVersion() SecretNamingVersion + + // We really shouldn't want/need such a method but unfortunately some resources have specific KeyVault handling for how + // they name things so our abstraction breaks down + IsKeyVault() bool } type SecretOwner interface { @@ -26,11 +37,31 @@ type SecretOwner interface { // Options contains the inputs available for passing to some methods of the secret clients type Options struct { - Owner SecretOwner - Scheme *runtime.Scheme - Activates *time.Time - Expires *time.Time - Flatten bool + Owner SecretOwner + Scheme *runtime.Scheme + Activates *time.Time + Expires *time.Time + Flatten bool + FlattenSuffixes []string +} + +// SecretKey contains the details required to generate a unique key used for identifying a secret +type SecretKey struct { + // Name is the name of the resource the secret is for. + // We don't need the full "path" to the Azure resource because those relationships are all flattened in Kubernetes + // and since Kubernetes forbids conflicting resources of the same kind in the same namespace + name we only need the + // 3-tuple of kind, namespace, name. + Name string + // Namespace is the namespace of the resource the secret is for + Namespace string + // Kind is the kind of resource - this can be gathered from metav1.TypeMeta.Kind usually + Kind string +} + +var _ fmt.Stringer = SecretKey{} + +func (s SecretKey) String() string { + return fmt.Sprintf("Kind: %q, Namespace: %q, Name: %q", s.Kind, s.Namespace, s.Name) } // SecretOption wraps a function that sets a value in the options struct @@ -65,8 +96,9 @@ func WithScheme(scheme *runtime.Scheme) SecretOption { } // Flatten can be used to create individual string secrets -func Flatten(flatten bool) SecretOption { +func Flatten(flatten bool, suffixes ...string) SecretOption { return func(op *Options) { op.Flatten = flatten + op.FlattenSuffixes = suffixes } } diff --git a/pkg/secrets/keyvault/client.go b/pkg/secrets/keyvault/client.go index b2cb77a7b1d..5e564b0da0f 100644 --- a/pkg/secrets/keyvault/client.go +++ b/pkg/secrets/keyvault/client.go @@ -6,16 +6,13 @@ package keyvault import ( "context" "encoding/json" - "fmt" "strings" "time" - "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault" keyvaults "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault" "github.com/Azure/go-autorest/autorest/date" - "github.com/Azure/go-autorest/autorest/to" + "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "github.com/Azure/azure-service-operator/api/v1alpha1" "github.com/Azure/azure-service-operator/pkg/errhelp" @@ -24,10 +21,21 @@ import ( "github.com/Azure/azure-service-operator/pkg/secrets" ) -// KeyvaultSecretClient struct has the Key vault BaseClient that Azure uses and the KeyVault name -type KeyvaultSecretClient struct { - KeyVaultClient keyvaults.BaseClient - KeyVaultName string +// SecretClient struct has the Key vault BaseClient that Azure uses and the KeyVault name +type SecretClient struct { + KeyVaultClient keyvaults.BaseClient + KeyVaultName string + SecretNamingVersion secrets.SecretNamingVersion +} + +var _ secrets.SecretClient = &SecretClient{} + +func (k *SecretClient) IsKeyVault() bool { + return true +} + +func (k *SecretClient) GetSecretNamingVersion() secrets.SecretNamingVersion { + return k.SecretNamingVersion } // GetKeyVaultName extracts the KeyVault name from the generic runtime object @@ -42,7 +50,7 @@ func GetKeyVaultName(instance runtime.Object) string { return target.Spec.KeyVaultToStoreSecrets } -func getVaultsURL(ctx context.Context, vaultName string) string { +func GetVaultsURL(vaultName string) string { vaultURL := "https://" + vaultName + "." + config.Environment().KeyVaultDNSSuffix //default return vaultURL } @@ -52,149 +60,66 @@ func getVaultsURL(ctx context.Context, vaultName string) string { // redundant since that's in the credentials, but it's used to // override the one specified in credentials so it might be right to // keep it. Confirm this. -func New(keyvaultName string, creds config.Credentials) *KeyvaultSecretClient { +func New(keyVaultName string, creds config.Credentials, secretNamingVersion secrets.SecretNamingVersion) *SecretClient { keyvaultClient := keyvaults.New() a, _ := iam.GetKeyvaultAuthorizer(creds) keyvaultClient.Authorizer = a keyvaultClient.AddToUserAgent(config.UserAgent()) - return &KeyvaultSecretClient{ - KeyVaultClient: keyvaultClient, - KeyVaultName: keyvaultName, + return &SecretClient{ + KeyVaultClient: keyvaultClient, + KeyVaultName: keyVaultName, + SecretNamingVersion: secretNamingVersion, } - } -func IsKeyVaultAccessible(kvsecretclient secrets.SecretClient) bool { +// TODO: This method is awkward -- move to interface? +// TODO: or even better, delete this method entirely +func IsKeyVaultAccessible(client secrets.SecretClient) bool { ctx := context.Background() - key := types.NamespacedName{Name: "", Namespace: ""} + key := secrets.SecretKey{Name: "test", Namespace: "default", Kind: "test"} - data, err := kvsecretclient.Get(ctx, key) - if strings.Contains(err.Error(), errhelp.NoSuchHost) { //keyvault unavailable - return false - } else if strings.Contains(err.Error(), errhelp.Forbidden) { //Access policies missing - return false + // Here we are attempting to get a key which we expect will not exist. If we can + // get this key then either somebody has created a secret with the name we're attempting + // to use (which would be bad), or we've been able to create a secret with the given + // key in the past but for some reason the delete below hasn't been successful. + data, err := client.Get(ctx, key) + if err != nil { + if strings.Contains(err.Error(), errhelp.NoSuchHost) { // keyvault unavailable + return false + } else if strings.Contains(err.Error(), errhelp.Forbidden) { // Access policies missing + return false + } } data = map[string][]byte{ "test": []byte(""), } - err = kvsecretclient.Upsert(ctx, key, data) - if strings.Contains(err.Error(), errhelp.Forbidden) { + err = client.Upsert(ctx, key, data) + if err != nil && strings.Contains(err.Error(), errhelp.Forbidden) { return false } - err = kvsecretclient.Delete(ctx, key) - if strings.Contains(err.Error(), errhelp.Forbidden) { + err = client.Delete(ctx, key) + if err != nil && strings.Contains(err.Error(), errhelp.Forbidden) { return false } return true } -// Create creates a key in KeyVault if it does not exist already -func (k *KeyvaultSecretClient) Create(ctx context.Context, key types.NamespacedName, data map[string][]byte, opts ...secrets.SecretOption) error { +// Upsert updates a key in KeyVault even if it exists already, creates if it doesn't exist +func (k *SecretClient) Upsert(ctx context.Context, key secrets.SecretKey, data map[string][]byte, opts ...secrets.SecretOption) error { options := &secrets.Options{} for _, opt := range opts { opt(options) } - var secretBaseName string - vaultBaseURL := getVaultsURL(ctx, k.KeyVaultName) - - if len(key.Namespace) != 0 { - secretBaseName = key.Namespace + "-" + key.Name - } else { - secretBaseName = key.Name - } - - secretVersion := "" - enabled := true - var activationDateUTC date.UnixTime - var expireDateUTC date.UnixTime - - // Initialize secret attributes - secretAttributes := keyvaults.SecretAttributes{ - Enabled: &enabled, - } - - if options.Activates != nil { - activationDateUTC = date.UnixTime(*options.Activates) - secretAttributes.NotBefore = &activationDateUTC - } - - if options.Expires != nil { - expireDateUTC = date.UnixTime(*options.Expires) - secretAttributes.Expires = &expireDateUTC - } - - // if the caller is looking for flat secrets iterate over the array and individually persist each string - if options.Flatten { - var err error - - for formatName, formatValue := range data { - secretName := secretBaseName + "-" + formatName - stringSecret := string(formatValue) - - // Initialize secret parameters - secretParams := keyvaults.SecretSetParameters{ - Value: &stringSecret, - SecretAttributes: &secretAttributes, - } - - if _, err := k.KeyVaultClient.GetSecret(ctx, vaultBaseURL, secretName, secretVersion); err == nil { - return fmt.Errorf("secret already exists %v", err) - } - - _, err = k.KeyVaultClient.SetSecret(ctx, vaultBaseURL, secretName, secretParams) - - if err != nil { - return err - } - } - // If flatten has not been declared, convert the map into a json string for persistance - } else { - jsonData, err := json.Marshal(data) - if err != nil { - return err - } - stringSecret := string(jsonData) - contentType := "json" - - // Initialize secret parameters - secretParams := keyvaults.SecretSetParameters{ - Value: &stringSecret, - SecretAttributes: &secretAttributes, - ContentType: &contentType, - } - - if _, err := k.KeyVaultClient.GetSecret(ctx, vaultBaseURL, secretBaseName, secretVersion); err == nil { - return fmt.Errorf("secret already exists %v", err) - } - - _, err = k.KeyVaultClient.SetSecret(ctx, vaultBaseURL, secretBaseName, secretParams) - + vaultBaseURL := GetVaultsURL(k.KeyVaultName) + secretBaseName, err := k.makeSecretName(key) + if err != nil { return err } - return nil -} - -// Upsert updates a key in KeyVault even if it exists already, creates if it doesn't exist -func (k *KeyvaultSecretClient) Upsert(ctx context.Context, key types.NamespacedName, data map[string][]byte, opts ...secrets.SecretOption) error { - //return nil - options := &secrets.Options{} - for _, opt := range opts { - opt(options) - } - - vaultBaseURL := getVaultsURL(ctx, k.KeyVaultName) - var secretBaseName string - if len(key.Namespace) != 0 { - secretBaseName = key.Namespace + "-" + key.Name - } else { - secretBaseName = key.Name - } - //secretVersion := "" enabled := true var activationDateUTC date.UnixTime @@ -240,14 +165,17 @@ func (k *KeyvaultSecretClient) Upsert(ctx context.Context, key types.NamespacedN }*/ _, err = k.KeyVaultClient.SetSecret(ctx, vaultBaseURL, secretName, secretParams) - if err != nil { - return err + return errors.Wrapf(err, "error setting secret %q in %q", secretBaseName, vaultBaseURL) } } - // If flatten has not been declared, convert the map into a json string for perisstence + // If flatten has not been declared, convert the map into a json string for persistence } else { jsonData, err := json.Marshal(data) + if err != nil { + return errors.Wrapf(err, "unable to marshal secret") + } + stringSecret := string(jsonData) // Initialize secret parameters @@ -256,37 +184,23 @@ func (k *KeyvaultSecretClient) Upsert(ctx context.Context, key types.NamespacedN SecretAttributes: &secretAttributes, } - /*if _, err := k.KeyVaultClient.GetSecret(ctx, vaultBaseURL, secretBaseName, secretVersion); err == nil { - // If secret exists we delete it and recreate it again - _, err = k.KeyVaultClient.DeleteSecret(ctx, vaultBaseURL, secretBaseName) - if err != nil { - return fmt.Errorf("Upsert failed: Trying to delete existing secret failed with %v", err) - } - }*/ - _, err = k.KeyVaultClient.SetSecret(ctx, vaultBaseURL, secretBaseName, secretParams) - - return err + if err != nil { + return errors.Wrapf(err, "error setting secret %q in %q", secretBaseName, vaultBaseURL) + } } return nil } -// Delete deletes a key in KeyVault -func (k *KeyvaultSecretClient) Delete(ctx context.Context, key types.NamespacedName) error { - vaultBaseURL := getVaultsURL(ctx, k.KeyVaultName) - var secretName string - if len(key.Namespace) != 0 { - secretName = key.Namespace + "-" + key.Name - } else { - secretName = key.Name - } - _, err := k.KeyVaultClient.DeleteSecret(ctx, vaultBaseURL, secretName) +func (k *SecretClient) deleteKeyVaultSecret(ctx context.Context, secretName string) error { + vaultBaseURL := GetVaultsURL(k.KeyVaultName) + _, err := k.KeyVaultClient.DeleteSecret(ctx, vaultBaseURL, secretName) if err != nil { azerr := errhelp.NewAzureError(err) if azerr.Type != errhelp.SecretNotFound { // If not found still need to purge - return err + return errors.Wrapf(err, "error deleting secret %q in %q", secretName, vaultBaseURL) } } @@ -297,6 +211,7 @@ func (k *KeyvaultSecretClient) Delete(ctx context.Context, key types.NamespacedN if azerr.Type == errhelp.NotSupported { // Keyvault not softdelete enabled; ignore error return nil } + // TODO: Improve this... the way we're trying again is really awkward if azerr.Type == errhelp.RequestConflictError { // keyvault is still deleting and so purge encounters a "conflict"; purge again time.Sleep(2 * time.Second) _, err = k.KeyVaultClient.PurgeDeletedSecret(ctx, vaultBaseURL, secretName) @@ -307,55 +222,109 @@ func (k *KeyvaultSecretClient) Delete(ctx context.Context, key types.NamespacedN return err } -// Get gets a key from KeyVault -func (k *KeyvaultSecretClient) Get(ctx context.Context, key types.NamespacedName) (map[string][]byte, error) { - vaultBaseURL := getVaultsURL(ctx, k.KeyVaultName) - var secretName string - if len(key.Namespace) != 0 { - secretName = key.Namespace + "-" + key.Name +// Delete deletes a key in KeyVault +func (k *SecretClient) Delete(ctx context.Context, key secrets.SecretKey, opts ...secrets.SecretOption) error { + options := &secrets.Options{} + for _, opt := range opts { + opt(options) + } + + if options.Flatten { + for _, suffix := range options.FlattenSuffixes { + secretName, err := k.makeSecretName(secrets.SecretKey{Name: key.Name + "-" + suffix, Namespace: key.Namespace, Kind: key.Kind}) + if err != nil { + return err + } + err = k.deleteKeyVaultSecret(ctx, secretName) + if err != nil { + return err + } + } + return nil } else { - secretName = key.Name + secretName, err := k.makeSecretName(key) + if err != nil { + return err + } + return k.deleteKeyVaultSecret(ctx, secretName) } +} - secretVersion := "" +// Get gets a key from KeyVault +func (k *SecretClient) Get(ctx context.Context, key secrets.SecretKey, opts ...secrets.SecretOption) (map[string][]byte, error) { + options := &secrets.Options{} + for _, opt := range opts { + opt(options) + } + + vaultBaseURL := GetVaultsURL(k.KeyVaultName) data := map[string][]byte{} + secretName, err := k.makeSecretName(key) + if err != nil { + return data, err + } + + secretVersion := "" result, err := k.KeyVaultClient.GetSecret(ctx, vaultBaseURL, secretName, secretVersion) if err != nil { - return data, fmt.Errorf("secret does not exist" + err.Error()) + return data, errors.Wrapf(err, "secret %q could not be found in KeyVault %q", secretName, vaultBaseURL) } stringSecret := *result.Value - // Convert the data from json string to map - jsonErr := json.Unmarshal([]byte(stringSecret), &data) - - // If Unmarshal fails on the input data, the secret likely not a json string so we return the string value directly rather than unmarshaling - if jsonErr != nil { - data = map[string][]byte{ - secretName: []byte(stringSecret), + // If flatten is enabled, we're getting a single secret entry and so it won't be JSON encoded + if options.Flatten { + // This is quite hacky -- it's only used in test though... + data["secret"] = []byte(stringSecret) + } else { + // Convert the data from json string to map + err = json.Unmarshal([]byte(stringSecret), &data) + if err != nil { + return nil, errors.Wrapf(err, "unable to deserialize secret %q in KeyVault %q", secretName, vaultBaseURL) } } return data, err } -// Create creates a key in KeyVault if it does not exist already -func (k *KeyvaultSecretClient) CreateEncryptionKey(ctx context.Context, name string) error { - vaultBaseURL := getVaultsURL(ctx, k.KeyVaultName) - var ksize int32 = 4096 - kops := keyvault.PossibleJSONWebKeyOperationValues() - katts := keyvault.KeyAttributes{ - Enabled: to.BoolPtr(true), +func (k *SecretClient) makeLegacySecretName(key secrets.SecretKey) (string, error) { + if len(key.Namespace) == 0 { + return "", errors.Errorf("secret key missing required namespace field, %s", key) } - params := keyvault.KeyCreateParameters{ - Kty: keyvault.RSA, - KeySize: &ksize, - KeyOps: &kops, - KeyAttributes: &katts, + if len(key.Name) == 0 { + return "", errors.Errorf("secret key missing required name field, %s", key) } - k.KeyVaultClient.CreateKey(ctx, vaultBaseURL, name, params) - return nil + var parts []string + + parts = append(parts, key.Namespace) + parts = append(parts, key.Name) + + return strings.Join(parts, "-"), nil +} + +func (k *SecretClient) makeSecretName(key secrets.SecretKey) (string, error) { + if k.SecretNamingVersion == secrets.SecretNamingV1 { + return k.makeLegacySecretName(key) + } + + if len(key.Kind) == 0 { + return "", errors.Errorf("secret key missing required kind field, %s", key) + } + if len(key.Namespace) == 0 { + return "", errors.Errorf("secret key missing required namespace field, %s", key) + } + if len(key.Name) == 0 { + return "", errors.Errorf("secret key missing required name field, %s", key) + } + + var parts []string + + parts = append(parts, strings.ToLower(key.Kind)) + parts = append(parts, key.Namespace) + parts = append(parts, key.Name) + + return strings.Join(parts, "-"), nil } diff --git a/pkg/secrets/keyvault/client_test.go b/pkg/secrets/keyvault/client_test.go index 150eb08ca6a..eb91f71274a 100644 --- a/pkg/secrets/keyvault/client_test.go +++ b/pkg/secrets/keyvault/client_test.go @@ -1,41 +1,43 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -package keyvault +package keyvault_test import ( "context" - "log" - "net/http" + "fmt" "strconv" "time" - "github.com/Azure/azure-service-operator/pkg/errhelp" - "github.com/Azure/azure-service-operator/pkg/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/Azure/azure-service-operator/controllers" "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" kvhelper "github.com/Azure/azure-service-operator/pkg/resourcemanager/keyvaults" - rghelper "github.com/Azure/azure-service-operator/pkg/resourcemanager/resourcegroups" "github.com/Azure/azure-service-operator/pkg/secrets" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/types" + "github.com/Azure/azure-service-operator/pkg/secrets/keyvault" ) +func getExpectedSecretName(secretKey secrets.SecretKey, namingScheme secrets.SecretNamingVersion) string { + switch namingScheme { + case secrets.SecretNamingV1: + return secretKey.Namespace + "-" + secretKey.Name + case secrets.SecretNamingV2: + return secretKey.Kind + "-" + secretKey.Namespace + "-" + secretKey.Name + default: + panic("unknown secret naming scheme") + } +} + var _ = Describe("Keyvault Secrets Client", func() { var ctx context.Context - var err error - var timeout time.Duration - var retry time.Duration // Define resource group & keyvault constants var keyVaultName string - var resourcegroupName string - var resourcegroupLocation string - var userID string - - resourceGroupManager := rghelper.NewAzureResourceGroupManager(config.GlobalCredentials()) - kvManager := kvhelper.NewAzureKeyVaultManager(config.GlobalCredentials(), nil) + var kvManager *kvhelper.AzureKeyVaultManager + var vaultBaseUrl string BeforeEach(func() { // Add any setup steps that needs to be executed before each test @@ -43,202 +45,185 @@ var _ = Describe("Keyvault Secrets Client", func() { // Create a context to use in the tests ctx = context.Background() - // Set timeout to 300 seconds - timeout = 300 * time.Second - - // Set retryinterval to 1 second - retry = 1 * time.Second - // Initialize service principal ID to give access to the keyvault - userID = config.GlobalCredentials().ClientID() - - // Initialize resource names - keyVaultName = "t-kvtest-kv" + strconv.FormatInt(GinkgoRandomSeed(), 10) - resourcegroupName = "t-kvtest-rg" + helpers.RandomString(10) - resourcegroupLocation = config.DefaultLocation() + userID := config.GlobalCredentials().ClientID() - // Create a resource group - log.Println("Creating resource group with name " + resourcegroupName + " in location " + resourcegroupLocation) - _, err = resourceGroupManager.CreateGroup(ctx, resourcegroupName, resourcegroupLocation) - Expect(err).NotTo(HaveOccurred()) - - Eventually(func() bool { - result, _ := resourceGroupManager.CheckExistence(ctx, resourcegroupName) - return result.Response.StatusCode == http.StatusNoContent - }, timeout, retry, - ).Should(BeTrue()) + kvManager = kvhelper.NewAzureKeyVaultManager(config.GlobalCredentials(), nil) + keyVaultName = controllers.GenerateTestResourceNameWithRandom("keyvault", 5) + vaultBaseUrl = keyvault.GetVaultsURL(keyVaultName) // Create a keyvault - _, err = kvManager.CreateVaultWithAccessPolicies(ctx, resourcegroupName, keyVaultName, resourcegroupLocation, userID) + err := controllers.CreateVaultWithAccessPolicies(ctx, config.GlobalCredentials(), resourceGroupName, keyVaultName, config.DefaultLocation(), userID) Expect(err).NotTo(HaveOccurred()) - }) AfterEach(func() { // Add any teardown steps that needs to be executed after each test // Delete the keyvault - kvManager.DeleteVault(ctx, resourcegroupName, keyVaultName) - //Expect(err).NotTo(HaveOccurred()) - - // Delete the resource group - _, err = resourceGroupManager.DeleteGroup(context.Background(), resourcegroupName) - if err != nil { - azerr := errhelp.NewAzureError(err) - if azerr.Type == errhelp.AsyncOpIncompleteError { - err = nil - } - } + _, err := kvManager.DeleteVault(ctx, resourceGroupName, keyVaultName) Expect(err).NotTo(HaveOccurred()) - - Eventually(func() bool { - result, _ := resourceGroupManager.CheckExistence(ctx, resourcegroupName) - return result.Response.StatusCode == http.StatusNoContent - }, timeout, retry, - ).Should(BeFalse()) }) - // Add Tests for OpenAPI validation (or additonal CRD features) specified in - // your API definition. + supportedSecretNamingSchemes := []secrets.SecretNamingVersion{ + secrets.SecretNamingV1, + secrets.SecretNamingV2, + } + // Avoid adding tests for vanilla CRUD operations because they would // test Kubernetes API server, which isn't the goal here. Context("Create and Delete", func() { - It("should create and delete secret in Keyvault", func() { - secretName := "kvsecret" + strconv.FormatInt(GinkgoRandomSeed(), 10) - activationDate := time.Date(2018, time.January, 22, 15, 34, 0, 0, time.UTC) - expiryDate := time.Date(2030, time.February, 1, 12, 22, 0, 0, time.UTC) - - var err error - data := map[string][]byte{ - "test": []byte("data"), - "sweet": []byte("potato"), - } + for _, secretNamingScheme := range supportedSecretNamingSchemes { + secretNamingScheme := secretNamingScheme + It(fmt.Sprintf("should create and delete secret in KeyVault with naming scheme %q", secretNamingScheme), func() { + secretName := "kvsecret" + strconv.FormatInt(GinkgoRandomSeed(), 10) + activationDate := time.Date(2018, time.January, 22, 15, 34, 0, 0, time.UTC) + expiryDate := time.Date(2030, time.February, 1, 12, 22, 0, 0, time.UTC) - client := New(keyVaultName, config.GlobalCredentials()) - - key := types.NamespacedName{Name: secretName, Namespace: "default"} - - Context("creating secret with KeyVault client", func() { - err = client.Create(ctx, key, data, secrets.WithActivation(&activationDate), secrets.WithExpiration(&expiryDate)) - Expect(err).To(BeNil()) - }) + var err error - Context("ensuring secret exists using keyvault client", func() { - d, err := client.Get(ctx, key) - Expect(err).To(BeNil()) - - for k, v := range d { - Expect(data[k]).To(Equal(v)) + data := map[string][]byte{ + "test": []byte("data"), + "sweet": []byte("potato"), } - }) - datanew := map[string][]byte{ - "french": []byte("fries"), - "hot": []byte("dogs"), - } + client := keyvault.New(keyVaultName, config.GlobalCredentials(), secretNamingScheme) + key := secrets.SecretKey{Name: secretName, Namespace: "default", Kind: "Test"} - Context("upserting the secret to make sure it can be written", func() { - err = client.Upsert(ctx, key, datanew, secrets.WithActivation(&activationDate), secrets.WithExpiration(&expiryDate)) - Expect(err).To(BeNil()) - }) + Context("creating secret with KeyVault client", func() { + err = client.Upsert(ctx, key, data, secrets.WithActivation(&activationDate), secrets.WithExpiration(&expiryDate)) + Expect(err).To(BeNil()) + }) - Context("ensuring secret exists using keyvault client", func() { - d, err := client.Get(ctx, key) - Expect(err).To(BeNil()) + Context("ensuring secret exists using keyvault client", func() { + d, err := client.Get(ctx, key) + Expect(err).To(BeNil()) - for k, v := range d { - Expect(datanew[k]).To(Equal(v)) - } - Expect(datanew["french"]).To(Equal([]byte("fries"))) - }) + for k, v := range d { + Expect(data[k]).To(Equal(v)) + } - Context("delete secret and ensure it is gone", func() { - err = client.Delete(ctx, key) - Expect(err).To(BeNil()) + // Also ensure that the raw secret is named the expected value + _, err = client.KeyVaultClient.GetSecret(ctx, vaultBaseUrl, getExpectedSecretName(key, secretNamingScheme), "") + Expect(err).To(BeNil()) + }) - d, err := client.Get(ctx, key) - Expect(err).ToNot(BeNil()) - for k, v := range d { - Expect(data[k]).To(Equal(v)) + dataNew := map[string][]byte{ + "french": []byte("fries"), + "hot": []byte("dogs"), } - }) - }) - - It("should create and delete secrets in Keyvault with Flatten enabled", func() { - secretName := "kvsecret" + strconv.FormatInt(GinkgoRandomSeed(), 10) - var err error + Context("upserting the secret to make sure it can be written", func() { + err = client.Upsert(ctx, key, dataNew, secrets.WithActivation(&activationDate), secrets.WithExpiration(&expiryDate)) + Expect(err).To(BeNil()) + }) - data := map[string][]byte{ - "test": []byte("data"), - "sweet": []byte("potato"), - } + Context("ensuring secret exists using keyvault client", func() { + d, err := client.Get(ctx, key) + Expect(err).To(BeNil()) - client := New(keyVaultName, config.GlobalCredentials()) + for k, v := range d { + Expect(dataNew[k]).To(Equal(v)) + } + Expect(dataNew["french"]).To(Equal([]byte("fries"))) + }) - key := types.NamespacedName{Name: secretName, Namespace: "default"} + Context("delete secret and ensure it is gone", func() { + err = client.Delete(ctx, key) + Expect(err).To(BeNil()) - Context("creating flattened secret with KeyVault client", func() { - err = client.Create(ctx, key, data, secrets.Flatten(true)) - Expect(err).To(BeNil()) + d, err := client.Get(ctx, key) + Expect(err).ToNot(BeNil()) + for k, v := range d { + Expect(data[k]).To(Equal(v)) + } + }) }) - Context("ensuring flattened secrets exist using keyvault client", func() { - // Look for each originally passed secret item in the keyvault - for testKey, testValue := range data { - returnedValue, err := client.Get( - ctx, - types.NamespacedName{Namespace: "default", Name: secretName + "-" + testKey}, - ) - - Expect(err).To(BeNil()) + It(fmt.Sprintf("should create and delete secrets in KeyVault with Flatten enabled with secret naming scheme %q", secretNamingScheme), func() { + secretName := "kvsecret" + strconv.FormatInt(GinkgoRandomSeed(), 10) - expectedReturnSecretKey := "default-" + secretName + "-" + testKey + var err error - Expect(testValue).To(Equal(returnedValue[expectedReturnSecretKey])) + data := map[string][]byte{ + "test": []byte("data"), + "sweet": []byte("potato"), } - }) - datanew := map[string][]byte{ - "french": []byte("fries"), - "hot": []byte("dogs"), - } - - Context("upserting the flattened secret to make sure it can be overwritten", func() { - err = client.Upsert(ctx, key, datanew, secrets.Flatten(true)) - Expect(err).To(BeNil()) - }) - - Context("ensuring updated flattened secret exists using keyvault client", func() { - // Look for each originally passed secret item in the keyvault - for testKey, testValue := range datanew { - returnedValue, err := client.Get( - ctx, - types.NamespacedName{Namespace: "default", Name: secretName + "-" + testKey}, - ) + client := keyvault.New(keyVaultName, config.GlobalCredentials(), secretNamingScheme) + key := secrets.SecretKey{Name: secretName, Namespace: "default", Kind: "Test"} + Context("creating flattened secret with KeyVault client", func() { + err = client.Upsert(ctx, key, data, secrets.Flatten(true)) Expect(err).To(BeNil()) - - expectedReturnSecretKey := "default-" + secretName + "-" + testKey - - Expect(testValue).To(Equal(returnedValue[expectedReturnSecretKey])) + }) + + Context("ensuring flattened secrets exist using keyvault client", func() { + // Look for each originally passed secret item in the keyvault + for testKey, testValue := range data { + flattenedKey := secrets.SecretKey{Name: secretName + "-" + testKey, Namespace: "default", Kind: "Test"} + returnedValue, err := client.Get( + ctx, + flattenedKey, + secrets.Flatten(true), + ) + Expect(err).To(BeNil()) + + Expect(testValue).To(Equal(returnedValue["secret"])) + + // Also ensure that the raw secret is named the expected value + _, err = client.KeyVaultClient.GetSecret(ctx, vaultBaseUrl, getExpectedSecretName(flattenedKey, secretNamingScheme), "") + Expect(err).To(BeNil()) + } + }) + + dataNew := map[string][]byte{ + "french": []byte("fries"), + "hot": []byte("dogs"), } - }) - Context("delete flattened secrets and ensure they're gone", func() { - for testKey, _ := range datanew { + Context("upserting the flattened secret to make sure it can be overwritten", func() { + err = client.Upsert(ctx, key, dataNew, secrets.Flatten(true)) + Expect(err).To(BeNil()) + }) + + Context("ensuring updated flattened secret exists using keyvault client", func() { + // Look for each originally passed secret item in the keyvault + for testKey, testValue := range dataNew { + flattenedKey := secrets.SecretKey{Name: secretName + "-" + testKey, Namespace: "default", Kind: "Test"} + returnedValue, err := client.Get( + ctx, + flattenedKey, + secrets.Flatten(true), + ) + + Expect(err).To(BeNil()) + Expect(testValue).To(Equal(returnedValue["secret"])) + + // Also ensure that the raw secret is named the expected value + _, err = client.KeyVaultClient.GetSecret(ctx, vaultBaseUrl, getExpectedSecretName(flattenedKey, secretNamingScheme), "") + Expect(err).To(BeNil()) + } + }) + + Context("delete flattened secrets and ensure they're gone", func() { + var keys []string + for testKey, _ := range dataNew { + keys = append(keys, testKey) + } err := client.Delete( ctx, - types.NamespacedName{Namespace: "default", Name: secretName + "-" + testKey}, - ) - + secrets.SecretKey{Name: secretName, Namespace: "default", Kind: "Test"}, + secrets.Flatten(true, keys...)) Expect(err).To(BeNil()) - - _, err = client.Get(ctx, key) - Expect(err).ToNot(BeNil()) - } + for testKey, _ := range dataNew { + key := secrets.SecretKey{Name: secretName + "-" + testKey, Namespace: "default", Kind: "Test"} + _, err = client.Get(ctx, key, secrets.Flatten(true)) + Expect(err).ToNot(BeNil()) + } + }) }) - }) + } }) }) diff --git a/pkg/secrets/keyvault/keyvault_suite_test.go b/pkg/secrets/keyvault/keyvault_suite_test.go index 02d6ce8a44c..27ee39f0dc0 100644 --- a/pkg/secrets/keyvault/keyvault_suite_test.go +++ b/pkg/secrets/keyvault/keyvault_suite_test.go @@ -4,15 +4,30 @@ package keyvault_test import ( + "context" + "log" + "net/http" "testing" + "time" - resourceconfig "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" + "github.com/gobuffalo/envy" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + + "github.com/Azure/azure-service-operator/controllers" + "github.com/Azure/azure-service-operator/pkg/errhelp" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" + rghelper "github.com/Azure/azure-service-operator/pkg/resourcemanager/resourcegroups" ) +var resourceGroupName string +var resourceGroupManager *rghelper.AzureResourceGroupManager + +var eventuallyTimeout = 600 * time.Second +var eventuallyRetry = 3 * time.Second + func TestKeyvault(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Keyvault Suite") @@ -22,8 +37,31 @@ var _ = BeforeSuite(func(done Done) { logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) By("BeforeSuite - KeyVault Suite") + envy.Set("POD_NAMESPACE", "default") + + ctx := context.Background() + + err := config.ParseEnvironment() + Expect(err).NotTo(HaveOccurred()) - resourceconfig.ParseEnvironment() + resourceGroupName = controllers.GenerateTestResourceNameWithRandom(controllers.TestResourceGroupPrefix, 6) + resourceGroupLocation := config.DefaultLocation() + resourceGroupManager = rghelper.NewAzureResourceGroupManager(config.GlobalCredentials()) + + // Create a resource group + log.Println("Creating resource group with name " + resourceGroupName + " in location " + resourceGroupLocation) + _, err = resourceGroupManager.CreateGroup(ctx, resourceGroupName, resourceGroupLocation) + Expect(err).NotTo(HaveOccurred()) + + Eventually( + func() bool { + result, _ := resourceGroupManager.CheckExistence(ctx, resourceGroupName) + return result.Response.StatusCode == http.StatusNoContent + }, + eventuallyTimeout, + eventuallyRetry, + ).Should(BeTrue()) + log.Println("Created resource group " + resourceGroupName) close(done) }, 60) @@ -33,4 +71,21 @@ var _ = AfterSuite(func() { By("AfterSuite - KeyVault Suite") + ctx := context.Background() + + // Delete the resource group + _, err := resourceGroupManager.DeleteGroup(context.Background(), resourceGroupName) + if err != nil { + azerr := errhelp.NewAzureError(err) + if azerr.Type == errhelp.AsyncOpIncompleteError { + err = nil + } + } + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() bool { + result, _ := resourceGroupManager.CheckExistence(ctx, resourceGroupName) + return result.Response.StatusCode == http.StatusNoContent + }, eventuallyTimeout, eventuallyRetry, + ).Should(BeFalse()) }) diff --git a/pkg/secrets/kube/client.go b/pkg/secrets/kube/client.go index bee9d12264c..ad9871a5e1a 100644 --- a/pkg/secrets/kube/client.go +++ b/pkg/secrets/kube/client.go @@ -5,82 +5,66 @@ package kube import ( "context" - "fmt" + "strings" - "github.com/Azure/azure-service-operator/pkg/secrets" + "github.com/pkg/errors" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/Azure/azure-service-operator/pkg/secrets" ) -type KubeSecretClient struct { - KubeClient client.Client +type SecretClient struct { + KubeClient client.Client + SecretNamingVersion secrets.SecretNamingVersion } -func New(kubeclient client.Client) *KubeSecretClient { - return &KubeSecretClient{ - KubeClient: kubeclient, - } -} +var _ secrets.SecretClient = &SecretClient{} -func (k *KubeSecretClient) Create(ctx context.Context, key types.NamespacedName, data map[string][]byte, opts ...secrets.SecretOption) error { - options := &secrets.Options{} - for _, opt := range opts { - opt(options) +func New(kubeClient client.Client, secretNamingVersion secrets.SecretNamingVersion) *SecretClient { + return &SecretClient{ + KubeClient: kubeClient, + SecretNamingVersion: secretNamingVersion, } +} - if options.Flatten { - return fmt.Errorf("FlattenedSecretsNotSupported") - } - - secret := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: key.Name, - Namespace: key.Namespace, - }, - // Needed to avoid nil map error - Data: map[string][]byte{}, - Type: "Opaque", - } - - if err := k.KubeClient.Get(ctx, key, secret); err == nil { - return fmt.Errorf("secret already exists") - } - - secret.Data = data - - if options.Owner != nil && options.Scheme != nil { - if err := controllerutil.SetControllerReference(options.Owner, secret, options.Scheme); err != nil { - return err - } - } +func (k *SecretClient) IsKeyVault() bool { + return false +} - return k.KubeClient.Create(ctx, secret) +func (k *SecretClient) GetSecretNamingVersion() secrets.SecretNamingVersion { + return k.SecretNamingVersion } -func (k *KubeSecretClient) Upsert(ctx context.Context, key types.NamespacedName, data map[string][]byte, opts ...secrets.SecretOption) error { +func (k *SecretClient) Upsert(ctx context.Context, key secrets.SecretKey, data map[string][]byte, opts ...secrets.SecretOption) error { options := &secrets.Options{} for _, opt := range opts { opt(options) } if options.Flatten { - return fmt.Errorf("FlattenedSecretsNotSupported") + return errors.Errorf("FlattenedSecretsNotSupported") + } + + namespacedName, err := k.makeSecretName(key) + if err != nil { + return err } secret := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: key.Name, - Namespace: key.Namespace, + Name: namespacedName.Name, + Namespace: namespacedName.Namespace, }, // Needed to avoid nil map error Data: map[string][]byte{}, Type: "Opaque", } - _, err := controllerutil.CreateOrUpdate(ctx, k.KubeClient, secret, func() error { + _, err = controllerutil.CreateOrUpdate(ctx, k.KubeClient, secret, func() error { for k, v := range data { secret.Data[k] = v } @@ -105,15 +89,29 @@ func (k *KubeSecretClient) Upsert(ctx context.Context, key types.NamespacedName, return err } -func (k *KubeSecretClient) Get(ctx context.Context, key types.NamespacedName) (map[string][]byte, error) { +func (k *SecretClient) Get(ctx context.Context, key secrets.SecretKey, opts ...secrets.SecretOption) (map[string][]byte, error) { data := map[string][]byte{} + options := &secrets.Options{} + for _, opt := range opts { + opt(options) + } + + if options.Flatten { + return data, errors.Errorf("FlattenedSecretsNotSupported") + } + secret := &v1.Secret{} - if err := k.KubeClient.Get(ctx, key, secret); err != nil { + namespacedName, err := k.makeSecretName(key) + if err != nil { return data, err } + if err := k.KubeClient.Get(ctx, namespacedName, secret); err != nil { + return data, errors.Wrapf(err, "error getting Kubernetes secret %q", namespacedName) + } + for k, v := range secret.Data { data[k] = v } @@ -121,20 +119,74 @@ func (k *KubeSecretClient) Get(ctx context.Context, key types.NamespacedName) (m return data, nil } -func (k *KubeSecretClient) Delete(ctx context.Context, key types.NamespacedName) error { +func (k *SecretClient) Delete(ctx context.Context, key secrets.SecretKey, opts ...secrets.SecretOption) error { + options := &secrets.Options{} + for _, opt := range opts { + opt(options) + } + + if options.Flatten { + return errors.Errorf("FlattenedSecretsNotSupported") + } + + namespacedName, err := k.makeSecretName(key) + if err != nil { + return err + } + secret := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: key.Name, - Namespace: key.Namespace, + Name: namespacedName.Name, + Namespace: namespacedName.Namespace, }, // Needed to avoid nil map error Data: map[string][]byte{}, Type: "Opaque", } - if err := k.KubeClient.Get(ctx, key, secret); err != nil { + if err := k.KubeClient.Get(ctx, namespacedName, secret); err != nil { return nil } - return k.KubeClient.Delete(ctx, secret) + err = k.KubeClient.Delete(ctx, secret) + if err != nil { + return errors.Wrapf(err, "deleting secret %q", namespacedName) + } + + return nil +} + +func (k *SecretClient) makeLegacySecretName(key secrets.SecretKey) (types.NamespacedName, error) { + if len(key.Namespace) == 0 { + return types.NamespacedName{}, errors.Errorf("secret key missing required namespace field, %s", key) + } + if len(key.Name) == 0 { + return types.NamespacedName{}, errors.Errorf("secret key missing required name field, %s", key) + } + + return types.NamespacedName{Namespace: key.Namespace, Name: key.Name}, nil +} + +func (k *SecretClient) makeSecretName(key secrets.SecretKey) (types.NamespacedName, error) { + if k.SecretNamingVersion == secrets.SecretNamingV1 { + return k.makeLegacySecretName(key) + } + + if len(key.Kind) == 0 { + return types.NamespacedName{}, errors.Errorf("secret key missing required kind field, %s", key) + } + if len(key.Namespace) == 0 { + return types.NamespacedName{}, errors.Errorf("secret key missing required namespace field, %s", key) + } + if len(key.Name) == 0 { + return types.NamespacedName{}, errors.Errorf("secret key missing required name field, %s", key) + } + + var parts []string + + parts = append(parts, strings.ToLower(key.Kind)) + parts = append(parts, key.Name) + + name := strings.Join(parts, "-") + return types.NamespacedName{Namespace: key.Namespace, Name: name}, nil } diff --git a/pkg/secrets/kube/client_test.go b/pkg/secrets/kube/client_test.go index 522152816d5..8f00860d993 100644 --- a/pkg/secrets/kube/client_test.go +++ b/pkg/secrets/kube/client_test.go @@ -5,14 +5,29 @@ package kube import ( "context" + "fmt" "strconv" + "strings" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + + "github.com/Azure/azure-service-operator/pkg/secrets" ) +func getExpectedSecretName(secretKey secrets.SecretKey, namingScheme secrets.SecretNamingVersion) types.NamespacedName { + switch namingScheme { + case secrets.SecretNamingV1: + return types.NamespacedName{Namespace: secretKey.Namespace, Name: secretKey.Name} + case secrets.SecretNamingV2: + return types.NamespacedName{Namespace: secretKey.Namespace, Name: strings.ToLower(secretKey.Kind) + "-" + secretKey.Name} + default: + panic("unknown secret naming scheme") + } +} + var _ = Describe("Kube Secrets Client", func() { BeforeEach(func() { @@ -29,46 +44,54 @@ var _ = Describe("Kube Secrets Client", func() { // test Kubernetes API server, which isn't the goal here. Context("Create and Delete", func() { - It("should create and delete secret in k8s", func() { - //s := strconv.FormatInt(GinkgoRandomSeed(), 10) - secretName := "secret" + strconv.FormatInt(GinkgoRandomSeed(), 10) - var err error - ctx := context.Background() + supportedSecretNamingSchemes := []secrets.SecretNamingVersion{ + secrets.SecretNamingV1, + secrets.SecretNamingV2, + } - data := map[string][]byte{ - "test": []byte("data"), - "sweet": []byte("potato"), - } + for _, secretNamingScheme := range supportedSecretNamingSchemes { + secretNamingScheme := secretNamingScheme + It(fmt.Sprintf("should create and delete secret in k8s with secret naming scheme %q", secretNamingScheme), func() { + secretName := "secret" + strconv.FormatInt(GinkgoRandomSeed(), 10) - client := New(K8sClient) + var err error + ctx := context.Background() - key := types.NamespacedName{Name: secretName, Namespace: "default"} + data := map[string][]byte{ + "test": []byte("data"), + "sweet": []byte("potato"), + } - Context("creating secret with secret client", func() { - err = client.Create(ctx, key, data) - Expect(err).To(BeNil()) - }) + client := New(k8sClient, secretNamingScheme) - secret := &v1.Secret{} - Context("ensuring secret exists using k8s client", func() { - err = K8sClient.Get(ctx, key, secret) - Expect(err).To(BeNil()) - d, err := client.Get(ctx, key) - Expect(err).To(BeNil()) + key := secrets.SecretKey{Name: secretName, Namespace: "default", Kind: "Test"} - for k, v := range d { - Expect(data[k]).To(Equal(v)) - } - }) + Context("creating secret with secret client", func() { + err = client.Upsert(ctx, key, data) + Expect(err).To(BeNil()) + }) + + secret := &v1.Secret{} + Context("ensuring secret exists using k8s client", func() { + err = k8sClient.Get(ctx, getExpectedSecretName(key, secretNamingScheme), secret) + Expect(err).To(BeNil()) + d, err := client.Get(ctx, key) + Expect(err).To(BeNil()) + + for k, v := range d { + Expect(data[k]).To(Equal(v)) + } + }) - Context("delete secret and ensure it is gone", func() { - err = client.Delete(ctx, key) - Expect(err).To(BeNil()) + Context("delete secret and ensure it is gone", func() { + err = client.Delete(ctx, key) + Expect(err).To(BeNil()) - err = K8sClient.Get(ctx, key, secret) - Expect(err).ToNot(BeNil()) + err = k8sClient.Get(ctx, getExpectedSecretName(key, secretNamingScheme), secret) + Expect(err).ToNot(BeNil()) + }) }) - }) + } }) }) diff --git a/pkg/secrets/kube/suite_test.go b/pkg/secrets/kube/suite_test.go index 1ecbf3feedc..468283dd3e4 100644 --- a/pkg/secrets/kube/suite_test.go +++ b/pkg/secrets/kube/suite_test.go @@ -9,18 +9,18 @@ import ( "path/filepath" "testing" - "k8s.io/client-go/rest" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/envtest/printer" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" // +kubebuilder:scaffold:imports ) @@ -28,7 +28,7 @@ import ( // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. var testEnv *envtest.Environment -var K8sClient client.Client +var k8sClient client.Client func TestAPIs(t *testing.T) { t.Parallel() @@ -62,10 +62,9 @@ var _ = BeforeSuite(func() { err = azurev1alpha1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) - //k8sClient = k8sManager.GetClient() - K8sClient, _ = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) Expect(err).ToNot(HaveOccurred()) - Expect(K8sClient).ToNot(BeNil()) + Expect(k8sClient).ToNot(BeNil()) }) var _ = AfterSuite(func() {