From d34994cb5f808c49e27a114253223f25a1990300 Mon Sep 17 00:00:00 2001 From: Steve Kriss Date: Wed, 5 Sep 2018 14:16:15 -0600 Subject: [PATCH] set azure restic env vars based on default backup location's config Signed-off-by: Steve Kriss --- docs/cli-reference/ark_restic_server.md | 5 ++- examples/azure/20-restic-daemonset.yaml | 10 ----- pkg/cloudprovider/azure/common.go | 16 ++++++++ pkg/cloudprovider/azure/object_store.go | 53 ++++++++++++------------- pkg/cmd/cli/restic/server.go | 22 ++++++++-- pkg/cmd/server/server.go | 12 +++--- 6 files changed, 71 insertions(+), 47 deletions(-) diff --git a/docs/cli-reference/ark_restic_server.md b/docs/cli-reference/ark_restic_server.md index 9914269ad..734f02459 100644 --- a/docs/cli-reference/ark_restic_server.md +++ b/docs/cli-reference/ark_restic_server.md @@ -13,8 +13,9 @@ ark restic server [flags] ### Options ``` - -h, --help help for server - --log-level the level at which to log. Valid values are debug, info, warning, error, fatal, panic. (default info) + --default-backup-storage-location string name of the default backup storage location (default "default") + -h, --help help for server + --log-level the level at which to log. Valid values are debug, info, warning, error, fatal, panic. (default info) ``` ### Options inherited from parent commands diff --git a/examples/azure/20-restic-daemonset.yaml b/examples/azure/20-restic-daemonset.yaml index a5c30c5a8..8d6137a6b 100644 --- a/examples/azure/20-restic-daemonset.yaml +++ b/examples/azure/20-restic-daemonset.yaml @@ -61,15 +61,5 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - - name: AZURE_ACCOUNT_NAME - valueFrom: - secretKeyRef: - name: cloud-credentials - key: AZURE_STORAGE_ACCOUNT_ID - - name: AZURE_ACCOUNT_KEY - valueFrom: - secretKeyRef: - name: cloud-credentials - key: AZURE_STORAGE_KEY - name: ARK_SCRATCH_DIR value: /scratch \ No newline at end of file diff --git a/pkg/cloudprovider/azure/common.go b/pkg/cloudprovider/azure/common.go index d40c77ac5..0f772a76c 100644 --- a/pkg/cloudprovider/azure/common.go +++ b/pkg/cloudprovider/azure/common.go @@ -17,6 +17,7 @@ limitations under the License. package azure import ( + "os" "strings" "github.com/Azure/go-autorest/autorest/adal" @@ -31,6 +32,21 @@ const ( clientSecretEnvVar = "AZURE_CLIENT_SECRET" ) +// SetResticEnvVars sets the environment variables that restic +// relies on (AZURE_ACCOUNT_NAME and AZURE_ACCOUNT_KEY) based +// on info in the provided object storage location config map. +func SetResticEnvVars(config map[string]string) error { + os.Setenv("AZURE_ACCOUNT_NAME", config[storageAccountConfigKey]) + + storageAccountKey, err := getStorageAccountKey(config) + if err != nil { + return err + } + os.Setenv("AZURE_ACCOUNT_KEY", storageAccountKey) + + return nil +} + func newServicePrincipalToken(tenantID, clientID, clientSecret, scope string) (*adal.ServicePrincipalToken, error) { oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, tenantID) if err != nil { diff --git a/pkg/cloudprovider/azure/object_store.go b/pkg/cloudprovider/azure/object_store.go index 1b2a39288..6c1900e3a 100644 --- a/pkg/cloudprovider/azure/object_store.go +++ b/pkg/cloudprovider/azure/object_store.go @@ -46,8 +46,30 @@ func NewObjectStore(logger logrus.FieldLogger) cloudprovider.ObjectStore { return &objectStore{log: logger} } -func getStorageAccountKey(client storagemgmt.AccountsClient, resourceGroup, storageAccount string) (string, error) { - res, err := client.ListKeys(resourceGroup, storageAccount) +func getStorageAccountKey(config map[string]string) (string, error) { + // 1. we need AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID + envVars, err := getRequiredValues(os.Getenv, tenantIDEnvVar, clientIDEnvVar, clientSecretEnvVar, subscriptionIDEnvVar) + if err != nil { + return "", errors.Wrap(err, "unable to get all required environment variables") + } + + // 2. we need config["resourceGroup"], config["storageAccount"] + if _, err := getRequiredValues(mapLookup(config), resourceGroupConfigKey, storageAccountConfigKey); err != nil { + return "", errors.Wrap(err, "unable to get all required config values") + } + + // 3. get SPT + spt, err := newServicePrincipalToken(envVars[tenantIDEnvVar], envVars[clientIDEnvVar], envVars[clientSecretEnvVar], azure.PublicCloud.ResourceManagerEndpoint) + if err != nil { + return "", errors.Wrap(err, "error getting service principal token") + } + + // 4. get storageAccountsClient + storageAccountsClient := storagemgmt.NewAccountsClient(envVars[subscriptionIDEnvVar]) + storageAccountsClient.Authorizer = autorest.NewBearerAuthorizer(spt) + + // 5. get storage key + res, err := storageAccountsClient.ListKeys(config[resourceGroupConfigKey], config[storageAccountConfigKey]) if err != nil { return "", errors.WithStack(err) } @@ -56,7 +78,6 @@ func getStorageAccountKey(client storagemgmt.AccountsClient, resourceGroup, stor } var storageKey string - for _, key := range *res.Keys { // uppercase both strings for comparison because the ListKeys call returns e.g. "FULL" but // the storagemgmt.Full constant in the SDK is defined as "Full". @@ -80,31 +101,9 @@ func mapLookup(data map[string]string) func(string) string { } func (o *objectStore) Init(config map[string]string) error { - // 1. we need AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID - envVars, err := getRequiredValues(os.Getenv, tenantIDEnvVar, clientIDEnvVar, clientSecretEnvVar, subscriptionIDEnvVar) + storageAccountKey, err := getStorageAccountKey(config) if err != nil { - return errors.Wrap(err, "unable to get all required environment variables") - } - - // 2. we need config["resourceGroup"], config["storageAccount"] - if _, err := getRequiredValues(mapLookup(config), resourceGroupConfigKey, storageAccountConfigKey); err != nil { - return errors.Wrap(err, "unable to get all required config values") - } - - // 3. get SPT - spt, err := newServicePrincipalToken(envVars[tenantIDEnvVar], envVars[clientIDEnvVar], envVars[clientSecretEnvVar], azure.PublicCloud.ResourceManagerEndpoint) - if err != nil { - return errors.Wrap(err, "error getting service principal token") - } - - // 4. get storageAccountsClient - storageAccountsClient := storagemgmt.NewAccountsClient(envVars[subscriptionIDEnvVar]) - storageAccountsClient.Authorizer = autorest.NewBearerAuthorizer(spt) - - // 5. get storage key - storageAccountKey, err := getStorageAccountKey(storageAccountsClient, config[resourceGroupConfigKey], config[storageAccountConfigKey]) - if err != nil { - return errors.Wrap(err, "error getting storage account key") + return err } // 6. get storageClient and blobClient diff --git a/pkg/cmd/cli/restic/server.go b/pkg/cmd/cli/restic/server.go index 6767616b6..bc6f008a1 100644 --- a/pkg/cmd/cli/restic/server.go +++ b/pkg/cmd/cli/restic/server.go @@ -34,6 +34,7 @@ import ( "github.com/heptio/ark/pkg/buildinfo" "github.com/heptio/ark/pkg/client" + "github.com/heptio/ark/pkg/cloudprovider/azure" "github.com/heptio/ark/pkg/cmd" "github.com/heptio/ark/pkg/cmd/util/signals" "github.com/heptio/ark/pkg/controller" @@ -44,7 +45,10 @@ import ( ) func NewServerCommand(f client.Factory) *cobra.Command { - var logLevelFlag = logging.LogLevelFlag(logrus.InfoLevel) + var ( + logLevelFlag = logging.LogLevelFlag(logrus.InfoLevel) + location = "default" + ) var command = &cobra.Command{ Use: "server", @@ -57,7 +61,7 @@ func NewServerCommand(f client.Factory) *cobra.Command { logger := logging.DefaultLogger(logLevel) logger.Infof("Starting Ark restic server %s", buildinfo.FormattedGitSHA()) - s, err := newResticServer(logger, fmt.Sprintf("%s-%s", c.Parent().Name(), c.Name())) + s, err := newResticServer(logger, fmt.Sprintf("%s-%s", c.Parent().Name(), c.Name()), location) cmd.CheckError(err) s.run() @@ -65,6 +69,7 @@ func NewServerCommand(f client.Factory) *cobra.Command { } command.Flags().Var(logLevelFlag, "log-level", fmt.Sprintf("the level at which to log. Valid values are %s.", strings.Join(logLevelFlag.AllowedValues(), ", "))) + command.Flags().StringVar(&location, "default-backup-storage-location", location, "name of the default backup storage location") return command } @@ -81,7 +86,7 @@ type resticServer struct { cancelFunc context.CancelFunc } -func newResticServer(logger logrus.FieldLogger, baseName string) (*resticServer, error) { +func newResticServer(logger logrus.FieldLogger, baseName, locationName string) (*resticServer, error) { clientConfig, err := client.Config("", "", baseName) if err != nil { return nil, err @@ -97,6 +102,17 @@ func newResticServer(logger logrus.FieldLogger, baseName string) (*resticServer, return nil, errors.WithStack(err) } + location, err := arkClient.ArkV1().BackupStorageLocations(os.Getenv("HEPTIO_ARK_NAMESPACE")).Get(locationName, metav1.GetOptions{}) + if err != nil { + return nil, errors.WithStack(err) + } + + if location.Spec.Provider == "azure" { + if err := azure.SetResticEnvVars(location.Spec.Config); err != nil { + return nil, err + } + } + // use a stand-alone pod informer because we want to use a field selector to // filter to only pods scheduled on this node. podInformer := corev1informers.NewFilteredPodInformer( diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 4666d21cc..40821040a 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -54,6 +54,7 @@ import ( "github.com/heptio/ark/pkg/buildinfo" "github.com/heptio/ark/pkg/client" "github.com/heptio/ark/pkg/cloudprovider" + "github.com/heptio/ark/pkg/cloudprovider/azure" "github.com/heptio/ark/pkg/cmd" "github.com/heptio/ark/pkg/cmd/util/signals" "github.com/heptio/ark/pkg/controller" @@ -285,7 +286,7 @@ func (s *server) run() error { } if backupStorageLocation.Spec.Config[restic.ResticLocationConfigKey] != "" { - if err := s.initRestic(backupStorageLocation.Spec.Provider); err != nil { + if err := s.initRestic(backupStorageLocation); err != nil { return err } } @@ -488,7 +489,7 @@ func getBlockStore(cloudConfig api.CloudProviderConfig, manager plugin.Manager) return blockStore, nil } -func (s *server) initRestic(providerName string) error { +func (s *server) initRestic(location *api.BackupStorageLocation) error { // warn if restic daemonset does not exist if _, err := s.kubeClient.AppsV1().DaemonSets(s.namespace).Get(restic.DaemonSet, metav1.GetOptions{}); apierrors.IsNotFound(err) { s.logger.Warn("Ark restic daemonset not found; restic backups/restores will not work until it's created") @@ -502,9 +503,10 @@ func (s *server) initRestic(providerName string) error { } // set the env vars that restic uses for creds purposes - if providerName == string(restic.AzureBackend) { - os.Setenv("AZURE_ACCOUNT_NAME", os.Getenv("AZURE_STORAGE_ACCOUNT_ID")) - os.Setenv("AZURE_ACCOUNT_KEY", os.Getenv("AZURE_STORAGE_KEY")) + if location.Spec.Provider == string(restic.AzureBackend) { + if err := azure.SetResticEnvVars(location.Spec.Config); err != nil { + return err + } } // use a stand-alone secrets informer so we can filter to only the restic credentials