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

backend/azurerm: upgrading the SDK / support for proxies #19414

Merged
merged 4 commits into from
Nov 21, 2018
Merged
Show file tree
Hide file tree
Changes from 2 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
131 changes: 131 additions & 0 deletions backend/remote-state/azure/arm_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package azure

import (
"context"
"fmt"
"os"
"time"

"github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources"
armStorage "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/storage/mgmt/storage"
"github.com/Azure/azure-sdk-for-go/storage"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/hashicorp/go-azure-helpers/authentication"
"github.com/hashicorp/terraform/httpclient"
)

type ArmClient struct {
// These Clients are only initialized if an Access Key isn't provided
groupsClient *resources.GroupsClient
storageAccountsClient *armStorage.AccountsClient

accessKey string
environment azure.Environment
resourceGroupName string
storageAccountName string
}

func buildArmClient(config BackendConfig) (*ArmClient, error) {
env, err := authentication.DetermineEnvironment(config.Environment)
if err != nil {
return nil, err
}
client := ArmClient{
environment: *env,
resourceGroupName: config.ResourceGroupName,
storageAccountName: config.StorageAccountName,
}

// if we have an Access Key - we don't need the other clients
if config.AccessKey != "" {
client.accessKey = config.AccessKey
return &client, nil
}

builder := authentication.Builder{
ClientID: config.ClientID,
ClientSecret: config.ClientSecret,
SubscriptionID: config.SubscriptionID,
TenantID: config.TenantID,
Environment: config.Environment,

// Feature Toggles
SupportsClientSecretAuth: true,
// TODO: support for Azure CLI / Client Certificate / MSI
tombuildsstuff marked this conversation as resolved.
Show resolved Hide resolved
}
armConfig, err := builder.Build()
if err != nil {
return nil, fmt.Errorf("Error building ARM Config: %+v", err)
}

oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, armConfig.TenantID)
if err != nil {
return nil, err
}

auth, err := armConfig.GetAuthorizationToken(oauthConfig, env.ResourceManagerEndpoint)
if err != nil {
return nil, err
}

accountsClient := armStorage.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, armConfig.SubscriptionID)
client.configureClient(&accountsClient.Client, auth)
client.storageAccountsClient = &accountsClient

groupsClient := resources.NewGroupsClientWithBaseURI(env.ResourceManagerEndpoint, armConfig.SubscriptionID)
client.configureClient(&groupsClient.Client, auth)
client.groupsClient = &groupsClient

return &client, nil
}

func (c ArmClient) getBlobClient(ctx context.Context) (*storage.BlobStorageClient, error) {
if c.accessKey != "" {
storageClient, err := storage.NewBasicClientOnSovereignCloud(c.storageAccountName, c.accessKey, c.environment)
if err != nil {
return nil, fmt.Errorf("Error creating storage client for storage account %q: %s", c.storageAccountName, err)
}
client := storageClient.GetBlobService()
return &client, nil
}

keys, err := c.storageAccountsClient.ListKeys(ctx, c.resourceGroupName, c.storageAccountName)
if err != nil {
return nil, fmt.Errorf("Error retrieving keys for Storage Account %q: %s", c.storageAccountName, err)
}

if keys.Keys == nil {
return nil, fmt.Errorf("Nil key returned for storage account %q", c.storageAccountName)
}

accessKeys := *keys.Keys
accessKey := accessKeys[0].Value

storageClient, err := storage.NewBasicClientOnSovereignCloud(c.storageAccountName, *accessKey, c.environment)
if err != nil {
return nil, fmt.Errorf("Error creating storage client for storage account %q: %s", c.storageAccountName, err)
}
client := storageClient.GetBlobService()
return &client, nil
}

func (c *ArmClient) configureClient(client *autorest.Client, auth autorest.Authorizer) {
client.UserAgent = buildUserAgent()
client.Authorizer = auth
client.Sender = buildSender()
client.SkipResourceProviderRegistration = false
client.PollingDuration = 60 * time.Minute
}

func buildUserAgent() string {
userAgent := httpclient.UserAgentString()

// append the CloudShell version to the user agent if it exists
if azureAgent := os.Getenv("AZURE_HTTP_USER_AGENT"); azureAgent != "" {
userAgent = fmt.Sprintf("%s %s", userAgent, azureAgent)
}

return userAgent
}
121 changes: 21 additions & 100 deletions backend/remote-state/azure/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,11 @@ import (
"context"
"fmt"

armStorage "github.com/Azure/azure-sdk-for-go/arm/storage"
"github.com/Azure/azure-sdk-for-go/storage"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"

"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/helper/schema"
)

