Merge pull request #15841 from hashicorp/jbardin/azurerm-backend

Rename the azure backend to azurerm
This commit is contained in:
James Bardin 2017-08-17 16:59:47 -04:00 committed by GitHub
commit 0da6b95ef8
5 changed files with 15 additions and 480 deletions

View File

@ -42,7 +42,8 @@ func init() {
"inmem": func() backend.Backend { return backendinmem.New() },
"swift": func() backend.Backend { return backendSwift.New() },
"s3": func() backend.Backend { return backendS3.New() },
"azure": func() backend.Backend { return backendAzure.New() },
"azure": deprecateBackend(backendAzure.New(),
`Warning: "azure" name is deprecated, please use "azurerm"`),
"azurerm": func() backend.Backend { return backendAzure.New() },
}

View File

@ -1,235 +0,0 @@
package remote
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"github.com/Azure/azure-sdk-for-go/arm/storage"
mainStorage "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"
riviera "github.com/jen20/riviera/azure"
)
func azureFactory(conf map[string]string) (Client, error) {
storageAccountName, ok := conf["storage_account_name"]
if !ok {
return nil, fmt.Errorf("missing 'storage_account_name' configuration")
}
containerName, ok := conf["container_name"]
if !ok {
return nil, fmt.Errorf("missing 'container_name' configuration")
}
keyName, ok := conf["key"]
if !ok {
return nil, fmt.Errorf("missing 'key' configuration")
}
env, err := getAzureEnvironmentFromConf(conf)
if err != nil {
return nil, err
}
accessKey, ok := confOrEnv(conf, "access_key", "ARM_ACCESS_KEY")
if !ok {
resourceGroupName, ok := conf["resource_group_name"]
if !ok {
return nil, fmt.Errorf("missing 'resource_group_name' configuration")
}
var err error
accessKey, err = getStorageAccountAccessKey(conf, resourceGroupName, storageAccountName, env)
if err != nil {
return nil, fmt.Errorf("Couldn't read access key from storage account: %s.", err)
}
}
storageClient, err := mainStorage.NewClient(storageAccountName, accessKey, env.StorageEndpointSuffix,
mainStorage.DefaultAPIVersion, true)
if err != nil {
return nil, fmt.Errorf("Error creating storage client for storage account %q: %s", storageAccountName, err)
}
blobClient := storageClient.GetBlobService()
leaseID, _ := confOrEnv(conf, "lease_id", "ARM_LEASE_ID")
return &AzureClient{
blobClient: &blobClient,
containerName: containerName,
keyName: keyName,
leaseID: leaseID,
}, nil
}
func getStorageAccountAccessKey(conf map[string]string, resourceGroupName, storageAccountName string, env azure.Environment) (string, error) {
creds, err := getCredentialsFromConf(conf, env)
if err != nil {
return "", err
}
oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, creds.TenantID)
if err != nil {
return "", err
}
if oauthConfig == nil {
return "", fmt.Errorf("Unable to configure OAuthConfig for tenant %s", creds.TenantID)
}
spt, err := adal.NewServicePrincipalToken(*oauthConfig, creds.ClientID, creds.ClientSecret, env.ResourceManagerEndpoint)
if err != nil {
return "", err
}
accountsClient := storage.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, creds.SubscriptionID)
accountsClient.Authorizer = autorest.NewBearerAuthorizer(spt)
keys, err := accountsClient.ListKeys(resourceGroupName, storageAccountName)
if err != nil {
return "", fmt.Errorf("Error retrieving keys for storage account %q: %s", storageAccountName, err)
}
if keys.Keys == nil {
return "", fmt.Errorf("Nil key returned for storage account %q", storageAccountName)
}
accessKeys := *keys.Keys
return *accessKeys[0].Value, nil
}
func getCredentialsFromConf(conf map[string]string, env azure.Environment) (*riviera.AzureResourceManagerCredentials, error) {
subscriptionID, ok := confOrEnv(conf, "arm_subscription_id", "ARM_SUBSCRIPTION_ID")
if !ok {
return nil, fmt.Errorf("missing 'arm_subscription_id' configuration")
}
clientID, ok := confOrEnv(conf, "arm_client_id", "ARM_CLIENT_ID")
if !ok {
return nil, fmt.Errorf("missing 'arm_client_id' configuration")
}
clientSecret, ok := confOrEnv(conf, "arm_client_secret", "ARM_CLIENT_SECRET")
if !ok {
return nil, fmt.Errorf("missing 'arm_client_secret' configuration")
}
tenantID, ok := confOrEnv(conf, "arm_tenant_id", "ARM_TENANT_ID")
if !ok {
return nil, fmt.Errorf("missing 'arm_tenant_id' configuration")
}
return &riviera.AzureResourceManagerCredentials{
SubscriptionID: subscriptionID,
ClientID: clientID,
ClientSecret: clientSecret,
TenantID: tenantID,
ActiveDirectoryEndpoint: env.ActiveDirectoryEndpoint,
ResourceManagerEndpoint: env.ResourceManagerEndpoint,
}, nil
}
func getAzureEnvironmentFromConf(conf map[string]string) (azure.Environment, error) {
envName, ok := confOrEnv(conf, "environment", "ARM_ENVIRONMENT")
if !ok {
return azure.PublicCloud, nil
}
env, err := azure.EnvironmentFromName(envName)
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", envName))
if innerErr != nil {
return env, fmt.Errorf("invalid 'environment' configuration: %s", err)
}
}
return env, nil
}
func confOrEnv(conf map[string]string, confKey, envVar string) (string, bool) {
value, ok := conf[confKey]
if ok {
return value, true
}
value = os.Getenv(envVar)
return value, value != ""
}
type AzureClient struct {
blobClient *mainStorage.BlobStorageClient
containerName string
keyName string
leaseID string
}
func (c *AzureClient) Get() (*Payload, error) {
containerReference := c.blobClient.GetContainerReference(c.containerName)
blobReference := containerReference.GetBlobReference(c.keyName)
options := &mainStorage.GetBlobOptions{}
blob, err := blobReference.Get(options)
if err != nil {
if storErr, ok := err.(mainStorage.AzureStorageServiceError); ok {
if storErr.Code == "BlobNotFound" {
return nil, nil
}
}
return nil, err
}
defer blob.Close()
data, err := ioutil.ReadAll(blob)
if err != nil {
return nil, err
}
payload := &Payload{
Data: data,
}
// If there was no data, then return nil
if len(payload.Data) == 0 {
return nil, nil
}
return payload, nil
}
func (c *AzureClient) Put(data []byte) error {
setOptions := &mainStorage.SetBlobPropertiesOptions{}
putOptions := &mainStorage.PutBlobOptions{}
containerReference := c.blobClient.GetContainerReference(c.containerName)
blobReference := containerReference.GetBlobReference(c.keyName)
blobReference.Properties.ContentType = "application/json"
blobReference.Properties.ContentLength = int64(len(data))
if c.leaseID != "" {
setOptions.LeaseID = c.leaseID
putOptions.LeaseID = c.leaseID
}
reader := bytes.NewReader(data)
err := blobReference.CreateBlockBlobFromReader(reader, putOptions)
if err != nil {
return err
}
return blobReference.SetProperties(setOptions)
}
func (c *AzureClient) Delete() error {
containerReference := c.blobClient.GetContainerReference(c.containerName)
blobReference := containerReference.GetBlobReference(c.keyName)
options := &mainStorage.DeleteBlobOptions{}
if c.leaseID != "" {
options.LeaseID = c.leaseID
}
return blobReference.Delete(options)
}

