Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storage: Import Support #1816

Merged
merged 9 commits into from
Aug 30, 2018
169 changes: 128 additions & 41 deletions azurerm/resource_arm_storage_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,29 @@ import (
"fmt"
"io"
"log"
"net/url"
"os"
"runtime"
"strings"
"sync"

"github.com/Azure/azure-sdk-for-go/storage"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceArmStorageBlob() *schema.Resource {
return &schema.Resource{
Create: resourceArmStorageBlobCreate,
Read: resourceArmStorageBlobRead,
Update: resourceArmStorageBlobUpdate,
Exists: resourceArmStorageBlobExists,
Delete: resourceArmStorageBlobDelete,
Create: resourceArmStorageBlobCreate,
Read: resourceArmStorageBlobRead,
Update: resourceArmStorageBlobUpdate,
Exists: resourceArmStorageBlobExists,
Delete: resourceArmStorageBlobDelete,
MigrateState: resourceStorageBlobMigrateState,
SchemaVersion: 1,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Expand Down Expand Up @@ -140,6 +147,7 @@ func validateArmStorageBlobType(v interface{}, k string) (ws []string, errors []
func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) error {
armClient := meta.(*ArmClient)
ctx := armClient.StopContext
env := armClient.environment

resourceGroupName := d.Get("resource_group_name").(string)
storageAccountName := d.Get("storage_account_name").(string)
Expand Down Expand Up @@ -210,7 +218,9 @@ func resourceArmStorageBlobCreate(d *schema.ResourceData, meta interface{}) erro
}
}

d.SetId(name)
// gives us https://example.blob.core.windows.net/container/file.vhd
id := fmt.Sprintf("https://%s.blob.%s/%s/%s", storageAccountName, env.StorageEndpointSuffix, cont, name)
d.SetId(id)
return resourceArmStorageBlobRead(d, meta)
}

Expand Down Expand Up @@ -573,15 +583,26 @@ func resourceArmStorageBlobRead(d *schema.ResourceData, meta interface{}) error
armClient := meta.(*ArmClient)
ctx := armClient.StopContext

resourceGroupName := d.Get("resource_group_name").(string)
storageAccountName := d.Get("storage_account_name").(string)
id, err := parseStorageBlobID(d.Id(), armClient.environment)
if err != nil {
return err
}

blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, resourceGroupName, storageAccountName)
resourceGroup, err := determineResourceGroupForStorageAccount(id.storageAccountName, armClient)
if err != nil {
return err
}

if resourceGroup == nil {
return fmt.Errorf("Unable to determine Resource Group for Storage Account %q", id.storageAccountName)
}

blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, *resourceGroup, id.storageAccountName)
if err != nil {
return err
}
if !accountExists {
log.Printf("[DEBUG] Storage account %q not found, removing blob %q from state", storageAccountName, d.Id())
log.Printf("[DEBUG] Storage account %q not found, removing blob %q from state", id.storageAccountName, d.Id())
d.SetId("")
return nil
}
Expand All @@ -596,22 +617,19 @@ func resourceArmStorageBlobRead(d *schema.ResourceData, meta interface{}) error
return nil
}

name := d.Get("name").(string)
storageContainerName := d.Get("storage_container_name").(string)

container := blobClient.GetContainerReference(storageContainerName)
blob := container.GetBlobReference(name)
container := blobClient.GetContainerReference(id.containerName)
blob := container.GetBlobReference(id.blobName)

options := &storage.GetBlobPropertiesOptions{}
err = blob.GetProperties(options)
if err != nil {
return fmt.Errorf("Error getting properties of blob %s (container %s, storage account %s): %+v", name, storageContainerName, storageAccountName, err)
return fmt.Errorf("Error getting properties of blob %s (container %s, storage account %s): %+v", id.blobName, id.containerName, id.storageAccountName, err)
}
d.Set("content_type", blob.Properties.ContentType)

url := blob.GetURL()
if url == "" {
log.Printf("[INFO] URL for %q is empty", name)
log.Printf("[INFO] URL for %q is empty", id.blobName)
}
d.Set("url", url)