// New creates a new backend for S3 remote state.
// New creates a new backend for Azure remote state.
func New() backend.Backend {
s := &schema.Backend{
Schema: map[string]*schema.Schema{
Expand All @@ -40,7 +34,7 @@ func New() backend.Backend {
Type: schema.TypeString,
Optional: true,
Description: "The Azure cloud environment.",
DefaultFunc: schema.EnvDefaultFunc("ARM_ENVIRONMENT", ""),
DefaultFunc: schema.EnvDefaultFunc("ARM_ENVIRONMENT", "public"),
},

"access_key": {
Expand Down Expand Up @@ -83,6 +77,9 @@ func New() backend.Backend {
Description: "The Tenant ID.",
DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""),
},

// TODO: rename these fields
// TODO: support for custom resource manager endpoints
tombuildsstuff marked this conversation as resolved.
Show resolved Hide resolved
},
}

Expand All @@ -95,22 +92,23 @@ type Backend struct {
*schema.Backend

// The fields below are set from configure
blobClient storage.BlobStorageClient

armClient *ArmClient
containerName string
keyName string
leaseID string
}

type BackendConfig struct {
AccessKey string
Environment string
ClientID string
ClientSecret string
ResourceGroupName string
// Required
StorageAccountName string
SubscriptionID string
TenantID string

// Optional
AccessKey string
ClientID string
ClientSecret string
Environment string
ResourceGroupName string
SubscriptionID string
TenantID string
}

func (b *Backend) configure(ctx context.Context) error {
Expand All @@ -135,92 +133,15 @@ func (b *Backend) configure(ctx context.Context) error {
TenantID: data.Get("arm_tenant_id").(string),
}

blobClient, err := getBlobClient(config)
armClient, err := buildArmClient(config)
if err != nil {
return err
}
b.blobClient = blobClient

return nil
}

func getBlobClient(config BackendConfig) (storage.BlobStorageClient, error) {
var client storage.BlobStorageClient

env, err := getAzureEnvironment(config.Environment)
if err != nil {
return client, err
}

accessKey, err := getAccessKey(config, env)
if err != nil {
return client, err
}

storageClient, err := storage.NewClient(config.StorageAccountName, accessKey, env.StorageEndpointSuffix,
storage.DefaultAPIVersion, true)
if err != nil {
return client, fmt.Errorf("Error creating storage client for storage account %q: %s", config.StorageAccountName, err)
}

client = storageClient.GetBlobService()
return client, nil
}

func getAccessKey(config BackendConfig, env azure.Environment) (string, error) {
if config.AccessKey != "" {
return config.AccessKey, nil
}

rgOk := config.ResourceGroupName != ""
subOk := config.SubscriptionID != ""
clientIDOk := config.ClientID != ""
clientSecretOK := config.ClientSecret != ""
tenantIDOk := config.TenantID != ""
if !rgOk || !subOk || !clientIDOk || !clientSecretOK || !tenantIDOk {
return "", fmt.Errorf("resource_group_name and credentials must be provided when access_key is absent")
if config.AccessKey == "" && config.ResourceGroupName == "" {
return fmt.Errorf("Either an Access Key or the Resource Group for the Storage Account must be specified")
}

oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, config.TenantID)
if err != nil {
return "", err
}

spt, err := adal.NewServicePrincipalToken(*oauthConfig, config.ClientID, config.ClientSecret, env.ResourceManagerEndpoint)
if err != nil {
return "", err
}

accountsClient := armStorage.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, config.SubscriptionID)
accountsClient.Authorizer = autorest.NewBearerAuthorizer(spt)

keys, err := accountsClient.ListKeys(config.ResourceGroupName, config.StorageAccountName)
if err != nil {
return "", fmt.Errorf("Error retrieving keys for storage account %q: %s", config.StorageAccountName, err)
}

if keys.Keys == nil {
return "", fmt.Errorf("Nil key returned for storage account %q", config.StorageAccountName)
}

accessKeys := *keys.Keys
return *accessKeys[0].Value, nil
}

func getAzureEnvironment(environment string) (azure.Environment, error) {
if environment == "" {
return azure.PublicCloud, nil
}

env, err := azure.EnvironmentFromName(environment)
if err != nil {
// try again with wrapped value to support readable values like german instead of AZUREGERMANCLOUD
var innerErr error
env, innerErr = azure.EnvironmentFromName(fmt.Sprintf("AZURE%sCLOUD", environment))
if innerErr != nil {
return env, fmt.Errorf("invalid 'environment' configuration: %s", err)
}
}

return env, nil
b.armClient = armClient
return nil
}
25 changes: 21 additions & 4 deletions backend/remote-state/azure/backend_state.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package azure

import (
"context"
"fmt"
"sort"
"strings"

"github.com/Azure/azure-sdk-for-go/storage"

"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
Expand All @@ -25,7 +25,12 @@ func (b *Backend) Workspaces() ([]string, error) {
Prefix: prefix,
}

container := b.blobClient.GetContainerReference(b.containerName)
ctx := context.TODO()
client, err := b.armClient.getBlobClient(ctx)
if err != nil {
return nil, err
}
container := client.GetContainerReference(b.containerName)
resp, err := container.ListBlobs(params)
if err != nil {
return nil, err
Expand Down Expand Up @@ -58,16 +63,28 @@ func (b *Backend) DeleteWorkspace(name string) error {
return fmt.Errorf("can't delete default state")
}

containerReference := b.blobClient.GetContainerReference(b.containerName)
ctx := context.TODO()
client, err := b.armClient.getBlobClient(ctx)
if err != nil {
return err
}

containerReference := client.GetContainerReference(b.containerName)
blobReference := containerReference.GetBlobReference(b.path(name))
options := &storage.DeleteBlobOptions{}

return blobReference.Delete(options)
}

func (b *Backend) StateMgr(name string) (state.State, error) {
ctx := context.TODO()
blobClient, err := b.armClient.getBlobClient(ctx)
if err != nil {
return nil, err
}

client := &RemoteClient{
blobClient: b.blobClient,
blobClient: *blobClient,
containerName: b.containerName,
keyName: b.path(name),
}
Expand Down
Loading