Skip to content

Commit 217501f

Browse files
feat: (storage) added contexts for resource google_storage_bucket_object (#15693)
1 parent 5af0660 commit 217501f

File tree

3 files changed

+330
-6
lines changed

3 files changed

+330
-6
lines changed

mmv1/third_party/terraform/services/storage/resource_storage_bucket_object.go

Lines changed: 169 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
1515
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
1616

17+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
1718
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1819
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1920

@@ -28,11 +29,14 @@ import (
2829

2930
func ResourceStorageBucketObject() *schema.Resource {
3031
return &schema.Resource{
31-
Create: resourceStorageBucketObjectCreate,
32-
Read: resourceStorageBucketObjectRead,
33-
Update: resourceStorageBucketObjectUpdate,
34-
Delete: resourceStorageBucketObjectDelete,
35-
CustomizeDiff: resourceStorageBucketObjectCustomizeDiff,
32+
Create: resourceStorageBucketObjectCreate,
33+
Read: resourceStorageBucketObjectRead,
34+
Update: resourceStorageBucketObjectUpdate,
35+
Delete: resourceStorageBucketObjectDelete,
36+
CustomizeDiff: customdiff.All(
37+
resourceStorageBucketObjectCustomizeDiff,
38+
validateContexts,
39+
),
3640

3741
Timeouts: &schema.ResourceTimeout{
3842
Create: schema.DefaultTimeout(4 * time.Minute),
@@ -109,6 +113,46 @@ func ResourceStorageBucketObject() *schema.Resource {
109113
Description: `Data as string to be uploaded. Must be defined if source is not. Note: The content field is marked as sensitive. To view the raw contents of the object, please define an output.`,
110114
},
111115

116+
"contexts": {
117+
Type: schema.TypeList,
118+
Optional: true,
119+
MaxItems: 1,
120+
Description: "Contexts attached to an object, in key-value pairs.",
121+
Elem: &schema.Resource{
122+
Schema: map[string]*schema.Schema{
123+
"custom": {
124+
Type: schema.TypeList,
125+
Required: true,
126+
Description: "A list of custom context key-value pairs.",
127+
Elem: &schema.Resource{
128+
Schema: map[string]*schema.Schema{
129+
"key": {
130+
Type: schema.TypeString,
131+
Required: true,
132+
Description: "An individual object context. Context keys and their corresponding values must start with an alphanumeric character.",
133+
},
134+
"value": {
135+
Type: schema.TypeString,
136+
Required: true,
137+
Description: "The value associated with this context. This field holds the primary information for the given context key.",
138+
},
139+
"create_time": {
140+
Type: schema.TypeString,
141+
Computed: true,
142+
Description: "The time when context was first added to the storage#object in RFC 3339 format.",
143+
},
144+
"update_time": {
145+
Type: schema.TypeString,
146+
Computed: true,
147+
Description: "The time when context was last updated in RFC 3339 format.",
148+
},
149+
},
150+
},
151+
},
152+
},
153+
},
154+
},
155+
112156
"generation": {
113157
Type: schema.TypeInt,
114158
Computed: true,
@@ -393,6 +437,10 @@ func resourceStorageBucketObjectCreate(d *schema.ResourceData, meta interface{})
393437
object.TemporaryHold = v.(bool)
394438
}
395439

440+
if v, ok := d.GetOk("contexts"); ok {
441+
object.Contexts = expandCustomObjectContexts(v.([]interface{}))
442+
}
443+
396444
insertCall := objectsService.Insert(bucket, object)
397445
insertCall.Name(name)
398446
if v, ok := d.GetOk("force_empty_content_type"); ok && v.(bool) {
@@ -461,6 +509,11 @@ func resourceStorageBucketObjectUpdate(d *schema.ResourceData, meta interface{})
461509
res.TemporaryHold = v.(bool)
462510
}
463511

512+
if d.HasChange("contexts") {
513+
v := d.Get("contexts")
514+
res.Contexts = expandCustomObjectContexts(v.([]interface{}))
515+
}
516+
464517
updateCall := objectsService.Update(bucket, name, res)
465518
if hasRetentionChanges {
466519
updateCall.OverrideUnlockedRetention(true)
@@ -471,7 +524,7 @@ func resourceStorageBucketObjectUpdate(d *schema.ResourceData, meta interface{})
471524
return fmt.Errorf("Error updating object %s: %s", name, err)
472525
}
473526

474-
return nil
527+
return resourceStorageBucketObjectRead(d, meta)
475528
}
476529
}
477530

@@ -565,6 +618,9 @@ func resourceStorageBucketObjectRead(d *schema.ResourceData, meta interface{}) e
565618
if err := d.Set("temporary_hold", res.TemporaryHold); err != nil {
566619
return fmt.Errorf("Error setting temporary_hold: %s", err)
567620
}
621+
if err := d.Set("contexts", flattenContexts(d, res.Contexts)); err != nil {
622+
return fmt.Errorf("Error reading Contexts: %s", err)
623+
}
568624

569625
d.SetId(objectGetID(res))
570626

@@ -651,6 +707,35 @@ func expandObjectRetention(configured interface{}) *storage.ObjectRetention {
651707
return objectRetention
652708
}
653709

710+
func expandCustomObjectContexts(objectContexts []interface{}) *storage.ObjectContexts {
711+
if len(objectContexts) == 0 {
712+
return nil
713+
}
714+
715+
contextsObj := objectContexts[0].(map[string]interface{})
716+
717+
customList := contextsObj["custom"]
718+
719+
tfCustomList := customList.([]interface{})
720+
721+
objContextPayload := make(map[string]storage.ObjectCustomContextPayload)
722+
for _, item := range tfCustomList {
723+
itemMap := item.(map[string]interface{})
724+
725+
key := itemMap["key"].(string)
726+
value := itemMap["value"].(string)
727+
728+
objContextPayload[key] = storage.ObjectCustomContextPayload{
729+
Value: value,
730+
}
731+
}
732+
733+
contexts := &storage.ObjectContexts{
734+
Custom: objContextPayload,
735+
}
736+
return contexts
737+
}
738+
654739
func flattenObjectRetention(objectRetention *storage.ObjectRetention) []map[string]interface{} {
655740
retentions := make([]map[string]interface{}, 0, 1)
656741

@@ -667,6 +752,51 @@ func flattenObjectRetention(objectRetention *storage.ObjectRetention) []map[stri
667752
return retentions
668753
}
669754

755+
func flattenContexts(d *schema.ResourceData, contexts *storage.ObjectContexts) interface{} {
756+
if contexts == nil || contexts.Custom == nil || len(contexts.Custom) == 0 {
757+
return nil
758+
}
759+
c, _ := d.GetOk("contexts")
760+
contextsList := c.([]interface{})
761+
if len(contextsList) == 0 {
762+
return nil
763+
}
764+
765+
contextsObj := contextsList[0].(map[string]interface{})
766+
767+
customObjectList := contextsObj["custom"]
768+
769+
customkeyValueList := customObjectList.([]interface{})
770+
771+
flattenKeyValueList := make([]interface{}, 0, len(contexts.Custom))
772+
for _, customKv := range customkeyValueList {
773+
customKvItem := customKv.(map[string]interface{})
774+
775+
k := customKvItem["key"].(string)
776+
customItem, ok := contexts.Custom[k]
777+
if !ok {
778+
continue
779+
} else {
780+
itemMap := make(map[string]interface{})
781+
782+
itemMap["key"] = k
783+
itemMap["value"] = customItem.Value
784+
itemMap["create_time"] = customItem.CreateTime
785+
itemMap["update_time"] = customItem.UpdateTime
786+
787+
flattenKeyValueList = append(flattenKeyValueList, itemMap)
788+
}
789+
}
790+
791+
customList := map[string]interface{}{
792+
"custom": flattenKeyValueList,
793+
}
794+
795+
contextList := []interface{}{customList}
796+
797+
return contextList
798+
}
799+
670800
func resourceStorageBucketObjectCustomizeDiff(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error {
671801
localMd5Hash := ""
672802

@@ -707,3 +837,36 @@ func showDiff(d *schema.ResourceDiff) error {
707837

708838
return nil
709839
}
840+
841+
// validate keys for duplicates in custom object contexts
842+
func validateContexts(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error {
843+
if !d.HasChange("contexts") {
844+
return nil
845+
}
846+
847+
_, new := d.GetChange("contexts")
848+
849+
contextsList := new.([]interface{})
850+
if len(contextsList) == 0 {
851+
return nil
852+
}
853+
854+
contextsObj := contextsList[0].(map[string]interface{})
855+
856+
customList := contextsObj["custom"]
857+
858+
keyValueList := customList.([]interface{})
859+
860+
keys := make(map[string]bool)
861+
for _, item := range keyValueList {
862+
itemMap := item.(map[string]interface{})
863+
864+
key := itemMap["key"].(string)
865+
866+
if keys[key] {
867+
return fmt.Errorf("duplicate key found in 'contexts' block: %s. Each 'key' must be unique", key)
868+
}
869+
keys[key] = true
870+
}
871+
return nil
872+
}

mmv1/third_party/terraform/services/storage/resource_storage_bucket_object_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,47 @@ func TestAccStorageObject_objectDeletionPolicy(t *testing.T) {
593593
})
594594
}
595595

596+
func TestAccStorageObject_addUpdateObjectContexts(t *testing.T) {
597+
t.Parallel()
598+
599+
bucketName := acctest.TestBucketName(t)
600+
601+
acctest.VcrTest(t, resource.TestCase{
602+
PreCheck: func() { acctest.AccTestPreCheck(t) },
603+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
604+
CheckDestroy: testAccStorageObjectDestroyProducer(t),
605+
Steps: []resource.TestStep{
606+
{
607+
Config: testGoogleStorageBucketsObjectContent(bucketName),
608+
},
609+
{
610+
Config: testGoogleStorageBucketsObjectContexts(bucketName, "testKey", "value"),
611+
Check: resource.ComposeTestCheckFunc(
612+
testAccCheckStorageObjectContexts(t, bucketName, "testKey"),
613+
),
614+
},
615+
{
616+
Config: testGoogleStorageBucketsObjectContexts(bucketName, "testKey1", "value1"),
617+
Check: resource.ComposeTestCheckFunc(
618+
testAccCheckStorageObjectContexts(t, bucketName, "testKey"),
619+
),
620+
},
621+
{
622+
Config: testGoogleStorageBucketsMultipleObjectContexts(bucketName),
623+
Check: resource.ComposeTestCheckFunc(
624+
testAccCheckStorageObjectContextsExists(t, bucketName, 2),
625+
),
626+
},
627+
{
628+
Config: testGoogleStorageBucketsObjectContent(bucketName),
629+
Check: resource.ComposeTestCheckFunc(
630+
testAccCheckStorageObjectContextsExists(t, bucketName, 0),
631+
),
632+
},
633+
},
634+
})
635+
}
636+
596637
func testAccCheckGoogleStorageObjectCrc32cHash(t *testing.T, bucket, object, crc32 string) resource.TestCheckFunc {
597638
return testAccCheckGoogleStorageObjectCrc32cWithEncryption(t, bucket, object, crc32, "")
598639
}
@@ -1097,3 +1138,87 @@ func testAccCheckStorageObjectExists(t *testing.T, bucketName string) resource.T
10971138
return nil
10981139
}
10991140
}
1141+
1142+
func testAccCheckStorageObjectContextsExists(t *testing.T, bucketName string, customKeys int) resource.TestCheckFunc {
1143+
return func(s *terraform.State) error {
1144+
1145+
config := acctest.GoogleProviderConfig(t)
1146+
1147+
res, err := config.NewStorageClient(config.UserAgent).Objects.Get(bucketName, objectName).Do()
1148+
if err != nil {
1149+
return err
1150+
}
1151+
1152+
if res.Contexts != nil && len(res.Contexts.Custom) == customKeys {
1153+
return nil
1154+
}
1155+
return nil
1156+
}
1157+
}
1158+
1159+
func testAccCheckStorageObjectContexts(t *testing.T, bucketName, customKey string) resource.TestCheckFunc {
1160+
return func(s *terraform.State) error {
1161+
config := acctest.GoogleProviderConfig(t)
1162+
1163+
res, err := config.NewStorageClient(config.UserAgent).Objects.Get(bucketName, objectName).Do()
1164+
if err != nil {
1165+
return err
1166+
}
1167+
1168+
if res.Contexts != nil && len(res.Contexts.Custom) > 0 {
1169+
kvPairs := res.Contexts.Custom
1170+
for k := range kvPairs {
1171+
if k == customKey {
1172+
return nil
1173+
}
1174+
}
1175+
}
1176+
return nil
1177+
}
1178+
}
1179+
1180+
func testGoogleStorageBucketsObjectContexts(bucketName string, key, value string) string {
1181+
return fmt.Sprintf(`
1182+
resource "google_storage_bucket" "bucket" {
1183+
name = "%s"
1184+
location = "US"
1185+
}
1186+
1187+
resource "google_storage_bucket_object" "object" {
1188+
name = "%s"
1189+
bucket = google_storage_bucket.bucket.name
1190+
content = "%s"
1191+
contexts {
1192+
custom{
1193+
key="%s"
1194+
value="%s"
1195+
}
1196+
}
1197+
}
1198+
`, bucketName, objectName, content, key, value)
1199+
}
1200+
1201+
func testGoogleStorageBucketsMultipleObjectContexts(bucketName string) string {
1202+
return fmt.Sprintf(`
1203+
resource "google_storage_bucket" "bucket" {
1204+
name = "%s"
1205+
location = "US"
1206+
}
1207+
1208+
resource "google_storage_bucket_object" "object" {
1209+
name = "%s"
1210+
bucket = google_storage_bucket.bucket.name
1211+
content = "%s"
1212+
contexts {
1213+
custom {
1214+
key="cloud"
1215+
value="gcp"
1216+
}
1217+
custom {
1218+
key="backup"
1219+
value="storage"
1220+
}
1221+
}
1222+
}
1223+
`, bucketName, objectName, content)
1224+
}

0 commit comments

Comments
 (0)