github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/state/remote/azure.go (about) 1 package remote 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "os" 8 9 "github.com/Azure/azure-sdk-for-go/arm/storage" 10 mainStorage "github.com/Azure/azure-sdk-for-go/storage" 11 "github.com/Azure/go-autorest/autorest/azure" 12 riviera "github.com/jen20/riviera/azure" 13 ) 14 15 func azureFactory(conf map[string]string) (Client, error) { 16 storageAccountName, ok := conf["storage_account_name"] 17 if !ok { 18 return nil, fmt.Errorf("missing 'storage_account_name' configuration") 19 } 20 containerName, ok := conf["container_name"] 21 if !ok { 22 return nil, fmt.Errorf("missing 'container_name' configuration") 23 } 24 keyName, ok := conf["key"] 25 if !ok { 26 return nil, fmt.Errorf("missing 'key' configuration") 27 } 28 29 env, err := getAzureEnvironmentFromConf(conf) 30 if err != nil { 31 return nil, err 32 } 33 34 accessKey, ok := confOrEnv(conf, "access_key", "ARM_ACCESS_KEY") 35 if !ok { 36 resourceGroupName, ok := conf["resource_group_name"] 37 if !ok { 38 return nil, fmt.Errorf("missing 'resource_group_name' configuration") 39 } 40 41 var err error 42 accessKey, err = getStorageAccountAccessKey(conf, resourceGroupName, storageAccountName, env) 43 if err != nil { 44 return nil, fmt.Errorf("Couldn't read access key from storage account: %s.", err) 45 } 46 } 47 48 storageClient, err := mainStorage.NewClient(storageAccountName, accessKey, env.StorageEndpointSuffix, 49 mainStorage.DefaultAPIVersion, true) 50 if err != nil { 51 return nil, fmt.Errorf("Error creating storage client for storage account %q: %s", storageAccountName, err) 52 } 53 54 blobClient := storageClient.GetBlobService() 55 leaseID, _ := confOrEnv(conf, "lease_id", "ARM_LEASE_ID") 56 57 return &AzureClient{ 58 blobClient: &blobClient, 59 containerName: containerName, 60 keyName: keyName, 61 leaseID: leaseID, 62 }, nil 63 } 64 65 func getStorageAccountAccessKey(conf map[string]string, resourceGroupName, storageAccountName string, env azure.Environment) (string, error) { 66 creds, err := getCredentialsFromConf(conf, env) 67 if err != nil { 68 return "", err 69 } 70 71 oauthConfig, err := env.OAuthConfigForTenant(creds.TenantID) 72 if err != nil { 73 return "", err 74 } 75 if oauthConfig == nil { 76 return "", fmt.Errorf("Unable to configure OAuthConfig for tenant %s", creds.TenantID) 77 } 78 79 spt, err := azure.NewServicePrincipalToken(*oauthConfig, creds.ClientID, creds.ClientSecret, env.ResourceManagerEndpoint) 80 if err != nil { 81 return "", err 82 } 83 84 accountsClient := storage.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, creds.SubscriptionID) 85 accountsClient.Authorizer = spt 86 87 keys, err := accountsClient.ListKeys(resourceGroupName, storageAccountName) 88 if err != nil { 89 return "", fmt.Errorf("Error retrieving keys for storage account %q: %s", storageAccountName, err) 90 } 91 92 if keys.Keys == nil { 93 return "", fmt.Errorf("Nil key returned for storage account %q", storageAccountName) 94 } 95 96 accessKeys := *keys.Keys 97 return *accessKeys[0].Value, nil 98 } 99 100 func getCredentialsFromConf(conf map[string]string, env azure.Environment) (*riviera.AzureResourceManagerCredentials, error) { 101 subscriptionID, ok := confOrEnv(conf, "arm_subscription_id", "ARM_SUBSCRIPTION_ID") 102 if !ok { 103 return nil, fmt.Errorf("missing 'arm_subscription_id' configuration") 104 } 105 clientID, ok := confOrEnv(conf, "arm_client_id", "ARM_CLIENT_ID") 106 if !ok { 107 return nil, fmt.Errorf("missing 'arm_client_id' configuration") 108 } 109 clientSecret, ok := confOrEnv(conf, "arm_client_secret", "ARM_CLIENT_SECRET") 110 if !ok { 111 return nil, fmt.Errorf("missing 'arm_client_secret' configuration") 112 } 113 tenantID, ok := confOrEnv(conf, "arm_tenant_id", "ARM_TENANT_ID") 114 if !ok { 115 return nil, fmt.Errorf("missing 'arm_tenant_id' configuration") 116 } 117 118 return &riviera.AzureResourceManagerCredentials{ 119 SubscriptionID: subscriptionID, 120 ClientID: clientID, 121 ClientSecret: clientSecret, 122 TenantID: tenantID, 123 ActiveDirectoryEndpoint: env.ActiveDirectoryEndpoint, 124 ResourceManagerEndpoint: env.ResourceManagerEndpoint, 125 }, nil 126 } 127 128 func getAzureEnvironmentFromConf(conf map[string]string) (azure.Environment, error) { 129 envName, ok := confOrEnv(conf, "environment", "ARM_ENVIRONMENT") 130 if !ok { 131 return azure.PublicCloud, nil 132 } 133 134 env, err := azure.EnvironmentFromName(envName) 135 if err != nil { 136 // try again with wrapped value to support readable values like german instead of AZUREGERMANCLOUD 137 var innerErr error 138 env, innerErr = azure.EnvironmentFromName(fmt.Sprintf("AZURE%sCLOUD", envName)) 139 if innerErr != nil { 140 return env, fmt.Errorf("invalid 'environment' configuration: %s", err) 141 } 142 } 143 144 return env, nil 145 } 146 147 func confOrEnv(conf map[string]string, confKey, envVar string) (string, bool) { 148 value, ok := conf[confKey] 149 if ok { 150 return value, true 151 } 152 153 value = os.Getenv(envVar) 154 155 return value, value != "" 156 } 157 158 type AzureClient struct { 159 blobClient *mainStorage.BlobStorageClient 160 containerName string 161 keyName string 162 leaseID string 163 } 164 165 func (c *AzureClient) Get() (*Payload, error) { 166 blob, err := c.blobClient.GetBlob(c.containerName, c.keyName) 167 if err != nil { 168 if storErr, ok := err.(mainStorage.AzureStorageServiceError); ok { 169 if storErr.Code == "BlobNotFound" { 170 return nil, nil 171 } 172 } 173 return nil, err 174 } 175 176 defer blob.Close() 177 178 data, err := ioutil.ReadAll(blob) 179 if err != nil { 180 return nil, err 181 } 182 183 payload := &Payload{ 184 Data: data, 185 } 186 187 // If there was no data, then return nil 188 if len(payload.Data) == 0 { 189 return nil, nil 190 } 191 192 return payload, nil 193 } 194 195 func (c *AzureClient) Put(data []byte) error { 196 headers := map[string]string{ 197 "Content-Type": "application/json", 198 } 199 200 if c.leaseID != "" { 201 headers["x-ms-lease-id"] = c.leaseID 202 } 203 204 return c.blobClient.CreateBlockBlobFromReader( 205 c.containerName, 206 c.keyName, 207 uint64(len(data)), 208 bytes.NewReader(data), 209 headers, 210 ) 211 } 212 213 func (c *AzureClient) Delete() error { 214 headers := map[string]string{} 215 if c.leaseID != "" { 216 headers["x-ms-lease-id"] = c.leaseID 217 } 218 219 return c.blobClient.DeleteBlob(c.containerName, c.keyName, headers) 220 }