Expand All @@ -622,32 +640,39 @@ func resourceArmStorageBlobExists(d *schema.ResourceData, meta interface{}) (boo
armClient := meta.(*ArmClient)
ctx := armClient.StopContext

resourceGroupName := d.Get("resource_group_name").(string)
storageAccountName := d.Get("storage_account_name").(string)
id, err := parseStorageBlobID(d.Id(), armClient.environment)
if err != nil {
return false, err
}

blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, resourceGroupName, storageAccountName)
resourceGroup, err := determineResourceGroupForStorageAccount(id.storageAccountName, armClient)
if err != nil {
return false, fmt.Errorf("Unable to determine Resource Group for Storage Account %q: %+v", id.storageAccountName, err)
}
if resourceGroup == nil {
return false, nil
}

blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, *resourceGroup, id.storageAccountName)
if err != nil {
return false, err
}
if !accountExists {
log.Printf("[DEBUG] Storage account %q not found, removing blob %q from state", storageAccountName, d.Id())
log.Printf("[DEBUG] Storage account %q not found, removing blob %q from state", id.storageAccountName, d.Id())
d.SetId("")
return false, nil
}

name := d.Get("name").(string)
storageContainerName := d.Get("storage_container_name").(string)

log.Printf("[INFO] Checking for existence of storage blob %q.", name)
container := blobClient.GetContainerReference(storageContainerName)
blob := container.GetBlobReference(name)
log.Printf("[INFO] Checking for existence of storage blob %q.", id.blobName)
container := blobClient.GetContainerReference(id.containerName)
blob := container.GetBlobReference(id.blobName)
exists, err := blob.Exists()
if err != nil {
return false, fmt.Errorf("error testing existence of storage blob %q: %s", name, err)
return false, fmt.Errorf("error testing existence of storage blob %q: %s", id.blobName, err)
}

if !exists {
log.Printf("[INFO] Storage blob %q no longer exists, removing from state...", name)
log.Printf("[INFO] Storage blob %q no longer exists, removing from state...", id.blobName)
d.SetId("")
}

Expand All @@ -658,30 +683,92 @@ func resourceArmStorageBlobDelete(d *schema.ResourceData, meta interface{}) erro
armClient := meta.(*ArmClient)
ctx := armClient.StopContext

resourceGroupName := d.Get("resource_group_name").(string)
storageAccountName := d.Get("storage_account_name").(string)
id, err := parseStorageBlobID(d.Id(), armClient.environment)
if err != nil {
return err
}

blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, resourceGroupName, storageAccountName)
resourceGroup, err := determineResourceGroupForStorageAccount(id.storageAccountName, armClient)
if err != nil {
return fmt.Errorf("Unable to determine Resource Group for Storage Account %q: %+v", id.storageAccountName, err)
}
if resourceGroup == nil {
log.Printf("[INFO] Resource Group doesn't exist so the blob won't exist")
return nil
}

blobClient, accountExists, err := armClient.getBlobStorageClientForStorageAccount(ctx, *resourceGroup, id.storageAccountName)
if err != nil {
return err
}
if !accountExists {
log.Printf("[INFO]Storage Account %q doesn't exist so the blob won't exist", storageAccountName)
log.Printf("[INFO] Storage Account %q doesn't exist so the blob won't exist", id.storageAccountName)
return nil
}

name := d.Get("name").(string)
storageContainerName := d.Get("storage_container_name").(string)

log.Printf("[INFO] Deleting storage blob %q", name)
log.Printf("[INFO] Deleting storage blob %q", id.blobName)
options := &storage.DeleteBlobOptions{}
container := blobClient.GetContainerReference(storageContainerName)
blob := container.GetBlobReference(name)
container := blobClient.GetContainerReference(id.containerName)
blob := container.GetBlobReference(id.blobName)
_, err = blob.DeleteIfExists(options)
if err != nil {
return fmt.Errorf("Error deleting storage blob %q: %s", name, err)
return fmt.Errorf("Error deleting storage blob %q: %s", id.blobName, err)
}

d.SetId("")
return nil
}

type storageBlobId struct {
storageAccountName string
containerName string
blobName string
}

