@@ -2,12 +2,15 @@ package google
22
33import (
44 "context"
5+ "encoding/json"
56 "fmt"
67 "log"
8+ "strings"
79 "time"
810
911 "cloud.google.com/go/bigtable"
1012 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
13+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure"
1114 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1215)
1316
@@ -56,9 +59,10 @@ func resourceBigtableGCPolicyCustomizeDiff(_ context.Context, d *schema.Resource
5659
5760func resourceBigtableGCPolicy () * schema.Resource {
5861 return & schema.Resource {
59- Create : resourceBigtableGCPolicyCreate ,
62+ Create : resourceBigtableGCPolicyUpsert ,
6063 Read : resourceBigtableGCPolicyRead ,
6164 Delete : resourceBigtableGCPolicyDestroy ,
65+ Update : resourceBigtableGCPolicyUpsert ,
6266 CustomizeDiff : resourceBigtableGCPolicyCustomizeDiff ,
6367
6468 Schema : map [string ]* schema.Schema {
@@ -84,12 +88,24 @@ func resourceBigtableGCPolicy() *schema.Resource {
8488 Description : `The name of the column family.` ,
8589 },
8690
91+ "gc_rules" : {
92+ Type : schema .TypeString ,
93+ Optional : true ,
94+ Description : `Serialized JSON string for garbage collection policy. Conflicts with "mode", "max_age" and "max_version".` ,
95+ ValidateFunc : validation .StringIsJSON ,
96+ ConflictsWith : []string {"mode" , "max_age" , "max_version" },
97+ StateFunc : func (v interface {}) string {
98+ json , _ := structure .NormalizeJsonString (v )
99+ return json
100+ },
101+ },
87102 "mode" : {
88- Type : schema .TypeString ,
89- Optional : true ,
90- ForceNew : true ,
91- Description : `If multiple policies are set, you should choose between UNION OR INTERSECTION.` ,
92- ValidateFunc : validation .StringInSlice ([]string {GCPolicyModeIntersection , GCPolicyModeUnion }, false ),
103+ Type : schema .TypeString ,
104+ Optional : true ,
105+ ForceNew : true ,
106+ Description : `If multiple policies are set, you should choose between UNION OR INTERSECTION.` ,
107+ ValidateFunc : validation .StringInSlice ([]string {GCPolicyModeIntersection , GCPolicyModeUnion }, false ),
108+ ConflictsWith : []string {"gc_rules" },
93109 },
94110
95111 "max_age" : {
@@ -120,6 +136,7 @@ func resourceBigtableGCPolicy() *schema.Resource {
120136 },
121137 },
122138 },
139+ ConflictsWith : []string {"gc_rules" },
123140 },
124141
125142 "max_version" : {
@@ -137,6 +154,7 @@ func resourceBigtableGCPolicy() *schema.Resource {
137154 },
138155 },
139156 },
157+ ConflictsWith : []string {"gc_rules" },
140158 },
141159
142160 "project" : {
@@ -151,7 +169,7 @@ func resourceBigtableGCPolicy() *schema.Resource {
151169 }
152170}
153171
154- func resourceBigtableGCPolicyCreate (d * schema.ResourceData , meta interface {}) error {
172+ func resourceBigtableGCPolicyUpsert (d * schema.ResourceData , meta interface {}) error {
155173 config := meta .(* Config )
156174 userAgent , err := generateUserAgentString (d , config .userAgent )
157175 if err != nil {
@@ -288,13 +306,22 @@ func generateBigtableGCPolicy(d *schema.ResourceData) (bigtable.GCPolicy, error)
288306 mode := d .Get ("mode" ).(string )
289307 ma , aok := d .GetOk ("max_age" )
290308 mv , vok := d .GetOk ("max_version" )
309+ gcRules , gok := d .GetOk ("gc_rules" )
291310
292- if ! aok && ! vok {
311+ if ! aok && ! vok && ! gok {
293312 return bigtable .NoGcPolicy (), nil
294313 }
295314
296315 if mode == "" && aok && vok {
297- return nil , fmt .Errorf ("If multiple policies are set, mode can't be empty" )
316+ return nil , fmt .Errorf ("if multiple policies are set, mode can't be empty" )
317+ }
318+
319+ if gok {
320+ var j map [string ]interface {}
321+ if err := json .Unmarshal ([]byte (gcRules .(string )), & j ); err != nil {
322+ return nil , err
323+ }
324+ return getGCPolicyFromJSON (j )
298325 }
299326
300327 if aok {
@@ -324,6 +351,100 @@ func generateBigtableGCPolicy(d *schema.ResourceData) (bigtable.GCPolicy, error)
324351 return policies [0 ], nil
325352}
326353
354+ func getGCPolicyFromJSON (topLevelPolicy map [string ]interface {}) (bigtable.GCPolicy , error ) {
355+ policy := []bigtable.GCPolicy {}
356+
357+ if err := validateNestedPolicy (topLevelPolicy , true ); err != nil {
358+ return nil , err
359+ }
360+
361+ for _ , p := range topLevelPolicy ["rules" ].([]interface {}) {
362+ childPolicy := p .(map [string ]interface {})
363+ if err := validateNestedPolicy (childPolicy , false ); err != nil {
364+ return nil , err
365+ }
366+
367+ if childPolicy ["max_age" ] != nil {
368+ maxAge := childPolicy ["max_age" ].(string )
369+ duration , err := time .ParseDuration (maxAge )
370+ if err != nil {
371+ return nil , fmt .Errorf ("invalid duration string: %v" , maxAge )
372+ }
373+ policy = append (policy , bigtable .MaxAgePolicy (duration ))
374+ }
375+
376+ if childPolicy ["max_version" ] != nil {
377+ version := childPolicy ["max_version" ].(float64 )
378+ policy = append (policy , bigtable .MaxVersionsPolicy (int (version )))
379+ }
380+
381+ if childPolicy ["mode" ] != nil {
382+ n , err := getGCPolicyFromJSON (childPolicy )
383+ if err != nil {
384+ return nil , err
385+ }
386+ policy = append (policy , n )
387+ }
388+ }
389+
390+ switch topLevelPolicy ["mode" ] {
391+ case strings .ToLower (GCPolicyModeUnion ):
392+ return bigtable .UnionPolicy (policy ... ), nil
393+ case strings .ToLower (GCPolicyModeIntersection ):
394+ return bigtable .IntersectionPolicy (policy ... ), nil
395+ default :
396+ return policy [0 ], nil
397+ }
398+ }
399+
400+ func validateNestedPolicy (p map [string ]interface {}, topLevel bool ) error {
401+ if len (p ) > 2 {
402+ return fmt .Errorf ("rules has more than 2 fields" )
403+ }
404+ maxVersion , maxVersionOk := p ["max_version" ]
405+ maxAge , maxAgeOk := p ["max_age" ]
406+ rulesObj , rulesOk := p ["rules" ]
407+
408+ _ , modeOk := p ["mode" ]
409+ rules , arrOk := rulesObj .([]interface {})
410+ _ , vCastOk := maxVersion .(float64 )
411+ _ , aCastOk := maxAge .(string )
412+
413+ if rulesOk && ! arrOk {
414+ return fmt .Errorf ("`rules` must be array" )
415+ }
416+
417+ if modeOk && len (rules ) < 2 {
418+ return fmt .Errorf ("`rules` need at least 2 GC rule when mode is specified" )
419+ }
420+
421+ if topLevel && ! rulesOk {
422+ return fmt .Errorf ("invalid nested policy, need `rules`" )
423+ }
424+
425+ if topLevel && ! modeOk && len (rules ) != 1 {
426+ return fmt .Errorf ("when `mode` is not specified, `rules` can only have 1 child rule" )
427+ }
428+
429+ if ! topLevel && len (p ) == 2 && (! modeOk || ! rulesOk ) {
430+ return fmt .Errorf ("need `mode` and `rules` for child nested policies" )
431+ }
432+
433+ if ! topLevel && len (p ) == 1 && ! maxVersionOk && ! maxAgeOk {
434+ return fmt .Errorf ("need `max_version` or `max_age` for the rule" )
435+ }
436+
437+ if maxVersionOk && ! vCastOk {
438+ return fmt .Errorf ("`max_version` must be a number" )
439+ }
440+
441+ if maxAgeOk && ! aCastOk {
442+ return fmt .Errorf ("`max_age must be a string" )
443+ }
444+
445+ return nil
446+ }
447+
327448func getMaxAgeDuration (values map [string ]interface {}) (time.Duration , error ) {
328449 d := values ["duration" ].(string )
329450 if d != "" {
0 commit comments