provider/azurerm: Add storage container and blob

These resources use ARM to get keys for the storage API, but then use
the storage REST API as per the ASM provider. The code is significantly
reworked with better logging and error handling. The key functions can
be reused for queues and file storage resources when they get added.
This commit is contained in:
James Nugent 2016-01-26 15:45:18 -05:00
parent d78e897b46
commit 4a57ab4022
4 changed files with 404 additions and 0 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
"github.com/Azure/azure-sdk-for-go/arm/scheduler"
"github.com/Azure/azure-sdk-for-go/arm/storage"
mainStorage "github.com/Azure/azure-sdk-for-go/storage"
"github.com/hashicorp/terraform/terraform"
)
@ -281,3 +282,31 @@ func (c *Config) getArmClient() (*ArmClient, error) {
return &client, nil
}
func (armClient *ArmClient) getKeyForStorageAccount(resourceGroupName, storageAccountName string) (string, error) {
keys, err := armClient.storageServiceClient.ListKeys(resourceGroupName, storageAccountName)
if err != nil {
return "", fmt.Errorf("Error retrieving keys for storage account %q: %s", storageAccountName, err)
}
if keys.Key1 == nil {
return "", fmt.Errorf("Nil key returned for storage account %q", storageAccountName)
}
return *keys.Key1, nil
}
func (armClient *ArmClient) getBlobStorageClientForStorageAccount(resourceGroupName, storageAccountName string) (*mainStorage.BlobStorageClient, error) {
key, err := armClient.getKeyForStorageAccount(resourceGroupName, storageAccountName)
if err != nil {
return nil, err
}
storageClient, err := mainStorage.NewBasicClient(storageAccountName, key)
if err != nil {
return nil, fmt.Errorf("Error creating storage client for storage account %q: %s", storageAccountName, err)
}
blobClient := storageClient.GetBlobService()
return &blobClient, nil
}

View File

@ -56,6 +56,8 @@ func Provider() terraform.ResourceProvider {
"azurerm_cdn_profile": resourceArmCdnProfile(),
"azurerm_cdn_endpoint": resourceArmCdnEndpoint(),
"azurerm_storage_account": resourceArmStorageAccount(),
"azurerm_storage_container": resourceArmStorageContainer(),
"azurerm_storage_blob": resourceArmStorageBlob(),
},
ConfigureFunc: providerConfigure,
}

View File

