From 725fd55f70082315e4d1e034a39b7a60265e8616 Mon Sep 17 00:00:00 2001 From: Julien Date: Fri, 27 Sep 2019 15:08:22 -0400 Subject: [PATCH 1/6] Can create a flag with string variations --- go.mod | 2 + launchdarkly/data_source_feature_flag.go | 20 ++++++++ launchdarkly/protocol.go | 6 +++ launchdarkly/resource_feature_flag.go | 60 ++++++++++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/go.mod b/go.mod index 12053ac..7d9cfbc 100644 --- a/go.mod +++ b/go.mod @@ -50,3 +50,5 @@ require ( google.golang.org/genproto v0.0.0-20180718234121-fedd2861243f google.golang.org/grpc v1.13.0 ) + +go 1.13 diff --git a/launchdarkly/data_source_feature_flag.go b/launchdarkly/data_source_feature_flag.go index 6ff8a94..7fa6c39 100644 --- a/launchdarkly/data_source_feature_flag.go +++ b/launchdarkly/data_source_feature_flag.go @@ -34,6 +34,26 @@ func dataSourceFeatureFlag() *schema.Resource { Type: schema.TypeBool, Computed: true, }, + "variations": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, "tags": { Type: schema.TypeList, Computed: true, diff --git a/launchdarkly/protocol.go b/launchdarkly/protocol.go index 7ec3061..a831a27 100644 --- a/launchdarkly/protocol.go +++ b/launchdarkly/protocol.go @@ -14,6 +14,11 @@ type JsonProject struct { Environments []JsonEnvironment `json:"environments"` } +type JsonVariations struct { + Value string `json:"value"` + Name string `json:"name"` +} + type JsonCustomProperty struct { Name string `json:"name"` Value []string `json:"value"` @@ -25,6 +30,7 @@ type JsonFeatureFlag struct { Description string `json:"description"` Temporary bool `json:"temporary"` IncludeInSnippet bool `json:"includeInSnippet"` + Variations []JsonVariations `json:"variations"` Tags []string `json:"tags"` CustomProperties map[string]JsonCustomProperty `json:"customProperties"` } diff --git a/launchdarkly/resource_feature_flag.go b/launchdarkly/resource_feature_flag.go index 99faf7b..c8f7372 100644 --- a/launchdarkly/resource_feature_flag.go +++ b/launchdarkly/resource_feature_flag.go @@ -43,6 +43,22 @@ func resourceFeatureFlag() *schema.Resource { Optional: true, Default: false, }, + "variations": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, "tags": { Type: schema.TypeList, Optional: true, @@ -87,8 +103,11 @@ func resourceFeatureFlagCreate(d *schema.ResourceData, m interface{}) error { temporary := d.Get("temporary").(bool) includeInSnippet := d.Get("include_in_snippet").(bool) tags := d.Get("tags").([]interface{}) + variations := d.Get("variations").([]interface{}) customProperties := d.Get("custom_properties").([]interface{}) + transformedVariations := transformVariationsFromTerraformFormat(variations) + transformedCustomProperties, err := transformCustomPropertiesFromTerraformFormat(customProperties) if err != nil { return err @@ -101,6 +120,7 @@ func resourceFeatureFlagCreate(d *schema.ResourceData, m interface{}) error { Temporary: temporary, IncludeInSnippet: includeInSnippet, Tags: transformTagsFromTerraformFormat(tags), + Variations: transformedVariations, CustomProperties: transformedCustomProperties, } @@ -117,6 +137,7 @@ func resourceFeatureFlagCreate(d *schema.ResourceData, m interface{}) error { d.Set("temporary", temporary) d.Set("include_in_snippet", includeInSnippet) d.Set("tags", tags) + d.Set("variations", variations) d.Set("custom_properties", customProperties) return nil @@ -144,6 +165,7 @@ func resourceFeatureFlagRead(d *schema.ResourceData, m interface{}) error { d.Set("temporary", response.Temporary) d.Set("include_in_snippet", response.IncludeInSnippet) d.Set("tags", response.Tags) + d.Set("variations", response.Variations) d.Set("custom_properties", transformedCustomProperties) return nil @@ -158,8 +180,11 @@ func resourceFeatureFlagUpdate(d *schema.ResourceData, m interface{}) error { temporary := d.Get("temporary").(bool) includeInSnippet := d.Get("include_in_snippet").(bool) tags := d.Get("tags").([]interface{}) + variations := d.Get("variations").([]interface{}) customProperties := d.Get("custom_properties").([]interface{}) + transformVariations := transformVariationsFromTerraformFormat(variations) + transformedCustomProperties, err := transformCustomPropertiesFromTerraformFormat(customProperties) if err != nil { return err @@ -185,6 +210,10 @@ func resourceFeatureFlagUpdate(d *schema.ResourceData, m interface{}) error { "op": "replace", "path": "/tags", "value": transformTagsFromTerraformFormat(tags), + }, { + "op": "replace", + "path": "/tags", + "value": transformVariations, }, { "op": "replace", "path": "/customProperties", @@ -222,6 +251,37 @@ func transformTagsFromTerraformFormat(tags []interface{}) []string { return transformed } +func transformVariationsFromTerraformFormat(variations []interface{}) []JsonVariations { + /*transformed := make([]JsonVariations, 0) + + for index, variation := range variations { + json := variation.(map[string]interface{}) + + value := json["value"].(string) + name := json["name"].(string) + + potate := JsonVariations{ + Name: name, + Value: value, + } + transformed[index] = potate + } + return transformed*/ + + transformed := make([]JsonVariations, len(variations)) + for index, raw := range variations { + rawshit := raw.(map[string]interface{}) + name := rawshit["name"].(string) + value := rawshit["value"].(string) + + transformed[index] = JsonVariations{ + Name: name, + Value: value, + } + } + return transformed +} + func transformCustomPropertiesFromTerraformFormat(properties []interface{}) (map[string]JsonCustomProperty, error) { transformed := make(map[string]JsonCustomProperty) From 284b8aabe8bd416196252a782d9082d1faabde03 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 2 Oct 2019 10:43:08 -0400 Subject: [PATCH 2/6] Can now create and update flag variations cleanly --- launchdarkly/protocol.go | 5 +- launchdarkly/resource_feature_flag.go | 78 +++++++++++++++++---------- launchdarkly/validations.go | 19 +++++++ 3 files changed, 73 insertions(+), 29 deletions(-) diff --git a/launchdarkly/protocol.go b/launchdarkly/protocol.go index a831a27..19e934a 100644 --- a/launchdarkly/protocol.go +++ b/launchdarkly/protocol.go @@ -15,8 +15,9 @@ type JsonProject struct { } type JsonVariations struct { - Value string `json:"value"` - Name string `json:"name"` + Value interface{} `json:"value"` + Name string `json:"name"` + Description string `json:"description"` } type JsonCustomProperty struct { diff --git a/launchdarkly/resource_feature_flag.go b/launchdarkly/resource_feature_flag.go index c8f7372..46298d1 100644 --- a/launchdarkly/resource_feature_flag.go +++ b/launchdarkly/resource_feature_flag.go @@ -1,6 +1,8 @@ package launchdarkly import ( + "strconv" + "github.com/hashicorp/terraform/helper/schema" ) @@ -43,9 +45,18 @@ func resourceFeatureFlag() *schema.Resource { Optional: true, Default: false, }, + "variations_kind": { + Type: schema.TypeString, + Optional: true, + Default: "boolean", + ValidateFunc: validateFeatureFlagVariationsType, + ForceNew: true, + }, "variations": { Type: schema.TypeList, Optional: true, + MinItems: 2, + ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "value": { @@ -56,6 +67,10 @@ func resourceFeatureFlag() *schema.Resource { Type: schema.TypeString, Required: true, }, + "description": { + Type: schema.TypeString, + Optional: true, + }, }, }, }, @@ -103,10 +118,14 @@ func resourceFeatureFlagCreate(d *schema.ResourceData, m interface{}) error { temporary := d.Get("temporary").(bool) includeInSnippet := d.Get("include_in_snippet").(bool) tags := d.Get("tags").([]interface{}) + variationsKind := d.Get("variations_kind").(string) variations := d.Get("variations").([]interface{}) customProperties := d.Get("custom_properties").([]interface{}) - transformedVariations := transformVariationsFromTerraformFormat(variations) + transformedVariations, err := transformVariationsFromTerraformFormat(variations, variationsKind) + if err != nil { + return err + } transformedCustomProperties, err := transformCustomPropertiesFromTerraformFormat(customProperties) if err != nil { @@ -180,11 +199,8 @@ func resourceFeatureFlagUpdate(d *schema.ResourceData, m interface{}) error { temporary := d.Get("temporary").(bool) includeInSnippet := d.Get("include_in_snippet").(bool) tags := d.Get("tags").([]interface{}) - variations := d.Get("variations").([]interface{}) customProperties := d.Get("custom_properties").([]interface{}) - transformVariations := transformVariationsFromTerraformFormat(variations) - transformedCustomProperties, err := transformCustomPropertiesFromTerraformFormat(customProperties) if err != nil { return err @@ -210,10 +226,6 @@ func resourceFeatureFlagUpdate(d *schema.ResourceData, m interface{}) error { "op": "replace", "path": "/tags", "value": transformTagsFromTerraformFormat(tags), - }, { - "op": "replace", - "path": "/tags", - "value": transformVariations, }, { "op": "replace", "path": "/customProperties", @@ -251,35 +263,47 @@ func transformTagsFromTerraformFormat(tags []interface{}) []string { return transformed } -func transformVariationsFromTerraformFormat(variations []interface{}) []JsonVariations { - /*transformed := make([]JsonVariations, 0) - - for index, variation := range variations { - json := variation.(map[string]interface{}) - - value := json["value"].(string) - name := json["name"].(string) - - potate := JsonVariations{ - Name: name, - Value: value, - } - transformed[index] = potate +func transformVariationsFromTerraformFormat(variations []interface{}, variationsKind string) ([]JsonVariations, error) { + transformed, err := setVariations(variations, variationsKind) + if err != nil { + return nil, err } - return transformed*/ + return transformed, nil +} + +func setVariations(variations []interface{}, variationsKind string) ([]JsonVariations, error) { transformed := make([]JsonVariations, len(variations)) for index, raw := range variations { rawshit := raw.(map[string]interface{}) + var value interface{} name := rawshit["name"].(string) - value := rawshit["value"].(string) + description := rawshit["description"].(string) + + if variationsKind == "string" { + value = rawshit["value"].(string) + } else if variationsKind == "number" { + convertedNumberValue, err := strconv.Atoi(rawshit["value"].(string)) + if err != nil { + return nil, err + } + value = convertedNumberValue + } else if variationsKind == "boolean" { + convertedBooleanValue, err := strconv.ParseBool(rawshit["value"].(string)) + if err != nil { + return nil, err + } + value = convertedBooleanValue + } transformed[index] = JsonVariations{ - Name: name, - Value: value, + Name: name, + Value: value, + Description: description, } } - return transformed + + return transformed, nil } func transformCustomPropertiesFromTerraformFormat(properties []interface{}) (map[string]JsonCustomProperty, error) { diff --git a/launchdarkly/validations.go b/launchdarkly/validations.go index 4129f06..de22aeb 100644 --- a/launchdarkly/validations.go +++ b/launchdarkly/validations.go @@ -6,6 +6,9 @@ import ( "regexp" ) +var supportedMultiVariationsType = [2]string{"number", "string"} +var supportedVariationsType = [3]string{"number", "string", "boolean"} + func validateKey(v interface{}, k string) ([]string, []error) { value := v.(string) @@ -42,6 +45,22 @@ func validateFeatureFlagKey(v interface{}, k string) ([]string, []error) { return nil, nil } +func validateFeatureFlagVariationsType(v interface{}, k string) ([]string, []error) { + value, ok := v.(string) + + if !ok { + return nil, []error{errors.New(fmt.Sprintf("expected %s to be string", k))} + } + + for _, validVariationsType := range supportedVariationsType { + if value == validVariationsType { + return nil, nil + } + } + + return nil, []error{errors.New(fmt.Sprintf("expected %s to be one of %v, got %s", k, []string{"number", "boolean", "string"}, value))} +} + func validateColor(v interface{}, k string) ([]string, []error) { value := v.(string) From d6da88b86514bdface352dc19e7f4fad933ad985 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 2 Oct 2019 11:28:58 -0400 Subject: [PATCH 3/6] Self review --- launchdarkly/data_source_feature_flag.go | 20 ---------------- launchdarkly/resource_feature_flag.go | 30 ++++++++---------------- launchdarkly/validations.go | 1 - 3 files changed, 10 insertions(+), 41 deletions(-) diff --git a/launchdarkly/data_source_feature_flag.go b/launchdarkly/data_source_feature_flag.go index 7fa6c39..6ff8a94 100644 --- a/launchdarkly/data_source_feature_flag.go +++ b/launchdarkly/data_source_feature_flag.go @@ -34,26 +34,6 @@ func dataSourceFeatureFlag() *schema.Resource { Type: schema.TypeBool, Computed: true, }, - "variations": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "value": { - Type: schema.TypeString, - Computed: true, - }, - "name": { - Type: schema.TypeString, - Computed: true, - }, - "description": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, "tags": { Type: schema.TypeList, Computed: true, diff --git a/launchdarkly/resource_feature_flag.go b/launchdarkly/resource_feature_flag.go index 46298d1..955be00 100644 --- a/launchdarkly/resource_feature_flag.go +++ b/launchdarkly/resource_feature_flag.go @@ -184,7 +184,6 @@ func resourceFeatureFlagRead(d *schema.ResourceData, m interface{}) error { d.Set("temporary", response.Temporary) d.Set("include_in_snippet", response.IncludeInSnippet) d.Set("tags", response.Tags) - d.Set("variations", response.Variations) d.Set("custom_properties", transformedCustomProperties) return nil @@ -264,46 +263,37 @@ func transformTagsFromTerraformFormat(tags []interface{}) []string { } func transformVariationsFromTerraformFormat(variations []interface{}, variationsKind string) ([]JsonVariations, error) { - transformed, err := setVariations(variations, variationsKind) - if err != nil { - return nil, err - } - - return transformed, nil -} - -func setVariations(variations []interface{}, variationsKind string) ([]JsonVariations, error) { - transformed := make([]JsonVariations, len(variations)) - for index, raw := range variations { - rawshit := raw.(map[string]interface{}) + transformedVariations := make([]JsonVariations, len(variations)) + for index, rawVariationValue := range variations { + variation := rawVariationValue.(map[string]interface{}) var value interface{} - name := rawshit["name"].(string) - description := rawshit["description"].(string) + name := variation["name"].(string) + description := variation["description"].(string) if variationsKind == "string" { - value = rawshit["value"].(string) + value = variation["value"].(string) } else if variationsKind == "number" { - convertedNumberValue, err := strconv.Atoi(rawshit["value"].(string)) + convertedNumberValue, err := strconv.Atoi(variation["value"].(string)) if err != nil { return nil, err } value = convertedNumberValue } else if variationsKind == "boolean" { - convertedBooleanValue, err := strconv.ParseBool(rawshit["value"].(string)) + convertedBooleanValue, err := strconv.ParseBool(variation["value"].(string)) if err != nil { return nil, err } value = convertedBooleanValue } - transformed[index] = JsonVariations{ + transformedVariations[index] = JsonVariations{ Name: name, Value: value, Description: description, } } - return transformed, nil + return transformedVariations, nil } func transformCustomPropertiesFromTerraformFormat(properties []interface{}) (map[string]JsonCustomProperty, error) { diff --git a/launchdarkly/validations.go b/launchdarkly/validations.go index de22aeb..254cb9c 100644 --- a/launchdarkly/validations.go +++ b/launchdarkly/validations.go @@ -6,7 +6,6 @@ import ( "regexp" ) -var supportedMultiVariationsType = [2]string{"number", "string"} var supportedVariationsType = [3]string{"number", "string", "boolean"} func validateKey(v interface{}, k string) ([]string, []error) { From 3a24acd27549c3e6031a40495ff3762a29c13846 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 2 Oct 2019 13:58:01 -0400 Subject: [PATCH 4/6] Make variation name optional --- launchdarkly/resource_feature_flag.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchdarkly/resource_feature_flag.go b/launchdarkly/resource_feature_flag.go index 955be00..75f01b9 100644 --- a/launchdarkly/resource_feature_flag.go +++ b/launchdarkly/resource_feature_flag.go @@ -65,7 +65,7 @@ func resourceFeatureFlag() *schema.Resource { }, "name": { Type: schema.TypeString, - Required: true, + Optional: true, }, "description": { Type: schema.TypeString, From aeffe0ba5be753e45dacb3c80f6027d4ac27a839 Mon Sep 17 00:00:00 2001 From: Julien Date: Thu, 3 Oct 2019 10:30:47 -0400 Subject: [PATCH 5/6] Add const value and add validation that variation.value is not an empty string --- launchdarkly/resource_feature_flag.go | 31 +++++++++++++++++---------- launchdarkly/validations.go | 18 ++++++++++++++-- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/launchdarkly/resource_feature_flag.go b/launchdarkly/resource_feature_flag.go index 75f01b9..32a1bee 100644 --- a/launchdarkly/resource_feature_flag.go +++ b/launchdarkly/resource_feature_flag.go @@ -6,6 +6,14 @@ import ( "github.com/hashicorp/terraform/helper/schema" ) +const DEFAULT_VARIATIONS_KIND = "boolean" +const VARIATION_NAME_KEY = "name" +const VARIATION_DESCRIPTION_KEY = "description" +const VARIATION_VALUE_KEY = "value" +const VARIATIONS_STRING_KIND = "string" +const VARIATIONS_NUMBER_KIND = "number" +const VARIATIONS_BOOLEAN_KIND = "boolean" + func resourceFeatureFlag() *schema.Resource { return &schema.Resource{ Create: resourceFeatureFlagCreate, @@ -48,7 +56,7 @@ func resourceFeatureFlag() *schema.Resource { "variations_kind": { Type: schema.TypeString, Optional: true, - Default: "boolean", + Default: DEFAULT_VARIATIONS_KIND, ValidateFunc: validateFeatureFlagVariationsType, ForceNew: true, }, @@ -60,8 +68,9 @@ func resourceFeatureFlag() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "value": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validateVariationValue, }, "name": { Type: schema.TypeString, @@ -267,19 +276,19 @@ func transformVariationsFromTerraformFormat(variations []interface{}, variations for index, rawVariationValue := range variations { variation := rawVariationValue.(map[string]interface{}) var value interface{} - name := variation["name"].(string) - description := variation["description"].(string) + name := variation[VARIATION_NAME_KEY].(string) + description := variation[VARIATION_DESCRIPTION_KEY].(string) - if variationsKind == "string" { - value = variation["value"].(string) - } else if variationsKind == "number" { - convertedNumberValue, err := strconv.Atoi(variation["value"].(string)) + if variationsKind == VARIATIONS_STRING_KIND { + value = variation[VARIATION_VALUE_KEY].(string) + } else if variationsKind == VARIATIONS_NUMBER_KIND { + convertedNumberValue, err := strconv.Atoi(variation[VARIATION_VALUE_KEY].(string)) if err != nil { return nil, err } value = convertedNumberValue - } else if variationsKind == "boolean" { - convertedBooleanValue, err := strconv.ParseBool(variation["value"].(string)) + } else if variationsKind == VARIATIONS_BOOLEAN_KIND { + convertedBooleanValue, err := strconv.ParseBool(variation[VARIATION_VALUE_KEY].(string)) if err != nil { return nil, err } diff --git a/launchdarkly/validations.go b/launchdarkly/validations.go index 254cb9c..61801b9 100644 --- a/launchdarkly/validations.go +++ b/launchdarkly/validations.go @@ -6,7 +6,7 @@ import ( "regexp" ) -var supportedVariationsType = [3]string{"number", "string", "boolean"} +var supportedVariationsType = [3]string{VARIATIONS_NUMBER_KIND, VARIATIONS_STRING_KIND, VARIATIONS_BOOLEAN_KIND} func validateKey(v interface{}, k string) ([]string, []error) { value := v.(string) @@ -48,7 +48,7 @@ func validateFeatureFlagVariationsType(v interface{}, k string) ([]string, []err value, ok := v.(string) if !ok { - return nil, []error{errors.New(fmt.Sprintf("expected %s to be string", k))} + return nil, []error{errors.New(fmt.Sprintf("expected %s to be a string", k))} } for _, validVariationsType := range supportedVariationsType { @@ -60,6 +60,20 @@ func validateFeatureFlagVariationsType(v interface{}, k string) ([]string, []err return nil, []error{errors.New(fmt.Sprintf("expected %s to be one of %v, got %s", k, []string{"number", "boolean", "string"}, value))} } +func validateVariationValue(v interface{}, k string) ([]string, []error) { + value, ok := v.(string) + + if !ok { + return nil, []error{errors.New(fmt.Sprintf("expected %s to be a string", k))} + } + + if len(value) < 1 { + return nil, []error{errors.New(fmt.Sprintf("%s cannot be an empty string", k))} + } + + return nil, nil +} + func validateColor(v interface{}, k string) ([]string, []error) { value := v.(string) From 13d40e1a1f7647e8698ade17db8aac8fc358223e Mon Sep 17 00:00:00 2001 From: Julien Date: Fri, 4 Oct 2019 08:41:32 -0400 Subject: [PATCH 6/6] Small refactoring --- launchdarkly/resource_feature_flag.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchdarkly/resource_feature_flag.go b/launchdarkly/resource_feature_flag.go index 32a1bee..0648f9a 100644 --- a/launchdarkly/resource_feature_flag.go +++ b/launchdarkly/resource_feature_flag.go @@ -6,13 +6,13 @@ import ( "github.com/hashicorp/terraform/helper/schema" ) -const DEFAULT_VARIATIONS_KIND = "boolean" const VARIATION_NAME_KEY = "name" const VARIATION_DESCRIPTION_KEY = "description" const VARIATION_VALUE_KEY = "value" const VARIATIONS_STRING_KIND = "string" const VARIATIONS_NUMBER_KIND = "number" const VARIATIONS_BOOLEAN_KIND = "boolean" +const DEFAULT_VARIATIONS_KIND = VARIATIONS_BOOLEAN_KIND func resourceFeatureFlag() *schema.Resource { return &schema.Resource{