github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/backend/remote-state/azure/helpers_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package azure 5 6 import ( 7 "context" 8 "fmt" 9 "log" 10 "os" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources" 16 armStorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-01-01/storage" 17 "github.com/Azure/go-autorest/autorest" 18 sasStorage "github.com/hashicorp/go-azure-helpers/storage" 19 "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers" 20 ) 21 22 const ( 23 // required for Azure Stack 24 sasSignedVersion = "2015-04-05" 25 ) 26 27 // verify that we are doing ACC tests or the Azure tests specifically 28 func testAccAzureBackend(t *testing.T) { 29 skip := os.Getenv("TF_ACC") == "" && os.Getenv("TF_AZURE_TEST") == "" 30 if skip { 31 t.Log("azure backend tests require setting TF_ACC or TF_AZURE_TEST") 32 t.Skip() 33 } 34 } 35 36 // these kind of tests can only run when within Azure (e.g. MSI) 37 func testAccAzureBackendRunningInAzure(t *testing.T) { 38 testAccAzureBackend(t) 39 40 if os.Getenv("TF_RUNNING_IN_AZURE") == "" { 41 t.Skip("Skipping test since not running in Azure") 42 } 43 } 44 45 // these kind of tests can only run when within GitHub Actions (e.g. OIDC) 46 func testAccAzureBackendRunningInGitHubActions(t *testing.T) { 47 testAccAzureBackend(t) 48 49 if os.Getenv("TF_RUNNING_IN_GITHUB_ACTIONS") == "" { 50 t.Skip("Skipping test since not running in GitHub Actions") 51 } 52 } 53 54 func buildTestClient(t *testing.T, res resourceNames) *ArmClient { 55 subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID") 56 tenantID := os.Getenv("ARM_TENANT_ID") 57 clientID := os.Getenv("ARM_CLIENT_ID") 58 clientSecret := os.Getenv("ARM_CLIENT_SECRET") 59 msiEnabled := strings.EqualFold(os.Getenv("ARM_USE_MSI"), "true") 60 environment := os.Getenv("ARM_ENVIRONMENT") 61 62 hasCredentials := (clientID != "" && clientSecret != "") || msiEnabled 63 if !hasCredentials { 64 t.Fatal("Azure credentials missing or incomplete") 65 } 66 67 if subscriptionID == "" { 68 t.Fatalf("Missing ARM_SUBSCRIPTION_ID") 69 } 70 71 if tenantID == "" { 72 t.Fatalf("Missing ARM_TENANT_ID") 73 } 74 75 if environment == "" { 76 t.Fatalf("Missing ARM_ENVIRONMENT") 77 } 78 79 // location isn't used in this method, but is in the other test methods 80 location := os.Getenv("ARM_LOCATION") 81 if location == "" { 82 t.Fatalf("Missing ARM_LOCATION") 83 } 84 85 // Endpoint is optional (only for Stack) 86 endpoint := os.Getenv("ARM_ENDPOINT") 87 88 armClient, err := buildArmClient(context.TODO(), BackendConfig{ 89 SubscriptionID: subscriptionID, 90 TenantID: tenantID, 91 ClientID: clientID, 92 ClientSecret: clientSecret, 93 CustomResourceManagerEndpoint: endpoint, 94 Environment: environment, 95 ResourceGroupName: res.resourceGroup, 96 StorageAccountName: res.storageAccountName, 97 UseMsi: msiEnabled, 98 UseAzureADAuthentication: res.useAzureADAuth, 99 }) 100 if err != nil { 101 t.Fatalf("Failed to build ArmClient: %+v", err) 102 } 103 104 return armClient 105 } 106 107 func buildSasToken(accountName, accessKey string) (*string, error) { 108 // grant full access to Objects in the Blob Storage Account 109 permissions := "rwdlacup" // full control 110 resourceTypes := "sco" // service, container, object 111 services := "b" // blob 112 113 // Details on how to do this are here: 114 // https://docs.microsoft.com/en-us/rest/api/storageservices/Constructing-an-Account-SAS 115 signedProtocol := "https,http" 116 signedIp := "" 117 signedVersion := sasSignedVersion 118 119 utcNow := time.Now().UTC() 120 121 // account for servers being up to 5 minutes out 122 startDate := utcNow.Add(time.Minute * -5).Format(time.RFC3339) 123 endDate := utcNow.Add(time.Hour * 24).Format(time.RFC3339) 124 125 sasToken, err := sasStorage.ComputeAccountSASToken(accountName, accessKey, permissions, services, resourceTypes, 126 startDate, endDate, signedProtocol, signedIp, signedVersion) 127 if err != nil { 128 return nil, fmt.Errorf("Error computing SAS Token: %+v", err) 129 } 130 log.Printf("SAS Token should be %q", sasToken) 131 return &sasToken, nil 132 } 133 134 type resourceNames struct { 135 resourceGroup string 136 location string 137 storageAccountName string 138 storageContainerName string 139 storageKeyName string 140 storageAccountAccessKey string 141 useAzureADAuth bool 142 } 143 144 func testResourceNames(rString string, keyName string) resourceNames { 145 return resourceNames{ 146 resourceGroup: fmt.Sprintf("acctestRG-backend-%s-%s", strings.Replace(time.Now().Local().Format("060102150405.00"), ".", "", 1), rString), 147 location: os.Getenv("ARM_LOCATION"), 148 storageAccountName: fmt.Sprintf("acctestsa%s", rString), 149 storageContainerName: "acctestcont", 150 storageKeyName: keyName, 151 useAzureADAuth: false, 152 } 153 } 154 155 func (c *ArmClient) buildTestResources(ctx context.Context, names *resourceNames) error { 156 log.Printf("Creating Resource Group %q", names.resourceGroup) 157 _, err := c.groupsClient.CreateOrUpdate(ctx, names.resourceGroup, resources.Group{Location: &names.location}) 158 if err != nil { 159 return fmt.Errorf("failed to create test resource group: %s", err) 160 } 161 162 log.Printf("Creating Storage Account %q in Resource Group %q", names.storageAccountName, names.resourceGroup) 163 storageProps := armStorage.AccountCreateParameters{ 164 Sku: &armStorage.Sku{ 165 Name: armStorage.StandardLRS, 166 Tier: armStorage.Standard, 167 }, 168 Location: &names.location, 169 } 170 if names.useAzureADAuth { 171 allowSharedKeyAccess := false 172 storageProps.AccountPropertiesCreateParameters = &armStorage.AccountPropertiesCreateParameters{ 173 AllowSharedKeyAccess: &allowSharedKeyAccess, 174 } 175 } 176 future, err := c.storageAccountsClient.Create(ctx, names.resourceGroup, names.storageAccountName, storageProps) 177 if err != nil { 178 return fmt.Errorf("failed to create test storage account: %s", err) 179 } 180 181 err = future.WaitForCompletionRef(ctx, c.storageAccountsClient.Client) 182 if err != nil { 183 return fmt.Errorf("failed waiting for the creation of storage account: %s", err) 184 } 185 186 containersClient := containers.NewWithEnvironment(c.environment) 187 if names.useAzureADAuth { 188 containersClient.Client.Authorizer = *c.azureAdStorageAuth 189 } else { 190 log.Printf("fetching access key for storage account") 191 resp, err := c.storageAccountsClient.ListKeys(ctx, names.resourceGroup, names.storageAccountName, "") 192 if err != nil { 193 return fmt.Errorf("failed to list storage account keys %s:", err) 194 } 195 196 keys := *resp.Keys 197 accessKey := *keys[0].Value 198 names.storageAccountAccessKey = accessKey 199 200 storageAuth, err := autorest.NewSharedKeyAuthorizer(names.storageAccountName, accessKey, autorest.SharedKey) 201 if err != nil { 202 return fmt.Errorf("Error building Authorizer: %+v", err) 203 } 204 205 containersClient.Client.Authorizer = storageAuth 206 } 207 208 log.Printf("Creating Container %q in Storage Account %q (Resource Group %q)", names.storageContainerName, names.storageAccountName, names.resourceGroup) 209 _, err = containersClient.Create(ctx, names.storageAccountName, names.storageContainerName, containers.CreateInput{}) 210 if err != nil { 211 return fmt.Errorf("failed to create storage container: %s", err) 212 } 213 214 return nil 215 } 216 217 func (c ArmClient) destroyTestResources(ctx context.Context, resources resourceNames) error { 218 log.Printf("[DEBUG] Deleting Resource Group %q..", resources.resourceGroup) 219 future, err := c.groupsClient.Delete(ctx, resources.resourceGroup) 220 if err != nil { 221 return fmt.Errorf("Error deleting Resource Group: %+v", err) 222 } 223 224 log.Printf("[DEBUG] Waiting for deletion of Resource Group %q..", resources.resourceGroup) 225 err = future.WaitForCompletionRef(ctx, c.groupsClient.Client) 226 if err != nil { 227 return fmt.Errorf("Error waiting for the deletion of Resource Group: %+v", err) 228 } 229 230 return nil 231 }