diff --git a/changelogs/unreleased/1938-andyzhangx b/changelogs/unreleased/1938-andyzhangx new file mode 100644 index 000000000..075e83349 --- /dev/null +++ b/changelogs/unreleased/1938-andyzhangx @@ -0,0 +1 @@ +Azure: add support for Azure China/German clouds diff --git a/pkg/cloudprovider/azure/common.go b/pkg/cloudprovider/azure/common.go index c73a6d91a..dd71761cb 100644 --- a/pkg/cloudprovider/azure/common.go +++ b/pkg/cloudprovider/azure/common.go @@ -31,6 +31,7 @@ const ( subscriptionIDEnvVar = "AZURE_SUBSCRIPTION_ID" clientIDEnvVar = "AZURE_CLIENT_ID" clientSecretEnvVar = "AZURE_CLIENT_SECRET" + cloudNameEnvVar = "AZURE_CLOUD_NAME" resourceGroupConfigKey = "resourceGroup" ) @@ -39,7 +40,7 @@ const ( // relies on (AZURE_ACCOUNT_NAME and AZURE_ACCOUNT_KEY) based // on info in the provided object storage location config map. func GetResticEnvVars(config map[string]string) (map[string]string, error) { - storageAccountKey, err := getStorageAccountKey(config) + storageAccountKey, _, err := getStorageAccountKey(config) if err != nil { return nil, err } @@ -63,13 +64,24 @@ func loadEnv() error { return nil } -func newServicePrincipalToken(tenantID, clientID, clientSecret, scope string) (*adal.ServicePrincipalToken, error) { - oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, tenantID) +// ParseAzureEnvironment returns an azure.Environment for the given cloud +// name, or azure.PublicCloud if cloudName is empty. +func parseAzureEnvironment(cloudName string) (*azure.Environment, error) { + if cloudName == "" { + return &azure.PublicCloud, nil + } + + env, err := azure.EnvironmentFromName(cloudName) + return &env, errors.WithStack(err) +} + +func newServicePrincipalToken(tenantID, clientID, clientSecret string, env *azure.Environment) (*adal.ServicePrincipalToken, error) { + oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, tenantID) if err != nil { return nil, errors.Wrap(err, "error getting OAuthConfig") } - return adal.NewServicePrincipalToken(*oauthConfig, clientID, clientSecret, scope) + return adal.NewServicePrincipalToken(*oauthConfig, clientID, clientSecret, env.ResourceManagerEndpoint) } func getRequiredValues(getValue func(string) string, keys ...string) (map[string]string, error) { diff --git a/pkg/cloudprovider/azure/object_store.go b/pkg/cloudprovider/azure/object_store.go index 27fc8f8ef..db36625c8 100644 --- a/pkg/cloudprovider/azure/object_store.go +++ b/pkg/cloudprovider/azure/object_store.go @@ -135,46 +135,53 @@ func NewObjectStore(logger logrus.FieldLogger) *ObjectStore { return &ObjectStore{log: logger} } -func getStorageAccountKey(config map[string]string) (string, error) { +func getStorageAccountKey(config map[string]string) (string, *azure.Environment, error) { // load environment vars from $AZURE_CREDENTIALS_FILE, if it exists if err := loadEnv(); err != nil { - return "", err + return "", nil, err } // 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") + return "", nil, errors.Wrap(err, "unable to get all required environment variables") } - // 2. check whether a different subscription ID was set for backups in config["subscriptionId"] + // 2. Get Azure cloud from AZURE_CLOUD_NAME, if it exists. If the env var does not + // exist, parseAzureEnvironment will return azure.PublicCloud. + env, err := parseAzureEnvironment(os.Getenv(cloudNameEnvVar)) + if err != nil { + return "", nil, errors.Wrap(err, "unable to parse azure cloud name environment variable") + } + + // 3. check whether a different subscription ID was set for backups in config["subscriptionId"] subscriptionId := envVars[subscriptionIDEnvVar] if val := config[subscriptionIdConfigKey]; val != "" { subscriptionId = val } - // 3. we need config["resourceGroup"], config["storageAccount"] + // 4. 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") + return "", env, errors.Wrap(err, "unable to get all required config values") } - // 4. get SPT - spt, err := newServicePrincipalToken(envVars[tenantIDEnvVar], envVars[clientIDEnvVar], envVars[clientSecretEnvVar], azure.PublicCloud.ResourceManagerEndpoint) + // 5. get SPT + spt, err := newServicePrincipalToken(envVars[tenantIDEnvVar], envVars[clientIDEnvVar], envVars[clientSecretEnvVar], env) if err != nil { - return "", errors.Wrap(err, "error getting service principal token") + return "", env, errors.Wrap(err, "error getting service principal token") } - // 5. get storageAccountsClient - storageAccountsClient := storagemgmt.NewAccountsClient(subscriptionId) + // 6. get storageAccountsClient + storageAccountsClient := storagemgmt.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionId) storageAccountsClient.Authorizer = autorest.NewBearerAuthorizer(spt) - // 6. get storage key + // 7. get storage key res, err := storageAccountsClient.ListKeys(context.TODO(), config[resourceGroupConfigKey], config[storageAccountConfigKey]) if err != nil { - return "", errors.WithStack(err) + return "", env, errors.WithStack(err) } if res.Keys == nil || len(*res.Keys) == 0 { - return "", errors.New("No storage keys found") + return "", env, errors.New("No storage keys found") } var storageKey string @@ -188,10 +195,10 @@ func getStorageAccountKey(config map[string]string) (string, error) { } if storageKey == "" { - return "", errors.New("No storage key with Full permissions found") + return "", env, errors.New("No storage key with Full permissions found") } - return storageKey, nil + return storageKey, env, nil } func mapLookup(data map[string]string) func(string) string { @@ -209,13 +216,13 @@ func (o *ObjectStore) Init(config map[string]string) error { return err } - storageAccountKey, err := getStorageAccountKey(config) + storageAccountKey, env, err := getStorageAccountKey(config) if err != nil { return err } // 6. get storageClient and blobClient - storageClient, err := storage.NewBasicClient(config[storageAccountConfigKey], storageAccountKey) + storageClient, err := storage.NewBasicClientOnSovereignCloud(config[storageAccountConfigKey], storageAccountKey, *env) if err != nil { return errors.Wrap(err, "error getting storage client") } diff --git a/pkg/cloudprovider/azure/volume_snapshotter.go b/pkg/cloudprovider/azure/volume_snapshotter.go index 10f426f72..06d875196 100644 --- a/pkg/cloudprovider/azure/volume_snapshotter.go +++ b/pkg/cloudprovider/azure/volume_snapshotter.go @@ -27,7 +27,6 @@ import ( disk "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute" "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/azure" "github.com/pkg/errors" uuid "github.com/satori/go.uuid" "github.com/sirupsen/logrus" @@ -98,7 +97,14 @@ func (b *VolumeSnapshotter) Init(config map[string]string) error { snapshotsSubscriptionId = val } - // 3. if config["apiTimeout"] is empty, default to 2m; otherwise, parse it + // 3. Get Azure cloud from AZURE_CLOUD_NAME, if it exists. If the env var does not + // exist, parseAzureEnvironment will return azure.PublicCloud. + env, err := parseAzureEnvironment(os.Getenv(cloudNameEnvVar)) + if err != nil { + return errors.Wrap(err, "unable to parse azure cloud name environment variable") + } + + // 4. if config["apiTimeout"] is empty, default to 2m; otherwise, parse it var apiTimeout time.Duration if val := config[apiTimeoutConfigKey]; val == "" { apiTimeout = 2 * time.Minute @@ -109,15 +115,15 @@ func (b *VolumeSnapshotter) Init(config map[string]string) error { } } - // 4. get SPT - spt, err := newServicePrincipalToken(envVars[tenantIDEnvVar], envVars[clientIDEnvVar], envVars[clientSecretEnvVar], azure.PublicCloud.ResourceManagerEndpoint) + // 5. get SPT + spt, err := newServicePrincipalToken(envVars[tenantIDEnvVar], envVars[clientIDEnvVar], envVars[clientSecretEnvVar], env) if err != nil { return errors.Wrap(err, "error getting service principal token") } - // 5. set up clients - disksClient := disk.NewDisksClient(envVars[subscriptionIDEnvVar]) - snapsClient := disk.NewSnapshotsClient(snapshotsSubscriptionId) + // 6. set up clients + disksClient := disk.NewDisksClientWithBaseURI(env.ResourceManagerEndpoint, envVars[subscriptionIDEnvVar]) + snapsClient := disk.NewSnapshotsClientWithBaseURI(env.ResourceManagerEndpoint, snapshotsSubscriptionId) disksClient.PollingDelay = 5 * time.Second snapsClient.PollingDelay = 5 * time.Second diff --git a/site/docs/master/azure-config.md b/site/docs/master/azure-config.md index 97082f66f..e06d12c16 100644 --- a/site/docs/master/azure-config.md +++ b/site/docs/master/azure-config.md @@ -165,9 +165,12 @@ To integrate Velero with Azure, you must create a Velero-specific [service princ AZURE_CLIENT_ID=${AZURE_CLIENT_ID} AZURE_CLIENT_SECRET=${AZURE_CLIENT_SECRET} AZURE_RESOURCE_GROUP=${AZURE_RESOURCE_GROUP} + AZURE_CLOUD_NAME=AzurePublicCloud EOF ``` + > available `AZURE_CLOUD_NAME` values: `AzurePublicCloud`, `AzureUSGovernmentCloud`, `AzureChinaCloud`, `AzureGermanCloud` + ## Install and start Velero Install Velero, including all prerequisites, into the cluster and start the deployment. This will create a namespace called `velero`, and place a deployment named `velero` in it.