View File

@ -1,231 +0,0 @@
package remote
import (
"fmt"
"os"
"strings"
"testing"
mainStorage "github.com/Azure/azure-sdk-for-go/storage"
"github.com/hashicorp/terraform/helper/acctest"
riviera "github.com/jen20/riviera/azure"
"github.com/jen20/riviera/storage"
"github.com/satori/uuid"
)
func TestAzureClient_impl(t *testing.T) {
var _ Client = new(AzureClient)
}
// This test creates a bucket in Azure and populates it.
// It may incur costs, so it will only run if Azure credential environment
// variables are present.
func TestAzureClient(t *testing.T) {
config := getAzureConfig(t)
setup(t, config)
defer teardown(t, config)
client, err := azureFactory(config)
if err != nil {
t.Fatalf("Error for valid config: %v", err)
}
testClient(t, client)
}
// This test is the same as TestAzureClient with the addition of passing an
// empty string in the lease_id, we expect the client to pass tests
func TestAzureClientEmptyLease(t *testing.T) {
config := getAzureConfig(t)
config["lease_id"] = ""
setup(t, config)
defer teardown(t, config)
client, err := azureFactory(config)
if err != nil {
t.Fatalf("Error for valid config: %v", err)
}
testClient(t, client)
}
// This test is the same as TestAzureClient with the addition of using the
// lease_id config option
func TestAzureClientLease(t *testing.T) {
leaseID := uuid.NewV4().String()
config := getAzureConfig(t)
config["lease_id"] = leaseID
setup(t, config)
defer teardown(t, config)
client, err := azureFactory(config)
if err != nil {
t.Fatalf("Error for valid config: %v", err)
}
azureClient := client.(*AzureClient)
containerReference := azureClient.blobClient.GetContainerReference(azureClient.containerName)
blobReference := containerReference.GetBlobReference(azureClient.keyName)
// put empty blob so we can acquire lease against it
options := &mainStorage.PutBlobOptions{}
err = blobReference.CreateBlockBlob(options)
if err != nil {
t.Fatalf("Error creating blob for leasing: %v", err)
}
leaseOptions := &mainStorage.LeaseOptions{}
_, err = blobReference.AcquireLease(-1, leaseID, leaseOptions)
if err != nil {
t.Fatalf("Error acquiring lease: %v", err)
}
// no need to release lease as blob is deleted in testing
testClient(t, client)
}
func getAzureConfig(t *testing.T) map[string]string {
config := map[string]string{
"arm_subscription_id": os.Getenv("ARM_SUBSCRIPTION_ID"),
"arm_client_id": os.Getenv("ARM_CLIENT_ID"),
"arm_client_secret": os.Getenv("ARM_CLIENT_SECRET"),
"arm_tenant_id": os.Getenv("ARM_TENANT_ID"),
"environment": os.Getenv("ARM_ENVIRONMENT"),
}
for k, v := range config {
if v == "" {
t.Skipf("skipping; %s must be set", strings.ToUpper(k))
}
}
rs := acctest.RandString(8)
config["resource_group_name"] = fmt.Sprintf("terraform-%s", rs)
config["storage_account_name"] = fmt.Sprintf("terraform%s", rs)
config["container_name"] = "terraform"
config["key"] = "test.tfstate"
return config
}
func setup(t *testing.T, conf map[string]string) {
env, err := getAzureEnvironmentFromConf(conf)
if err != nil {
t.Fatalf("Error getting Azure environment from conf: %v", err)
}
creds, err := getCredentialsFromConf(conf, env)
if err != nil {
t.Fatalf("Error getting credentials from conf: %v", err)
}
rivieraClient, err := getRivieraClient(creds)
if err != nil {
t.Fatalf("Error instantiating the riviera client: %v", err)
}
// Create resource group
r := rivieraClient.NewRequest()
r.Command = riviera.CreateResourceGroup{
Name: conf["resource_group_name"],
Location: riviera.WestUS,
}
response, err := r.Execute()
if err != nil {
t.Fatalf("Error creating a resource group: %v", err)
}
if !response.IsSuccessful() {
t.Fatalf("Error creating a resource group: %v", response.Error.Error())
}
// Create storage account
r = rivieraClient.NewRequest()
r.Command = storage.CreateStorageAccount{
ResourceGroupName: conf["resource_group_name"],
Name: conf["storage_account_name"],
AccountType: riviera.String("Standard_LRS"),
Location: riviera.WestUS,
}
response, err = r.Execute()
if err != nil {
t.Fatalf("Error creating a storage account: %v", err)
}
if !response.IsSuccessful() {
t.Fatalf("Error creating a storage account: %v", response.Error.Error())
}
// Create container
accessKey, err := getStorageAccountAccessKey(conf, conf["resource_group_name"], conf["storage_account_name"], env)
if err != nil {
t.Fatalf("Error creating a storage account: %v", err)
}
storageClient, err := mainStorage.NewClient(conf["storage_account_name"], accessKey, env.StorageEndpointSuffix,
mainStorage.DefaultAPIVersion, true)
if err != nil {
t.Fatalf("Error creating storage client for storage account %q: %s", conf["storage_account_name"], err)
}
blobClient := storageClient.GetBlobService()
containerName := conf["container_name"]
containerReference := blobClient.GetContainerReference(containerName)
options := &mainStorage.CreateContainerOptions{
Access: mainStorage.ContainerAccessTypePrivate,
}
_, err = containerReference.CreateIfNotExists(options)
if err != nil {
t.Fatalf("Couldn't create container with name %s: %s.", conf["container_name"], err)
}
}
func teardown(t *testing.T, conf map[string]string) {
env, err := getAzureEnvironmentFromConf(conf)
if err != nil {
t.Fatalf("Error getting Azure environment from conf: %v", err)
}
creds, err := getCredentialsFromConf(conf, env)
if err != nil {
t.Fatalf("Error getting credentials from conf: %v", err)
}
rivieraClient, err := getRivieraClient(creds)
if err != nil {
t.Fatalf("Error instantiating the riviera client: %v", err)
}
r := rivieraClient.NewRequest()
r.Command = riviera.DeleteResourceGroup{
Name: conf["resource_group_name"],
}
response, err := r.Execute()
if err != nil {
t.Fatalf("Error deleting the resource group: %v", err)
}
if !response.IsSuccessful() {
t.Fatalf("Error deleting the resource group: %v", err)
}
}
func getRivieraClient(credentials *riviera.AzureResourceManagerCredentials) (*riviera.Client, error) {
rivieraClient, err := riviera.NewClient(credentials)
if err != nil {
return nil, fmt.Errorf("Error creating Riviera client: %s", err)
}
request := rivieraClient.NewRequest()
request.Command = riviera.RegisterResourceProvider{
Namespace: "Microsoft.Storage",
}
response, err := request.Execute()
if err != nil {
return nil, fmt.Errorf("Cannot request provider registration for Azure Resource Manager: %s.", err)
}
if !response.IsSuccessful() {
return nil, fmt.Errorf("Credentials for acessing the Azure Resource Manager API are likely " +
"to be incorrect, or\n the service principal does not have permission to use " +
"the Azure Service Management\n API.")
}
return rivieraClient, nil
}