func parseStorageBlobID(input string, environment azure.Environment) (*storageBlobId, error) {
uri, err := url.Parse(input)
if err != nil {
return nil, fmt.Errorf("Error parsing %q as URI: %+v", input, err)
}

segments := strings.Split(uri.Path, "/")
if len(segments) < 2 {
return nil, fmt.Errorf("Expected number of segments in the path to be < 2 but got %d", len(segments))
}

storageAccountName := strings.Replace(uri.Host, fmt.Sprintf(".blob.%s", environment.StorageEndpointSuffix), "", 1)
containerName := segments[0]
blobName := strings.TrimPrefix(uri.Path, fmt.Sprintf("/%s/", containerName))

id := storageBlobId{
storageAccountName: storageAccountName,
containerName: containerName,
blobName: blobName,
}
return &id, nil
}

func determineResourceGroupForStorageAccount(accountName string, client *ArmClient) (*string, error) {
storageClient := client.storageServiceClient
ctx := client.StopContext

// first locate which resource group the storage account is in
groupsResp, err := storageClient.List(ctx)
if err != nil {
return nil, fmt.Errorf("Error loading the Resource Groups for Storage Account %q: %+v", accountName, err)
}

if groups := groupsResp.Value; groups != nil {
for _, group := range *groups {
if group.Name != nil && *group.Name == accountName {
groupId, err := parseAzureResourceID(*group.ID)
if err != nil {
return nil, err
}

return &groupId.ResourceGroup, nil
}
}
}

return nil, nil
}
40 changes: 40 additions & 0 deletions azurerm/resource_arm_storage_blob_migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package azurerm

import (
"fmt"
"log"

"github.com/hashicorp/terraform/terraform"
)

func resourceStorageBlobMigrateState(v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
switch v {
case 0:
log.Println("[INFO] Found AzureRM Storage Blob State v0; migrating to v1")
return migrateStorageBlobStateV0toV1(is, meta)
default:
return is, fmt.Errorf("Unexpected schema version: %d", v)
}
}

func migrateStorageBlobStateV0toV1(is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
if is.Empty() {
log.Println("[DEBUG] Empty InstanceState; nothing to migrate.")
return is, nil
}

log.Printf("[DEBUG] ARM Storage Blob Attributes before Migration: %#v", is.Attributes)

environment := meta.(*ArmClient).environment

blobName := is.Attributes["name"]
containerName := is.Attributes["storage_container_name"]
storageAccountName := is.Attributes["storage_account_name"]
newID := fmt.Sprintf("https://%s.blob.%s/%s/%s", storageAccountName, environment.StorageEndpointSuffix, containerName, blobName)
is.Attributes["id"] = newID
is.ID = newID

log.Printf("[DEBUG] ARM Storage Blob Attributes after State Migration: %#v", is.Attributes)

return is, nil
}
67 changes: 67 additions & 0 deletions azurerm/resource_arm_storage_blob_migration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package azurerm

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/terraform"
)

// NOTE: this is intentionally an acceptance test (and we're not explicitly setting the env)
// as we want to run this depending on the cloud we're in.
func TestAccAzureRMStorageBlobMigrateState(t *testing.T) {
config := testGetAzureConfig(t)
if config == nil {
t.SkipNow()
return
}

client, err := getArmClient(config)
if err != nil {
t.Fatal(fmt.Errorf("Error building ARM Client: %+v", err))
return
}

client.StopContext = testAccProvider.StopContext()

suffix := client.environment.StorageEndpointSuffix

cases := map[string]struct {
StateVersion int
ID string
InputAttributes map[string]string
ExpectedAttributes map[string]string
}{
"v0_1_without_value": {
StateVersion: 0,
ID: "some_id",
InputAttributes: map[string]string{
"name": "blob.vhd",
"storage_container_name": "container",
"storage_account_name": "example",
},
ExpectedAttributes: map[string]string{
"id": fmt.Sprintf("https://example.blob.%s/container/blob.vhd", suffix),
},
},
}

for tn, tc := range cases {
is := &terraform.InstanceState{
ID: tc.ID,
Attributes: tc.InputAttributes,
}
is, err := resourceStorageBlobMigrateState(tc.StateVersion, is, client)

if err != nil {
t.Fatalf("bad: %s, err: %#v", tn, err)
}

for k, v := range tc.ExpectedAttributes {
actual := is.Attributes[k]
if actual != v {
t.Fatalf("Bad Storage Blob Migrate for %q: %q\n\n expected: %q", k, actual, v)
}
}
}
}
Loading