terraform/backend/remote-state/azure/arm_client.go

172 lines
5.5 KiB
Go

package azure
import (
"context"
"fmt"
"log"
"net/url"
"os"
"strings"
"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/azure"
"github.com/hashicorp/go-azure-helpers/authentication"
"github.com/hashicorp/go-azure-helpers/sender"
"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
sasToken string
}
func buildArmClient(config BackendConfig) (*ArmClient, error) {
env, err := buildArmEnvironment(config)
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
}
// likewise with a SAS token
if config.SasToken != "" {
client.sasToken = config.SasToken
return &client, nil
}
builder := authentication.Builder{
ClientID: config.ClientID,
ClientSecret: config.ClientSecret,
SubscriptionID: config.SubscriptionID,
TenantID: config.TenantID,
CustomResourceManagerEndpoint: config.CustomResourceManagerEndpoint,
Environment: config.Environment,
MsiEndpoint: config.MsiEndpoint,
// Feature Toggles
SupportsAzureCliToken: true,
SupportsClientSecretAuth: true,
SupportsManagedServiceIdentity: config.UseMsi,
// TODO: support for Client Certificate auth
}
armConfig, err := builder.Build()
if err != nil {
return nil, fmt.Errorf("Error building ARM Config: %+v", err)
}
oauthConfig, err := armConfig.BuildOAuthConfig(env.ActiveDirectoryEndpoint)
if err != nil {
return nil, err
}
auth, err := armConfig.GetAuthorizationToken(sender.BuildSender("backend/remote-state/azure"), oauthConfig, env.TokenAudience)
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 buildArmEnvironment(config BackendConfig) (*azure.Environment, error) {
if config.CustomResourceManagerEndpoint != "" {
log.Printf("[DEBUG] Loading Environment from Endpoint %q", config.CustomResourceManagerEndpoint)
return authentication.LoadEnvironmentFromUrl(config.CustomResourceManagerEndpoint)
}
log.Printf("[DEBUG] Loading Environment %q", config.Environment)
return authentication.DetermineEnvironment(config.Environment)
}
func (c ArmClient) getBlobClient(ctx context.Context) (*storage.BlobStorageClient, error) {
if c.accessKey != "" {
log.Printf("[DEBUG] Building the Blob Client from an Access Token")
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
}
if c.sasToken != "" {
log.Printf("[DEBUG] Building the Blob Client from a SAS Token")
token := strings.TrimPrefix(c.sasToken, "?")
uri, err := url.ParseQuery(token)
if err != nil {
return nil, fmt.Errorf("Error parsing SAS Token: %+v", err)
}
storageClient := storage.NewAccountSASClient(c.storageAccountName, uri, c.environment)
client := storageClient.GetBlobService()
return &client, nil
}
log.Printf("[DEBUG] Building the Blob Client from an Access Token (using user credentials)")
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
}