View File

@ -1,12 +1,12 @@
---
layout: "backend-types"
page_title: "Backend Type: azure"
sidebar_current: "docs-backends-types-standard-azure"
page_title: "Backend Type: azurerm"
sidebar_current: "docs-backends-types-standard-azurerm"
description: |-
Terraform can store state remotely in Azure Storage.
---
# azure
# azurerm
**Kind: Standard (with state locking)**
@ -16,7 +16,7 @@ Stores the state as a given key in a given bucket on [Microsoft Azure Storage](h
```hcl
terraform {
backend "azure" {
backend "azurerm" {
storage_account_name = "abcd1234"
container_name = "tfstate"
key = "prod.terraform.tfstate"
@ -31,7 +31,7 @@ Note that for the access credentials we recommend using a
```hcl
data "terraform_remote_state" "foo" {
backend = "azure"
backend = "azurerm"
config {
storage_account_name = "terraform123abc"
container_name = "terraform-state"

View File

@ -26,8 +26,8 @@
<li<%= sidebar_current("docs-backends-types-standard-artifactory") %>>
<a href="/docs/backends/types/artifactory.html">artifactory</a>
</li>
<li<%= sidebar_current("docs-backends-types-standard-azure") %>>
<a href="/docs/backends/types/azure.html">azure</a>
<li<%= sidebar_current("docs-backends-types-standard-azurerm") %>>
<a href="/docs/backends/types/azurerm.html">azure</a>
</li>
<li<%= sidebar_current("docs-backends-types-standard-consul") %>>
<a href="/docs/backends/types/consul.html">consul</a>