@ -0,0 +1,185 @@
package azurerm
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceArmStorageBlob() *schema.Resource {
return &schema.Resource{
Create: resourceArmStorageBlobCreate,
Read: resourceArmStorageBlobRead,
Exists: resourceArmStorageBlobExists,
Delete: resourceArmStorageBlobDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"resource_group_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"storage_account_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"storage_container_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateArmStorageBlobType,
},
"size": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Default: 0,
},
"url": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func validateArmStorageBlobType(v interface{}, k string) (ws []string, errors []error) {
value := strings.ToLower(v.(string))
validTypes := map[string]struct{}{
"blob": struct{}{},
"page": struct{}{},
}
if _, ok := validTypes[value]; !ok {
errors = append(errors, fmt.Errorf("Blob type %q is invalid, must be %q or %q", value, "blob", "page"))
}
return
}
func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) error {
armClient := meta.(*ArmClient)
resourceGroupName := d.Get("resource_group_name").(string)
storageAccountName := d.Get("storage_account_name").(string)
blobClient, err := armClient.getBlobStorageClientForStorageAccount(resourceGroupName, storageAccountName)
if err != nil {
return err
}
name := d.Get("name").(string)
blobType := d.Get("type").(string)
cont := d.Get("storage_container_name").(string)
log.Printf("[INFO] Creating blob %q in storage account %q", name, storageAccountName)
switch strings.ToLower(blobType) {
case "block":
err = blobClient.CreateBlockBlob(cont, name)
case "page":
size := int64(d.Get("size").(int))
err = blobClient.PutPageBlob(cont, name, size, map[string]string{})
}
if err != nil {
return fmt.Errorf("Error creating storage blob on Azure: %s", err)
}
d.SetId(name)
return resourceArmStorageBlobRead(d, meta)
}
func resourceArmStorageBlobRead(d *schema.ResourceData, meta interface{}) error {
armClient := meta.(*ArmClient)
resourceGroupName := d.Get("resource_group_name").(string)
storageAccountName := d.Get("storage_account_name").(string)
blobClient, err := armClient.getBlobStorageClientForStorageAccount(resourceGroupName, storageAccountName)
if err != nil {
return err
}
exists, err := resourceArmStorageBlobExists(d, meta)
if err != nil {
return err
}
if !exists {
// Exists already removed this from state
return nil
}
name := d.Get("name").(string)
storageContainerName := d.Get("storage_container_name").(string)
url := blobClient.GetBlobURL(storageContainerName, name)
if url == "" {
log.Printf("[INFO] URL for %q is empty", name)
}
d.Set("url", url)
return nil
}
func resourceArmStorageBlobExists(d *schema.ResourceData, meta interface{}) (bool, error) {
armClient := meta.(*ArmClient)
resourceGroupName := d.Get("resource_group_name").(string)
storageAccountName := d.Get("storage_account_name").(string)
blobClient, err := armClient.getBlobStorageClientForStorageAccount(resourceGroupName, storageAccountName)
if err != nil {
return false, err
}
name := d.Get("name").(string)
storageContainerName := d.Get("storage_container_name").(string)
log.Printf("[INFO] Checking for existence of storage blob %q.", name)
exists, err := blobClient.BlobExists(storageContainerName, name)
if err != nil {
return false, fmt.Errorf("error testing existence of storage blob %q: %s", name, err)
}
if !exists {
log.Printf("[INFO] Storage blob %q no longer exists, removing from state...", name)
d.SetId("")
}
return exists, nil
}
func resourceArmStorageBlobDelete(d *schema.ResourceData, meta interface{}) error {
armClient := meta.(*ArmClient)
resourceGroupName := d.Get("resource_group_name").(string)
storageAccountName := d.Get("storage_account_name").(string)
blobClient, err := armClient.getBlobStorageClientForStorageAccount(resourceGroupName, storageAccountName)
if err != nil {
return err
}
name := d.Get("name").(string)
storageContainerName := d.Get("storage_container_name").(string)
log.Printf("[INFO] Deleting storage blob %q", name)
if _, err = blobClient.DeleteBlobIfExists(storageContainerName, name); err != nil {
return fmt.Errorf("Error deleting storage blob %q: %s", name, err)
}
d.SetId("")
return nil
}

View File

