Skip to content

Commit f392827

Browse files
fix: (storage) added source_md5hash field in bucket object (#14117) (#23267)
[upstream:e8fa4171a1bed7a0d45d11249b04976c53bd9615] Signed-off-by: Modular Magician <[email protected]>
1 parent 54746ca commit f392827

File tree

4 files changed

+229
-2
lines changed

4 files changed

+229
-2
lines changed

.changelog/14117.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
storage: added `source_md5hash` field in `google_storage_bucket_object`
3+
```

google/services/storage/resource_storage_bucket_object.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"os"
2727
"time"
2828

29+
"github.com/hashicorp/go-cty/cty"
2930
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
3031
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
3132

@@ -148,9 +149,16 @@ func ResourceStorageBucketObject() *schema.Resource {
148149
Description: `A path to the data you want to upload. Must be defined if content is not.`,
149150
},
150151

152+
"source_md5hash": {
153+
Type: schema.TypeString,
154+
Optional: true,
155+
Description: `User-provided md5hash, Base 64 MD5 hash of the object data.`,
156+
},
157+
151158
// Detect changes to local file or changes made outside of Terraform to the file stored on the server.
152159
"detect_md5hash": {
153-
Type: schema.TypeString,
160+
Type: schema.TypeString,
161+
Deprecated: "`detect_md5hash` is deprecated and will be removed in future release. Start using `source_md5hash` instead",
154162
// This field is not Computed because it needs to trigger a diff.
155163
Optional: true,
156164
// Makes the diff message nicer:
@@ -163,6 +171,12 @@ func ResourceStorageBucketObject() *schema.Resource {
163171
// 3. Don't suppress the diff iff they don't match
164172
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
165173
localMd5Hash := ""
174+
if d.GetRawConfig().GetAttr("source_md5hash") == cty.UnknownVal(cty.String) {
175+
return true
176+
}
177+
if v, ok := d.GetOk("source_md5hash"); ok && v != "" {
178+
return true
179+
}
166180
if source, ok := d.GetOkExists("source"); ok {
167181
localMd5Hash = tpgresource.GetFileMd5Hash(source.(string))
168182
}
@@ -408,7 +422,7 @@ func resourceStorageBucketObjectUpdate(d *schema.ResourceData, meta interface{})
408422
bucket := d.Get("bucket").(string)
409423
name := d.Get("name").(string)
410424

411-
if d.HasChange("content") || d.HasChange("detect_md5hash") {
425+
if d.HasChange("content") || d.HasChange("source_md5hash") || d.HasChange("detect_md5hash") {
412426
// The KMS key name are not able to be set on create :
413427
// or you get error: Error uploading object test-maarc: googleapi: Error 400: Malformed Cloud KMS crypto key: projects/myproject/locations/myregion/keyRings/mykeyring/cryptoKeys/mykeyname/cryptoKeyVersions/1, invalid
414428
d.Set("kms_key_name", nil)
@@ -496,6 +510,9 @@ func resourceStorageBucketObjectRead(d *schema.ResourceData, meta interface{}) e
496510
if err := d.Set("detect_md5hash", res.Md5Hash); err != nil {
497511
return fmt.Errorf("Error setting detect_md5hash: %s", err)
498512
}
513+
if err := d.Set("source_md5hash", d.Get("source_md5hash")); err != nil {
514+
return fmt.Errorf("Error setting source_md5hash: %s", err)
515+
}
499516
if err := d.Set("generation", res.Generation); err != nil {
500517
return fmt.Errorf("Error setting generation: %s", err)
501518
}
@@ -642,6 +659,11 @@ func flattenObjectRetention(objectRetention *storage.ObjectRetention) []map[stri
642659

643660
func resourceStorageBucketObjectCustomizeDiff(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error {
644661
localMd5Hash := ""
662+
663+
if (d.GetRawConfig().GetAttr("source_md5hash") == cty.UnknownVal(cty.String)) || d.HasChange("source_md5hash") {
664+
return showDiff(d)
665+
}
666+
645667
if source, ok := d.GetOkExists("source"); ok {
646668
localMd5Hash = tpgresource.GetFileMd5Hash(source.(string))
647669
}
@@ -656,7 +678,10 @@ func resourceStorageBucketObjectCustomizeDiff(ctx context.Context, d *schema.Res
656678
if ok && oldMd5Hash == localMd5Hash {
657679
return nil
658680
}
681+
return showDiff(d)
682+
}
659683

684+
func showDiff(d *schema.ResourceDiff) error {
660685
err := d.SetNewComputed("md5hash")
661686
if err != nil {
662687
return fmt.Errorf("Error re-setting md5hash: %s", err)
@@ -669,5 +694,6 @@ func resourceStorageBucketObjectCustomizeDiff(ctx context.Context, d *schema.Res
669694
if err != nil {
670695
return fmt.Errorf("Error re-setting generation: %s", err)
671696
}
697+
672698
return nil
673699
}

google/services/storage/resource_storage_bucket_object_test.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,93 @@ func TestResourceStorageBucketObjectUpdate_ContentChange(t *testing.T) {
525525
})
526526
}
527527

528+
func TestAccStorageObject_sourceMd5Hash(t *testing.T) {
529+
t.Parallel()
530+
531+
bucketName := acctest.TestBucketName(t)
532+
533+
data := []byte("data data data")
534+
535+
writeMd5 := func(data []byte) string {
536+
h := md5.New()
537+
if _, err := h.Write(data); err != nil {
538+
t.Errorf("error calculating md5: %v", err)
539+
}
540+
dataMd5 := base64.StdEncoding.EncodeToString(h.Sum(nil))
541+
return dataMd5
542+
}
543+
544+
dataMd5 := writeMd5(data)
545+
546+
updatedata := []byte("datum")
547+
updatedDataMd5 := writeMd5(updatedata)
548+
549+
testFile := getNewTmpTestFile(t, "tf-test")
550+
if err := ioutil.WriteFile(testFile.Name(), data, 0644); err != nil {
551+
t.Errorf("error writing file: %v", err)
552+
}
553+
554+
updateMd5 := []byte("sample")
555+
newMd5 := writeMd5(updateMd5)
556+
557+
acctest.VcrTest(t, resource.TestCase{
558+
PreCheck: func() { acctest.AccTestPreCheck(t) },
559+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
560+
CheckDestroy: testAccStorageObjectDestroyProducer(t),
561+
Steps: []resource.TestStep{
562+
{
563+
Config: testGoogleStorageBucketsObjectBasic(bucketName, testFile.Name()),
564+
Check: testAccCheckGoogleStorageObject(t, bucketName, objectName, dataMd5),
565+
},
566+
{
567+
PreConfig: func() {
568+
if err := ioutil.WriteFile(testFile.Name(), updatedata, 0644); err != nil {
569+
t.Errorf("error writing file: %v", err)
570+
}
571+
},
572+
Config: testGoogleStorageBucketsObjectFileMd5(bucketName, testFile.Name(), updatedDataMd5),
573+
Check: testAccCheckGoogleStorageObject(t, bucketName, objectName, updatedDataMd5),
574+
},
575+
{
576+
Config: testGoogleStorageBucketsObjectFileMd5(bucketName, testFile.Name(), newMd5),
577+
Check: testAccCheckGoogleStorageObject(t, bucketName, objectName, updatedDataMd5),
578+
},
579+
},
580+
})
581+
}
582+
583+
func TestAccStorageObject_knownAfterApply(t *testing.T) {
584+
t.Parallel()
585+
586+
bucketName := acctest.TestBucketName(t)
587+
destinationFilePath := getNewTmpTestFile(t, "tf-test-apply-")
588+
589+
acctest.VcrTest(t, resource.TestCase{
590+
PreCheck: func() { acctest.AccTestPreCheck(t) },
591+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
592+
CheckDestroy: testAccStorageObjectDestroyProducer(t),
593+
ExternalProviders: map[string]resource.ExternalProvider{
594+
"local": resource.ExternalProvider{
595+
VersionConstraint: "> 2.5.0",
596+
},
597+
},
598+
Steps: []resource.TestStep{
599+
{
600+
Config: testGoogleStorageBucketObject(bucketName, "first", destinationFilePath.Name()),
601+
Check: resource.ComposeTestCheckFunc(
602+
testAccCheckGoogleStorageValidOutput(t),
603+
),
604+
},
605+
{
606+
Config: testGoogleStorageBucketObjectKnownAfterApply(bucketName, "second", destinationFilePath.Name()),
607+
Check: resource.ComposeTestCheckFunc(
608+
testAccCheckGoogleStorageValidOutput(t),
609+
),
610+
},
611+
},
612+
})
613+
}
614+
528615
func testAccCheckGoogleStorageObject(t *testing.T, bucket, object, md5 string) resource.TestCheckFunc {
529616
return testAccCheckGoogleStorageObjectWithEncryption(t, bucket, object, md5, "")
530617
}
@@ -863,3 +950,112 @@ func getNewTmpTestFile(t *testing.T, prefix string) *os.File {
863950
}
864951
return testFile
865952
}
953+
954+
func testGoogleStorageBucketsObjectFileMd5(bucketName, sourceFilename, md5hash string) string {
955+
return fmt.Sprintf(`
956+
resource "google_storage_bucket" "bucket" {
957+
name = "%s"
958+
location = "US"
959+
}
960+
961+
resource "google_storage_bucket_object" "bo_1861894" {
962+
name = "%s"
963+
source_md5hash = "%s"
964+
bucket = google_storage_bucket.bucket.name
965+
source = "%s"
966+
}
967+
`, bucketName, objectName, md5hash, sourceFilename)
968+
}
969+
970+
func testAccCheckGoogleStorageValidOutput(t *testing.T) resource.TestCheckFunc {
971+
return func(s *terraform.State) error {
972+
var root = s.Modules[0]
973+
var outputs, ok = root.Outputs["valid"]
974+
975+
if !ok {
976+
return fmt.Errorf("Error: `valid` output missing")
977+
}
978+
979+
if outputs == nil {
980+
return fmt.Errorf("Terraform output `valid` does not exists")
981+
}
982+
983+
if outputs.Value == false {
984+
return fmt.Errorf("File content is not valid")
985+
}
986+
return nil
987+
}
988+
}
989+
990+
func testGoogleStorageBucketObject(bucketName, content, filename string) string {
991+
return fmt.Sprintf(`
992+
resource "google_storage_bucket" "bucket" {
993+
name = "%s"
994+
location = "US"
995+
}
996+
997+
resource "google_storage_bucket_object" "changing" {
998+
bucket = google_storage_bucket.bucket.name
999+
name = "dynamic"
1000+
content = "%s"
1001+
}
1002+
1003+
resource "local_file" "test" {
1004+
content = jsonencode(google_storage_bucket_object.changing.content)
1005+
filename = "%s"
1006+
}
1007+
1008+
resource "google_storage_bucket_object" "bo" {
1009+
source = local_file.test.filename
1010+
bucket = google_storage_bucket.bucket.name
1011+
name = "test-file-bucket"
1012+
}
1013+
1014+
data "google_storage_bucket_object_content" "bo" {
1015+
bucket = google_storage_bucket_object.bo.bucket
1016+
name = google_storage_bucket_object.bo.name
1017+
depends_on = [google_storage_bucket_object.bo]
1018+
}
1019+
1020+
output "valid" {
1021+
value = nonsensitive(local_file.test.content) == data.google_storage_bucket_object_content.bo.content
1022+
}
1023+
`, bucketName, content, filename)
1024+
}
1025+
1026+
func testGoogleStorageBucketObjectKnownAfterApply(bucketName, content, filename string) string {
1027+
return fmt.Sprintf(`
1028+
resource "google_storage_bucket" "bucket" {
1029+
name = "%s"
1030+
location = "US"
1031+
}
1032+
1033+
resource "google_storage_bucket_object" "changing" {
1034+
bucket = google_storage_bucket.bucket.name
1035+
name = "dynamic"
1036+
content = "%s"
1037+
}
1038+
1039+
resource "local_file" "test" {
1040+
content = jsonencode(google_storage_bucket_object.changing.content)
1041+
filename = "%s"
1042+
}
1043+
1044+
resource "google_storage_bucket_object" "bo" {
1045+
source = local_file.test.filename
1046+
source_md5hash = local_file.test.content_md5
1047+
bucket = google_storage_bucket.bucket.name
1048+
name = "test-file-bucket"
1049+
}
1050+
1051+
data "google_storage_bucket_object_content" "bo" {
1052+
bucket = google_storage_bucket_object.bo.bucket
1053+
name = google_storage_bucket_object.bo.name
1054+
depends_on = [google_storage_bucket_object.bo]
1055+
}
1056+
1057+
output "valid" {
1058+
value = nonsensitive(local_file.test.content) == data.google_storage_bucket_object_content.bo.content
1059+
}
1060+
`, bucketName, content, filename)
1061+
}

website/docs/r/storage_bucket_object.html.markdown

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ One of the following is required:
101101

102102
* `kms_key_name` - (Optional) The resource name of the Cloud KMS key that will be used to [encrypt](https://cloud.google.com/storage/docs/encryption/using-customer-managed-keys) the object.
103103

104+
* `source_md5hash` - (Optional) User-provided md5hash to trigger replacement of object in storage bucket, Must be Base 64 MD5 hash of the object data. The usual way to set this is filemd5("file.zip"), where "file.zip" is the local filename
105+
104106
---
105107

106108
<a name="nested_customer_encryption"></a>The `customer_encryption` block supports:

0 commit comments

Comments
 (0)