Skip to content

Commit

Permalink
set azure restic env vars based on default backup location's config
Browse files Browse the repository at this point in the history
Signed-off-by: Steve Kriss <steve@heptio.com>
  • Loading branch information
skriss committed Sep 5, 2018
1 parent ae373bd commit d34994c
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 47 deletions.
5 changes: 3 additions & 2 deletions docs/cli-reference/ark_restic_server.md
Expand Up @@ -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
Expand Down
10 changes: 0 additions & 10 deletions examples/azure/20-restic-daemonset.yaml
Expand Up @@ -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
16 changes: 16 additions & 0 deletions pkg/cloudprovider/azure/common.go
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package azure

import (
"os"
"strings"

"github.com/Azure/go-autorest/autorest/adal"
Expand All @@ -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 {
Expand Down
53 changes: 26 additions & 27 deletions pkg/cloudprovider/azure/object_store.go
Expand Up @@ -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)
}
Expand All @@ -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".
Expand All @@ -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)
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])
storageAccountKey, err := getStorageAccountKey(config)
if err != nil {
return errors.Wrap(err, "error getting storage account key")
return err
}

// 6. get storageClient and blobClient
Expand Down
22 changes: 19 additions & 3 deletions pkg/cmd/cli/restic/server.go
Expand Up @@ -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"
Expand All @@ -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",
Expand All @@ -57,14 +61,15 @@ 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()
},
}

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
}
Expand All @@ -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
Expand All @@ -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(
Expand Down
12 changes: 7 additions & 5 deletions pkg/cmd/server/server.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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")
Expand All @@ -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
Expand Down

0 comments on commit d34994c

Please sign in to comment.