@ -0,0 +1,188 @@
package azurerm
import (
"fmt"
"log"
"strings"
"github.com/Azure/azure-sdk-for-go/storage"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceArmStorageContainer() *schema.Resource {
return &schema.Resource{
Create: resourceArmStorageContainerCreate,
Read: resourceArmStorageContainerRead,
Exists: resourceArmStorageContainerExists,
Delete: resourceArmStorageContainerDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"resource_group_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"storage_account_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"container_access_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "private",
ValidateFunc: validateArmStorageContainerAccessType,
},
"properties": &schema.Schema{
Type: schema.TypeMap,
Computed: true,
},
},
}
}
func validateArmStorageContainerAccessType(v interface{}, k string) (ws []string, errors []error) {
value := strings.ToLower(v.(string))
validTypes := map[string]struct{}{
"private": struct{}{},
"blob": struct{}{},
"container": struct{}{},
}
if _, ok := validTypes[value]; !ok {
errors = append(errors, fmt.Errorf("Storage container access type %q is invalid, must be %q, %q or %q", value, "private", "blob", "page"))
}
return
}
func resourceArmStorageContainerCreate(d *schema.ResourceData, meta interface{}) error {
armClient := meta.(*ArmClient)
resourceGroupName := d.Get("resource_group_name").(string)
storageAccountName := d.Get("storage_account_name").(string)
blobClient, err := armClient.getBlobStorageClientForStorageAccount(resourceGroupName, storageAccountName)
if err != nil {
return err
}
name := d.Get("name").(string)
var accessType storage.ContainerAccessType
if d.Get("container_access_type").(string) == "private" {
accessType = storage.ContainerAccessType("")
} else {
accessType = storage.ContainerAccessType(d.Get("container_access_type").(string))
}
log.Printf("[INFO] Creating container %q in storage account %q.", name, storageAccountName)
_, err = blobClient.CreateContainerIfNotExists(name, accessType)
if err != nil {
return fmt.Errorf("Error creating container %q in storage account %q: %s", name, storageAccountName, err)
}
d.SetId(name)
return resourceArmStorageContainerRead(d, meta)
}
// resourceAzureStorageContainerRead does all the necessary API calls to
// read the status of the storage container off Azure.
func resourceArmStorageContainerRead(d *schema.ResourceData, meta interface{}) error {
armClient := meta.(*ArmClient)
resourceGroupName := d.Get("resource_group_name").(string)
storageAccountName := d.Get("storage_account_name").(string)
blobClient, err := armClient.getBlobStorageClientForStorageAccount(resourceGroupName, storageAccountName)
if err != nil {
return err
}
name := d.Get("name").(string)
containers, err := blobClient.ListContainers(storage.ListContainersParameters{
Prefix: name,
Timeout: 90,
})
if err != nil {
return fmt.Errorf("Failed to retrieve storage containers in account %q: %s", name, err)
}
var found bool
for _, cont := range containers.Containers {
if cont.Name == name {
found = true
props := make(map[string]interface{})
props["last_modified"] = cont.Properties.LastModified
props["lease_status"] = cont.Properties.LeaseStatus
props["lease_state"] = cont.Properties.LeaseState
props["lease_duration"] = cont.Properties.LeaseDuration
d.Set("properties", props)
}
}
if !found {
log.Printf("[INFO] Storage container %q does not exist in account %q, removing from state...", name, storageAccountName)
d.SetId("")
}
return nil
}
func resourceArmStorageContainerExists(d *schema.ResourceData, meta interface{}) (bool, error) {
armClient := meta.(*ArmClient)
resourceGroupName := d.Get("resource_group_name").(string)
storageAccountName := d.Get("storage_account_name").(string)
blobClient, err := armClient.getBlobStorageClientForStorageAccount(resourceGroupName, storageAccountName)
if err != nil {
return false, err
}
name := d.Get("name").(string)
log.Printf("[INFO] Checking existence of storage container %q in storage account %q", name, storageAccountName)
exists, err := blobClient.ContainerExists(name)
if err != nil {
return false, fmt.Errorf("Error querying existence of storage container %q in storage account %q: %s", name, storageAccountName, err)
}
if !exists {
log.Printf("[INFO] Storage container %q does not exist in account %q, removing from state...", name, storageAccountName)
d.SetId("")
}
return exists, nil
}
// resourceAzureStorageContainerDelete does all the necessary API calls to
// delete a storage container off Azure.
func resourceArmStorageContainerDelete(d *schema.ResourceData, meta interface{}) error {
armClient := meta.(*ArmClient)
resourceGroupName := d.Get("resource_group_name").(string)
storageAccountName := d.Get("storage_account_name").(string)
blobClient, err := armClient.getBlobStorageClientForStorageAccount(resourceGroupName, storageAccountName)
if err != nil {
return err
}
name := d.Get("name").(string)
log.Printf("[INFO] Deleting storage container %q in account %q", name, storageAccountName)
if _, err := blobClient.DeleteContainerIfExists(name); err != nil {
return fmt.Errorf("Error deleting storage container %q from storage account %q: %s", name, storageAccountName, err)
}
d.SetId("")
return nil
}