Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure china/germany support #1938

Merged
merged 6 commits into from
Oct 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelogs/unreleased/1938-andyzhangx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Azure: add support for Azure China/German clouds
20 changes: 16 additions & 4 deletions pkg/cloudprovider/azure/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
subscriptionIDEnvVar = "AZURE_SUBSCRIPTION_ID"
clientIDEnvVar = "AZURE_CLIENT_ID"
clientSecretEnvVar = "AZURE_CLIENT_SECRET"
cloudNameEnvVar = "AZURE_CLOUD_NAME"

resourceGroupConfigKey = "resourceGroup"
)
Expand All @@ -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
}
Expand All @@ -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) {
Expand Down
43 changes: 25 additions & 18 deletions pkg/cloudprovider/azure/object_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -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. 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")
}

// 2. check whether a different subscription ID was set for backups in config["subscriptionId"]
// 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
Expand All @@ -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 {
Expand All @@ -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")
}
Expand Down
20 changes: 13 additions & 7 deletions pkg/cloudprovider/azure/volume_snapshotter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions site/docs/master/azure-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down