Merge pull request #15841 from hashicorp/jbardin/azurerm-backend
Rename the azure backend to azurerm
This commit is contained in:
commit
0da6b95ef8
|
@ -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() },
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue