diff --git a/internal/services/storage/blobs.go b/internal/services/storage/blobs.go index 93fa20974661..116c69ad9445 100644 --- a/internal/services/storage/blobs.go +++ b/internal/services/storage/blobs.go @@ -79,7 +79,7 @@ func (sbu BlobUpload) Create(ctx context.Context) error { return sbu.copy(ctx) } if sbu.SourceContent != "" { - return fmt.Errorf("`source_content` cannot be specified for a Page blob") + return sbu.uploadPageBlobFromContent(ctx) } if sbu.Source != "" { return sbu.uploadPageBlob(ctx) @@ -185,6 +185,22 @@ func (sbu BlobUpload) createEmptyPageBlob(ctx context.Context) error { return nil } +func (sbu BlobUpload) uploadPageBlobFromContent(ctx context.Context) error { + tmpFile, err := os.CreateTemp(os.TempDir(), "upload-") + if err != nil { + return fmt.Errorf("creating temporary file: %s", err) + } + defer os.Remove(tmpFile.Name()) + + if _, err = tmpFile.Write([]byte(sbu.SourceContent)); err != nil { + return fmt.Errorf("writing Source Content to Temp File: %s", err) + } + defer tmpFile.Close() + + sbu.Source = tmpFile.Name() + return sbu.uploadPageBlob(ctx) +} + func (sbu BlobUpload) uploadPageBlob(ctx context.Context) error { if sbu.Size != 0 { return fmt.Errorf("`size` cannot be set for an uploaded page blob") diff --git a/internal/services/storage/storage_blob_resource.go b/internal/services/storage/storage_blob_resource.go index d3f864de5feb..d2c5b1c227ab 100644 --- a/internal/services/storage/storage_blob_resource.go +++ b/internal/services/storage/storage_blob_resource.go @@ -4,6 +4,7 @@ package storage import ( + "context" "fmt" "log" "strings" @@ -151,6 +152,15 @@ func resourceStorageBlob() *pluginsdk.Resource { "metadata": MetaDataComputedSchema(), }, + + CustomizeDiff: func(ctx context.Context, diff *pluginsdk.ResourceDiff, i interface{}) error { + if content := diff.Get("source_content"); content != "" && diff.Get("type") == "Page" { + if len(content.(string))%512 != 0 { + return fmt.Errorf(`"source" must be aligned to 512-byte boundary for "type" set to "Page"`) + } + } + return nil + }, } } diff --git a/internal/services/storage/storage_blob_resource_test.go b/internal/services/storage/storage_blob_resource_test.go index de807a0d4e23..d37b88f11b3f 100644 --- a/internal/services/storage/storage_blob_resource_test.go +++ b/internal/services/storage/storage_blob_resource_test.go @@ -8,6 +8,7 @@ import ( "crypto/rand" "fmt" "os" + "regexp" "testing" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" @@ -389,6 +390,32 @@ func TestAccStorageBlob_pageFromLocalFile(t *testing.T) { }) } +func TestAccStorageBlob_pageFromInlineContent(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_storage_blob", "test") + r := StorageBlobResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.pageFromInlineContent(data, 512), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("parallelism", "size", "source_content", "type"), + { + Config: r.pageFromInlineContent(data, 1024), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("parallelism", "size", "source_content", "type"), + { + Config: r.pageFromInlineContent(data, 511), + ExpectError: regexp.MustCompile(`"source" must be aligned to 512-byte boundary for "type" set to "Page"`), + }, + }) +} + func TestAccStorageBlob_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_storage_blob", "test") r := StorageBlobResource{} @@ -950,6 +977,25 @@ resource "azurerm_storage_blob" "test" { `, template, fileName) } +func (r StorageBlobResource) pageFromInlineContent(data acceptance.TestData, length int) string { + template := r.template(data, "private") + return fmt.Sprintf(` +%s + +provider "azurerm" { + features {} +} + +resource "azurerm_storage_blob" "test" { + name = "rick.morty" + storage_account_name = azurerm_storage_account.test.name + storage_container_name = azurerm_storage_container.test.name + type = "Page" + source_content = join("", [for i in range(0, %d) : "a"]) +} +`, template, length) +} + func (r StorageBlobResource) requiresImport(data acceptance.TestData) string { template := r.blockFromPublicBlob(data) return fmt.Sprintf(`