diff --git a/builtin/providers/azurerm/config.go b/builtin/providers/azurerm/config.go index de4da1382..5136a22b0 100644 --- a/builtin/providers/azurerm/config.go +++ b/builtin/providers/azurerm/config.go @@ -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 +} diff --git a/builtin/providers/azurerm/provider.go b/builtin/providers/azurerm/provider.go index 56911cfd6..4ff5e7f3f 100644 --- a/builtin/providers/azurerm/provider.go +++ b/builtin/providers/azurerm/provider.go @@ -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, } diff --git a/builtin/providers/azurerm/resource_arm_storage_blob.go b/builtin/providers/azurerm/resource_arm_storage_blob.go new file mode 100644 index 000000000..d075fe215 --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_storage_blob.go @@ -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 +} diff --git a/builtin/providers/azurerm/resource_arm_storage_container.go b/builtin/providers/azurerm/resource_arm_storage_container.go new file mode 100644 index 000000000..a87e44ec3 --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_storage_container.go @@ -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 +}