2018-11-21 22:06:03 +01:00
|
|
|
package azure
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
2018-11-22 16:52:27 +01:00
|
|
|
"strings"
|
2018-11-21 22:06:03 +01:00
|
|
|
"testing"
|
2018-11-22 18:02:33 +01:00
|
|
|
"time"
|
2018-11-21 22:06:03 +01:00
|
|
|
|
|
|
|
"github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources"
|
2021-03-22 18:49:34 +01:00
|
|
|
armStorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-01-01/storage"
|
2020-05-20 17:29:02 +02:00
|
|
|
"github.com/Azure/go-autorest/autorest"
|
2018-11-22 18:02:33 +01:00
|
|
|
sasStorage "github.com/hashicorp/go-azure-helpers/storage"
|
2020-05-20 17:29:02 +02:00
|
|
|
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
|
2018-11-22 18:02:33 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2018-11-26 14:42:16 +01:00
|
|
|
// required for Azure Stack
|
|
|
|
sasSignedVersion = "2015-04-05"
|
2018-11-21 22:06:03 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// verify that we are doing ACC tests or the Azure tests specifically
|
|
|
|
func testAccAzureBackend(t *testing.T) {
|
|
|
|
skip := os.Getenv("TF_ACC") == "" && os.Getenv("TF_AZURE_TEST") == ""
|
|
|
|
if skip {
|
|
|
|
t.Log("azure backend tests require setting TF_ACC or TF_AZURE_TEST")
|
|
|
|
t.Skip()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-22 16:52:27 +01:00
|
|
|
// these kind of tests can only run when within Azure (e.g. MSI)
|
|
|
|
func testAccAzureBackendRunningInAzure(t *testing.T) {
|
|
|
|
testAccAzureBackend(t)
|
|
|
|
|
|
|
|
if os.Getenv("TF_RUNNING_IN_AZURE") == "" {
|
|
|
|
t.Skip("Skipping test since not running in Azure")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-21 22:06:03 +01:00
|
|
|
func buildTestClient(t *testing.T, res resourceNames) *ArmClient {
|
|
|
|
subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID")
|
|
|
|
tenantID := os.Getenv("ARM_TENANT_ID")
|
|
|
|
clientID := os.Getenv("ARM_CLIENT_ID")
|
|
|
|
clientSecret := os.Getenv("ARM_CLIENT_SECRET")
|
2018-11-22 16:52:27 +01:00
|
|
|
msiEnabled := strings.EqualFold(os.Getenv("ARM_USE_MSI"), "true")
|
2018-11-21 22:06:03 +01:00
|
|
|
environment := os.Getenv("ARM_ENVIRONMENT")
|
|
|
|
|
2018-11-22 16:52:27 +01:00
|
|
|
hasCredentials := (clientID != "" && clientSecret != "") || msiEnabled
|
|
|
|
if !hasCredentials {
|
2018-11-21 22:06:03 +01:00
|
|
|
t.Fatal("Azure credentials missing or incomplete")
|
|
|
|
}
|
|
|
|
|
2018-11-22 16:52:27 +01:00
|
|
|
if subscriptionID == "" {
|
|
|
|
t.Fatalf("Missing ARM_SUBSCRIPTION_ID")
|
|
|
|
}
|
|
|
|
|
|
|
|
if tenantID == "" {
|
|
|
|
t.Fatalf("Missing ARM_TENANT_ID")
|
|
|
|
}
|
|
|
|
|
|
|
|
if environment == "" {
|
|
|
|
t.Fatalf("Missing ARM_ENVIRONMENT")
|
|
|
|
}
|
|
|
|
|
2018-11-26 14:42:16 +01:00
|
|
|
// location isn't used in this method, but is in the other test methods
|
|
|
|
location := os.Getenv("ARM_LOCATION")
|
2018-11-22 16:52:27 +01:00
|
|
|
if location == "" {
|
|
|
|
t.Fatalf("Missing ARM_LOCATION")
|
|
|
|
}
|
|
|
|
|
2018-11-26 14:42:16 +01:00
|
|
|
// Endpoint is optional (only for Stack)
|
|
|
|
endpoint := os.Getenv("ARM_ENDPOINT")
|
|
|
|
|
2020-10-02 01:34:51 +02:00
|
|
|
armClient, err := buildArmClient(context.TODO(), BackendConfig{
|
2018-11-26 14:42:16 +01:00
|
|
|
SubscriptionID: subscriptionID,
|
|
|
|
TenantID: tenantID,
|
|
|
|
ClientID: clientID,
|
|
|
|
ClientSecret: clientSecret,
|
|
|
|
CustomResourceManagerEndpoint: endpoint,
|
|
|
|
Environment: environment,
|
|
|
|
ResourceGroupName: res.resourceGroup,
|
|
|
|
StorageAccountName: res.storageAccountName,
|
|
|
|
UseMsi: msiEnabled,
|
2021-03-22 18:49:34 +01:00
|
|
|
UseAzureADAuthentication: res.useAzureADAuth,
|
2018-11-21 22:06:03 +01:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to build ArmClient: %+v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return armClient
|
|
|
|
}
|
|
|
|
|
2018-11-22 18:02:33 +01:00
|
|
|
func buildSasToken(accountName, accessKey string) (*string, error) {
|
|
|
|
// grant full access to Objects in the Blob Storage Account
|
|
|
|
permissions := "rwdlacup" // full control
|
|
|
|
resourceTypes := "sco" // service, container, object
|
|
|
|
services := "b" // blob
|
|
|
|
|
|
|
|
// Details on how to do this are here:
|
|
|
|
// https://docs.microsoft.com/en-us/rest/api/storageservices/Constructing-an-Account-SAS
|
|
|
|
signedProtocol := "https,http"
|
|
|
|
signedIp := ""
|
|
|
|
signedVersion := sasSignedVersion
|
|
|
|
|
|
|
|
utcNow := time.Now().UTC()
|
2018-11-26 14:42:16 +01:00
|
|
|
|
|
|
|
// account for servers being up to 5 minutes out
|
|
|
|
startDate := utcNow.Add(time.Minute * -5).Format(time.RFC3339)
|
2018-11-22 18:02:33 +01:00
|
|
|
endDate := utcNow.Add(time.Hour * 24).Format(time.RFC3339)
|
|
|
|
|
2019-02-01 11:48:56 +01:00
|
|
|
sasToken, err := sasStorage.ComputeAccountSASToken(accountName, accessKey, permissions, services, resourceTypes,
|
2018-11-22 18:02:33 +01:00
|
|
|
startDate, endDate, signedProtocol, signedIp, signedVersion)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error computing SAS Token: %+v", err)
|
|
|
|
}
|
|
|
|
log.Printf("SAS Token should be %q", sasToken)
|
|
|
|
return &sasToken, nil
|
|
|
|
}
|
|
|
|
|
2018-11-21 22:06:03 +01:00
|
|
|
type resourceNames struct {
|
|
|
|
resourceGroup string
|
|
|
|
location string
|
|
|
|
storageAccountName string
|
|
|
|
storageContainerName string
|
|
|
|
storageKeyName string
|
|
|
|
storageAccountAccessKey string
|
2021-03-22 18:49:34 +01:00
|
|
|
useAzureADAuth bool
|
2018-11-21 22:06:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func testResourceNames(rString string, keyName string) resourceNames {
|
|
|
|
return resourceNames{
|
2020-05-20 17:29:02 +02:00
|
|
|
resourceGroup: fmt.Sprintf("acctestRG-backend-%s-%s", strings.Replace(time.Now().Local().Format("060102150405.00"), ".", "", 1), rString),
|
2018-11-21 22:06:03 +01:00
|
|
|
location: os.Getenv("ARM_LOCATION"),
|
|
|
|
storageAccountName: fmt.Sprintf("acctestsa%s", rString),
|
|
|
|
storageContainerName: "acctestcont",
|
|
|
|
storageKeyName: keyName,
|
2021-03-22 18:49:34 +01:00
|
|
|
useAzureADAuth: false,
|
2018-11-21 22:06:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ArmClient) buildTestResources(ctx context.Context, names *resourceNames) error {
|
|
|
|
log.Printf("Creating Resource Group %q", names.resourceGroup)
|
|
|
|
_, err := c.groupsClient.CreateOrUpdate(ctx, names.resourceGroup, resources.Group{Location: &names.location})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to create test resource group: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("Creating Storage Account %q in Resource Group %q", names.storageAccountName, names.resourceGroup)
|
2021-03-22 18:49:34 +01:00
|
|
|
storageProps := armStorage.AccountCreateParameters{
|
2018-11-21 22:06:03 +01:00
|
|
|
Sku: &armStorage.Sku{
|
|
|
|
Name: armStorage.StandardLRS,
|
|
|
|
Tier: armStorage.Standard,
|
|
|
|
},
|
|
|
|
Location: &names.location,
|
2021-03-22 18:49:34 +01:00
|
|
|
}
|
|
|
|
if names.useAzureADAuth {
|
|
|
|
allowSharedKeyAccess := false
|
|
|
|
storageProps.AccountPropertiesCreateParameters = &armStorage.AccountPropertiesCreateParameters{
|
|
|
|
AllowSharedKeyAccess: &allowSharedKeyAccess,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
future, err := c.storageAccountsClient.Create(ctx, names.resourceGroup, names.storageAccountName, storageProps)
|
2018-11-21 22:06:03 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to create test storage account: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = future.WaitForCompletionRef(ctx, c.storageAccountsClient.Client)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed waiting for the creation of storage account: %s", err)
|
|
|
|
}
|
|
|
|
|
2020-05-20 17:29:02 +02:00
|
|
|
containersClient := containers.NewWithEnvironment(c.environment)
|
2021-03-22 18:49:34 +01:00
|
|
|
if names.useAzureADAuth {
|
|
|
|
containersClient.Client.Authorizer = *c.azureAdStorageAuth
|
|
|
|
} else {
|
|
|
|
log.Printf("fetching access key for storage account")
|
|
|
|
resp, err := c.storageAccountsClient.ListKeys(ctx, names.resourceGroup, names.storageAccountName, "")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to list storage account keys %s:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
keys := *resp.Keys
|
|
|
|
accessKey := *keys[0].Value
|
|
|
|
names.storageAccountAccessKey = accessKey
|
|
|
|
|
|
|
|
storageAuth, err := autorest.NewSharedKeyAuthorizer(names.storageAccountName, accessKey, autorest.SharedKey)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error building Authorizer: %+v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
containersClient.Client.Authorizer = storageAuth
|
|
|
|
}
|
2020-05-20 17:29:02 +02:00
|
|
|
|
2018-11-21 22:06:03 +01:00
|
|
|
log.Printf("Creating Container %q in Storage Account %q (Resource Group %q)", names.storageContainerName, names.storageAccountName, names.resourceGroup)
|
2020-05-20 17:29:02 +02:00
|
|
|
_, err = containersClient.Create(ctx, names.storageAccountName, names.storageContainerName, containers.CreateInput{})
|
2018-11-21 22:06:03 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to create storage container: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c ArmClient) destroyTestResources(ctx context.Context, resources resourceNames) error {
|
|
|
|
log.Printf("[DEBUG] Deleting Resource Group %q..", resources.resourceGroup)
|
|
|
|
future, err := c.groupsClient.Delete(ctx, resources.resourceGroup)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error deleting Resource Group: %+v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Waiting for deletion of Resource Group %q..", resources.resourceGroup)
|
|
|
|
err = future.WaitForCompletionRef(ctx, c.groupsClient.Client)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error waiting for the deletion of Resource Group: %+v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|