From be20145857a8bc65f2273187f6ea2a2f6c22d089 Mon Sep 17 00:00:00 2001 From: Isabelle Miller Date: Tue, 3 Aug 2021 11:10:06 +0200 Subject: [PATCH 01/14] Update CHANGELOG.md fix error in changelog --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03e7dcf6..bd529225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,11 +26,7 @@ NOTES: - The `launchdarkly_feature_flag_environment` resource's `targeting_enabled` argument has been deprecated in favor of `on`. Please update your config to use `on` in order to maintain compatibility with future versions. -<<<<<<< HEAD - -- # The `resource_launchdarkly_webhook` resource's `policy_statements` argument has been deprecated in favor of `inline_roles`. Please update your config to use `inline_roles` in order to maintain compatibility with future versions. - The `resource_launchdarkly_access_token` resource's `policy_statements` argument has been deprecated in favor of `inline_roles`. Please update your config to use `inline_roles` in order to maintain compatibility with future versions. - > > > > > > > 090c48d257abdc15f10ea4d4e9a8fab1321ad036 ## [1.6.0] (July 20, 2021) From b3dc2ca9d4ae8f9700f8bdba2c0eba2d6f282f58 Mon Sep 17 00:00:00 2001 From: Isabelle Miller Date: Thu, 5 Aug 2021 14:39:12 +0200 Subject: [PATCH 02/14] Imiller/ch117193/rename flag_fallthrough to fallthrough & user_targets to targets (#131) * add fallthrough and deprecate message to flag_fallthrough * update doc * conflicts with * handle * update a test * update changelog * fix typos in docs * add targets attribute * fix linter complaint * handle in create * update * handle read * update commetn * update tests * hack remove user_targets from plan * set both computed attributes in read * update changelog * always set * simplify helper * fix data source test * add test for deprecated field updates * fix fromResourceData functions * check renamed on as well * Update data source d oc * update data source tests --- CHANGELOG.md | 6 + ...nchdarkly_feature_flag_environment_test.go | 2 + launchdarkly/fallthrough_helper.go | 9 +- .../feature_flag_environment_helper.go | 31 +++- launchdarkly/keys.go | 6 +- .../resource_launchdarkly_feature_flag.go | 2 +- ...e_launchdarkly_feature_flag_environment.go | 18 +- ...nchdarkly_feature_flag_environment_test.go | 156 ++++++++++++++---- launchdarkly/target_helper.go | 17 +- .../d/feature_flag_environment.html.markdown | 31 ++-- .../r/feature_flag_environment.html.markdown | 16 +- 11 files changed, 215 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd529225..370cf35a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## [Unreleased] +NOTES: + +- The `launchdarkly_feature_flag_environment` resource and data source's `flag_fallthrough` argument has been deprecated in favor of `fallthrough`. Please update your config to use `fallthrough` in order to maintain compatibility with future versions. + +- The `launchdarkly_feature_flag_environment` resource and data source's `user_targets` argument has been deprecated in favor of `targets`. Please update your config to use `targets` in order to maintain compatibility with future versions. + ## [1.7.0] (August 2, 2021) FEATURES: diff --git a/launchdarkly/data_source_launchdarkly_feature_flag_environment_test.go b/launchdarkly/data_source_launchdarkly_feature_flag_environment_test.go index 7742fdb9..25da4f7c 100644 --- a/launchdarkly/data_source_launchdarkly_feature_flag_environment_test.go +++ b/launchdarkly/data_source_launchdarkly_feature_flag_environment_test.go @@ -174,6 +174,8 @@ func TestAccDataSourceFeatureFlagEnvironment_exists(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "prerequisites.0.variation", fmt.Sprint(thisConfig.Prerequisites[0].Variation)), resource.TestCheckResourceAttr(resourceName, "off_variation", fmt.Sprint(thisConfig.OffVariation)), // user targets will be two long because there is an empty one for the 0 value + resource.TestCheckResourceAttr(resourceName, "targets.1.values.#", fmt.Sprint(len(thisConfig.Targets[0].Values))), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", fmt.Sprint(thisConfig.Fallthrough_.Variation)), resource.TestCheckResourceAttr(resourceName, "user_targets.1.values.#", fmt.Sprint(len(thisConfig.Targets[0].Values))), resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", fmt.Sprint(thisConfig.Fallthrough_.Variation)), ), diff --git a/launchdarkly/fallthrough_helper.go b/launchdarkly/fallthrough_helper.go index a1372c64..e48768d4 100644 --- a/launchdarkly/fallthrough_helper.go +++ b/launchdarkly/fallthrough_helper.go @@ -68,7 +68,14 @@ func isPercentRollout(fall []interface{}) bool { } func fallthroughFromResourceData(d *schema.ResourceData) (fallthroughModel, error) { - f := d.Get(FLAG_FALLTHROUGH).([]interface{}) + var f []interface{} + fallthroughHasChange := d.HasChange(FALLTHROUGH) + flagFallthroughHasChange := d.HasChange(FLAG_FALLTHROUGH) + if fallthroughHasChange { + f = d.Get(FALLTHROUGH).([]interface{}) + } else if flagFallthroughHasChange { + f = d.Get(FLAG_FALLTHROUGH).([]interface{}) + } err := validateFallThroughResourceData(f) if err != nil { return fallthroughModel{}, err diff --git a/launchdarkly/feature_flag_environment_helper.go b/launchdarkly/feature_flag_environment_helper.go index 2e08720e..f32bcef4 100644 --- a/launchdarkly/feature_flag_environment_helper.go +++ b/launchdarkly/feature_flag_environment_helper.go @@ -13,6 +13,16 @@ import ( ) func baseFeatureFlagEnvironmentSchema() map[string]*schema.Schema { + deprecatedFallthrough := fallthroughSchema() + deprecatedFallthrough.Deprecated = "'flag_fallthrough' is deprecated in favor of 'fallthrough'. This field will be removed in the next major release of the LaunchDarkly provider" + deprecatedFallthrough.ConflictsWith = []string{FALLTHROUGH} + newFallthrough := fallthroughSchema() + newFallthrough.ConflictsWith = []string{FLAG_FALLTHROUGH} + deprecatedTargets := targetsSchema() + deprecatedTargets.Deprecated = "'user_targets' is deprecated in favor of 'targets'. This field will be removed in the next major release of the LaunchDarkly provider" + deprecatedTargets.ConflictsWith = []string{TARGETS} + newTargets := targetsSchema() + newTargets.ConflictsWith = []string{USER_TARGETS} return map[string]*schema.Schema{ FLAG_ID: { Type: schema.TypeString, @@ -43,10 +53,12 @@ func baseFeatureFlagEnvironmentSchema() map[string]*schema.Schema { Computed: true, ConflictsWith: []string{TARGETING_ENABLED}, }, - USER_TARGETS: targetsSchema(), + USER_TARGETS: deprecatedTargets, + TARGETS: newTargets, RULES: rulesSchema(), PREREQUISITES: prerequisitesSchema(), - FLAG_FALLTHROUGH: fallthroughSchema(), + FLAG_FALLTHROUGH: deprecatedFallthrough, + FALLTHROUGH: newFallthrough, TRACK_EVENTS: { Type: schema.TypeBool, Optional: true, @@ -122,15 +134,26 @@ func featureFlagEnvironmentRead(d *schema.ResourceData, raw interface{}, isDataS return fmt.Errorf("failed to set rules on flag with key %q: %v", flagKey, err) } - err = d.Set(USER_TARGETS, targetsToResourceData(environment.Targets)) + // user_targets is deprecated in favor of targets + err = d.Set(TARGETS, targetsToResourceData(environment.Targets)) if err != nil { return fmt.Errorf("failed to set targets on flag with key %q: %v", flagKey, err) } + err = d.Set(USER_TARGETS, targetsToResourceData(environment.Targets)) + if err != nil { + return fmt.Errorf("failed to set user_targets on flag with key %q: %v", flagKey, err) + } + // flag_fallthrough is deprecated in favor of fallthrough + err = d.Set(FALLTHROUGH, fallthroughToResourceData(environment.Fallthrough_)) + if err != nil { + return fmt.Errorf("failed to set fallthrough on flag with key %q: %v", flagKey, err) + } err = d.Set(FLAG_FALLTHROUGH, fallthroughToResourceData(environment.Fallthrough_)) if err != nil { - return fmt.Errorf("failed to set flag fallthrough on flag with key %q: %v", flagKey, err) + return fmt.Errorf("failed to set flag_fallthrough on flag with key %q: %v", flagKey, err) } + return nil } diff --git a/launchdarkly/keys.go b/launchdarkly/keys.go index 9e48c65e..7e977fd2 100644 --- a/launchdarkly/keys.go +++ b/launchdarkly/keys.go @@ -59,13 +59,15 @@ const ( BUCKET_BY = "bucket_by" ROLLOUT_WEIGHTS = "rollout_weights" VARIATION = "variation" - USER_TARGETS = "user_targets" + USER_TARGETS = "user_targets" // deprecated + TARGETS = "targets" PREREQUISITES = "prerequisites" FLAG_KEY = "flag_key" TARGETING_ENABLED = "targeting_enabled" TRACK_EVENTS = "track_events" OFF_VARIATION = "off_variation" - FLAG_FALLTHROUGH = "flag_fallthrough" + FLAG_FALLTHROUGH = "flag_fallthrough" // deprecated + FALLTHROUGH = "fallthrough" KIND = "kind" CONFIG = "config" DEFAULT_ON_VARIATION = "default_on_variation" diff --git a/launchdarkly/resource_launchdarkly_feature_flag.go b/launchdarkly/resource_launchdarkly_feature_flag.go index 9f0054e5..7d04518a 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag.go +++ b/launchdarkly/resource_launchdarkly_feature_flag.go @@ -38,7 +38,7 @@ func resourceFeatureFlagCreate(d *schema.ResourceData, metaRaw interface{}) erro if err != nil { return err } - return fmt.Errorf("Cannot find project with key %q", projectKey) + return fmt.Errorf("cannot find project with key %q", projectKey) } key := d.Get(KEY).(string) diff --git a/launchdarkly/resource_launchdarkly_feature_flag_environment.go b/launchdarkly/resource_launchdarkly_feature_flag_environment.go index e48a5057..8ec1ca86 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag_environment.go +++ b/launchdarkly/resource_launchdarkly_feature_flag_environment.go @@ -97,14 +97,16 @@ func resourceFeatureFlagEnvironmentCreate(d *schema.ResourceData, metaRaw interf patches = append(patches, patchReplace(patchFlagEnvPath(d, "prerequisites"), prerequisites)) } - _, ok = d.GetOk(USER_TARGETS) - if ok { - targets := targetsFromResourceData(d, USER_TARGETS) + _, oldOk := d.GetOk(USER_TARGETS) + _, newOk := d.GetOk(TARGETS) + if oldOk || newOk { + targets := targetsFromResourceData(d) patches = append(patches, patchReplace(patchFlagEnvPath(d, "targets"), targets)) } - _, ok = d.GetOk(FLAG_FALLTHROUGH) - if ok { + _, newOk = d.GetOk(FALLTHROUGH) + _, oldOk = d.GetOk(FLAG_FALLTHROUGH) + if oldOk || newOk { fall, err := fallthroughFromResourceData(d) if err != nil { return err @@ -150,7 +152,7 @@ func resourceFeatureFlagEnvironmentUpdate(d *schema.ResourceData, metaRaw interf if err != nil { return err } - return fmt.Errorf("Cannot find project with key %q", projectKey) + return fmt.Errorf("cannot find project with key %q", projectKey) } if exists, err := environmentExists(projectKey, envKey, client); !exists { @@ -167,7 +169,7 @@ func resourceFeatureFlagEnvironmentUpdate(d *schema.ResourceData, metaRaw interf } trackEvents := d.Get(TRACK_EVENTS).(bool) prerequisites := prerequisitesFromResourceData(d, PREREQUISITES) - targets := targetsFromResourceData(d, USER_TARGETS) + targets := targetsFromResourceData(d) offVariation := d.Get(OFF_VARIATION).(int) fall, err := fallthroughFromResourceData(d) @@ -212,7 +214,7 @@ func resourceFeatureFlagEnvironmentDelete(d *schema.ResourceData, metaRaw interf if err != nil { return err } - return fmt.Errorf("Cannot find project with key %q", projectKey) + return fmt.Errorf("cannot find project with key %q", projectKey) } if exists, err := environmentExists(projectKey, envKey, client); !exists { diff --git a/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go b/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go index 064c62d9..b701fc5d 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go +++ b/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go @@ -38,6 +38,9 @@ resource "launchdarkly_feature_flag_environment" "basic" { flag_fallthrough { variation = 1 } + user_targets { + values = ["user1"] + } } ` @@ -86,13 +89,13 @@ resource "launchdarkly_feature_flag_environment" "basic" { env_key = "test" targeting_enabled = true track_events = true - user_targets { + targets { values = [] } - user_targets { + targets { values = ["user1", "user2"] } - user_targets { + targets { values = [] } rules { @@ -115,12 +118,49 @@ resource "launchdarkly_feature_flag_environment" "basic" { bucket_by = "email" } - flag_fallthrough { + fallthrough { rollout_weights = [60000, 40000, 0] bucket_by = "email" } } ` + testAccFeatureFlagEnvironmentUpdateDeprecated = ` +resource "launchdarkly_feature_flag" "basic" { + project_key = launchdarkly_project.test.key + key = "basic-flag" + name = "Basic feature flag" + variation_type = "number" + variations { + value = 0 + } + variations { + value = 10 + } + variations { + value = 30 + } +} + +resource "launchdarkly_feature_flag_environment" "basic" { + flag_id = launchdarkly_feature_flag.basic.id + env_key = "test" + targeting_enabled = true + track_events = true + user_targets { + values = ["user1", "user2"] + } + user_targets { + values = [] + } + user_targets { + values = [] + } + flag_fallthrough { + variation = 2 + } +} +` + testAccFeatureFlagEnvironmentJSONVariations = ` resource "launchdarkly_feature_flag" "json" { project_key = launchdarkly_project.test.key @@ -315,6 +355,8 @@ func TestAccFeatureFlagEnvironment_Basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "false"), resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.#", "1"), resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", "1"), + resource.TestCheckResourceAttr(resourceName, "user_targets.#", "1"), + resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.0", "user1"), ), }, { @@ -359,7 +401,7 @@ func TestAccFeatureFlagEnvironment_Empty(t *testing.T) { }) } -func TestAccFeatureFlagEnvironment_Update(t *testing.T) { +func TestAccFeatureFlagEnvironment_UpdateDeprecatedFields(t *testing.T) { projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resourceName := "launchdarkly_feature_flag_environment.basic" resource.ParallelTest(t, resource.TestCase{ @@ -373,32 +415,86 @@ func TestAccFeatureFlagEnvironment_Update(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckFeatureFlagEnvironmentExists(resourceName), resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "on", "false"), resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.#", "1"), resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", "1"), resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.rollout.#", "0"), - resource.TestCheckResourceAttr(resourceName, "user_targets.#", "0"), + resource.TestCheckResourceAttr(resourceName, "user_targets.#", "1"), + resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.0", "user1"), resource.TestCheckResourceAttr(resourceName, "rules.#", "0"), ), }, { - Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentUpdate), + Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentUpdateDeprecated), Check: resource.ComposeTestCheckFunc( testAccCheckFeatureFlagEnvironmentExists(resourceName), + // computed values should come through resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "on", "true"), resource.TestCheckResourceAttr(resourceName, "track_events", "true"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.#", "1"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "2"), resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.#", "1"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", "0"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.rollout_weights.#", "3"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.rollout_weights.0", "60000"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.rollout_weights.1", "40000"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.rollout_weights.2", "0"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.bucket_by", "email"), + resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", "2"), + resource.TestCheckResourceAttr(resourceName, "targets.#", "3"), + resource.TestCheckResourceAttr(resourceName, "targets.0.values.#", "2"), + resource.TestCheckResourceAttr(resourceName, "targets.0.values.0", "user1"), + resource.TestCheckResourceAttr(resourceName, "targets.0.values.1", "user2"), + resource.TestCheckResourceAttr(resourceName, "targets.1.values.#", "0"), + resource.TestCheckResourceAttr(resourceName, "targets.2.values.#", "0"), resource.TestCheckResourceAttr(resourceName, "user_targets.#", "3"), - resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.#", "0"), - resource.TestCheckResourceAttr(resourceName, "user_targets.1.values.#", "2"), - resource.TestCheckResourceAttr(resourceName, "user_targets.1.values.0", "user1"), - resource.TestCheckResourceAttr(resourceName, "user_targets.1.values.1", "user2"), + resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.#", "2"), + resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.0", "user1"), + resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.1", "user2"), + resource.TestCheckResourceAttr(resourceName, "user_targets.1.values.#", "0"), resource.TestCheckResourceAttr(resourceName, "user_targets.2.values.#", "0"), + ), + }, + }, + }) +} + +func TestAccFeatureFlagEnvironment_Update(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "launchdarkly_feature_flag_environment.basic" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentBasic), + Check: resource.ComposeTestCheckFunc( + testAccCheckFeatureFlagEnvironmentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.#", "1"), + resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", "1"), + resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.rollout.#", "0"), + resource.TestCheckResourceAttr(resourceName, "user_targets.#", "1"), + resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.0", "user1"), + resource.TestCheckResourceAttr(resourceName, "rules.#", "0"), + ), + }, + { + Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentUpdate), + Check: resource.ComposeTestCheckFunc( + testAccCheckFeatureFlagEnvironmentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "track_events", "true"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.#", "1"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "0"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.#", "3"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.0", "60000"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.1", "40000"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.2", "0"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.bucket_by", "email"), + resource.TestCheckResourceAttr(resourceName, "targets.#", "3"), + resource.TestCheckResourceAttr(resourceName, "targets.0.values.#", "0"), + resource.TestCheckResourceAttr(resourceName, "targets.1.values.#", "2"), + resource.TestCheckResourceAttr(resourceName, "targets.1.values.0", "user1"), + resource.TestCheckResourceAttr(resourceName, "targets.1.values.1", "user2"), + resource.TestCheckResourceAttr(resourceName, "targets.2.values.#", "0"), resource.TestCheckResourceAttr(resourceName, "rules.#", "2"), resource.TestCheckResourceAttr(resourceName, "rules.0.variation", "0"), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.#", "1"), @@ -427,19 +523,19 @@ func TestAccFeatureFlagEnvironment_Update(t *testing.T) { testAccCheckFeatureFlagEnvironmentExists(resourceName), resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "true"), resource.TestCheckResourceAttr(resourceName, "track_events", "true"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.#", "1"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", "0"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.rollout_weights.#", "3"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.rollout_weights.0", "60000"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.rollout_weights.1", "40000"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.rollout_weights.2", "0"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.bucket_by", "email"), - resource.TestCheckResourceAttr(resourceName, "user_targets.#", "3"), - resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.#", "0"), - resource.TestCheckResourceAttr(resourceName, "user_targets.1.values.#", "2"), - resource.TestCheckResourceAttr(resourceName, "user_targets.1.values.0", "user1"), - resource.TestCheckResourceAttr(resourceName, "user_targets.1.values.1", "user2"), - resource.TestCheckResourceAttr(resourceName, "user_targets.2.values.#", "0"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.#", "1"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "0"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.#", "3"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.0", "60000"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.1", "40000"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.2", "0"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.bucket_by", "email"), + resource.TestCheckResourceAttr(resourceName, "targets.#", "3"), + resource.TestCheckResourceAttr(resourceName, "targets.0.values.#", "0"), + resource.TestCheckResourceAttr(resourceName, "targets.1.values.#", "2"), + resource.TestCheckResourceAttr(resourceName, "targets.1.values.0", "user1"), + resource.TestCheckResourceAttr(resourceName, "targets.1.values.1", "user2"), + resource.TestCheckResourceAttr(resourceName, "targets.2.values.#", "0"), resource.TestCheckResourceAttr(resourceName, "rules.#", "2"), resource.TestCheckResourceAttr(resourceName, "rules.0.variation", "0"), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.#", "1"), diff --git a/launchdarkly/target_helper.go b/launchdarkly/target_helper.go index 2202a92a..9263105d 100644 --- a/launchdarkly/target_helper.go +++ b/launchdarkly/target_helper.go @@ -27,12 +27,15 @@ func targetsSchema() *schema.Schema { } } -func targetsFromResourceData(d *schema.ResourceData, metaRaw interface{}) []ldapi.Target { - tgts, ok := d.GetOk(USER_TARGETS) - if !ok { - return []ldapi.Target{} +func targetsFromResourceData(d *schema.ResourceData) []ldapi.Target { + var schemaTargets []interface{} + targetsHasChange := d.HasChange(TARGETS) + userTargetsHasChange := d.HasChange(USER_TARGETS) + if targetsHasChange { + schemaTargets = d.Get(TARGETS).([]interface{}) + } else if userTargetsHasChange { + schemaTargets = d.Get(USER_TARGETS).([]interface{}) } - schemaTargets := tgts.([]interface{}) targets := make([]ldapi.Target, len(schemaTargets)) for i, target := range schemaTargets { v := targetFromResourceData(i, target) @@ -58,9 +61,9 @@ func targetFromResourceData(variation int, val interface{}) ldapi.Target { return p } -// targetToResourceData converts the user_target information returned +// targetToResourceData converts the `target` information returned // by the LaunchDarkly API into a format suitable for Terraform -// If no user_targets are specified for a given variation, LaunchDarkly may +// If no `targets` are specified for a given variation, LaunchDarkly may // omit this information in the response. For example: // "targets": [ // { diff --git a/website/docs/d/feature_flag_environment.html.markdown b/website/docs/d/feature_flag_environment.html.markdown index 59974af7..a6ff47aa 100644 --- a/website/docs/d/feature_flag_environment.html.markdown +++ b/website/docs/d/feature_flag_environment.html.markdown @@ -38,11 +38,11 @@ In addition to the arguments above, the resource exports the following attribute - `prerequisites` - List of nested blocks describing prerequisite feature flags rules. To learn more, read [Nested Prequisites Blocks](#nested-prerequisites-blocks). -- `user_targets` - List of nested blocks describing the individual user targets for each variation. The order of the `user_targets` blocks determines the index of the variation to serve if a `user_target` is matched. To learn more, read [Nested User Target Blocks](#nested-user-targets-blocks). +- `targets` (previously `user_targets`) - List of nested blocks describing the individual user targets for each variation. The order of the `targets` blocks determines the index of the variation to serve if a `target` is matched. To learn more, read [Nested Target Blocks](#nested-targets-blocks). - `rules` - List of logical targeting rules. To learn more, read [Nested Rules Blocks](#nested-rules-blocks). -- `flag_fallthrough` - Nested block describing the default variation to serve if no `prerequisites`, `user_target`, or `rules` apply. To learn more, read [Nested Flag Fallthrough Block](#nested-flag-fallthrough-block). +- `fallthrough` (previously `flag_fallthrough`) - Nested block describing the default variation to serve if no `prerequisites`, `target`, or `rules` apply. To learn more, read [Nested Fallthrough Block](#nested-fallthrough-block). ### Nested Prerequisites Blocks @@ -52,21 +52,21 @@ Nested `prerequisites` blocks have the following structure: - `variation` - The index of the prerequisite feature flag's variation targeted. -### Nested User Targets Blocks +### Nested Targets Blocks -Nested `user_targets` blocks have the following structure: +Nested `targets` blocks have the following structure: - `values` - List of `user` strings to target. -### Nested Flag Fallthrough Block +### Nested Fallthrough Block -The nested `flag_fallthrough` block has the following structure: +The nested `fallthrough` block has the following structure: -- `variation` - The default integer variation index served if no `prerequisites`, `user_target`, or `rules` apply. +- `variation` - The default integer variation index served if no `prerequisites`, `target`, or `rules` apply. -- `rollout_weights` - List of integer percentage rollout weights applied to each variation when no `prerequisites`, `user_target`, or `rules` apply. +- `rollout_weights` - List of integer percentage rollout weights applied to each variation when no `prerequisites`, `target`, or `rules` apply. -- `bucket_by` - Group percentage rollout by a custom attribute. +- `bucket_by` - Group percentage rollout by a custom attribute. ### Nested Rules Blocks @@ -74,11 +74,11 @@ Nested `rules` blocks have the following structure: - `clauses` - List of nested blocks specifying the logical clauses evaluated. To learn more, read [Nested Clauses Blocks](#nested-clauses-blocks). -- `variation` - The integer variation index served if the rule clauses evaluate to `true`. +- `variation` - The integer variation index served if the rule clauses evaluate to `true`. -- `rollout_weights` - List of integer percentage rollout weights applied to each variation when the rule clauses evaluates to `true`. +- `rollout_weights` - List of integer percentage rollout weights applied to each variation when the rule clauses evaluates to `true`. -- `bucket_by` - Group percentage rollout by a custom attribute. +- `bucket_by` - Group percentage rollout by a custom attribute. ### Nested Clauses Blocks @@ -94,9 +94,8 @@ Nested `clauses` blocks have the following structure: - `negate` - Whether the rule clause is negated. -Nested `flag_fallthrough` blocks have the following structure: +Nested `fallthrough` blocks have the following structure: -- `variation` - The integer variation index served when the rule clauses evaluate to `true`. - -- `rollout_weights` - List of integer percentage rollout weights applied to each variation when the rule clauses evaluates to `true`. +- `variation` - The integer variation index served when the rule clauses evaluate to `true`. +- `rollout_weights` - List of integer percentage rollout weights applied to each variation when the rule clauses evaluates to `true`. diff --git a/website/docs/r/feature_flag_environment.html.markdown b/website/docs/r/feature_flag_environment.html.markdown index 05f2538f..b0bbd38b 100644 --- a/website/docs/r/feature_flag_environment.html.markdown +++ b/website/docs/r/feature_flag_environment.html.markdown @@ -51,7 +51,7 @@ resource "launchdarkly_feature_flag_environment" "number_env" { variation = 0 } - flag_fallthrough { + fallthrough { rollout_weights = [60000, 40000, 0] } } @@ -77,7 +77,9 @@ resource "launchdarkly_feature_flag_environment" "number_env" { - `rules` - (Optional) List of logical targeting rules. To learn more, read [Nested Rules Blocks](#nested-rules-blocks). -- `flag_fallthrough` - (Optional) Nested block describing the default variation to serve if no `prerequisites`, `user_target`, or `rules` apply. To learn more, read [Nested Flag Fallthrough Block](#nested-flag-fallthrough-block). +- `flag_fallthrough` - (Optional, **Deprecated**) Nested block describing the default variation to serve if no `prerequisites`, `user_target`, or `rules` apply. This attribute is **deprecated** in favor of `fallthrough`. Please update all references of `flag_fallthrough` to `fallthrough` to maintain compatibility with future versions. + +- `fallthrough` - (Optional) Nested block describing the default variation to serve if no `prerequisites`, `user_target`, or `rules` apply.To learn more, read [Nested Fallthrough Block](#nested-fallthrough-block). ### Nested Prerequisites Blocks @@ -93,9 +95,9 @@ Nested `user_targets` blocks have the following structure: - `values` - (Optional) List of `user` strings to target. -### Nested Flag Fallthrough Block +### Nested Fallthrough Block -The nested `flag_fallthrough` block has the following structure: +The nested `fallthrough` (previously `flag_fallthrough`) block has the following structure: - `variation` - (Optional) The default integer variation index to serve if no `prerequisites`, `user_target`, or `rules` apply. You must specify either `variation` or `rollout_weights`. @@ -129,12 +131,6 @@ Nested `clauses` blocks have the following structure: - `negate` - (Required) Whether to negate the rule clause. -Nested `flag_fallthrough` blocks have the following structure: - -- `variation` - (Optional) The integer variation index to serve if the rule clauses evaluate to `true`. You must specify either `variation` or `rollout_weights`. - -- `rollout_weights` - (Optional) List of integer percentage rollout weights (in thousandths of a percent) to apply to each variation if the rule clauses evaluates to `true`. The sum of the `rollout_weights` must equal 100000. You must specify either `variation` or `rollout_weights`. - ## Attributes Reference In addition to the arguments above, the resource exports the following attribute: From 6922e7945532a038a9798ee049b877b95f8f502f Mon Sep 17 00:00:00 2001 From: Isabelle Miller Date: Mon, 16 Aug 2021 15:14:54 +0100 Subject: [PATCH 03/14] Imiller/ch117375/GitHub issue empty string variation (#135) * should fix it * add test case * update changelog and doc --- CHANGELOG.md | 4 ++ ...resource_launchdarkly_feature_flag_test.go | 45 +++++++++++++++++++ launchdarkly/variations_helper.go | 11 ++++- website/docs/r/feature_flag.html.markdown | 8 ++++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 370cf35a..89cf4918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## [Unreleased] +BUG FIXES: + +- Fixes [a bug](https://github.com/launchdarkly/terraform-provider-launchdarkly/issues/60) where attempts to create `resource_launchdarkly_feature_flag` variations with an empty string value were throwing a panic. + NOTES: - The `launchdarkly_feature_flag_environment` resource and data source's `flag_fallthrough` argument has been deprecated in favor of `fallthrough`. Please update your config to use `fallthrough` in order to maintain compatibility with future versions. diff --git a/launchdarkly/resource_launchdarkly_feature_flag_test.go b/launchdarkly/resource_launchdarkly_feature_flag_test.go index d7aaac29..4ac85cb1 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag_test.go +++ b/launchdarkly/resource_launchdarkly_feature_flag_test.go @@ -352,6 +352,20 @@ resource "launchdarkly_feature_flag" "defaults-multivariate" { value = "d" } } +` + testAccFeatureFlagEmptyStringVariation = ` +resource "launchdarkly_feature_flag" "empty_string_variation" { + project_key = launchdarkly_project.test.key + key = "empty-variation" + name = "string flag with empty string variation" + variation_type = "string" + variations { + value = "" + } + variations { + value = "non-empty" + } +} ` ) @@ -866,6 +880,37 @@ func TestAccFeatureFlag_UpdateMultivariateDefaults(t *testing.T) { }) } +func TestAccFeatureFlag_EmptyStringVariation(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "launchdarkly_feature_flag.empty_string_variation" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccFeatureFlagEmptyStringVariation), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckFeatureFlagExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "variations.0.value", ""), + resource.TestCheckResourceAttr(resourceName, "variations.0.name", ""), + resource.TestCheckResourceAttr(resourceName, "variations.0.description", ""), + resource.TestCheckResourceAttr(resourceName, "variations.1.value", "non-empty"), + resource.TestCheckResourceAttr(resourceName, "variations.1.name", ""), + resource.TestCheckResourceAttr(resourceName, "variations.1.description", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCustomPropertyKey(key string, subKey string) string { return fmt.Sprintf("custom_properties.%d.%s", hashcode.String(key), subKey) } diff --git a/launchdarkly/variations_helper.go b/launchdarkly/variations_helper.go index 0cca7790..1b927202 100644 --- a/launchdarkly/variations_helper.go +++ b/launchdarkly/variations_helper.go @@ -176,8 +176,17 @@ func boolVariationFromResourceData(variation interface{}) ldapi.Variation { } func stringVariationFromResourceData(variation interface{}) ldapi.Variation { + var v interface{} + if variation == nil { // handle empty string value + v = "" + return ldapi.Variation{ + Name: "", + Description: "", + Value: &v, + } + } variationMap := variation.(map[string]interface{}) - v := variationMap[VALUE] + v = variationMap[VALUE] return ldapi.Variation{ Name: variationMap[NAME].(string), Description: variationMap[DESCRIPTION].(string), diff --git a/website/docs/r/feature_flag.html.markdown b/website/docs/r/feature_flag.html.markdown index 1508a5b2..644c95c3 100644 --- a/website/docs/r/feature_flag.html.markdown +++ b/website/docs/r/feature_flag.html.markdown @@ -104,6 +104,14 @@ Nested `variations` blocks have the following structure: - `value` - (Required) The variation value. The value's type must correspond to the `variation_type` argument. For example: `variation_type = "boolean"` accepts only `true` or `false`. The `"number"` variation type accepts both floats and ints, but please note that any trailing zeroes on floats will be trimmed (i.e. `1.1` and `1.100` will both be converted to `1.1`). +If you wish to define an empty string variation, you must still define the value field on the variations block like so: + +``` +variations { + value = "" +} +``` + - `name` - (Optional) The name of the variation. - `description` - (Optional) The variation's description. From 61c544d280d285b0a7f7fdbcf3269d9fbe78e9bc Mon Sep 17 00:00:00 2001 From: Sunny Guduru Date: Wed, 25 Aug 2021 15:16:49 -0700 Subject: [PATCH 04/14] Fix duplicate text in CHANGELOG.md Somehow the changelog test was duplicated. Removing, and we can fix the changelog on our next release. --- CHANGELOG.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7efd940..272a3d48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,16 +10,6 @@ NOTES: - The `launchdarkly_feature_flag_environment` resource and data source's `user_targets` argument has been deprecated in favor of `targets`. Please update your config to use `targets` in order to maintain compatibility with future versions. -BUG FIXES: - -- Fixes [a bug](https://github.com/launchdarkly/terraform-provider-launchdarkly/issues/60) where attempts to create `resource_launchdarkly_feature_flag` variations with an empty string value were throwing a panic. - -NOTES: - -- The `launchdarkly_feature_flag_environment` resource and data source's `flag_fallthrough` argument has been deprecated in favor of `fallthrough`. Please update your config to use `fallthrough` in order to maintain compatibility with future versions. - -- The `launchdarkly_feature_flag_environment` resource and data source's `user_targets` argument has been deprecated in favor of `targets`. Please update your config to use `targets` in order to maintain compatibility with future versions. - ## [1.7.0] (August 2, 2021) FEATURES: From 361e8c24cd90a47db159c29e364d6b0a3cf8060a Mon Sep 17 00:00:00 2001 From: Isabelle Miller Date: Tue, 31 Aug 2021 13:15:19 +0200 Subject: [PATCH 05/14] V2 main (#128) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * destination on should default to false (v2) (#124) * remove enabled * test that on defaults to false when removed * Imiller/ch115579/fix include in snippet bug (#129) * all it needed was a default val * add test to ensure optional attributes get set correctly when removed * fix test * use TestCheckNoResourceAttr instead of TestCheckResourceAttr * update changelog * Imiller/ch113419/upgrade terraform client to v2 (#114) * run v2 helper * replace hashcode.String() with schema.HashString() * replace terraform.ResourceProvider with *schema.Provider * fix data source TypeMaps * bump v2 sdk to latest (#116) * merge * Remove versions.tf (#119) * Imiller/ch114162/upgrade resource launchdarkly environment (#121) * make project_key required * update descriptions * update changelog * Imiller/ch114165/v1 project audit (#120) * add descriptions * update doc dscription * audit team member for v1 (#122) * update descriptions * add import instructions to team member docs * add team member examples * ForceNew when email is updated * update test file structure to better reflect provider pattern * add AtLeastOneOf for role and custom_roles * oops wrong doc * use validation.StringInSlice() * refactor key conversion functions into helper file * remove owner * forgot changelog * Imiller/ch114163/v1 feature flag env (#112) * add descriptions for everything * fix dangling default values * fix bug where creating a non-bool flag with no variations creates a bool flag * add bug fixes to changelog * built in an idiotic bug, fixed now * see if this fixes all of the tests * found typo: * fix broken tests * add descriptions * update some descriptions for clarity * make clauses RequiredWith on variations to preempt api error * fix error in doc * fix docs properly * deprecated targeting_enabled in favor of on * remove ReuqiredWith because it's causing a bug * fix plan bug * hopefully this will fix it * ughhhhh it was that it needed to be computed * forgot the doc oops * forgot the changelog too meep * destination on should default to false (v2) (#124) * remove enabled * test that on defaults to false when removed * Imiller/ch114160/v1 custom role (#125) * add descriptions * reformat tests to match pattern * check removal of optional attributes removes * holy crap how did this not break earlier * not sure how that one stuck around either * somehow we were not actually setting the flag_id * fix feature flag read * fix csa on project read * apparently errors are different too * remove lingering merge conflict text from changelog * fix noMatchReturnsError errors * remove hash indices on type set * remove hash function for custom role policy * fix destination tests * fix feature flag tests * fix env tests * fix project tests * fix segment tests * fix segment tests and remove unused testAccTagKey function * revert computed * define CSA inline * update changelog * remove MaxItems property from data sources * fix forProject Co-authored-by: Henry Barrow * Imiller/ch115576/revert feature flag env optional attributes (#130) * remove deprecated attribute targeting_enabled (replaced by on) * on should default to false when removed * forgot to fix data source tests * fix test to handle on reversion * make rules not computed * make user_targets not computed * update comments * finally got fallthrough working * fix test * clarify * fix more tests * remove deprecated user_targets and flag_fallthrough from schema * update read * revert helpers * update create * update tests * update example * update doc * remove deprecated fields from keys * update changelog * fix fallthrough and fix test * update changelog * missed a bit on the docs' * make prerequisite not computed * udate changelog again * add test case for removing prereqs * update flag_id description for clarity * add defaults for optional fields * update test again * update changelog again * handle off_variation defaults * fix data source test * have to use GetOkExists instead of GetOk to be able to set 0 off_variation on create * remove commented-out code * Imiller/ch115576/make fallthrough off variation required (#134) * make required in schemas * fix some test configs * add required attributes to rest of tests. tests all failing * always set both on read * no need to check if fallthrough not set because tf will enforce * update CRU functions * fix data source tests * update changelog and docs for new required attributes * remove comment no longer applicable * fix typo * remove `enabled` on webhooks * removed `policy_statements` on webhooks * removed `targeting_enabled` on feature flag environment * update change log * Change schema function to use recommended one. * Imiller/ch119139/make env configs required on project resource (#137) * make required * delete envs that were previously in state and have been removed * remove redundant test & add required envs * ImportSTateVerifyIgnore envs * update docs * move full_config example into v1_full_config * add v2 full config * update example * update test project scaffolding function * change State to StateContext function * update comment * fix destination test bug * add test case for when we fix env defaults * add note to import about envs * update changelog * remove debug bin * add debug_bin to gitignore * address pr comments by adding back stuff to docs * update all example versions to 1.7 * Added logic to delete remote webhook statements when statements blockā€¦ (#133) * Added logic to delete remote webhook statements when statements block is removed * refactored tests * Added has changes check around the webhook policy statements * Reverted changes in version file * Removed check for deprecated policy_statements * Removed deprecated fields and refactored helper functions using those deprecated fields * Removed ConflictsWith for the webhook ON schema since ENABLED no longer exists in the schema * Fixed test cases * Updated changelog, refactored getWebhookOn and updated the docs * Updated version in example and added default config for webhook ON element * Imiller/ch119137/make segment clause negate field optional (#136) * make oiptional and default to false + update test * add example segment config for 2.0 * this should be v2.0.0 * Do not require fallthrough and off_variation in ff_env data source (#142) * added version to versions.tf file for webhook * v2 for custom_role and destination. Update read me * Change targets schema to set and add 'variation' attribute (#141) * Revise targets schema * update docs * Make target values required * Apply suggestions from code review Co-authored-by: Isabelle Miller * Change List to Set in description * remove debug_bin Co-authored-by: Isabelle Miller * Imiller/ch115572/restructure default flag variations (#146) * add new defaults to schema * update tests to expected configs / outcomes * add default on * restructure defaultVariationsFromResourceData * update test cases * hitting a non-empty plan error in test * ffs just needed to be Computed * update tests * update docs and changelog * fix test * lost a brace * fix variation helper * fix changelog * small doc fixes (#147) * remove enabled from destination docs * update webhook doc * update doc on name issue (#151) * environment and data_source_webhook examples (#149) * added more examples * added comments * Imiller/ch120560/feature flag maintainer id should be removed (#150) * make computed and update tes * update doc * update changelog * Imiller/ch119620/audit examples (#152) * make separate v1 and v2 feature flag examples * ensure v2 ff example works * move some stuff around * some more reorg * more reorg * update toc in readme * update v2 feature flag readme * Bug Fix: Revert optional env attributes to defaults (#148) * Fixed and tests passing * improve env update test * default_ttl default, and create new test * default_ttl should reset to 0 * update docs and changelog Co-authored-by: Isabelle Miller * remove test examples shouldn't have been checked in * add terraform version note to changelog Co-authored-by: Henry Barrow Co-authored-by: Sunny Co-authored-by: Cliff Tawiah <82856282+ctawiah@users.noreply.github.com> --- .gitignore | 4 +- CHANGELOG.md | 74 +++- examples/README.md | 20 +- examples/custom_role/example.tf | 4 - .../versions.tf | 7 +- examples/team_member/example.tf | 4 - examples/team_member/versions.tf | 1 + examples/{ => v1}/feature_flags/README.md | 14 +- .../feature_flags/flag_types_example.tf | 0 examples/{ => v1}/feature_flags/setup.tf | 2 +- .../feature_flags/targeting_example.tf | 18 +- examples/v1/feature_flags/versions.tf | 8 + examples/v1/full_config/README.md | 26 ++ examples/{ => v1}/full_config/env-dev.tf | 0 examples/{ => v1}/full_config/env-staging.tf | 2 +- examples/{ => v1}/full_config/flags.tf | 0 examples/{ => v1}/full_config/project.tf | 0 examples/{ => v1}/full_config/roles.tf | 0 examples/{ => v1}/multiple_projects/README.md | 0 .../{ => v1}/multiple_projects/example.tf | 4 +- .../data_source_destination/example.tf | 0 .../v2/data_source_destination/versions.tf | 9 + examples/v2/data_source_webhook/example.tf | 23 ++ examples/v2/environment/example.tf | 21 ++ examples/v2/feature_flags/README.md | 301 ++++++++++++++++ .../v2/feature_flags/flag_types_example.tf | 97 ++++++ examples/v2/feature_flags/setup.tf | 25 ++ .../v2/feature_flags/targeting_example.tf | 70 ++++ examples/v2/feature_flags/versions.tf | 8 + examples/{ => v2}/full_config/README.md | 11 +- examples/v2/full_config/flags.tf | 59 ++++ .../v2/full_config/production-env-configs.tf | 23 ++ examples/v2/full_config/project.tf | 31 ++ examples/v2/full_config/roles.tf | 36 ++ .../v2/full_config/staging-env-configs.tf | 25 ++ examples/v2/full_config/versions.tf | 9 + examples/v2/segment/example.tf | 35 ++ examples/v2/segment/versions.tf | 9 + examples/{ => v2}/webhook/README.md | 14 +- examples/{ => v2}/webhook/example.tf | 10 +- examples/v2/webhook/versions.tf | 9 + go.mod | 2 +- go.sum | 179 ++++------ launchdarkly/clause_helper.go | 7 +- launchdarkly/clause_helper_test.go | 2 +- launchdarkly/custom_properties_helper.go | 7 +- launchdarkly/custom_properties_helper_test.go | 2 +- .../data_source_launchdarkly_environment.go | 2 +- ...ta_source_launchdarkly_environment_test.go | 6 +- .../data_source_launchdarkly_feature_flag.go | 4 +- ...e_launchdarkly_feature_flag_environment.go | 4 +- ...nchdarkly_feature_flag_environment_test.go | 19 +- ...a_source_launchdarkly_feature_flag_test.go | 10 +- .../data_source_launchdarkly_project.go | 4 +- .../data_source_launchdarkly_project_test.go | 10 +- .../data_source_launchdarkly_segment.go | 2 +- .../data_source_launchdarkly_segment_test.go | 6 +- .../data_source_launchdarkly_team_member.go | 2 +- ...ta_source_launchdarkly_team_member_test.go | 4 +- .../data_source_launchdarkly_webhook.go | 7 +- .../data_source_launchdarkly_webhook_test.go | 11 +- launchdarkly/default_variations_helper.go | 93 +---- .../default_variations_helper_test.go | 123 ++----- launchdarkly/destination_helper.go | 2 +- launchdarkly/environments_helper.go | 20 +- launchdarkly/fallthrough_helper.go | 27 +- .../feature_flag_environment_helper.go | 68 ++-- launchdarkly/feature_flags_helper.go | 71 ++-- launchdarkly/keys.go | 7 +- launchdarkly/policies_helper.go | 5 +- launchdarkly/policy_statements_helper.go | 4 +- launchdarkly/policy_statements_helper_test.go | 2 +- launchdarkly/prerequisite_helper.go | 5 +- launchdarkly/project_helper.go | 10 +- launchdarkly/provider.go | 7 +- launchdarkly/provider_test.go | 17 +- .../resource_launchdarkly_access_token.go | 4 +- ...resource_launchdarkly_access_token_test.go | 12 +- .../resource_launchdarkly_custom_role.go | 2 +- .../resource_launchdarkly_custom_role_test.go | 41 +-- .../resource_launchdarkly_destination.go | 43 +-- .../resource_launchdarkly_destination_test.go | 60 ++-- .../resource_launchdarkly_environment.go | 2 +- .../resource_launchdarkly_environment_test.go | 94 +++-- .../resource_launchdarkly_feature_flag.go | 2 +- ...e_launchdarkly_feature_flag_environment.go | 62 +--- ...nchdarkly_feature_flag_environment_test.go | 329 ++++++++---------- ...resource_launchdarkly_feature_flag_test.go | 233 ++++++++----- launchdarkly/resource_launchdarkly_project.go | 88 +++-- .../resource_launchdarkly_project_test.go | 100 +++++- launchdarkly/resource_launchdarkly_segment.go | 2 +- .../resource_launchdarkly_segment_test.go | 23 +- .../resource_launchdarkly_team_member.go | 6 +- .../resource_launchdarkly_team_member_test.go | 17 +- launchdarkly/resource_launchdarkly_webhook.go | 72 +--- .../resource_launchdarkly_webhook_test.go | 66 ++-- launchdarkly/rollout_helper.go | 4 +- launchdarkly/rule_helper.go | 5 +- launchdarkly/segment_rule_helper.go | 4 +- launchdarkly/segments_helper.go | 2 +- launchdarkly/tags_helper.go | 2 +- launchdarkly/target_helper.go | 96 ++--- launchdarkly/target_helper_test.go | 18 +- launchdarkly/validation_helper.go | 4 +- launchdarkly/variations_helper.go | 4 +- launchdarkly/variations_helper_test.go | 2 +- launchdarkly/webhooks_helper.go | 53 +-- main.go | 2 +- website/docs/d/feature_flag.html.markdown | 15 +- .../d/feature_flag_environment.html.markdown | 6 +- website/docs/d/project.html.markdown | 2 + website/docs/d/webhook.html.markdown | 8 +- website/docs/r/destination.html.markdown | 4 +- website/docs/r/environment.html.markdown | 12 +- website/docs/r/feature_flag.html.markdown | 26 +- .../r/feature_flag_environment.html.markdown | 46 +-- website/docs/r/project.html.markdown | 20 +- website/docs/r/team_member.html.markdown | 4 +- website/docs/r/webhook.html.markdown | 14 +- 119 files changed, 2032 insertions(+), 1343 deletions(-) rename examples/{data_source_destination => custom_role}/versions.tf (50%) rename examples/{ => v1}/feature_flags/README.md (97%) rename examples/{ => v1}/feature_flags/flag_types_example.tf (100%) rename examples/{ => v1}/feature_flags/setup.tf (95%) rename examples/{ => v1}/feature_flags/targeting_example.tf (85%) create mode 100644 examples/v1/feature_flags/versions.tf create mode 100644 examples/v1/full_config/README.md rename examples/{ => v1}/full_config/env-dev.tf (100%) rename examples/{ => v1}/full_config/env-staging.tf (96%) rename examples/{ => v1}/full_config/flags.tf (100%) rename examples/{ => v1}/full_config/project.tf (100%) rename examples/{ => v1}/full_config/roles.tf (100%) rename examples/{ => v1}/multiple_projects/README.md (100%) rename examples/{ => v1}/multiple_projects/example.tf (98%) rename examples/{ => v2}/data_source_destination/example.tf (100%) create mode 100644 examples/v2/data_source_destination/versions.tf create mode 100644 examples/v2/data_source_webhook/example.tf create mode 100644 examples/v2/environment/example.tf create mode 100644 examples/v2/feature_flags/README.md create mode 100644 examples/v2/feature_flags/flag_types_example.tf create mode 100644 examples/v2/feature_flags/setup.tf create mode 100644 examples/v2/feature_flags/targeting_example.tf create mode 100644 examples/v2/feature_flags/versions.tf rename examples/{ => v2}/full_config/README.md (89%) create mode 100644 examples/v2/full_config/flags.tf create mode 100644 examples/v2/full_config/production-env-configs.tf create mode 100644 examples/v2/full_config/project.tf create mode 100644 examples/v2/full_config/roles.tf create mode 100644 examples/v2/full_config/staging-env-configs.tf create mode 100644 examples/v2/full_config/versions.tf create mode 100644 examples/v2/segment/example.tf create mode 100644 examples/v2/segment/versions.tf rename examples/{ => v2}/webhook/README.md (83%) rename examples/{ => v2}/webhook/example.tf (81%) create mode 100644 examples/v2/webhook/versions.tf diff --git a/.gitignore b/.gitignore index 57d850fd..22624338 100644 --- a/.gitignore +++ b/.gitignore @@ -34,11 +34,13 @@ /terraform-provider-launchdarkly /build/ /dist +/vendor/ +launchdarkly/__debug_bin website/vendor website/.vagrant website/.bundle website/build website/node_modules -.vscode/ \ No newline at end of file +.vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 272a3d48..85bc42f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,76 @@ ## [1.7.1] (August 24, 2021) +ENHANCEMENTS: + +- Improved test coverage. + +NOTES: + +- As part of the ongoing deprecation of Terraform 0.11, the LaunchDarkly provider now only supports Terraform 0.12 and higher. + +- This release changes the way LaunchDarkly recommends you manage `launchdarkly_environment` and `launchdarkly_project` resources in tandem. It is recommended you do not manage environments as separate resources _unless_ you wish to manage the encapsulating project externally (not via Terraform). As such, at least one `environments` attribute will now be `Required` on the `launchdarkly_project` resource, but you will also be able to manage environments outside of Terraform on Terraform-managed projects if you do not import them into the Terraform state as a configuration block on the encapsulating project resource. + +- The deprecated `launchdarkly_destination` resource `enabled` field has been removed in favor of `on`. `on` now defaults to `false` when not explicitly set. + +- The `default_on_variation` and `default_off_variation` properties on the `launchdarkly_feature_flag` resource have now been replaced with a computed `defaults` block containing the properties `on_variation` and `off_variation` that refer to the variations in question by index rather than value. + +- The `launchdarkly_feature_flag_environment` resource and data source `target` attribute schema has been modified to include a new `variation` attribute. Here `variation` represents the index of the feature flag variation to serve if a user target is matched. + +- The deprecated `launchdarkly_feature_flag_environment` resource `targeting_enabled` field has been removed in favor of `on`. `on` now defaults to `false` when not explicitly set. + +- The deprecated `launchdarkly_feature_flag_environment` resource `user_targets` field has been removed in favor of `targets`. `targets` now defaults to null when not explicitly set. + +- The deprecated `launchdarkly_feature_flag_environment` resource `flag_fallthrough` field has been removed in favor of `fallthrough`. + +- The deprecated `launchdarkly_webhooks` resource `enabled` field has been removed in favor of `on`. `on` is now a required field. + +- The deprecated `launchdarkly_webhooks` resource `policy_statements` field has been removed in favor of `statements`. + +- `off_variation` and `fallthrough` (previously `flag_fallthrough`) on `launchdarkly_feature_flag_environment` are now `Required` fields. + +- Most optional fields will now be removed or revert to their null / false value when not explicitly set and / or when removed, including: + + - `on` on the `launchdarkly_destination` resource + + - `include_in_snippet` on the `launchdarkly_project` resource + + - on the `launchdarkly_environment` resource and in `environment` blocks on the `launchdarkly_project` resource: + + - `secure_mode` + + - `default_track_events` + + - `require_comments` + + - `confirm_changes` + + - `default_ttl` (reverts to `0`) + + - on the `launchdarkly_feature_flag_environment` resource: + + - `on` (previously `targeting_enabled`, reverts to `false`) + + - `rules` + + - `targets` (previously `user_targets`) + + - `prerequisites` + + - `track_events` (reverts to `false`) + +BUG FIXES: + +- Fixed a bug in the `launchdarkly_webhook` resource where `statements` removed from the configuration were not being deleted in LaunchDarkly. + +- The `launchdarkly_feature_flag` resource `maintainer_id` field is now computed and will update the state with the most recently-set value when not explicitly set. + +- The `client_side_availability` attribute on the `launchdarkly_feature_flag` and `launchdarkly_project` data sources has been corrected to an array with a single map item. This means that you will need to add an index 0 when accessing this property from the state (ex. `client_side_availability.using_environment_id` will now have to be accessed as `client_side_availability.0.using_environment_id`). + +## [Unreleased] + BUG FIXES: -- Fixes [a bug](https://github.com/launchdarkly/terraform-provider-launchdarkly/issues/60) where attempts to create `resource_launchdarkly_feature_flag` variations with an empty string value were throwing a panic. +- Fixes [a bug](https://github.com/launchdarkly/terraform-provider-launchdarkly/issues/60) where attempts to create `launchdarkly_feature_flag` variations with an empty string value were throwing a panic. NOTES: @@ -36,7 +104,7 @@ NOTES: - The `launchdarkly_feature_flag_environment` resource's `targeting_enabled` argument has been deprecated in favor of `on`. Please update your config to use `on` in order to maintain compatibility with future versions. -- The `resource_launchdarkly_access_token` resource's `policy_statements` argument has been deprecated in favor of `inline_roles`. Please update your config to use `inline_roles` in order to maintain compatibility with future versions. +- The `launchdarkly_access_token` resource's `policy_statements` argument has been deprecated in favor of `inline_roles`. Please update your config to use `inline_roles` in order to maintain compatibility with future versions. ## [1.6.0] (July 20, 2021) @@ -229,7 +297,7 @@ BUG FIXES: FEATURES: -- Add tags attribute to `resource_launchdarkly_environment`. [#5](https://github.com/launchdarkly/terraform-provider-launchdarkly/issues/5) +- Add tags attribute to `launchdarkly_environment` resource. [#5](https://github.com/launchdarkly/terraform-provider-launchdarkly/issues/5) - Add `maintainer_id` input validation. ENHANCEMENTS: diff --git a/examples/README.md b/examples/README.md index ff3d5bf8..c526ba94 100644 --- a/examples/README.md +++ b/examples/README.md @@ -12,11 +12,21 @@ Before getting started with the LaunchDarkly Terraform provider, make sure you h ## Contents -- [full_config](./full_config) contains an example of a simple but fully fleshed-out LaunchDarkly [project](https://docs.launchdarkly.com/home/managing-flags/projects) configuration, including [environments](https://docs.launchdarkly.com/home/managing-flags/environments), [feature flags](https://docs.launchdarkly.com/home/managing-flags), [team members](https://docs.launchdarkly.com/home/account-security/managing-your-team), [roles](https://docs.launchdarkly.com/home/account-security/custom-roles), and [segments](https://docs.launchdarkly.com/home/managing-users/segments). It also provides an example of how to organize a more complex configuration with multiple resources. -- [multiple_projects](./multiple_projects) contains an example of the simultaneous configuration of multiple [projects](https://docs.launchdarkly.com/home/managing-flags/projects) and associated [environments](https://docs.launchdarkly.com/home/managing-flags/environments) and [flags](https://docs.launchdarkly.com/home/managing-flags) in a single file. -- [custom_role](./custom_role) contains an example of how to configure a [custom role](https://docs.launchdarkly.com/home/account-security/custom-roles) within LaunchDarkly using Terraform. -- [feature_flags](./feature_flags) contains a full range of flag examples, covering both [flag variation types](https://docs.launchdarkly.com/home/managing-flags/flag-variations) and complex [targeting rules](https://docs.launchdarkly.com/home/managing-flags/targeting-users). -- [webhook](./webhook) contains an example of a [LaunchDarkly webhook](https://docs.launchdarkly.com/integrations/webhooks) configuration to send LD event notifications to an external endpoint. +- [v1](./v1) contains examples of configurations compatible with v1.X of the LaunchDarkly provider. Please note that this version is no longer maintained. + - [v1/full_config](./v1/full_config) contains an example of a simple but fully fleshed-out LaunchDarkly [project](https://docs.launchdarkly.com/home/managing-flags/projects) configuration, including [environments](https://docs.launchdarkly.com/home/managing-flags/environments), [feature flags](https://docs.launchdarkly.com/home/managing-flags), [team members](https://docs.launchdarkly.com/home/account-security/managing-your-team), [roles](https://docs.launchdarkly.com/home/account-security/custom-roles), and [segments](https://docs.launchdarkly.com/home/managing-users/segments). It provides an example of how to organize a more complex configuration with multiple resources. + - [v1/multiple_projects](./v1/multiple_projects) contains an example of the simultaneous configuration of multiple [projects](https://docs.launchdarkly.com/home/managing-flags/projects) and associated [environments](https://docs.launchdarkly.com/home/managing-flags/environments) and [flags](https://docs.launchdarkly.com/home/managing-flags) in a single file. + - [v1/feature_flags](./v1/feature_flags) contains a full range of flag examples, covering both [flag variation types](https://docs.launchdarkly.com/home/managing-flags/flag-variations) and complex [targeting rules](https://docs.launchdarkly.com/home/managing-flags/targeting-users). +- [v2](./v2) contains examples of configurations compatible with v2+ of the LaunchDarkly provider. + - [v2/full_config](./v2/full_config) contains an example of a simple but fully fleshed-out LaunchDarkly [project](https://docs.launchdarkly.com/home/managing-flags/projects) configuration with nested environments, [feature flags](https://docs.launchdarkly.com/home/managing-flags), [team members](https://docs.launchdarkly.com/home/account-security/managing-your-team), [roles](https://docs.launchdarkly.com/home/account-security/custom-roles), and [segments](https://docs.launchdarkly.com/home/managing-users/segments). It provides an example of how to organize a more complex configuration with multiple resources. + - [v2/feature_flags](./v2/feature_flags) contains a full range of flag examples, covering both [flag variation types](https://docs.launchdarkly.com/home/managing-flags/flag-variations) and complex [targeting rules](https://docs.launchdarkly.com/home/managing-flags/targeting-users). + - [v2/environment](./v2/environment) contains an example of how to configure a standalone [LaunchDarkly environment](https://docs.launchdarkly.com/home/organize/environments) in Terraform. Please note that this is only recommended in v2 of the provider if you wish to manage the encapsulating project outside of Terraform. + - [v2/segment](./v2/segment) contains an example of a [LaunchDarkly segment](https://docs.launchdarkly.com/home/data-export/segment) configuration to send LD event notifications to an external endpoint. + - [v2/webhook](./v2/webhook) contains an example of a [LaunchDarkly webhook](https://docs.launchdarkly.com/integrations/webhooks) configuration to send LD event notifications to an external endpoint. + - [v2/data_source_destination](./v2/data_source_destination) provides an example of how to configure a [LaunchDarkly data export destination](https://docs.launchdarkly.com/home/data-export) data source for easy reference in other resources. + - [v2/data_source_webhook](./v2/data_source_webhook) provides an example of how to configure a [LaunchDarkly webhook](https://docs.launchdarkly.com/integrations/webhooks) data source for easy reference in other resources. +- [access_token](./access_token) contains an example of how to configure LaunchDarkly [access tokens](https://docs.launchdarkly.com/home/account-security/api-access-tokens) using Terraform. This configuration is compatible with both v1 and v2 of the provider. +- [custom_role](./custom_role) contains an example of how to configure a [custom role](https://docs.launchdarkly.com/home/account-security/custom-roles) within LaunchDarkly using Terraform. This configuration is compatible with both v1 and v2 of the provider. +- [team_member](./team_member) contains an example of how to configure a [team member](https://docs.launchdarkly.com/home/members/managing) within LaunchDarkly using Terraform. This configuration is compatible with both v1 and v2 of the provider. - For an example of how to configure your provider if using Terraform version 0.13 or above, please see the [terraform_0.13](./terraform_0.13). diff --git a/examples/custom_role/example.tf b/examples/custom_role/example.tf index c7cb2bac..d778f995 100644 --- a/examples/custom_role/example.tf +++ b/examples/custom_role/example.tf @@ -1,9 +1,5 @@ # This config provides an example of a custom role that prevents management of any flag # with a "terraform-managed" tag to ensure these are only managed via terraform. - -provider "launchdarkly" { - version = "~> 1.0" -} resource "launchdarkly_custom_role" "exclude_terraform" { key = "exclude-terraform" name = "Exclude Terraform" diff --git a/examples/data_source_destination/versions.tf b/examples/custom_role/versions.tf similarity index 50% rename from examples/data_source_destination/versions.tf rename to examples/custom_role/versions.tf index d62355bc..cd0e962a 100644 --- a/examples/data_source_destination/versions.tf +++ b/examples/custom_role/versions.tf @@ -2,13 +2,8 @@ terraform { required_providers { launchdarkly = { source = "launchdarkly/launchdarkly" - version = "~> 1.5.1" + version = "~> 2.0" } } required_version = ">= 0.13" } - -# provider "launchdarkly" { -# version = "~> 1.6.0" -# source = "local/terraform-provider-launchdarkly" -# } \ No newline at end of file diff --git a/examples/team_member/example.tf b/examples/team_member/example.tf index 80e62b79..85901f32 100644 --- a/examples/team_member/example.tf +++ b/examples/team_member/example.tf @@ -1,7 +1,3 @@ -provider "launchdarkly" { - version = ">= 1.6.0" -} - resource "launchdarkly_team_member" "joe" { email = "joe@example.com" first_name = "Joe" diff --git a/examples/team_member/versions.tf b/examples/team_member/versions.tf index 6867ea9d..7052a021 100644 --- a/examples/team_member/versions.tf +++ b/examples/team_member/versions.tf @@ -2,6 +2,7 @@ terraform { required_providers { launchdarkly = { source = "launchdarkly/launchdarkly" + version = ">= 1.7" } } required_version = ">= 0.13" diff --git a/examples/feature_flags/README.md b/examples/v1/feature_flags/README.md similarity index 97% rename from examples/feature_flags/README.md rename to examples/v1/feature_flags/README.md index 6ee10948..cabe2521 100644 --- a/examples/feature_flags/README.md +++ b/examples/v1/feature_flags/README.md @@ -5,11 +5,13 @@ The LaunchDarkly provider provides two resources for configuring feature flags: [`launchdarkly_feature_flag`](https://www.terraform.io/docs/providers/launchdarkly/r/feature_flag.html), which allows you to configure and manipulate project-wide feature flag settings and [`launchdarkly_feature_flag_environment`](https://www.terraform.io/docs/providers/launchdarkly/r/feature_flag_environment.html), which allows you to manage environment-specific feature flag settings, such as [targeting rules](https://docs.launchdarkly.com/home/managing-flags/targeting-users) and [prerequisites](https://docs.launchdarkly.com/home/managing-flags/flag-prerequisites). This example contains three config files: + - [setup.tf](./setup.tf), which auths the provider and creates a project under which the flags will be created - [flag_types_example.tf](./flag_types_example.tf), which provides examples of the different ways you can define binary (boolean) and multivariate (string, numeric, and JSON) flag variations using the `launchdarkly_feature_flag` resource - [targeting_example.tf](./targeting_example.tf), which provides complex examples of user targeting using the `launchdarkly_feature_flag_environment` resource. For more detail on user targeting, see the [official LaunchDarkly documentation](https://docs.launchdarkly.com/home/managing-flags/targeting-users). ### Run + Init your working directory from the CL with `terraform init` and then apply the changes with `terraform apply`. You should see output resembling the following: ``` @@ -42,10 +44,10 @@ Terraform will perform the following actions: + env_key = "test" + flag_id = (known after apply) + id = (known after apply) - + targeting_enabled = true + + on = true + track_events = true - + flag_fallthrough { + + fallthrough { + bucket_by = "company" + rollout_weights = [ + 60000, @@ -71,18 +73,18 @@ Terraform will perform the following actions: } } - + user_targets { + + targets { + values = [ + "test_user0", ] } - + user_targets { + + targets { + values = [ + "test_user1", + "test_user2", ] } - + user_targets { + + targets { + values = [ + "test_user3", ] @@ -148,4 +150,4 @@ To view your flags, navigate to the Feature flags section on the left sidebar an You should be able to view specific flag policies by clicking into them. Here you can see how the policies for the user_targeting_flag would look: -![user_targeting_flag policies](../assets/images/feature-flag-targeting.png) \ No newline at end of file +![user_targeting_flag policies](../assets/images/feature-flag-targeting.png) diff --git a/examples/feature_flags/flag_types_example.tf b/examples/v1/feature_flags/flag_types_example.tf similarity index 100% rename from examples/feature_flags/flag_types_example.tf rename to examples/v1/feature_flags/flag_types_example.tf diff --git a/examples/feature_flags/setup.tf b/examples/v1/feature_flags/setup.tf similarity index 95% rename from examples/feature_flags/setup.tf rename to examples/v1/feature_flags/setup.tf index 39e29354..13222357 100644 --- a/examples/feature_flags/setup.tf +++ b/examples/v1/feature_flags/setup.tf @@ -2,7 +2,7 @@ # since feature flags require association with a specific project provider "launchdarkly" { - version = ">= 1.2.0" + version = ">= 1.7" } # since all projects are automatically created with a "test" and "production" diff --git a/examples/feature_flags/targeting_example.tf b/examples/v1/feature_flags/targeting_example.tf similarity index 85% rename from examples/feature_flags/targeting_example.tf rename to examples/v1/feature_flags/targeting_example.tf index 833d974d..5ec15919 100644 --- a/examples/feature_flags/targeting_example.tf +++ b/examples/v1/feature_flags/targeting_example.tf @@ -7,7 +7,7 @@ resource "launchdarkly_feature_flag_environment" "prereq_flag" { flag_id = launchdarkly_feature_flag.number_flag.id env_key = "production" - targeting_enabled = true + on = true prerequisites { flag_key = launchdarkly_feature_flag.boolean_flag.key @@ -26,8 +26,6 @@ resource "launchdarkly_feature_flag_environment" "prereq_flag" { # This flag provides an example of user-specific targeting in the test environment # on the string_flag defined in "flag_types_example.tf". -# The order of the user_targets blocks determines the index of the variation -# to be served to each set of users. # The rules block of this resource determines that the 0-index variation ("string1") will # be served to users whose names start with the letters a-e. # flag_fallthrough describes the default to serve if none of the other rules apply: @@ -37,16 +35,16 @@ resource "launchdarkly_feature_flag_environment" "prereq_flag" { resource "launchdarkly_feature_flag_environment" "user_targeting_flag" { flag_id = launchdarkly_feature_flag.string_flag.id env_key = "test" - targeting_enabled = true + on = true track_events = true - user_targets { - values = ["test_user0"] + targets { + values = ["test_user0"] } - user_targets { - values = ["test_user1", "test_user2"] + targets { + values = ["test_user1", "test_user2"] } - user_targets { - values = ["test_user3"] + targets { + values = ["test_user3"] } rules { clauses { diff --git a/examples/v1/feature_flags/versions.tf b/examples/v1/feature_flags/versions.tf new file mode 100644 index 00000000..6867ea9d --- /dev/null +++ b/examples/v1/feature_flags/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + launchdarkly = { + source = "launchdarkly/launchdarkly" + } + } + required_version = ">= 0.13" +} diff --git a/examples/v1/full_config/README.md b/examples/v1/full_config/README.md new file mode 100644 index 00000000..d1b1c7e3 --- /dev/null +++ b/examples/v1/full_config/README.md @@ -0,0 +1,26 @@ +## Example: full LD config + +### Introduction + +The LaunchDarkly Terraform provider allows you to configure a full suite of LaunchDarkly features using Terraform. Please note that the examples contained in this directory are compatible with an earlier version of our Terraform provider that is no longer maintained. For a more up-to-date example, see the [V2 configuration example](./../v2_full_config/). + +The sample configuration in this directory provides an example of how to organize a full project configuration that takes advantage of the following LaunchDarkly resource types: + +- [launchdarkly_project](https://www.terraform.io/docs/providers/launchdarkly/r/project.html) in [project.tf](./project.tf) +- [launchdarkly_environment](https://www.terraform.io/docs/providers/launchdarkly/r/environment.html) in [env-staging.tf](./env-staging.tf) and [env-dev.tf](./env-dev.tf) +- [launchdarkly_custom_role](https://www.terraform.io/docs/providers/launchdarkly/r/custom_role.html) and [launchdarkly_team_member](https://www.terraform.io/docs/providers/launchdarkly/r/team_member.html) in [roles.tf](./roles.tf) +- [launchdarkly_feature_flag](https://www.terraform.io/docs/providers/launchdarkly/r/feature_flag.html) and [launchdarkly_feature_flag_environment](https://www.terraform.io/docs/providers/launchdarkly/r/feature_flag_environment.html) in [flags.tf](./flags.tf) and [env-staging.tf](./env-staging.tf), respectively +- [launchdarkly_segment](https://www.terraform.io/docs/providers/launchdarkly/r/segment.html) in [env-dev.tf](./env-dev.tf) + +Resources not included in this example are: + +- [launchdarkly_webhook](https://www.terraform.io/docs/providers/launchdarkly/r/webhook.html), an example of which can be found in the [webhook](../webhook) directory +- [launchdarkly_destination](https://www.terraform.io/docs/providers/launchdarkly/r/destination.html) + +### Important notes + +- If you wish to use Terraform to configure existing LaunchDarkly resources, you will first need to import them from the CL using `terraform import`. For details on the precise syntax, select the relevant resource [from this page](https://www.terraform.io/docs/providers/launchdarkly/index.html) and scroll to the bottom. For example, if you want to configure the automatically-created production environment after creating the project, run `terraform import launchdarkly_environment.production tf-full-config/production` before running your env config files. + +### Run + +Init your working directory from the CL with `terraform init` and then apply the changes with `terraform apply`. If you are creating it for the first time, the output should end with something like `Plan: 11 to add, 0 to change, 0 to destroy.` diff --git a/examples/full_config/env-dev.tf b/examples/v1/full_config/env-dev.tf similarity index 100% rename from examples/full_config/env-dev.tf rename to examples/v1/full_config/env-dev.tf diff --git a/examples/full_config/env-staging.tf b/examples/v1/full_config/env-staging.tf similarity index 96% rename from examples/full_config/env-staging.tf rename to examples/v1/full_config/env-staging.tf index dec7a4b3..9b80ef5a 100644 --- a/examples/full_config/env-staging.tf +++ b/examples/v1/full_config/env-staging.tf @@ -15,7 +15,7 @@ resource "launchdarkly_environment" "staging" { resource "launchdarkly_feature_flag_environment" "ld_internal_tester_staging" { flag_id = launchdarkly_feature_flag.ld_internal_tester.id env_key = "staging" - targeting_enabled = true + on = true prerequisites { flag_key = launchdarkly_feature_flag.binary_flag.key diff --git a/examples/full_config/flags.tf b/examples/v1/full_config/flags.tf similarity index 100% rename from examples/full_config/flags.tf rename to examples/v1/full_config/flags.tf diff --git a/examples/full_config/project.tf b/examples/v1/full_config/project.tf similarity index 100% rename from examples/full_config/project.tf rename to examples/v1/full_config/project.tf diff --git a/examples/full_config/roles.tf b/examples/v1/full_config/roles.tf similarity index 100% rename from examples/full_config/roles.tf rename to examples/v1/full_config/roles.tf diff --git a/examples/multiple_projects/README.md b/examples/v1/multiple_projects/README.md similarity index 100% rename from examples/multiple_projects/README.md rename to examples/v1/multiple_projects/README.md diff --git a/examples/multiple_projects/example.tf b/examples/v1/multiple_projects/example.tf similarity index 98% rename from examples/multiple_projects/example.tf rename to examples/v1/multiple_projects/example.tf index eb85c39d..e2d25fe8 100644 --- a/examples/multiple_projects/example.tf +++ b/examples/v1/multiple_projects/example.tf @@ -4,7 +4,7 @@ # ----------------------------------------------------------------------------------- # # AUTH CONFIG provider "launchdarkly" { - version = "~> 1.0" + version = "~> 1.7" } # ----------------------------------------------------------------------------------- # @@ -59,7 +59,7 @@ resource "launchdarkly_feature_flag_environment" "basic_variation" { # key must be retrieved through the `launchdarkly_project` resource. env_key = launchdarkly_project.tf_project_1.environments.0.key - targeting_enabled = true + on = true rules { clauses { diff --git a/examples/data_source_destination/example.tf b/examples/v2/data_source_destination/example.tf similarity index 100% rename from examples/data_source_destination/example.tf rename to examples/v2/data_source_destination/example.tf diff --git a/examples/v2/data_source_destination/versions.tf b/examples/v2/data_source_destination/versions.tf new file mode 100644 index 00000000..cd0e962a --- /dev/null +++ b/examples/v2/data_source_destination/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_providers { + launchdarkly = { + source = "launchdarkly/launchdarkly" + version = "~> 2.0" + } + } + required_version = ">= 0.13" +} diff --git a/examples/v2/data_source_webhook/example.tf b/examples/v2/data_source_webhook/example.tf new file mode 100644 index 00000000..39821f9c --- /dev/null +++ b/examples/v2/data_source_webhook/example.tf @@ -0,0 +1,23 @@ +// Use the webhook datasource to grab existing webhook information from LaunchDarkly. +// All you need to provide is the id of the webhook. + +terraform { + required_providers { + launchdarkly = { + source = "launchdarkly/launchdarkly" + version = "~> 2.0" + } + } + required_version = ">= 0.13" +} + +// Get data from LaunchDarkly on an existing webhook, all you need to do is provide the id +data "launchdarkly_webhook" "example" { + id = "60f004b957922d2639124f6d" +} + +// Print out the name of the "example" webhook we just recieved from LaunchDarkly +output "launchdarkly_webhook_print" { + value = data.launchdarkly_webhook.example.name + sensitive = false +} diff --git a/examples/v2/environment/example.tf b/examples/v2/environment/example.tf new file mode 100644 index 00000000..4a71c47c --- /dev/null +++ b/examples/v2/environment/example.tf @@ -0,0 +1,21 @@ +// Managing environments directly using the launchdarkly_environment resource is only +// recommended if you intend to manage the project outside of Terraform. If you wish to +// test this configuration, please update the project_key to match an existing project in +// your LaunchDarkly account. See the documentation for more information. +terraform { + required_providers { + launchdarkly = { + source = "launchdarkly/launchdarkly" + version = "~> 2.0" + } + } + required_version = ">= 0.13" +} + +resource "launchdarkly_environment" "env_test" { + key = "testing-bug" + name = "testing bug" + color = "AAAAAA" + project_key = "default" + confirm_changes = "false" +} diff --git a/examples/v2/feature_flags/README.md b/examples/v2/feature_flags/README.md new file mode 100644 index 00000000..d11156c9 --- /dev/null +++ b/examples/v2/feature_flags/README.md @@ -0,0 +1,301 @@ +## Example: feature flags + +### Introduction + +The LaunchDarkly provider provides two resources for configuring feature flags: [`launchdarkly_feature_flag`](https://www.terraform.io/docs/providers/launchdarkly/r/feature_flag.html), which allows you to configure and manipulate project-wide feature flag settings and [`launchdarkly_feature_flag_environment`](https://www.terraform.io/docs/providers/launchdarkly/r/feature_flag_environment.html), which allows you to manage environment-specific feature flag settings, such as [targeting rules](https://docs.launchdarkly.com/home/managing-flags/targeting-users) and [prerequisites](https://docs.launchdarkly.com/home/managing-flags/flag-prerequisites). + +This example contains three config files: + +- [setup.tf](./setup.tf), which auths the provider and creates a project under which the flags will be created +- [flag_types_example.tf](./flag_types_example.tf), which provides examples of the different ways you can define binary (boolean) and multivariate (string, numeric, and JSON) flag variations using the `launchdarkly_feature_flag` resource +- [targeting_example.tf](./targeting_example.tf), which provides complex examples of user targeting using the `launchdarkly_feature_flag_environment` resource. For more detail on user targeting, see the [official LaunchDarkly documentation](https://docs.launchdarkly.com/home/managing-flags/targeting-users). + +### Run + +Init your working directory from the CL with `terraform init` and then apply the changes with `terraform apply`. You should see output resembling the following: + +``` +An execution plan has been generated and is shown below. +Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + # launchdarkly_feature_flag.boolean_flag will be created + + resource "launchdarkly_feature_flag" "boolean_flag" { + + description = "An example boolean feature flag that can be turned either on or off" + + id = (known after apply) + + include_in_snippet = false + + key = "boolean-flag" + + maintainer_id = (known after apply) + + name = "Bool feature flag" + + project_key = "tf-flag-examples" + + temporary = false + + variation_type = "boolean" + + + defaults { + + off_variation = (known after apply) + + on_variation = (known after apply) + } + + + variations { + + description = (known after apply) + + name = (known after apply) + + value = (known after apply) + } + } + + # launchdarkly_feature_flag.json_flag will be created + + resource "launchdarkly_feature_flag" "json_flag" { + + description = "An example of a multivariate feature flag with JSON variations" + + id = (known after apply) + + include_in_snippet = false + + key = "json-flag" + + maintainer_id = (known after apply) + + name = "JSON-based feature flag" + + project_key = "tf-flag-examples" + + tags = [ + + "terraform-managed", + ] + + temporary = false + + variation_type = "json" + + + defaults { + + off_variation = (known after apply) + + on_variation = (known after apply) + } + + + variations { + + value = jsonencode( + { + + foo = "bar" + } + ) + } + + variations { + + value = jsonencode( + { + + extra = { + + nested = "json" + } + + foo = "baz" + } + ) + } + } + + # launchdarkly_feature_flag.number_flag will be created + + resource "launchdarkly_feature_flag" "number_flag" { + + description = "An example of a multivariate feature flag with numeric variations" + + id = (known after apply) + + include_in_snippet = false + + key = "number-flag" + + maintainer_id = (known after apply) + + name = "Number value-based feature flag" + + project_key = "tf-flag-examples" + + tags = [ + + "terraform-managed", + ] + + temporary = false + + variation_type = "number" + + + defaults { + + off_variation = (known after apply) + + on_variation = (known after apply) + } + + + variations { + + name = "Big Number Variation" + + value = "123000000" + } + + variations { + + name = "Small Number Variation" + + value = "100" + } + + variations { + + name = "Float Variation" + + value = "123.45" + } + } + + # launchdarkly_feature_flag.string_flag will be created + + resource "launchdarkly_feature_flag" "string_flag" { + + description = "An example of a multivariate feature flag with string variations" + + id = (known after apply) + + include_in_snippet = false + + key = "string-flag" + + maintainer_id = (known after apply) + + name = "String-based feature flag" + + project_key = "tf-flag-examples" + + tags = [ + + "terraform-managed", + ] + + temporary = false + + variation_type = "string" + + + defaults { + + off_variation = (known after apply) + + on_variation = (known after apply) + } + + + variations { + + description = "one of three variations" + + name = "A String" + + value = "string1" + } + + variations { + + name = "Another String" + + value = "string2" + } + + variations { + + name = "Yet Another String" + + value = "string3" + } + } + + # launchdarkly_feature_flag_environment.prereq_flag will be created + + resource "launchdarkly_feature_flag_environment" "prereq_flag" { + + env_key = "production" + + flag_id = (known after apply) + + id = (known after apply) + + off_variation = 1 + + on = true + + track_events = false + + + fallthrough { + + variation = 0 + } + + + prerequisites { + + flag_key = "boolean-flag" + + variation = 1 + } + + + rules { + + clauses { + + attribute = "country" + + negate = false + + op = "matches" + + value_type = "string" + + values = [ + + "uk", + + "aus", + + "usa", + ] + } + } + } + + # launchdarkly_feature_flag_environment.user_targeting_flag will be created + + resource "launchdarkly_feature_flag_environment" "user_targeting_flag" { + + env_key = "test" + + flag_id = (known after apply) + + id = (known after apply) + + off_variation = 0 + + on = true + + track_events = true + + + fallthrough { + + bucket_by = "company" + + rollout_weights = [ + + 60000, + + 30000, + + 10000, + ] + } + + + rules { + + variation = 0 + + + clauses { + + attribute = "name" + + negate = false + + op = "startsWith" + + value_type = "string" + + values = [ + + "a", + + "b", + + "c", + + "d", + + "e", + ] + } + } + + + targets { + + values = [ + + "test_user0", + ] + + variation = 0 + } + + targets { + + values = [ + + "test_user1", + + "test_user2", + ] + + variation = 1 + } + + targets { + + values = [ + + "test_user3", + ] + + variation = 2 + } + } + + # launchdarkly_project.tf_flag_examples will be created + + resource "launchdarkly_project" "tf_flag_examples" { + + id = (known after apply) + + include_in_snippet = false + + key = "tf-flag-examples" + + name = "Terraform Project for Flag Examples" + + tags = [ + + "terraform-managed", + ] + + + environments { + + api_key = (sensitive value) + + client_side_id = (sensitive value) + + color = "ababab" + + confirm_changes = (known after apply) + + default_track_events = (known after apply) + + default_ttl = (known after apply) + + key = "example-env" + + mobile_key = (sensitive value) + + name = "example environment" + + require_comments = (known after apply) + + secure_mode = (known after apply) + } + } + +Plan: 7 to add, 0 to change, 0 to destroy. +``` + +Since Terraform handles all the files in a given directory as a single configuration, all the configurations from the three files in this directory will be applied together. Once you have confirmed the changes to Terraform's prompt, it will apply them with output resembling the following: + +``` +launchdarkly_project.tf_flag_examples: Creating... +launchdarkly_project.tf_flag_examples: Creation complete after 0s [id=tf-flag-examples] +launchdarkly_feature_flag.boolean_flag: Creating... +launchdarkly_feature_flag.json_flag: Creating... +launchdarkly_feature_flag.number_flag: Creating... +launchdarkly_feature_flag.string_flag: Creating... +launchdarkly_feature_flag.boolean_flag: Creation complete after 1s [id=tf-flag-examples/boolean-flag] +launchdarkly_feature_flag.json_flag: Creation complete after 1s [id=tf-flag-examples/json-flag] +launchdarkly_feature_flag.string_flag: Creation complete after 1s [id=tf-flag-examples/string-flag] +launchdarkly_feature_flag.number_flag: Creation complete after 1s [id=tf-flag-examples/number-flag] +launchdarkly_feature_flag_environment.user_targeting_flag: Creating... +launchdarkly_feature_flag_environment.prereq_flag: Creating... +launchdarkly_feature_flag_environment.user_targeting_flag: Creation complete after 0s [id=tf-flag-examples/test/string-flag] +launchdarkly_feature_flag_environment.prereq_flag: Creation complete after 0s [id=tf-flag-examples/production/number-flag] + +Apply complete! Resources: 7 added, 0 changed, 0 destroyed. +``` + +To view your flags, navigate to the Feature flags section on the left sidebar and search for the "terraform-managed" tag: + +!["terraform-managed" tags](../assets/images/feature-flags-variation-types.png) + +You should be able to view specific flag policies by clicking into them. Here you can see how the policies for the user_targeting_flag would look: + +![user_targeting_flag policies](../assets/images/feature-flag-targeting.png) diff --git a/examples/v2/feature_flags/flag_types_example.tf b/examples/v2/feature_flags/flag_types_example.tf new file mode 100644 index 00000000..403118d4 --- /dev/null +++ b/examples/v2/feature_flags/flag_types_example.tf @@ -0,0 +1,97 @@ +# This config provides examples of various flag variation types: boolean, string, numeric, and JSON. + +# ----------------------------------------------------------------------------------- # +# BINARY FLAG +resource "launchdarkly_feature_flag" "boolean_flag" { + project_key = launchdarkly_project.tf_flag_examples.key + key = "boolean-flag" + name = "Bool feature flag" + description = "An example boolean feature flag that can be turned either on or off" + variation_type = "boolean" +} + +# ----------------------------------------------------------------------------------- # +# MULTIVARIATE FLAGS +# For multivariate flags, each variation must be described in a separate 'variations' block +# with required 'value' field and optional 'name' and 'description' fields. + +# create a multivariate flag with string-value variations +resource "launchdarkly_feature_flag" "string_flag" { + project_key = launchdarkly_project.tf_flag_examples.key + key = "string-flag" + name = "String-based feature flag" + description = "An example of a multivariate feature flag with string variations" + + variation_type = "string" + variations { + name = "A String" + description = "one of three variations" + value = "string1" + } + variations { + name = "Another String" + value = "string2" + } + variations { + name = "Yet Another String" + value = "string3" + } + tags = [ + "terraform-managed" + ] +} + +# create a multivariate flag with number-value variations +# Both ints and floats are acceptable, but please note that trailing zeroes +# will be trimmed off of floats, i.e. both 123 and 123.00 will return output 123. +resource "launchdarkly_feature_flag" "number_flag" { + project_key = launchdarkly_project.tf_flag_examples.key + key = "number-flag" + name = "Number value-based feature flag" + description = "An example of a multivariate feature flag with numeric variations" + + variation_type = "number" + variations { + name = "Big Number Variation" + value = 123000000 + } + variations { + name = "Small Number Variation" + value = 100 + } + variations { + name = "Float Variation" + value = 123.45 + } + tags = [ + "terraform-managed" + ] +} + +# create a multivariate flag with JSON-value variations +# Please note that since terraform evaluates all input as strings, +# multi-line input such as jsons must use a marker like "<= len(schemaVariations) { + return nil, fmt.Errorf("default on_variation %v is out of range, must be between 0 and %v inclusive", on, len(schemaVariations)-1) } - if !offFound { - return fmt.Errorf("default_off_variation %q is not defined as a variation", offValue) - } - return nil -} - -func defaultVariationsFromResourceData(d *schema.ResourceData) (*ldapi.Defaults, error) { - err := validateDefaultVariations(d) - if err != nil { - if err == errDefaultsNotSet { - return nil, nil - } - return nil, err - } - schemaVariations := d.Get(VARIATIONS).([]interface{}) - if len(schemaVariations) == 0 { - schemaVariations = defaultBooleanVariations - } - onValue := d.Get(DEFAULT_ON_VARIATION).(string) - offValue := d.Get(DEFAULT_OFF_VARIATION).(string) - - var on *int - var off *int - for i, v := range schemaVariations { - i := i - variation := v.(map[string]interface{}) - val := variation[VALUE].(string) - if val == onValue { - on = &i - } - if val == offValue { - off = &i - } + if off >= len(schemaVariations) { + return nil, fmt.Errorf("default off_variation %v is out of range, must be between 0 and %v inclusive", off, len(schemaVariations)-1) } - return &ldapi.Defaults{OnVariation: int32(*on), OffVariation: int32(*off)}, nil + return &ldapi.Defaults{OnVariation: int32(on), OffVariation: int32(off)}, nil } diff --git a/launchdarkly/default_variations_helper_test.go b/launchdarkly/default_variations_helper_test.go index 5860ba89..4ea95467 100644 --- a/launchdarkly/default_variations_helper_test.go +++ b/launchdarkly/default_variations_helper_test.go @@ -4,7 +4,7 @@ import ( "errors" "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,9 +18,8 @@ func TestDefaultVariationsFromResourceData(t *testing.T) { expectedErr error }{ { - name: "no defaults", + name: "no defaults on boolean", vars: map[string]interface{}{ - VARIATION_TYPE: "boolean", VARIATIONS: []interface{}{ map[string]interface{}{ VALUE: "true", @@ -30,33 +29,14 @@ func TestDefaultVariationsFromResourceData(t *testing.T) { }, }, }, - expected: nil, - }, - { - name: "basic defaults, implicit variations", - vars: map[string]interface{}{ - VARIATION_TYPE: "boolean", - DEFAULT_ON_VARIATION: "true", - DEFAULT_OFF_VARIATION: "false", - }, expected: &ldapi.Defaults{ OnVariation: 0, OffVariation: 1, }, }, - { - name: "invalid defaults, implicit variations", - vars: map[string]interface{}{ - VARIATION_TYPE: "boolean", - DEFAULT_ON_VARIATION: "a", - DEFAULT_OFF_VARIATION: "c", - }, - expectedErr: errors.New(`default_on_variation "a" is not defined as a variation`), - }, { name: "basic defaults", vars: map[string]interface{}{ - VARIATION_TYPE: "boolean", VARIATIONS: []interface{}{ map[string]interface{}{ VALUE: "true", @@ -65,8 +45,11 @@ func TestDefaultVariationsFromResourceData(t *testing.T) { VALUE: "false", }, }, - DEFAULT_ON_VARIATION: "true", - DEFAULT_OFF_VARIATION: "false", + DEFAULTS: []interface{}{ + map[string]interface{}{ + ON_VARIATION: 0, + OFF_VARIATION: 1, + }}, }, expected: &ldapi.Defaults{ OnVariation: 0, @@ -85,46 +68,16 @@ func TestDefaultVariationsFromResourceData(t *testing.T) { VALUE: "false", }, }, - DEFAULT_ON_VARIATION: "not a boolean", - DEFAULT_OFF_VARIATION: "false", - }, - expectedErr: errors.New(`default_on_variation "not a boolean" is not defined as a variation`), - }, - { - name: "invalid default off value", - vars: map[string]interface{}{ - VARIATION_TYPE: "boolean", - VARIATIONS: []interface{}{ - map[string]interface{}{ - VALUE: "true", - }, + DEFAULTS: []interface{}{ map[string]interface{}{ - VALUE: "false", - }, - }, - DEFAULT_ON_VARIATION: "true", - DEFAULT_OFF_VARIATION: "not a boolean", - }, - expectedErr: errors.New(`default_off_variation "not a boolean" is not defined as a variation`), - }, - { - name: "missing default off", - vars: map[string]interface{}{ - VARIATION_TYPE: "boolean", - VARIATIONS: []interface{}{ - map[string]interface{}{ - VALUE: "true", - }, - map[string]interface{}{ - VALUE: "false", - }, - }, - DEFAULT_ON_VARIATION: "true", + ON_VARIATION: 2, + OFF_VARIATION: 1, + }}, }, - expectedErr: errors.New(`default_off_variation is required when default_on_variation is defined`), + expectedErr: errors.New(`default on_variation 2 is out of range, must be between 0 and 1 inclusive`), }, { - name: "missing default on", + name: "invalid default off value", vars: map[string]interface{}{ VARIATION_TYPE: "boolean", VARIATIONS: []interface{}{ @@ -135,33 +88,13 @@ func TestDefaultVariationsFromResourceData(t *testing.T) { VALUE: "false", }, }, - DEFAULT_OFF_VARIATION: "false", - }, - expectedErr: errors.New(`default_on_variation is required when default_off_variation is defined`), - }, - { - name: "string variations", - vars: map[string]interface{}{ - VARIATION_TYPE: "string", - VARIATIONS: []interface{}{ + DEFAULTS: []interface{}{ map[string]interface{}{ - NAME: "nameValue", - DESCRIPTION: "descValue", - VALUE: "a string value", - }, - map[string]interface{}{ - NAME: "nameValue2", - DESCRIPTION: "descValue2", - VALUE: "another string value", - }, - }, - DEFAULT_ON_VARIATION: "a string value", - DEFAULT_OFF_VARIATION: "a string value", - }, - expected: &ldapi.Defaults{ - OnVariation: 0, - OffVariation: 0, + ON_VARIATION: 0, + OFF_VARIATION: 5, + }}, }, + expectedErr: errors.New(`default off_variation 5 is out of range, must be between 0 and 1 inclusive`), }, } @@ -169,13 +102,21 @@ func TestDefaultVariationsFromResourceData(t *testing.T) { t.Run(tc.name, func(t *testing.T) { resourceData := schema.TestResourceDataRaw(t, map[string]*schema.Schema{VARIATION_TYPE: variationTypeSchema(), VARIATIONS: variationsSchema(), - DEFAULT_ON_VARIATION: { - Type: schema.TypeString, - Optional: true, - }, - DEFAULT_OFF_VARIATION: { - Type: schema.TypeString, + DEFAULTS: { + Type: schema.TypeList, Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + ON_VARIATION: { + Type: schema.TypeInt, + Required: true, + }, + OFF_VARIATION: { + Type: schema.TypeInt, + Required: true, + }, + }, + }, }}, tc.vars, ) diff --git a/launchdarkly/destination_helper.go b/launchdarkly/destination_helper.go index b221641d..525a040a 100644 --- a/launchdarkly/destination_helper.go +++ b/launchdarkly/destination_helper.go @@ -3,7 +3,7 @@ package launchdarkly import ( "fmt" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) var ( diff --git a/launchdarkly/environments_helper.go b/launchdarkly/environments_helper.go index 24399d0a..ffb10871 100644 --- a/launchdarkly/environments_helper.go +++ b/launchdarkly/environments_helper.go @@ -5,8 +5,8 @@ import ( "log" "net/http" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) @@ -41,31 +41,31 @@ func baseEnvironmentSchema(forProject bool) map[string]*schema.Schema { DEFAULT_TTL: { Type: schema.TypeInt, Optional: true, - Computed: true, + Default: 0, // Default TTL should be between 0 and 60 minutes: https://docs.launchdarkly.com/docs/environments Description: "The TTL for the environment. This must be between 0 and 60 minutes. The TTL setting only applies to environments using the PHP SDK", ValidateFunc: validation.IntBetween(0, 60), }, SECURE_MODE: { - Computed: true, + Default: false, Type: schema.TypeBool, Optional: true, Description: "Whether or not to use secure mode. Secure mode ensures a user of the client-side SDK cannot impersonate another user", }, DEFAULT_TRACK_EVENTS: { - Computed: true, + Default: false, Type: schema.TypeBool, Optional: true, Description: "Whether or not to default to sending data export events for flags created in the environment", }, REQUIRE_COMMENTS: { - Computed: true, + Default: false, Type: schema.TypeBool, Optional: true, Description: "Whether or not to require comments for flag and segment changes in this environment", }, CONFIRM_CHANGES: { - Computed: true, + Default: false, Type: schema.TypeBool, Optional: true, Description: "Whether or not to require confirmation for flag and segment changes in this environment", @@ -132,8 +132,8 @@ func environmentSchema(forProject bool) map[string]*schema.Schema { return schemaMap } -func dataSourceEnvironmentSchema(forPoject bool) map[string]*schema.Schema { - schemaMap := baseEnvironmentSchema(forPoject) +func dataSourceEnvironmentSchema(forProject bool) map[string]*schema.Schema { + schemaMap := baseEnvironmentSchema(forProject) schemaMap[NAME] = &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -225,7 +225,7 @@ func environmentRead(d *schema.ResourceData, meta interface{}, isDataSource bool env := envRaw.(ldapi.Environment) d.SetId(projectKey + "/" + key) - _ = d.Set(key, env.Key) + _ = d.Set(KEY, env.Key) _ = d.Set(NAME, env.Name) _ = d.Set(API_KEY, env.ApiKey) _ = d.Set(MOBILE_KEY, env.MobileKey) diff --git a/launchdarkly/fallthrough_helper.go b/launchdarkly/fallthrough_helper.go index e48768d4..986f10dc 100644 --- a/launchdarkly/fallthrough_helper.go +++ b/launchdarkly/fallthrough_helper.go @@ -3,18 +3,18 @@ package launchdarkly import ( "errors" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) -func fallthroughSchema() *schema.Schema { +func fallthroughSchema(forDataSource bool) *schema.Schema { return &schema.Schema{ Type: schema.TypeList, - Optional: true, + Required: !forDataSource, + Optional: forDataSource, Description: "Nested block describing the default variation to serve if no prerequisites, user_target, or rules apply. You must specify either variation or rollout_weights", - Computed: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -42,10 +42,6 @@ type fallthroughModel struct { } func validateFallThroughResourceData(f []interface{}) error { - if len(f) == 0 { - return nil - } - if !isPercentRollout(f) { fall := f[0].(map[string]interface{}) if bucketBy, ok := fall[BUCKET_BY]; ok { @@ -68,23 +64,12 @@ func isPercentRollout(fall []interface{}) bool { } func fallthroughFromResourceData(d *schema.ResourceData) (fallthroughModel, error) { - var f []interface{} - fallthroughHasChange := d.HasChange(FALLTHROUGH) - flagFallthroughHasChange := d.HasChange(FLAG_FALLTHROUGH) - if fallthroughHasChange { - f = d.Get(FALLTHROUGH).([]interface{}) - } else if flagFallthroughHasChange { - f = d.Get(FLAG_FALLTHROUGH).([]interface{}) - } + f := d.Get(FALLTHROUGH).([]interface{}) err := validateFallThroughResourceData(f) if err != nil { return fallthroughModel{}, err } - if len(f) == 0 { - return fallthroughModel{Variation: intPtr(0)}, nil - } - fall := f[0].(map[string]interface{}) if isPercentRollout(f) { rollout := fallthroughModel{Rollout: rolloutFromResourceData(fall[ROLLOUT_WEIGHTS])} diff --git a/launchdarkly/feature_flag_environment_helper.go b/launchdarkly/feature_flag_environment_helper.go index f32bcef4..8129da65 100644 --- a/launchdarkly/feature_flag_environment_helper.go +++ b/launchdarkly/feature_flag_environment_helper.go @@ -7,27 +7,17 @@ import ( "strings" "github.com/antihax/optional" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) -func baseFeatureFlagEnvironmentSchema() map[string]*schema.Schema { - deprecatedFallthrough := fallthroughSchema() - deprecatedFallthrough.Deprecated = "'flag_fallthrough' is deprecated in favor of 'fallthrough'. This field will be removed in the next major release of the LaunchDarkly provider" - deprecatedFallthrough.ConflictsWith = []string{FALLTHROUGH} - newFallthrough := fallthroughSchema() - newFallthrough.ConflictsWith = []string{FLAG_FALLTHROUGH} - deprecatedTargets := targetsSchema() - deprecatedTargets.Deprecated = "'user_targets' is deprecated in favor of 'targets'. This field will be removed in the next major release of the LaunchDarkly provider" - deprecatedTargets.ConflictsWith = []string{TARGETS} - newTargets := targetsSchema() - newTargets.ConflictsWith = []string{USER_TARGETS} +func baseFeatureFlagEnvironmentSchema(forDataSource bool) map[string]*schema.Schema { return map[string]*schema.Schema{ FLAG_ID: { Type: schema.TypeString, Required: true, - Description: "The feature flag's unique id in the format `/`", + Description: "The global feature flag's unique id in the format `/`", ForceNew: true, ValidateFunc: validateFlagID, }, @@ -38,38 +28,27 @@ func baseFeatureFlagEnvironmentSchema() map[string]*schema.Schema { ForceNew: true, ValidateFunc: validateKey(), }, - TARGETING_ENABLED: { - Type: schema.TypeBool, - Optional: true, - Deprecated: "'targeting_enabled' is deprecated in favor of 'on'", - Description: "Whether targeting is enabled", - Computed: true, - ConflictsWith: []string{ON}, - }, ON: { - Type: schema.TypeBool, - Optional: true, - Description: "Whether targeting is enabled", - Computed: true, - ConflictsWith: []string{TARGETING_ENABLED}, + Type: schema.TypeBool, + Optional: true, + Description: "Whether targeting is enabled", + Default: false, }, - USER_TARGETS: deprecatedTargets, - TARGETS: newTargets, - RULES: rulesSchema(), - PREREQUISITES: prerequisitesSchema(), - FLAG_FALLTHROUGH: deprecatedFallthrough, - FALLTHROUGH: newFallthrough, + TARGETS: targetsSchema(), + RULES: rulesSchema(), + PREREQUISITES: prerequisitesSchema(), + FALLTHROUGH: fallthroughSchema(forDataSource), TRACK_EVENTS: { Type: schema.TypeBool, Optional: true, Description: "Whether to send event data back to LaunchDarkly", - Computed: true, + Default: false, }, OFF_VARIATION: { Type: schema.TypeInt, - Optional: true, + Required: !forDataSource, + Optional: forDataSource, Description: "The index of the variation to serve if targeting is disabled", - Computed: true, ValidateFunc: validation.IntAtLeast(0), }, } @@ -116,12 +95,10 @@ func featureFlagEnvironmentRead(d *schema.ResourceData, raw interface{}, isDataS if isDataSource { d.SetId(projectKey + "/" + envKey + "/" + flagKey) } - _ = d.Set(KEY, flag.Key) + _ = d.Set(FLAG_ID, projectKey+"/"+flag.Key) // Computed values are set even if they do not exist on the config - _ = d.Set(TARGETING_ENABLED, environment.On) _ = d.Set(ON, environment.On) - _ = d.Set(OFF_VARIATION, environment.OffVariation) _ = d.Set(TRACK_EVENTS, environment.TrackEvents) _ = d.Set(PREREQUISITES, prerequisitesToResourceData(environment.Prerequisites)) @@ -134,24 +111,19 @@ func featureFlagEnvironmentRead(d *schema.ResourceData, raw interface{}, isDataS return fmt.Errorf("failed to set rules on flag with key %q: %v", flagKey, err) } - // user_targets is deprecated in favor of targets err = d.Set(TARGETS, targetsToResourceData(environment.Targets)) if err != nil { return fmt.Errorf("failed to set targets on flag with key %q: %v", flagKey, err) } - err = d.Set(USER_TARGETS, targetsToResourceData(environment.Targets)) - if err != nil { - return fmt.Errorf("failed to set user_targets on flag with key %q: %v", flagKey, err) - } - // flag_fallthrough is deprecated in favor of fallthrough err = d.Set(FALLTHROUGH, fallthroughToResourceData(environment.Fallthrough_)) if err != nil { - return fmt.Errorf("failed to set fallthrough on flag with key %q: %v", flagKey, err) + return fmt.Errorf("failed to set flag fallthrough on flag with key %q: %v", flagKey, err) } - err = d.Set(FLAG_FALLTHROUGH, fallthroughToResourceData(environment.Fallthrough_)) + + err = d.Set(OFF_VARIATION, environment.OffVariation) if err != nil { - return fmt.Errorf("failed to set flag_fallthrough on flag with key %q: %v", flagKey, err) + return fmt.Errorf("failed to set off_variation on flag with key %q: %v", flagKey, err) } return nil diff --git a/launchdarkly/feature_flags_helper.go b/launchdarkly/feature_flags_helper.go index e2f61582..354f0045 100644 --- a/launchdarkly/feature_flags_helper.go +++ b/launchdarkly/feature_flags_helper.go @@ -6,7 +6,8 @@ import ( "net/http" "strings" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) @@ -29,7 +30,8 @@ func baseFeatureFlagSchema() map[string]*schema.Schema { MAINTAINER_ID: { Type: schema.TypeString, Optional: true, - Description: "The LaunchDarkly id of the user who will maintain the flag", + Computed: true, + Description: "The LaunchDarkly id of the user who will maintain the flag. If not set, the API will automatically apply the member associated with your Terraform API key or the most recently set maintainer", ValidateFunc: validateID(), }, DESCRIPTION: { @@ -52,15 +54,28 @@ func baseFeatureFlagSchema() map[string]*schema.Schema { }, TAGS: tagsSchema(), CUSTOM_PROPERTIES: customPropertiesSchema(), - DEFAULT_ON_VARIATION: { - Type: schema.TypeString, + DEFAULTS: { + Type: schema.TypeList, Optional: true, - Description: "The value of the variation served when the flag is on for new environments", - }, - DEFAULT_OFF_VARIATION: { - Type: schema.TypeString, - Optional: true, - Description: "The value of the variation served when the flag is off for new environments", + Computed: true, + Description: "The default variations used for this flag in new environments. If omitted, the first and last variation will be used", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + ON_VARIATION: { + Type: schema.TypeInt, + Required: true, + Description: "The index of the variation served when the flag is on for new environments", + ValidateFunc: validation.IntAtLeast(0), + }, + OFF_VARIATION: { + Type: schema.TypeInt, + Required: true, + Description: "The index of the variation served when the flag is off for new environments", + ValidateFunc: validation.IntAtLeast(0), + }, + }, + }, }, } } @@ -85,7 +100,7 @@ func featureFlagRead(d *schema.ResourceData, raw interface{}, isDataSource bool) } transformedCustomProperties := customPropertiesToResourceData(flag.CustomProperties) - _ = d.Set(key, flag.Key) + _ = d.Set(KEY, flag.Key) _ = d.Set(NAME, flag.Name) _ = d.Set(DESCRIPTION, flag.Description) _ = d.Set(INCLUDE_IN_SNIPPET, flag.IncludeInSnippet) @@ -93,10 +108,10 @@ func featureFlagRead(d *schema.ResourceData, raw interface{}, isDataSource bool) if isDataSource { CSA := *flag.ClientSideAvailability - clientSideAvailability := map[string]string{ - "using_environment_id": fmt.Sprintf("%v", CSA.UsingEnvironmentId), - "using_mobile_key": fmt.Sprintf("%v", CSA.UsingMobileKey), - } + clientSideAvailability := []map[string]interface{}{{ + "using_environment_id": CSA.UsingEnvironmentId, + "using_mobile_key": CSA.UsingMobileKey, + }} _ = d.Set(CLIENT_SIDE_AVAILABILITY, clientSideAvailability) } else { _ = d.Set(INCLUDE_IN_SNIPPET, flag.IncludeInSnippet) @@ -136,23 +151,19 @@ func featureFlagRead(d *schema.ResourceData, raw interface{}, isDataSource bool) return fmt.Errorf("failed to set custom properties on flag with key %q: %v", flag.Key, err) } + var defaults []map[string]interface{} if flag.Defaults != nil { - // this is to prevent the dangling plan values on subsequent post-removal applies - _, ok := d.GetOk(DEFAULT_ON_VARIATION) - // you cannot define one without the other so this should suffice - if ok { - onValue, err := variationValueToString(flag.Variations[flag.Defaults.OnVariation].Value, variationType) - if err != nil { - return err - } - _ = d.Set(DEFAULT_ON_VARIATION, onValue) - offValue, err := variationValueToString(flag.Variations[flag.Defaults.OffVariation].Value, variationType) - if err != nil { - return err - } - _ = d.Set(DEFAULT_OFF_VARIATION, offValue) - } + defaults = []map[string]interface{}{{ + ON_VARIATION: flag.Defaults.OnVariation, + OFF_VARIATION: flag.Defaults.OffVariation, + }} + } else { + defaults = []map[string]interface{}{{ + ON_VARIATION: 0, + OFF_VARIATION: len(flag.Variations) - 1, + }} } + _ = d.Set(DEFAULTS, defaults) d.SetId(projectKey + "/" + key) return nil diff --git a/launchdarkly/keys.go b/launchdarkly/keys.go index 7e977fd2..0429b811 100644 --- a/launchdarkly/keys.go +++ b/launchdarkly/keys.go @@ -59,19 +59,18 @@ const ( BUCKET_BY = "bucket_by" ROLLOUT_WEIGHTS = "rollout_weights" VARIATION = "variation" - USER_TARGETS = "user_targets" // deprecated TARGETS = "targets" PREREQUISITES = "prerequisites" FLAG_KEY = "flag_key" - TARGETING_ENABLED = "targeting_enabled" TRACK_EVENTS = "track_events" - OFF_VARIATION = "off_variation" - FLAG_FALLTHROUGH = "flag_fallthrough" // deprecated FALLTHROUGH = "fallthrough" KIND = "kind" CONFIG = "config" DEFAULT_ON_VARIATION = "default_on_variation" DEFAULT_OFF_VARIATION = "default_off_variation" + DEFAULTS = "defaults" + ON_VARIATION = "on_variation" + OFF_VARIATION = "off_variation" SERVICE_TOKEN = "service_token" DEFAULT_API_VERSION = "default_api_version" TOKEN = "token" diff --git a/launchdarkly/policies_helper.go b/launchdarkly/policies_helper.go index 72d23c08..6cb1b606 100644 --- a/launchdarkly/policies_helper.go +++ b/launchdarkly/policies_helper.go @@ -4,8 +4,7 @@ import ( "fmt" "sort" - "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" ) @@ -87,5 +86,5 @@ func policiesToResourceData(policies []ldapi.Policy) interface{} { // https://godoc.org/github.com/hashicorp/terraform/helper/schema#SchemaSetFunc func policyHash(val interface{}) int { policy := policyFromResourceData(val) - return hashcode.String(fmt.Sprintf("%v", policy)) + return schema.HashString(fmt.Sprintf("%v", policy)) } diff --git a/launchdarkly/policy_statements_helper.go b/launchdarkly/policy_statements_helper.go index 1c509fd0..ad3df49e 100644 --- a/launchdarkly/policy_statements_helper.go +++ b/launchdarkly/policy_statements_helper.go @@ -3,8 +3,8 @@ package launchdarkly import ( "errors" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) diff --git a/launchdarkly/policy_statements_helper_test.go b/launchdarkly/policy_statements_helper_test.go index 501a0bf0..a8404606 100644 --- a/launchdarkly/policy_statements_helper_test.go +++ b/launchdarkly/policy_statements_helper_test.go @@ -3,7 +3,7 @@ package launchdarkly import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/launchdarkly/prerequisite_helper.go b/launchdarkly/prerequisite_helper.go index cda3e71b..812f4c0c 100644 --- a/launchdarkly/prerequisite_helper.go +++ b/launchdarkly/prerequisite_helper.go @@ -3,8 +3,8 @@ package launchdarkly import ( "log" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) @@ -13,7 +13,6 @@ func prerequisitesSchema() *schema.Schema { Type: schema.TypeList, Optional: true, Description: "List of nested blocks describing prerequisite feature flags rules", - Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ FLAG_KEY: { diff --git a/launchdarkly/project_helper.go b/launchdarkly/project_helper.go index 53dacafd..439d6678 100644 --- a/launchdarkly/project_helper.go +++ b/launchdarkly/project_helper.go @@ -5,7 +5,7 @@ import ( "log" "net/http" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" ) @@ -62,10 +62,10 @@ func projectRead(d *schema.ResourceData, meta interface{}, isDataSource bool) er } if isDataSource { defaultCSA := *project.DefaultClientSideAvailability - clientSideAvailability := map[string]string{ - "using_environment_id": fmt.Sprintf("%v", defaultCSA.UsingEnvironmentId), - "using_mobile_key": fmt.Sprintf("%v", defaultCSA.UsingMobileKey), - } + clientSideAvailability := []map[string]interface{}{{ + "using_environment_id": defaultCSA.UsingEnvironmentId, + "using_mobile_key": defaultCSA.UsingMobileKey, + }} err = d.Set(CLIENT_SIDE_AVAILABILITY, clientSideAvailability) if err != nil { return fmt.Errorf("could not set client_side_availability on project with key %q: %v", project.Key, err) diff --git a/launchdarkly/provider.go b/launchdarkly/provider.go index 77d01fdb..4041863b 100644 --- a/launchdarkly/provider.go +++ b/launchdarkly/provider.go @@ -3,8 +3,7 @@ package launchdarkly import ( "fmt" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) // Environment Variables @@ -21,8 +20,8 @@ const ( api_host = "api_host" ) -// Provider returns a terraform.ResourceProvider. -func Provider() terraform.ResourceProvider { +// Provider returns a *schema.Provider. +func Provider() *schema.Provider { return &schema.Provider{ Schema: map[string]*schema.Schema{ access_token: { diff --git a/launchdarkly/provider_test.go b/launchdarkly/provider_test.go index 49f0b68f..53de189b 100644 --- a/launchdarkly/provider_test.go +++ b/launchdarkly/provider_test.go @@ -1,23 +1,20 @@ package launchdarkly import ( - "fmt" "os" "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) // This map is most commonly constructed once in a common init() method of the Providerā€™s main test file, // and includes an object of the current Provider type. https://www.terraform.io/docs/extend/testing/acceptance-tests/testcase.html -var testAccProviders map[string]terraform.ResourceProvider +var testAccProviders map[string]*schema.Provider var testAccProvider *schema.Provider func init() { - testAccProvider = Provider().(*schema.Provider) - testAccProviders = map[string]terraform.ResourceProvider{ + testAccProvider = Provider() + testAccProviders = map[string]*schema.Provider{ "launchdarkly": testAccProvider, } } @@ -27,9 +24,3 @@ func testAccPreCheck(t *testing.T) { t.Fatalf("%s env var must be set for acceptance tests", LAUNCHDARKLY_ACCESS_TOKEN) } } - -// Tags are a TypeSet. TF represents this a as a map of hashes to actual values. -// The hashes are always the same for a given value so this is repeatable. -func testAccTagKey(val string) string { - return fmt.Sprintf("tags.%d", hashcode.String(val)) -} diff --git a/launchdarkly/resource_launchdarkly_access_token.go b/launchdarkly/resource_launchdarkly_access_token.go index 915f1a7b..156bbfbc 100644 --- a/launchdarkly/resource_launchdarkly_access_token.go +++ b/launchdarkly/resource_launchdarkly_access_token.go @@ -6,8 +6,8 @@ import ( "net/http" "github.com/antihax/optional" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) diff --git a/launchdarkly/resource_launchdarkly_access_token_test.go b/launchdarkly/resource_launchdarkly_access_token_test.go index 013a7508..76744ce7 100644 --- a/launchdarkly/resource_launchdarkly_access_token_test.go +++ b/launchdarkly/resource_launchdarkly_access_token_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) const ( @@ -296,7 +296,7 @@ func TestAccAccessToken_UpdateCustomRole(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAccessTokenExists(resourceName), resource.TestCheckResourceAttr(resourceName, "custom_roles.#", "1"), - resource.TestCheckResourceAttr(resourceName, "custom_roles."+testAccMemberCustomRolePropertyKey(name), name), + resource.TestCheckResourceAttr(resourceName, "custom_roles.0", name), ), }, { @@ -305,8 +305,8 @@ func TestAccAccessToken_UpdateCustomRole(t *testing.T) { testAccCheckAccessTokenExists(resourceName), resource.TestCheckResourceAttr(resourceName, "name", "Updated - "+name), resource.TestCheckResourceAttr(resourceName, "custom_roles.#", "2"), - resource.TestCheckResourceAttr(resourceName, "custom_roles."+testAccMemberCustomRolePropertyKey(name), name), - resource.TestCheckResourceAttr(resourceName, "custom_roles."+testAccMemberCustomRolePropertyKey(name+"2"), name+"2"), + resource.TestCheckResourceAttr(resourceName, "custom_roles.0", name), + resource.TestCheckResourceAttr(resourceName, "custom_roles.1", name+"2"), ), }, }, diff --git a/launchdarkly/resource_launchdarkly_custom_role.go b/launchdarkly/resource_launchdarkly_custom_role.go index c8f2319f..4b32f21b 100644 --- a/launchdarkly/resource_launchdarkly_custom_role.go +++ b/launchdarkly/resource_launchdarkly_custom_role.go @@ -5,7 +5,7 @@ import ( "log" "net/http" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" ) diff --git a/launchdarkly/resource_launchdarkly_custom_role_test.go b/launchdarkly/resource_launchdarkly_custom_role_test.go index 01d3ce3b..f5b073d7 100644 --- a/launchdarkly/resource_launchdarkly_custom_role_test.go +++ b/launchdarkly/resource_launchdarkly_custom_role_test.go @@ -4,11 +4,9 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" - ldapi "github.com/launchdarkly/api-client-go" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) const ( @@ -65,11 +63,6 @@ func TestAccCustomRole_Create(t *testing.T) { key := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) name := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resourceName := "launchdarkly_custom_role.test" - policy := ldapi.Policy{ - Resources: []string{"proj/*:env/production"}, - Actions: []string{"*"}, - Effect: "deny", - } resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) @@ -84,11 +77,11 @@ func TestAccCustomRole_Create(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "Custom role - "+name), resource.TestCheckResourceAttr(resourceName, "description", "Deny all actions on production environments"), resource.TestCheckResourceAttr(resourceName, "policy.#", "1"), - resource.TestCheckResourceAttr(resourceName, testAccPolicyKey(policy, "actions.#"), "1"), - resource.TestCheckResourceAttr(resourceName, testAccPolicyKey(policy, "actions.0"), "*"), - resource.TestCheckResourceAttr(resourceName, testAccPolicyKey(policy, "resources.#"), "1"), - resource.TestCheckResourceAttr(resourceName, testAccPolicyKey(policy, "resources.0"), "proj/*:env/production"), - resource.TestCheckResourceAttr(resourceName, testAccPolicyKey(policy, EFFECT), "deny"), + resource.TestCheckResourceAttr(resourceName, "policy.0.actions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy.0.actions.0", "*"), + resource.TestCheckResourceAttr(resourceName, "policy.0.resources.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy.0.resources.0", "proj/*:env/production"), + resource.TestCheckResourceAttr(resourceName, "policy.0.effect", "deny"), ), }, }, @@ -134,11 +127,6 @@ func TestAccCustomRole_Update(t *testing.T) { key := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) name := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resourceName := "launchdarkly_custom_role.test" - policy := ldapi.Policy{ - Resources: []string{"proj/*:env/staging"}, - Actions: []string{"*"}, - Effect: "allow", - } resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) @@ -159,11 +147,11 @@ func TestAccCustomRole_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "Updated - "+name), resource.TestCheckResourceAttr(resourceName, "description", ""), // should be empty after removal resource.TestCheckResourceAttr(resourceName, "policy.#", "1"), - resource.TestCheckResourceAttr(resourceName, testAccPolicyKey(policy, "actions.#"), "1"), - resource.TestCheckResourceAttr(resourceName, testAccPolicyKey(policy, "actions.0"), "*"), - resource.TestCheckResourceAttr(resourceName, testAccPolicyKey(policy, "resources.#"), "1"), - resource.TestCheckResourceAttr(resourceName, testAccPolicyKey(policy, "resources.0"), "proj/*:env/staging"), - resource.TestCheckResourceAttr(resourceName, testAccPolicyKey(policy, EFFECT), "allow"), + resource.TestCheckResourceAttr(resourceName, "policy.0.actions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy.0.actions.0", "*"), + resource.TestCheckResourceAttr(resourceName, "policy.0.resources.#", "1"), + resource.TestCheckResourceAttr(resourceName, "policy.0.resources.0", "proj/*:env/staging"), + resource.TestCheckResourceAttr(resourceName, "policy.0.effect", "allow"), ), }, }, @@ -205,9 +193,6 @@ func TestAccCustomRole_UpdateWithStatements(t *testing.T) { }, }) } -func testAccPolicyKey(policy ldapi.Policy, subkey string) string { - return fmt.Sprintf("policy.%d.%s", hashcode.String(fmt.Sprintf("%v", policy)), subkey) -} func testAccCheckCustomRoleExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { diff --git a/launchdarkly/resource_launchdarkly_destination.go b/launchdarkly/resource_launchdarkly_destination.go index cb931363..f904aa2d 100644 --- a/launchdarkly/resource_launchdarkly_destination.go +++ b/launchdarkly/resource_launchdarkly_destination.go @@ -6,8 +6,8 @@ import ( "net/http" "strings" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) @@ -56,18 +56,10 @@ func resourceDestination() *schema.Resource { Description: "The destination-specific configuration object corresponding to your data export kind - see documentation for required fields for each kind", Elem: &schema.Schema{Type: schema.TypeString}, }, - ENABLED: { - Type: schema.TypeBool, - Description: "Whether the data export destination is on or not. This field has been deprecated in favor of 'on'", - Deprecated: "'enabled' is deprecated in favor of 'on'", - Optional: true, - ConflictsWith: []string{ON}, - }, ON: { - Type: schema.TypeBool, - Description: "Whether the data export destination is on or not", - Optional: true, - ConflictsWith: []string{ENABLED}, + Type: schema.TypeBool, + Description: "Whether the data export destination is on or not", + Optional: true, }, TAGS: tagsSchema(), }, @@ -80,7 +72,7 @@ func resourceDestinationCreate(d *schema.ResourceData, metaRaw interface{}) erro destinationEnvKey := d.Get(ENV_KEY).(string) destinationName := d.Get(NAME).(string) destinationKind := d.Get(KIND).(string) - destinationOn := getDestinationOn(d) + destinationOn := d.Get(ON).(bool) destinationConfig, err := destinationConfigFromResourceData(d) if err != nil { @@ -138,11 +130,7 @@ func resourceDestinationRead(d *schema.ResourceData, metaRaw interface{}) error _ = d.Set(NAME, destination.Name) _ = d.Set(KIND, destination.Kind) _ = d.Set(CONFIG, preservedCfg) - if _, ok := d.GetOkExists(ENABLED); ok { - d.Set(ENABLED, destination.On) - } else { - d.Set(ON, destination.On) - } + _ = d.Set(ON, destination.On) d.SetId(strings.Join([]string{destinationProjKey, destinationEnvKey, destination.Id}, "/")) return nil @@ -162,7 +150,7 @@ func resourceDestinationUpdate(d *schema.ResourceData, metaRaw interface{}) erro if err != nil { return err } - destinationOn := getDestinationOn(d) + destinationOn := d.Get(ON).(bool) patch := []ldapi.PatchOperation{ patchReplace("/name", &destinationName), @@ -246,18 +234,3 @@ func destinationImportIDtoKeys(importID string) (projKey, envKey, destinationID projKey, envKey, destinationID = parts[0], parts[1], parts[2] return projKey, envKey, destinationID, nil } - -// getDestinationOn is a helper function used for deprecating ENABLED in favor of ON to match -// LD's API response. It will default to false if neither is set and we will overwrite the existing -// value with false if it is removed. -func getDestinationOn(d *schema.ResourceData) bool { - var destinationOn bool - on, onSet := d.GetOkExists(ON) - enabled, enabledSet := d.GetOkExists(ENABLED) - if onSet { - destinationOn = on.(bool) - } else if enabledSet { - destinationOn = enabled.(bool) - } - return destinationOn -} diff --git a/launchdarkly/resource_launchdarkly_destination_test.go b/launchdarkly/resource_launchdarkly_destination_test.go index e487871d..97309f42 100644 --- a/launchdarkly/resource_launchdarkly_destination_test.go +++ b/launchdarkly/resource_launchdarkly_destination_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) const ( @@ -21,7 +21,7 @@ resource "launchdarkly_destination" "test" { role_arn = "arn:aws:iam::123456789012:role/marketingadmin" stream_name = "cat-stream" } - enabled = true + on = true tags = [ "terraform" ] } ` @@ -35,7 +35,6 @@ resource "launchdarkly_destination" "test" { project = "test-project" topic = "test-topic" } - enabled = true tags = [ "terraform" ] } ` @@ -51,7 +50,7 @@ resource "launchdarkly_destination" "test" { user_identity = "customer_id" environment = "production" } - enabled = true + on = true tags = [ "terraform" ] } ` @@ -65,7 +64,7 @@ resource "launchdarkly_destination" "test" { config = { write_key = "super-secret-write-key" } - enabled = true + on = true tags = [ "terraform" ] } ` @@ -98,7 +97,7 @@ resource "launchdarkly_destination" "test" { role_arn = "arn:aws:iam::123456789012:role/marketingadmin", stream_name = "cat-stream" } - enabled = true + on = true tags = [ "terraform", "updated" ] } ` @@ -112,7 +111,7 @@ resource "launchdarkly_destination" "test" { "project": "renamed-project", "topic": "test-topic" } - enabled = true + on = true tags = [ "terraform", "updated" ] } ` @@ -129,7 +128,7 @@ resource "launchdarkly_destination" "test" { user_identity = "customer_id" environment = "production" } - enabled = true + on = true tags = [ "terraform", "updated" ] } ` @@ -143,7 +142,6 @@ resource "launchdarkly_destination" "test" { config = { write_key = "updated-write-key" } - enabled = true tags = [ "terraform" ] } ` @@ -187,7 +185,8 @@ func TestAccDestination_CreateKinesis(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "kinesis-dest"), resource.TestCheckResourceAttr(resourceName, "kind", "kinesis"), resource.TestCheckResourceAttr(resourceName, "config.region", "us-east-1"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, { @@ -217,7 +216,7 @@ func TestAccDestination_CreateMparticle(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "mparticle-dest"), resource.TestCheckResourceAttr(resourceName, "kind", "mparticle"), resource.TestCheckResourceAttr(resourceName, "config.api_key", "apiKeyfromMParticle"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, }, @@ -241,8 +240,9 @@ func TestAccDestination_CreatePubsub(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "env_key", "test"), resource.TestCheckResourceAttr(resourceName, "name", "pubsub-dest"), resource.TestCheckResourceAttr(resourceName, "kind", "google-pubsub"), + resource.TestCheckResourceAttr(resourceName, "on", "false"), resource.TestCheckResourceAttr(resourceName, "config.project", "test-project"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, }, @@ -267,7 +267,7 @@ func TestAccDestination_CreateSegment(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "segment-dest"), resource.TestCheckResourceAttr(resourceName, "kind", "segment"), resource.TestCheckResourceAttr(resourceName, "config.write_key", "super-secret-write-key"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, }, @@ -295,7 +295,7 @@ func TestAccDestination_CreateAzureEventHubs(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "config.name", "name"), resource.TestCheckResourceAttr(resourceName, "config.policy_name", "policy-name"), resource.TestCheckResourceAttr(resourceName, "config.policy_key", "super-secret-policy-key"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, }, @@ -320,7 +320,7 @@ func TestAccDestination_UpdateKinesis(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "kinesis-dest"), resource.TestCheckResourceAttr(resourceName, "kind", "kinesis"), resource.TestCheckResourceAttr(resourceName, "config.role_arn", "arn:aws:iam::123456789012:role/marketingadmin"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, { @@ -331,8 +331,8 @@ func TestAccDestination_UpdateKinesis(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "updated-kinesis-dest"), resource.TestCheckResourceAttr(resourceName, "kind", "kinesis"), resource.TestCheckResourceAttr(resourceName, "config.role_arn", "arn:aws:iam::123456789012:role/marketingadmin"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("updated"), "updated"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "updated"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, }, @@ -357,7 +357,7 @@ func TestAccDestination_UpdatePubsub(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "pubsub-dest"), resource.TestCheckResourceAttr(resourceName, "kind", "google-pubsub"), resource.TestCheckResourceAttr(resourceName, "config.project", "test-project"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, { @@ -368,8 +368,8 @@ func TestAccDestination_UpdatePubsub(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "updated-pubsub-dest"), resource.TestCheckResourceAttr(resourceName, "kind", "google-pubsub"), resource.TestCheckResourceAttr(resourceName, "config.project", "renamed-project"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("updated"), "updated"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "updated"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, }, @@ -395,7 +395,7 @@ func TestAccDestination_UpdateMparticle(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "kind", "mparticle"), resource.TestCheckResourceAttr(resourceName, "config.secret", "mParticleSecret"), resource.TestCheckResourceAttr(resourceName, "config.environment", "production"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, { @@ -407,8 +407,8 @@ func TestAccDestination_UpdateMparticle(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "kind", "mparticle"), resource.TestCheckResourceAttr(resourceName, "config.secret", "updatedSecret"), resource.TestCheckResourceAttr(resourceName, "config.environment", "production"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("updated"), "updated"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "updated"), ), }, }, @@ -432,8 +432,9 @@ func TestAccDestination_UpdateSegment(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "env_key", "test"), resource.TestCheckResourceAttr(resourceName, "name", "segment-dest"), resource.TestCheckResourceAttr(resourceName, "kind", "segment"), + resource.TestCheckResourceAttr(resourceName, "on", "true"), resource.TestCheckResourceAttr(resourceName, "config.write_key", "super-secret-write-key"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, { @@ -444,8 +445,9 @@ func TestAccDestination_UpdateSegment(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "env_key", "test"), resource.TestCheckResourceAttr(resourceName, "name", "segment-dest"), resource.TestCheckResourceAttr(resourceName, "kind", "segment"), + resource.TestCheckResourceAttr(resourceName, "on", "false"), // should default to false when removed resource.TestCheckResourceAttr(resourceName, "config.write_key", "updated-write-key"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, }, @@ -474,7 +476,7 @@ func TestAccDestination_UpdateAzureEventHubs(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "config.policy_name", "policy-name"), resource.TestCheckResourceAttr(resourceName, "config.policy_key", "super-secret-policy-key"), resource.TestCheckResourceAttr(resourceName, "on", "true"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, { @@ -490,7 +492,7 @@ func TestAccDestination_UpdateAzureEventHubs(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "config.policy_name", "updated-policy-name"), resource.TestCheckResourceAttr(resourceName, "config.policy_key", "updated-policy-key"), resource.TestCheckResourceAttr(resourceName, "on", "false"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), ), }, }, diff --git a/launchdarkly/resource_launchdarkly_environment.go b/launchdarkly/resource_launchdarkly_environment.go index afe41c0e..d4a0ad2b 100644 --- a/launchdarkly/resource_launchdarkly_environment.go +++ b/launchdarkly/resource_launchdarkly_environment.go @@ -5,7 +5,7 @@ import ( "net/http" "strings" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" ) diff --git a/launchdarkly/resource_launchdarkly_environment_test.go b/launchdarkly/resource_launchdarkly_environment_test.go index f42a630b..7672c493 100644 --- a/launchdarkly/resource_launchdarkly_environment_test.go +++ b/launchdarkly/resource_launchdarkly_environment_test.go @@ -5,20 +5,20 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) const ( testAccEnvironmentCreate = ` resource "launchdarkly_environment" "staging" { name = "Staging1" - key = "staging1" - color = "ff00ff" - secure_mode = true - default_track_events = true - default_ttl = 50 + key = "staging1" + color = "ff00ff" + secure_mode = true + default_track_events = true + default_ttl = 50 project_key = launchdarkly_project.test.key tags = ["tagged", "terraform"] require_comments = true @@ -29,25 +29,34 @@ resource "launchdarkly_environment" "staging" { testAccEnvironmentUpdate = ` resource "launchdarkly_environment" "staging" { name = "The real staging1" - key = "staging1" - color = "000000" - secure_mode = false - default_track_events = false - default_ttl = 3 + key = "staging1" + color = "000000" + secure_mode = false + default_track_events = false + default_ttl = 3 project_key = launchdarkly_project.test.key require_comments = false confirm_changes = false } +` + + testAccEnvironmentRemoveOptionalAttributes = ` +resource "launchdarkly_environment" "staging" { + name = "The real staging1" + key = "staging1" + color = "000000" + project_key = launchdarkly_project.test.key +} ` testAccEnvironmentInvalid = ` resource "launchdarkly_environment" "staging" { name = "The real staging1" - key = "staging1" - color = "000000" - secure_mode = false - default_track_events = "maybe" - default_ttl = 3 + key = "staging1" + color = "000000" + secure_mode = false + default_track_events = "maybe" + default_ttl = 3 project_key = launchdarkly_project.test.key require_comments = false confirm_changes = true @@ -79,8 +88,8 @@ func TestAccEnvironment_Create(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags.#", "2"), resource.TestCheckResourceAttr(resourceName, "require_comments", "true"), resource.TestCheckResourceAttr(resourceName, "confirm_changes", "true"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("tagged"), "tagged"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "tagged"), ), }, { @@ -135,6 +144,49 @@ func TestAccEnvironment_Update(t *testing.T) { }) } +func TestAccEnvironment_RemoveAttributes(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "launchdarkly_environment.staging" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccEnvironmentCreate), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckEnvironmentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "Staging1"), + resource.TestCheckResourceAttr(resourceName, "key", "staging1"), + resource.TestCheckResourceAttr(resourceName, "color", "ff00ff"), + resource.TestCheckResourceAttr(resourceName, "secure_mode", "true"), + resource.TestCheckResourceAttr(resourceName, "default_track_events", "true"), + resource.TestCheckResourceAttr(resourceName, "default_ttl", "50"), + resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), + ), + }, + { + Config: withRandomProject(projectKey, testAccEnvironmentRemoveOptionalAttributes), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckEnvironmentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "The real staging1"), + resource.TestCheckResourceAttr(resourceName, "key", "staging1"), + resource.TestCheckResourceAttr(resourceName, "color", "000000"), + resource.TestCheckResourceAttr(resourceName, "secure_mode", "false"), + resource.TestCheckResourceAttr(resourceName, "default_track_events", "false"), + resource.TestCheckResourceAttr(resourceName, "default_ttl", "0"), + resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), + resource.TestCheckResourceAttr(resourceName, "require_comments", "false"), + resource.TestCheckResourceAttr(resourceName, "confirm_changes", "false"), + ), + }, + }, + }) +} + func TestAccEnvironment_Invalid(t *testing.T) { projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resourceName := "launchdarkly_environment.staging" @@ -146,7 +198,7 @@ func TestAccEnvironment_Invalid(t *testing.T) { Steps: []resource.TestStep{ { Config: withRandomProject(projectKey, testAccEnvironmentInvalid), - ExpectError: regexp.MustCompile("config is invalid"), + ExpectError: regexp.MustCompile("Error: Incorrect attribute value type"), // default_track_events should be bool }, { Config: withRandomProject(projectKey, testAccEnvironmentUpdate), diff --git a/launchdarkly/resource_launchdarkly_feature_flag.go b/launchdarkly/resource_launchdarkly_feature_flag.go index 7d04518a..5449abc0 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag.go +++ b/launchdarkly/resource_launchdarkly_feature_flag.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" ) diff --git a/launchdarkly/resource_launchdarkly_feature_flag_environment.go b/launchdarkly/resource_launchdarkly_feature_flag_environment.go index 8ec1ca86..a42d37f1 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag_environment.go +++ b/launchdarkly/resource_launchdarkly_feature_flag_environment.go @@ -6,7 +6,7 @@ import ( "net/http" "strings" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" ) @@ -20,7 +20,7 @@ func resourceFeatureFlagEnvironment() *schema.Resource { Importer: &schema.ResourceImporter{ State: resourceFeatureFlagEnvironmentImport, }, - Schema: baseFeatureFlagEnvironmentSchema(), + Schema: baseFeatureFlagEnvironmentSchema(false), } } @@ -64,18 +64,12 @@ func resourceFeatureFlagEnvironmentCreate(d *schema.ResourceData, metaRaw interf patches := make([]ldapi.PatchOperation, 0) - enabled, ok := getFeatureFlagEnvironmentOn(d) - if ok { - patches = append(patches, patchReplace(patchFlagEnvPath(d, "on"), enabled)) - } + on := d.Get(ON) + patches = append(patches, patchReplace(patchFlagEnvPath(d, "on"), on)) - // GetOKExists is marked deprecated by Hashicorp, however it seems to be the only solution for setting the - // offVariation to 0 during creation. According to Hashicorp, it will not be removed until a replacement function is - // implemented. https://github.com/hashicorp/terraform-plugin-sdk/pull/350#issuecomment-597888969 - offVariation, ok := d.GetOkExists(OFF_VARIATION) - if ok { - patches = append(patches, patchReplace(patchFlagEnvPath(d, "offVariation"), offVariation.(int))) - } + // off_variation is required + offVariation := d.Get(OFF_VARIATION) + patches = append(patches, patchReplace(patchFlagEnvPath(d, "offVariation"), offVariation.(int))) trackEvents, ok := d.GetOk(TRACK_EVENTS) if ok { @@ -97,22 +91,18 @@ func resourceFeatureFlagEnvironmentCreate(d *schema.ResourceData, metaRaw interf patches = append(patches, patchReplace(patchFlagEnvPath(d, "prerequisites"), prerequisites)) } - _, oldOk := d.GetOk(USER_TARGETS) - _, newOk := d.GetOk(TARGETS) - if oldOk || newOk { + _, ok = d.GetOk(TARGETS) + if ok { targets := targetsFromResourceData(d) patches = append(patches, patchReplace(patchFlagEnvPath(d, "targets"), targets)) } - _, newOk = d.GetOk(FALLTHROUGH) - _, oldOk = d.GetOk(FLAG_FALLTHROUGH) - if oldOk || newOk { - fall, err := fallthroughFromResourceData(d) - if err != nil { - return err - } - patches = append(patches, patchReplace(patchFlagEnvPath(d, "fallthrough"), fall)) + // fallthrough is required + fall, err := fallthroughFromResourceData(d) + if err != nil { + return err } + patches = append(patches, patchReplace(patchFlagEnvPath(d, "fallthrough"), fall)) if len(patches) > 0 { patch := ldapi.PatchComment{ @@ -162,7 +152,7 @@ func resourceFeatureFlagEnvironmentUpdate(d *schema.ResourceData, metaRaw interf return fmt.Errorf("failed to find environment with key %q", envKey) } - on, _ := getFeatureFlagEnvironmentOn(d) + on := d.Get(ON) rules, err := rulesFromResourceData(d) if err != nil { return err @@ -170,12 +160,12 @@ func resourceFeatureFlagEnvironmentUpdate(d *schema.ResourceData, metaRaw interf trackEvents := d.Get(TRACK_EVENTS).(bool) prerequisites := prerequisitesFromResourceData(d, PREREQUISITES) targets := targetsFromResourceData(d) - offVariation := d.Get(OFF_VARIATION).(int) fall, err := fallthroughFromResourceData(d) if err != nil { return err } + offVariation := d.Get(OFF_VARIATION) patch := ldapi.PatchComment{ Comment: "Terraform", @@ -184,9 +174,9 @@ func resourceFeatureFlagEnvironmentUpdate(d *schema.ResourceData, metaRaw interf patchReplace(patchFlagEnvPath(d, "rules"), rules), patchReplace(patchFlagEnvPath(d, "trackEvents"), trackEvents), patchReplace(patchFlagEnvPath(d, "prerequisites"), prerequisites), - patchReplace(patchFlagEnvPath(d, "offVariation"), offVariation), patchReplace(patchFlagEnvPath(d, "targets"), targets), patchReplace(patchFlagEnvPath(d, "fallthrough"), fall), + patchReplace(patchFlagEnvPath(d, "offVariation"), offVariation), }} log.Printf("[DEBUG] %+v\n", patch) @@ -270,21 +260,3 @@ func resourceFeatureFlagEnvironmentImport(d *schema.ResourceData, meta interface return []*schema.ResourceData{d}, nil } - -// getFeatureFlagEnvironmentOn is a helper function used for deprecating TARGETING_ENABLED in favor of ON to match -// LD's API response. It returns nil if neither is set so we can maintain current behavior - TODO in V2 it will need -// to be updated to default to false if neither is set. -func getFeatureFlagEnvironmentOn(d *schema.ResourceData) (bool, bool) { - var onValue bool - on, onSet := d.GetOk(ON) - enabled, enabledSet := d.GetOk(TARGETING_ENABLED) - if !onSet && !enabledSet { - return onValue, false - } - if onSet { - onValue = on.(bool) - } else if enabledSet { - onValue = enabled.(bool) - } - return onValue, true -} diff --git a/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go b/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go index b701fc5d..28be97b8 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go +++ b/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go @@ -6,9 +6,9 @@ import ( "testing" "github.com/antihax/optional" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ldapi "github.com/launchdarkly/api-client-go" ) @@ -34,12 +34,14 @@ resource "launchdarkly_feature_flag" "basic" { resource "launchdarkly_feature_flag_environment" "basic" { flag_id = launchdarkly_feature_flag.basic.id env_key = "test" - targeting_enabled = false - flag_fallthrough { + on = false + fallthrough { variation = 1 } - user_targets { - values = ["user1"] + off_variation = 2 + targets { + values = ["user1"] + variation = 0 } } ` @@ -64,6 +66,10 @@ resource "launchdarkly_feature_flag" "basic" { resource "launchdarkly_feature_flag_environment" "basic" { flag_id = launchdarkly_feature_flag.basic.id env_key = "test" + fallthrough { + variation = 0 + } + off_variation = 2 } ` @@ -87,16 +93,15 @@ resource "launchdarkly_feature_flag" "basic" { resource "launchdarkly_feature_flag_environment" "basic" { flag_id = launchdarkly_feature_flag.basic.id env_key = "test" - targeting_enabled = true + on = true track_events = true targets { - values = [] - } - targets { - values = ["user1", "user2"] + values = ["user1", "user2"] + variation = 1 } targets { - values = [] + values = [] + variation = 2 } rules { clauses { @@ -122,42 +127,7 @@ resource "launchdarkly_feature_flag_environment" "basic" { rollout_weights = [60000, 40000, 0] bucket_by = "email" } -} -` - testAccFeatureFlagEnvironmentUpdateDeprecated = ` -resource "launchdarkly_feature_flag" "basic" { - project_key = launchdarkly_project.test.key - key = "basic-flag" - name = "Basic feature flag" - variation_type = "number" - variations { - value = 0 - } - variations { - value = 10 - } - variations { - value = 30 - } -} - -resource "launchdarkly_feature_flag_environment" "basic" { - flag_id = launchdarkly_feature_flag.basic.id - env_key = "test" - targeting_enabled = true - track_events = true - user_targets { - values = ["user1", "user2"] - } - user_targets { - values = [] - } - user_targets { - values = [] - } - flag_fallthrough { - variation = 2 - } + off_variation = 1 } ` @@ -179,9 +149,9 @@ resource "launchdarkly_feature_flag_environment" "json_variations" { flag_id = launchdarkly_feature_flag.json.id env_key = "test" - flag_fallthrough { + fallthrough { variation = 1 - } + } off_variation = 0 } @@ -214,12 +184,49 @@ resource "launchdarkly_feature_flag" "basic" { resource "launchdarkly_feature_flag_environment" "prereq" { flag_id = launchdarkly_feature_flag.basic.id env_key = "test" - targeting_enabled = true - + on = true prerequisites { flag_key = launchdarkly_feature_flag.bool.key variation = 0 } + fallthrough { + variation = 1 + } + off_variation = 0 +} +` + + testAccFeatureFlagEnvironmentRemovePrereq = ` +resource "launchdarkly_feature_flag" "bool" { + project_key = launchdarkly_project.test.key + key = "bool-flag" + name = "boolean flag" + variation_type = "boolean" +} + +resource "launchdarkly_feature_flag" "basic" { + project_key = launchdarkly_project.test.key + key = "basic-flag" + name = "Basic feature flag" + variation_type = "number" + variations { + value = 10 + } + variations { + value = 20 + } + variations { + value = 30 + } +} + +resource "launchdarkly_feature_flag_environment" "prereq" { + flag_id = launchdarkly_feature_flag.basic.id + env_key = "test" + fallthrough { + variation = 1 + } + off_variation = 0 } ` @@ -234,8 +241,7 @@ resource "launchdarkly_feature_flag" "bool_flag" { resource "launchdarkly_feature_flag_environment" "bool_clause" { flag_id = launchdarkly_feature_flag.bool_flag.id env_key = "test" - targeting_enabled = true - + on = true rules { clauses { attribute = "is_vip" @@ -246,6 +252,10 @@ resource "launchdarkly_feature_flag_environment" "bool_clause" { } variation = 0 } + fallthrough { + variation = 0 + } + off_variation = 1 } ` @@ -260,8 +270,7 @@ resource "launchdarkly_feature_flag" "bool_flag" { resource "launchdarkly_feature_flag_environment" "number_clause" { flag_id = launchdarkly_feature_flag.bool_flag.id env_key = "test" - targeting_enabled = true - + on = true rules { clauses { attribute = "answer" @@ -272,6 +281,10 @@ resource "launchdarkly_feature_flag_environment" "number_clause" { } variation = 0 } + fallthrough { + variation = 0 + } + off_variation = 1 } ` @@ -295,11 +308,12 @@ resource "launchdarkly_feature_flag" "basic" { resource "launchdarkly_feature_flag_environment" "invalid_bucket_by" { flag_id = launchdarkly_feature_flag.basic.id env_key = "test" - targeting_enabled = true + on = true - flag_fallthrough { + fallthrough { bucket_by = "email" } + off_variation = 0 } ` @@ -323,8 +337,7 @@ resource "launchdarkly_feature_flag" "basic" { resource "launchdarkly_feature_flag_environment" "invalid_bucket_by" { flag_id = launchdarkly_feature_flag.basic.id env_key = "test" - targeting_enabled = true - + on = true rules { clauses { attribute = "name" @@ -335,6 +348,10 @@ resource "launchdarkly_feature_flag_environment" "invalid_bucket_by" { variation = 0 bucket_by = "name" } + fallthrough { + variation = 0 + } + off_variation = 1 } ` ) @@ -352,16 +369,19 @@ func TestAccFeatureFlagEnvironment_Basic(t *testing.T) { Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentBasic), Check: resource.ComposeTestCheckFunc( testAccCheckFeatureFlagEnvironmentExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "false"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.#", "1"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", "1"), - resource.TestCheckResourceAttr(resourceName, "user_targets.#", "1"), - resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.0", "user1"), + resource.TestCheckResourceAttr(resourceName, "on", "false"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.#", "1"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "1"), + resource.TestCheckResourceAttr(resourceName, "off_variation", "2"), + resource.TestCheckResourceAttr(resourceName, "targets.#", "1"), + resource.TestCheckResourceAttr(resourceName, "targets.0.values.0", "user1"), + resource.TestCheckResourceAttr(resourceName, "targets.0.variation", "0"), ), }, { - ResourceName: resourceName, - ImportState: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -380,28 +400,28 @@ func TestAccFeatureFlagEnvironment_Empty(t *testing.T) { Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentEmpty), Check: resource.ComposeTestCheckFunc( testAccCheckFeatureFlagEnvironmentExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "on", "false"), resource.TestCheckResourceAttr(resourceName, "off_variation", "2"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "0"), resource.TestCheckResourceAttr(resourceName, "track_events", "false"), resource.TestCheckNoResourceAttr(resourceName, "rules"), resource.TestCheckNoResourceAttr(resourceName, "rules.#"), resource.TestCheckNoResourceAttr(resourceName, "prerequisites"), resource.TestCheckNoResourceAttr(resourceName, "prerequisites.#"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.#", "1"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", "0"), - resource.TestCheckNoResourceAttr(resourceName, "user_targets"), - resource.TestCheckNoResourceAttr(resourceName, "user_targets.#"), + resource.TestCheckNoResourceAttr(resourceName, "targets"), + resource.TestCheckNoResourceAttr(resourceName, "targets.#"), ), }, { - ResourceName: resourceName, - ImportState: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) } -func TestAccFeatureFlagEnvironment_UpdateDeprecatedFields(t *testing.T) { +func TestAccFeatureFlagEnvironment_Update(t *testing.T) { projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resourceName := "launchdarkly_feature_flag_environment.basic" resource.ParallelTest(t, resource.TestCase{ @@ -414,73 +434,22 @@ func TestAccFeatureFlagEnvironment_UpdateDeprecatedFields(t *testing.T) { Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentBasic), Check: resource.ComposeTestCheckFunc( testAccCheckFeatureFlagEnvironmentExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "false"), resource.TestCheckResourceAttr(resourceName, "on", "false"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.#", "1"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", "1"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.rollout.#", "0"), - resource.TestCheckResourceAttr(resourceName, "user_targets.#", "1"), - resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.0", "user1"), - resource.TestCheckResourceAttr(resourceName, "rules.#", "0"), - ), - }, - { - Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentUpdateDeprecated), - Check: resource.ComposeTestCheckFunc( - testAccCheckFeatureFlagEnvironmentExists(resourceName), - // computed values should come through - resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "true"), - resource.TestCheckResourceAttr(resourceName, "on", "true"), - resource.TestCheckResourceAttr(resourceName, "track_events", "true"), resource.TestCheckResourceAttr(resourceName, "fallthrough.#", "1"), - resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "2"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.#", "1"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", "2"), - resource.TestCheckResourceAttr(resourceName, "targets.#", "3"), - resource.TestCheckResourceAttr(resourceName, "targets.0.values.#", "2"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "1"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout.#", "0"), + resource.TestCheckResourceAttr(resourceName, "targets.#", "1"), resource.TestCheckResourceAttr(resourceName, "targets.0.values.0", "user1"), - resource.TestCheckResourceAttr(resourceName, "targets.0.values.1", "user2"), - resource.TestCheckResourceAttr(resourceName, "targets.1.values.#", "0"), - resource.TestCheckResourceAttr(resourceName, "targets.2.values.#", "0"), - resource.TestCheckResourceAttr(resourceName, "user_targets.#", "3"), - resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.#", "2"), - resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.0", "user1"), - resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.1", "user2"), - resource.TestCheckResourceAttr(resourceName, "user_targets.1.values.#", "0"), - resource.TestCheckResourceAttr(resourceName, "user_targets.2.values.#", "0"), - ), - }, - }, - }) -} - -func TestAccFeatureFlagEnvironment_Update(t *testing.T) { - projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) - resourceName := "launchdarkly_feature_flag_environment.basic" - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentBasic), - Check: resource.ComposeTestCheckFunc( - testAccCheckFeatureFlagEnvironmentExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "false"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.#", "1"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", "1"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.rollout.#", "0"), - resource.TestCheckResourceAttr(resourceName, "user_targets.#", "1"), - resource.TestCheckResourceAttr(resourceName, "user_targets.0.values.0", "user1"), + resource.TestCheckResourceAttr(resourceName, "targets.0.variation", "0"), resource.TestCheckResourceAttr(resourceName, "rules.#", "0"), + resource.TestCheckResourceAttr(resourceName, "off_variation", "2"), ), }, { Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentUpdate), Check: resource.ComposeTestCheckFunc( testAccCheckFeatureFlagEnvironmentExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "on", "true"), resource.TestCheckResourceAttr(resourceName, "track_events", "true"), resource.TestCheckResourceAttr(resourceName, "fallthrough.#", "1"), resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "0"), @@ -489,12 +458,13 @@ func TestAccFeatureFlagEnvironment_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.1", "40000"), resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.2", "0"), resource.TestCheckResourceAttr(resourceName, "fallthrough.0.bucket_by", "email"), - resource.TestCheckResourceAttr(resourceName, "targets.#", "3"), - resource.TestCheckResourceAttr(resourceName, "targets.0.values.#", "0"), - resource.TestCheckResourceAttr(resourceName, "targets.1.values.#", "2"), - resource.TestCheckResourceAttr(resourceName, "targets.1.values.0", "user1"), - resource.TestCheckResourceAttr(resourceName, "targets.1.values.1", "user2"), - resource.TestCheckResourceAttr(resourceName, "targets.2.values.#", "0"), + resource.TestCheckResourceAttr(resourceName, "targets.#", "2"), + resource.TestCheckResourceAttr(resourceName, "targets.0.values.#", "2"), + resource.TestCheckResourceAttr(resourceName, "targets.0.values.0", "user1"), + resource.TestCheckResourceAttr(resourceName, "targets.0.variation", "1"), + resource.TestCheckResourceAttr(resourceName, "targets.0.values.1", "user2"), + resource.TestCheckResourceAttr(resourceName, "targets.1.values.#", "0"), + resource.TestCheckResourceAttr(resourceName, "targets.1.variation", "2"), resource.TestCheckResourceAttr(resourceName, "rules.#", "2"), resource.TestCheckResourceAttr(resourceName, "rules.0.variation", "0"), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.#", "1"), @@ -514,49 +484,27 @@ func TestAccFeatureFlagEnvironment_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "rules.1.clauses.0.values.#", "1"), resource.TestCheckResourceAttr(resourceName, "rules.1.clauses.0.values.0", "h"), resource.TestCheckResourceAttr(resourceName, "rules.1.clauses.0.negate", "false"), + resource.TestCheckResourceAttr(resourceName, "off_variation", "1"), ), }, - // After changes have been made to the resource, removing optional values should not change them. + // After changes have been made to the resource, removing optional values should revert to their default / null values. { Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentEmpty), Check: resource.ComposeTestCheckFunc( testAccCheckFeatureFlagEnvironmentExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "true"), - resource.TestCheckResourceAttr(resourceName, "track_events", "true"), - resource.TestCheckResourceAttr(resourceName, "fallthrough.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on", "false"), + resource.TestCheckResourceAttr(resourceName, "track_events", "false"), resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "0"), - resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.#", "3"), - resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.0", "60000"), - resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.1", "40000"), - resource.TestCheckResourceAttr(resourceName, "fallthrough.0.rollout_weights.2", "0"), - resource.TestCheckResourceAttr(resourceName, "fallthrough.0.bucket_by", "email"), - resource.TestCheckResourceAttr(resourceName, "targets.#", "3"), - resource.TestCheckResourceAttr(resourceName, "targets.0.values.#", "0"), - resource.TestCheckResourceAttr(resourceName, "targets.1.values.#", "2"), - resource.TestCheckResourceAttr(resourceName, "targets.1.values.0", "user1"), - resource.TestCheckResourceAttr(resourceName, "targets.1.values.1", "user2"), - resource.TestCheckResourceAttr(resourceName, "targets.2.values.#", "0"), - resource.TestCheckResourceAttr(resourceName, "rules.#", "2"), - resource.TestCheckResourceAttr(resourceName, "rules.0.variation", "0"), - resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.#", "1"), - resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.attribute", "country"), - resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.op", "startsWith"), - resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.values.#", "2"), - resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.values.0", "great"), - resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.values.1", "amazing"), - resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.negate", "false"), - resource.TestCheckResourceAttr(resourceName, "rules.1.rollout_weights.#", "3"), - resource.TestCheckResourceAttr(resourceName, "rules.1.rollout_weights.0", "90000"), - resource.TestCheckResourceAttr(resourceName, "rules.1.rollout_weights.1", "10000"), - resource.TestCheckResourceAttr(resourceName, "rules.1.rollout_weights.2", "0"), - resource.TestCheckResourceAttr(resourceName, "rules.1.bucket_by", "email"), - resource.TestCheckResourceAttr(resourceName, "rules.1.clauses.0.attribute", "name"), - resource.TestCheckResourceAttr(resourceName, "rules.1.clauses.0.op", "startsWith"), - resource.TestCheckResourceAttr(resourceName, "rules.1.clauses.0.values.#", "1"), - resource.TestCheckResourceAttr(resourceName, "rules.1.clauses.0.values.0", "h"), - resource.TestCheckResourceAttr(resourceName, "rules.1.clauses.0.negate", "false"), + resource.TestCheckResourceAttr(resourceName, "off_variation", "2"), + resource.TestCheckNoResourceAttr(resourceName, "targets.#"), + resource.TestCheckNoResourceAttr(resourceName, "rules.#"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -574,16 +522,17 @@ func TestAccFeatureFlagEnvironment_JSON_variations(t *testing.T) { Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentJSONVariations), Check: resource.ComposeTestCheckFunc( testAccCheckFeatureFlagEnvironmentExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "false"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.#", "1"), - resource.TestCheckResourceAttr(resourceName, "flag_fallthrough.0.variation", "1"), + resource.TestCheckResourceAttr(resourceName, "on", "false"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.#", "1"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "1"), resource.TestCheckResourceAttr(resourceName, "off_variation", "0"), ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{FALLTHROUGH, OFF_VARIATION}, }, }, }) @@ -602,12 +551,14 @@ func TestAccFeatureFlagEnvironment_BoolClauseValue(t *testing.T) { Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentBoolClauseValue), Check: resource.ComposeTestCheckFunc( testAccCheckFeatureFlagEnvironmentExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "on", "true"), resource.TestCheckResourceAttr(resourceName, "rules.#", "1"), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.#", "1"), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.value_type", "boolean"), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.values.#", "1"), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.values.0", "true"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "0"), + resource.TestCheckResourceAttr(resourceName, "off_variation", "1"), ), }, { @@ -632,13 +583,15 @@ func TestAccFeatureFlagEnvironment_NumberClauseValue(t *testing.T) { Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentNumberClauseValue), Check: resource.ComposeTestCheckFunc( testAccCheckFeatureFlagEnvironmentExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "on", "true"), resource.TestCheckResourceAttr(resourceName, "rules.#", "1"), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.#", "1"), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.value_type", "number"), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.values.#", "2"), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.values.0", "42"), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.values.1", "84"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "0"), + resource.TestCheckResourceAttr(resourceName, "off_variation", "1"), ), }, { @@ -684,9 +637,21 @@ func TestAccFeatureFlagEnvironment_Prereq(t *testing.T) { Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentPrereq), Check: resource.ComposeTestCheckFunc( testAccCheckFeatureFlagEnvironmentExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "targeting_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "on", "true"), resource.TestCheckResourceAttr(resourceName, "prerequisites.#", "1"), resource.TestCheckResourceAttr(resourceName, "prerequisites.0.flag_key", "bool-flag"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "1"), + resource.TestCheckResourceAttr(resourceName, "off_variation", "0"), + ), + }, + { + Config: withRandomProject(projectKey, testAccFeatureFlagEnvironmentRemovePrereq), + Check: resource.ComposeTestCheckFunc( + testAccCheckFeatureFlagEnvironmentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "on", "false"), + resource.TestCheckNoResourceAttr(resourceName, "prerequisites.#"), + resource.TestCheckResourceAttr(resourceName, "fallthrough.0.variation", "1"), + resource.TestCheckResourceAttr(resourceName, "off_variation", "0"), ), }, }, diff --git a/launchdarkly/resource_launchdarkly_feature_flag_test.go b/launchdarkly/resource_launchdarkly_feature_flag_test.go index 4ac85cb1..6faaf260 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag_test.go +++ b/launchdarkly/resource_launchdarkly_feature_flag_test.go @@ -5,10 +5,9 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) const ( @@ -30,8 +29,10 @@ resource "launchdarkly_feature_flag" "basic" { tags = ["update", "terraform"] include_in_snippet = true temporary = true - default_on_variation = "true" - default_off_variation = "false" + defaults { + on_variation = 0 + off_variation = 1 + } } ` @@ -131,8 +132,27 @@ resource "launchdarkly_feature_flag" "maintained" { } ` - //testAccFeatureFlagWasMaintained is used to test that feature flag maintainers can be unset - testAccFeatureFlagWasMaintained = ` + // if the maintainer id is removed from the config it should still be set in the state to + // the previous maintainer if that maintainer still exists + testAccFeatureFlagMaintainerComputed = ` +resource "launchdarkly_team_member" "test" { + email = "%s@example.com" + first_name = "first" + last_name = "last" + role = "admin" + custom_roles = [] +} + +resource "launchdarkly_feature_flag" "maintained" { + project_key = launchdarkly_project.test.key + key = "maintained-flag" + name = "Maintained feature flag" + variation_type = "boolean" +} +` + + //testAccFeatureFlagMaintainerDeleted is used to test that feature flag maintainers can be unset + testAccFeatureFlagMaintainerDeleted = ` resource "launchdarkly_feature_flag" "maintained" { project_key = launchdarkly_project.test.key key = "maintained-flag" @@ -256,6 +276,10 @@ resource "launchdarkly_feature_flag" "multivariate" { "value3" ] } + defaults { + on_variation = 2 + off_variation = 1 + } } ` @@ -265,8 +289,10 @@ resource "launchdarkly_feature_flag" "defaults" { key = "defaults-flag" name = "Feature flag with defaults" variation_type = "boolean" - default_on_variation = "true" - default_off_variation = "false" + defaults { + on_variation = 0 + off_variation = 1 + } } ` testAccFeatureFlagDefaultsUpdate = ` @@ -275,8 +301,10 @@ resource "launchdarkly_feature_flag" "defaults" { key = "defaults-flag" name = "Feature flag with defaults" variation_type = "boolean" - default_on_variation = "true" - default_off_variation = "true" + defaults { + on_variation = 0 + off_variation = 0 + } } ` testAccFeatureFlagDefaultsMissingOffInvalid = ` @@ -285,8 +313,10 @@ resource "launchdarkly_feature_flag" "defaults" { key = "defaults-flag" name = "Feature flag with defaults" variation_type = "boolean" - default_on_variation = "a" - default_off_variation = "b" + defaults { + on_variation = 2 + off_variation = 3 + } } ` @@ -296,8 +326,10 @@ resource "launchdarkly_feature_flag" "defaults-multivariate" { key = "defaults-multivariate-flag" name = "Multivariate feature flag with defaults" variation_type = "string" - default_on_variation = "b" - default_off_variation = "b" + defaults { + on_variation = 1 + off_variation = 1 + } variations { value = "a" } @@ -318,8 +350,10 @@ resource "launchdarkly_feature_flag" "defaults-multivariate" { key = "defaults-multivariate-flag" name = "Multivariate feature flag with defaults" variation_type = "string" - default_on_variation = "c" - default_off_variation = "c" + defaults { + on_variation = 2 + off_variation = 2 + } variations { value = "a" } @@ -340,8 +374,10 @@ resource "launchdarkly_feature_flag" "defaults-multivariate" { key = "defaults-multivariate-flag" name = "Multivariate fature flag with defaults" variation_type = "string" - default_on_variation = "c" - default_off_variation = "c" + defaults { + on_variation = 2 + off_variation = 2 + } variations { value = "b" } @@ -374,6 +410,11 @@ func withRandomProject(randomProject, resource string) string { resource "launchdarkly_project" "test" { name = "testProject" key = "%s" + environments { + name = "testEnvironment" + key = "test" + color = "000000" + } } %s`, randomProject, resource) @@ -441,12 +482,12 @@ func TestAccFeatureFlag_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), resource.TestCheckResourceAttr(resourceName, "description", "this is a boolean flag by default becausethe variations field is omitted"), resource.TestCheckResourceAttr(resourceName, "tags.#", "2"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("update"), "update"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "update"), resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "true"), resource.TestCheckResourceAttr(resourceName, "temporary", "true"), - resource.TestCheckResourceAttr(resourceName, "default_on_variation", "true"), - resource.TestCheckResourceAttr(resourceName, "default_off_variation", "false"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.on_variation", "0"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.off_variation", "1"), ), }, }, @@ -562,14 +603,28 @@ func TestAccFeatureFlag_WithMaintainer(t *testing.T) { ), }, { - Config: withRandomProject(projectKey, testAccFeatureFlagWasMaintained), + Config: withRandomProject(projectKey, fmt.Sprintf(testAccFeatureFlagMaintainerComputed, randomName)), Check: resource.ComposeTestCheckFunc( testAccCheckProjectExists("launchdarkly_project.test"), testAccCheckFeatureFlagExists(resourceName), resource.TestCheckResourceAttr(resourceName, "name", "Maintained feature flag"), resource.TestCheckResourceAttr(resourceName, "key", "maintained-flag"), resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), - resource.TestCheckResourceAttr(resourceName, "maintainer_id", ""), + // when removed it should reset back to the most recently-set maintainer + resource.TestCheckResourceAttrPair(resourceName, "maintainer_id", "launchdarkly_team_member.test", "id"), + ), + }, + { + Config: withRandomProject(projectKey, testAccFeatureFlagMaintainerDeleted), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckFeatureFlagExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "Maintained feature flag"), + resource.TestCheckResourceAttr(resourceName, "key", "maintained-flag"), + resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), + // it will still be set to the most recently set one even if that member has been deleted + // the UI will not show a maintainer because it will not be able to find the record post-member delete + resource.TestCheckResourceAttrSet(resourceName, "maintainer_id"), ), }, }, @@ -605,14 +660,16 @@ func TestAccFeatureFlag_InvalidMaintainer(t *testing.T) { ), }, { - Config: withRandomProject(projectKey, testAccFeatureFlagWasMaintained), + Config: withRandomProject(projectKey, testAccFeatureFlagMaintainerDeleted), Check: resource.ComposeTestCheckFunc( testAccCheckProjectExists("launchdarkly_project.test"), testAccCheckFeatureFlagExists(resourceName), resource.TestCheckResourceAttr(resourceName, "name", "Maintained feature flag"), resource.TestCheckResourceAttr(resourceName, "key", "maintained-flag"), resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), - resource.TestCheckResourceAttr(resourceName, "maintainer_id", ""), + // this is the best we can do. it should default back to the most recently-set maintainer but + // we have no easy way of a + resource.TestCheckResourceAttrSet(resourceName, "maintainer_id"), ), }, }, @@ -644,20 +701,21 @@ func TestAccFeatureFlag_CreateMultivariate(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "variations.1.value", "string2"), resource.TestCheckResourceAttr(resourceName, "variations.2.value", "another option"), resource.TestCheckResourceAttr(resourceName, "tags.#", "3"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("this"), "this"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("is"), "is"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("unordered"), "unordered"), + // the v2 terraform sdk forces you to index TypeSet attributes like tags on an ordered index + resource.TestCheckResourceAttr(resourceName, "tags.0", "is"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "this"), + resource.TestCheckResourceAttr(resourceName, "tags.2", "unordered"), resource.TestCheckResourceAttr(resourceName, "custom_properties.#", "2"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "key"), "some.property"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "name"), "Some Property"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "value.#"), "3"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "value.0"), "value1"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "value.1"), "value2"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "value.2"), "value3"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property2", "key"), "some.property2"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property2", "name"), "Some Property"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property2", "value.#"), "1"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property2", "value.0"), "very special custom property"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.key", "some.property"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.name", "Some Property"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.value.#", "3"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.value.0", "value1"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.value.1", "value2"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.value.2", "value3"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.1.key", "some.property2"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.1.name", "Some Property"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.1.value.#", "1"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.1.value.0", "very special custom property"), ), }, }, @@ -690,9 +748,9 @@ func TestAccFeatureFlag_CreateMultivariate2(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "variations.1.value", "123"), resource.TestCheckResourceAttr(resourceName, "variations.2.value", "123456789"), resource.TestCheckResourceAttr(resourceName, "tags.#", "3"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("this"), "this"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("is"), "is"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("unordered"), "unordered"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "is"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "this"), + resource.TestCheckResourceAttr(resourceName, "tags.2", "unordered"), ), }, }, @@ -720,16 +778,16 @@ func TestAccFeatureFlag_UpdateMultivariate(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "variations.1.value", "string2"), resource.TestCheckResourceAttr(resourceName, "variations.2.value", "another option"), resource.TestCheckResourceAttr(resourceName, "custom_properties.#", "2"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "key"), "some.property"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "name"), "Some Property"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "value.#"), "3"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "value.0"), "value1"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "value.1"), "value2"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "value.2"), "value3"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property2", "key"), "some.property2"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property2", "name"), "Some Property"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property2", "value.#"), "1"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property2", "value.0"), "very special custom property"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.key", "some.property"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.name", "Some Property"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.value.#", "3"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.value.0", "value1"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.value.1", "value2"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.value.2", "value3"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.1.key", "some.property2"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.1.name", "Some Property"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.1.value.#", "1"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.1.value.0", "very special custom property"), ), }, { @@ -752,15 +810,17 @@ func TestAccFeatureFlag_UpdateMultivariate(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "variations.3.name", "the new variation"), resource.TestCheckResourceAttr(resourceName, "variations.3.description", "This one was added upon update"), resource.TestCheckResourceAttr(resourceName, "tags.#", "3"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("this"), "this"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("is"), "is"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("unordered"), "unordered"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "is"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "this"), + resource.TestCheckResourceAttr(resourceName, "tags.2", "unordered"), resource.TestCheckResourceAttr(resourceName, "custom_properties.#", "1"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "key"), "some.property"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "name"), "Some Property Updated"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "value.#"), "2"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "value.0"), "value1"), - resource.TestCheckResourceAttr(resourceName, testAccCustomPropertyKey("some.property", "value.1"), "value3"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.key", "some.property"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.name", "Some Property Updated"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.value.#", "2"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.value.0", "value1"), + resource.TestCheckResourceAttr(resourceName, "custom_properties.0.value.1", "value3"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.on_variation", "2"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.off_variation", "1"), ), }, { @@ -775,28 +835,14 @@ func TestAccFeatureFlag_UpdateMultivariate(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "variations.0.value", "string1"), resource.TestCheckResourceAttr(resourceName, "variations.1.value", "string2"), resource.TestCheckResourceAttr(resourceName, "variations.2.value", "another option"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.on_variation", "2"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.off_variation", "1"), ), }, }, }) } -func TestAccFeatureFlag_DefaultsInvalid(t *testing.T) { - projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: withRandomProject(projectKey, testAccFeatureFlagDefaultsMissingOffInvalid), - ExpectError: regexp.MustCompile(`invalid default variations: default_on_variation "a" is not defined as a variation`), - }, - }, - }) -} - func TestAccFeatureFlag_UpdateDefaults(t *testing.T) { projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resourceName := "launchdarkly_feature_flag.defaults" @@ -811,8 +857,8 @@ func TestAccFeatureFlag_UpdateDefaults(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckProjectExists("launchdarkly_project.test"), testAccCheckFeatureFlagExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "default_on_variation", "true"), - resource.TestCheckResourceAttr(resourceName, "default_off_variation", "false"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.on_variation", "0"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.off_variation", "1"), ), }, { @@ -820,8 +866,8 @@ func TestAccFeatureFlag_UpdateDefaults(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckProjectExists("launchdarkly_project.test"), testAccCheckFeatureFlagExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "default_on_variation", "true"), - resource.TestCheckResourceAttr(resourceName, "default_off_variation", "true"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.on_variation", "0"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.off_variation", "0"), ), }, { @@ -848,8 +894,8 @@ func TestAccFeatureFlag_UpdateMultivariateDefaults(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckProjectExists("launchdarkly_project.test"), testAccCheckFeatureFlagExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "default_on_variation", "b"), - resource.TestCheckResourceAttr(resourceName, "default_off_variation", "b"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.on_variation", "1"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.off_variation", "1"), ), }, { @@ -857,8 +903,8 @@ func TestAccFeatureFlag_UpdateMultivariateDefaults(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckProjectExists("launchdarkly_project.test"), testAccCheckFeatureFlagExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "default_on_variation", "c"), - resource.TestCheckResourceAttr(resourceName, "default_off_variation", "c"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.on_variation", "2"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.off_variation", "2"), ), }, { @@ -866,15 +912,14 @@ func TestAccFeatureFlag_UpdateMultivariateDefaults(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckProjectExists("launchdarkly_project.test"), testAccCheckFeatureFlagExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "default_on_variation", "c"), - resource.TestCheckResourceAttr(resourceName, "default_off_variation", "c"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.on_variation", "2"), + resource.TestCheckResourceAttr(resourceName, "defaults.0.off_variation", "2"), ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"default"}, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -911,10 +956,6 @@ func TestAccFeatureFlag_EmptyStringVariation(t *testing.T) { }) } -func testAccCustomPropertyKey(key string, subKey string) string { - return fmt.Sprintf("custom_properties.%d.%s", hashcode.String(key), subKey) -} - func testAccCheckFeatureFlagExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] diff --git a/launchdarkly/resource_launchdarkly_project.go b/launchdarkly/resource_launchdarkly_project.go index ac864859..5713cbc1 100644 --- a/launchdarkly/resource_launchdarkly_project.go +++ b/launchdarkly/resource_launchdarkly_project.go @@ -1,11 +1,12 @@ package launchdarkly import ( + "context" "fmt" "log" "net/http" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" ) @@ -18,7 +19,7 @@ func resourceProject() *schema.Resource { Exists: resourceProjectExists, Importer: &schema.ResourceImporter{ - State: resourceProjectImport, + StateContext: resourceProjectImport, }, Schema: map[string]*schema.Schema{ @@ -38,11 +39,12 @@ func resourceProject() *schema.Resource { Type: schema.TypeBool, Optional: true, Description: "Whether feature flags created under the project should be available to client-side SDKs by default", + Default: false, }, TAGS: tagsSchema(), ENVIRONMENTS: { Type: schema.TypeList, - Optional: true, + Required: true, Description: "List of nested `environments` blocks describing LaunchDarkly environments that belong to the project", Computed: false, Elem: &schema.Resource{ @@ -110,43 +112,61 @@ func resourceProjectUpdate(d *schema.ResourceData, metaRaw interface{}) error { return fmt.Errorf("failed to update project with key %q: %s", projectKey, handleLdapiErr(err)) } // Update environments if necessary - schemaEnvList, environmentsFound := d.GetOk(ENVIRONMENTS) - if environmentsFound { - // Get the project so we can see if we need to create any environments or just update existing environments - rawProject, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.ProjectsApi.GetProject(client.ctx, projectKey) + schemaEnvList := d.Get(ENVIRONMENTS) + // Get the project so we can see if we need to create any environments or just update existing environments + rawProject, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { + return client.ld.ProjectsApi.GetProject(client.ctx, projectKey) + }) + if err != nil { + return fmt.Errorf("failed to load project %q before updating environments: %s", projectKey, handleLdapiErr(err)) + } + project := rawProject.(ldapi.Project) + + environmentConfigs := schemaEnvList.([]interface{}) + // save envs in a key:config map so we can more easily figure out which need to be patchRemoved after + var envConfigsForCompare = make(map[string]map[string]interface{}, len(environmentConfigs)) + for _, env := range environmentConfigs { + + envConfig := env.(map[string]interface{}) + envKey := envConfig[KEY].(string) + envConfigsForCompare[envKey] = envConfig + // Check if the environment already exists. If it does not exist, create it + exists := environmentExistsInProject(project, envKey) + if !exists { + envPost := environmentPostFromResourceData(env) + _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { + return client.ld.EnvironmentsApi.PostEnvironment(client.ctx, projectKey, envPost) + }) + if err != nil { + return fmt.Errorf("failed to create environment %q in project %q: %s", envKey, projectKey, handleLdapiErr(err)) + } + } + + // by default patching an env that was not recently tracked in the state will import it into the tf state + patches := getEnvironmentUpdatePatches(envConfig) + _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { + return handleNoConflict(func() (interface{}, *http.Response, error) { + return client.ld.EnvironmentsApi.PatchEnvironment(client.ctx, projectKey, envKey, patches) + }) }) if err != nil { - return fmt.Errorf("failed to load project %q before updating environments: %s", projectKey, handleLdapiErr(err)) + return fmt.Errorf("failed to update environment with key %q for project: %q: %+v", envKey, projectKey, err) } - project := rawProject.(ldapi.Project) - - environmentConfigs := schemaEnvList.([]interface{}) - for _, env := range environmentConfigs { - envConfig := env.(map[string]interface{}) - envKey := envConfig[KEY].(string) - // Check if the environment already exists. If it does not exist, create it - exists := environmentExistsInProject(project, envKey) - if !exists { - envPost := environmentPostFromResourceData(env) - _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.EnvironmentsApi.PostEnvironment(client.ctx, projectKey, envPost) - }) - if err != nil { - return fmt.Errorf("failed to create environment %q in project %q: %s", envKey, projectKey, handleLdapiErr(err)) - } - } - - patches := getEnvironmentUpdatePatches(envConfig) + } + // we also want to delete environments that were previously tracked in state and have been removed from the config + old, _ := d.GetChange(ENVIRONMENTS) + oldEnvs := old.([]interface{}) + for _, env := range oldEnvs { + envConfig := env.(map[string]interface{}) + envKey := envConfig[KEY].(string) + if _, persists := envConfigsForCompare[envKey]; !persists { _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { - return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.EnvironmentsApi.PatchEnvironment(client.ctx, projectKey, envKey, patches) - }) + res, err := client.ld.EnvironmentsApi.DeleteEnvironment(client.ctx, projectKey, envKey) + return nil, res, err }) if err != nil { - return fmt.Errorf("failed to update environment with key %q for project: %q: %+v", envKey, projectKey, err) + return fmt.Errorf("failed to delete environment %q in project %q: %s", envKey, projectKey, handleLdapiErr(err)) } - } } @@ -188,7 +208,7 @@ func projectExists(projectKey string, meta *Client) (bool, error) { return true, nil } -func resourceProjectImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { +func resourceProjectImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { _ = d.Set(KEY, d.Id()) return []*schema.ResourceData{d}, nil diff --git a/launchdarkly/resource_launchdarkly_project_test.go b/launchdarkly/resource_launchdarkly_project_test.go index 823d9711..b1a96a90 100644 --- a/launchdarkly/resource_launchdarkly_project_test.go +++ b/launchdarkly/resource_launchdarkly_project_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) // Project resources should be formatted with a random project key because acceptance tests @@ -16,7 +16,13 @@ const ( resource "launchdarkly_project" "test" { key = "%s" name = "test project" + include_in_snippet = false tags = [ "terraform", "test" ] + environments { + name = "Test Environment" + key = "test-env" + color = "010101" + } } ` testAccProjectUpdate = ` @@ -24,7 +30,24 @@ resource "launchdarkly_project" "test" { key = "%s" name = "awesome test project" include_in_snippet = true - tags = [] + tags = [ "terraform" ] + environments { + name = "Test Environment 2.0" + key = "test-env" + color = "020202" + } +} +` + + testAccProjectUpdateRemoveOptional = ` +resource "launchdarkly_project" "test" { + key = "%s" + name = "awesome test project" + environments { + name = "Test Environment 2.0" + key = "test-env" + color = "020202" + } } ` @@ -64,6 +87,18 @@ resource "launchdarkly_project" "env_test" { tags = ["new"] } } +` + + testAccProjectWithEnvironmentUpdateRemove = ` +resource "launchdarkly_project" "env_test" { + key = "%s" + name = "test project" + environments { + key = "test-env" + name = "test environment updated" + color = "AAAAAA" + } +} ` ) @@ -83,14 +118,17 @@ func TestAccProject_Create(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "key", projectKey), resource.TestCheckResourceAttr(resourceName, "name", "test project"), resource.TestCheckResourceAttr(resourceName, "tags.#", "2"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("test"), "test"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "test"), ), }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + // we currently do not set the environments attr in the importer function because + // we are not forcing a complete list of nested environments on imported resource + ImportStateVerifyIgnore: []string{ENVIRONMENTS}, }, }, }) @@ -112,8 +150,13 @@ func TestAccProject_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "key", projectKey), resource.TestCheckResourceAttr(resourceName, "name", "test project"), resource.TestCheckResourceAttr(resourceName, "tags.#", "2"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("test"), "test"), + resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "false"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "test"), + resource.TestCheckResourceAttr(resourceName, "environments.#", "1"), + resource.TestCheckResourceAttr(resourceName, "environments.0.name", "Test Environment"), + resource.TestCheckResourceAttr(resourceName, "environments.0.key", "test-env"), + resource.TestCheckResourceAttr(resourceName, "environments.0.color", "010101"), ), }, { @@ -122,8 +165,24 @@ func TestAccProject_Update(t *testing.T) { testAccCheckProjectExists(resourceName), resource.TestCheckResourceAttr(resourceName, "key", projectKey), resource.TestCheckResourceAttr(resourceName, "name", "awesome test project"), - resource.TestCheckResourceAttr(resourceName, "tags.#", "0"), resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "true"), + resource.TestCheckResourceAttr(resourceName, "tags.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), + resource.TestCheckResourceAttr(resourceName, "environments.#", "1"), + resource.TestCheckResourceAttr(resourceName, "environments.0.name", "Test Environment 2.0"), + resource.TestCheckResourceAttr(resourceName, "environments.0.key", "test-env"), + resource.TestCheckResourceAttr(resourceName, "environments.0.color", "020202"), + ), + }, + { // make sure that removal of optional attributes reverts them to their null value + Config: fmt.Sprintf(testAccProjectUpdateRemoveOptional, projectKey), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "key", projectKey), + resource.TestCheckResourceAttr(resourceName, "name", "awesome test project"), + resource.TestCheckNoResourceAttr(resourceName, "tags"), + resource.TestCheckNoResourceAttr(resourceName, "tags.#"), + resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "false"), ), }, }, @@ -195,6 +254,29 @@ func TestAccProject_WithEnvironments(t *testing.T) { ResourceName: resourceName, ImportState: true, }, + { + Config: fmt.Sprintf(testAccProjectWithEnvironmentUpdateRemove, projectKey), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "key", projectKey), + resource.TestCheckResourceAttr(resourceName, "name", "test project"), + resource.TestCheckResourceAttr(resourceName, "environments.#", "1"), + + // Check that optional attributes defaulted back to false + resource.TestCheckResourceAttr(resourceName, "environments.0.name", "test environment updated"), + resource.TestCheckResourceAttr(resourceName, "environments.0.tags.#", "0"), + resource.TestCheckResourceAttr(resourceName, "environments.0.color", "AAAAAA"), + resource.TestCheckResourceAttr(resourceName, "environments.0.default_ttl", "0"), + resource.TestCheckResourceAttr(resourceName, "environments.0.secure_mode", "false"), + resource.TestCheckResourceAttr(resourceName, "environments.0.default_track_events", "false"), + resource.TestCheckResourceAttr(resourceName, "environments.0.require_comments", "false"), + resource.TestCheckResourceAttr(resourceName, "environments.0.confirm_changes", "false"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + }, }, }) } diff --git a/launchdarkly/resource_launchdarkly_segment.go b/launchdarkly/resource_launchdarkly_segment.go index 920e2fcc..274ccf56 100644 --- a/launchdarkly/resource_launchdarkly_segment.go +++ b/launchdarkly/resource_launchdarkly_segment.go @@ -5,7 +5,7 @@ import ( "net/http" "strings" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" ) diff --git a/launchdarkly/resource_launchdarkly_segment_test.go b/launchdarkly/resource_launchdarkly_segment_test.go index 955a0194..2d3cde1f 100644 --- a/launchdarkly/resource_launchdarkly_segment_test.go +++ b/launchdarkly/resource_launchdarkly_segment_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) const ( @@ -37,7 +37,6 @@ resource "launchdarkly_segment" "test" { attribute = "test_att" op = "in" values = ["test"] - negate = false } clauses { attribute = "test_att_1" @@ -104,8 +103,8 @@ func TestAccSegment_Create(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "segment name"), resource.TestCheckResourceAttr(resourceName, "description", "segment description"), resource.TestCheckResourceAttr(resourceName, "tags.#", "2"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("segmentTag1"), "segmentTag1"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("segmentTag2"), "segmentTag2"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "segmentTag1"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "segmentTag2"), resource.TestCheckResourceAttr(resourceName, "included.#", "2"), resource.TestCheckResourceAttr(resourceName, "included.0", "user1"), resource.TestCheckResourceAttr(resourceName, "included.1", "user2"), @@ -144,8 +143,8 @@ func TestAccSegment_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "segment name"), resource.TestCheckResourceAttr(resourceName, "description", "segment description"), resource.TestCheckResourceAttr(resourceName, "tags.#", "2"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("segmentTag1"), "segmentTag1"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("segmentTag2"), "segmentTag2"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "segmentTag1"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "segmentTag2"), resource.TestCheckResourceAttr(resourceName, "included.#", "2"), resource.TestCheckResourceAttr(resourceName, "included.0", "user1"), resource.TestCheckResourceAttr(resourceName, "included.1", "user2"), @@ -165,8 +164,8 @@ func TestAccSegment_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "segment name"), resource.TestCheckResourceAttr(resourceName, "description", "segment description"), resource.TestCheckResourceAttr(resourceName, "tags.#", "2"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("segmentTag1"), "segmentTag1"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey(".segmentTag2"), ".segmentTag2"), + resource.TestCheckResourceAttr(resourceName, "tags.0", ".segmentTag2"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "segmentTag1"), resource.TestCheckResourceAttr(resourceName, "included.#", "4"), resource.TestCheckResourceAttr(resourceName, "included.0", "user1"), resource.TestCheckResourceAttr(resourceName, "included.1", "user2"), @@ -201,8 +200,8 @@ func TestAccSegment_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "segment name"), resource.TestCheckResourceAttr(resourceName, "description", "segment description"), resource.TestCheckResourceAttr(resourceName, "tags.#", "2"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("segmentTag1"), "segmentTag1"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("segmentTag2"), "segmentTag2"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "segmentTag1"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "segmentTag2"), resource.TestCheckResourceAttr(resourceName, "included.#", "2"), resource.TestCheckResourceAttr(resourceName, "included.0", "user1"), resource.TestCheckResourceAttr(resourceName, "included.1", "user2"), diff --git a/launchdarkly/resource_launchdarkly_team_member.go b/launchdarkly/resource_launchdarkly_team_member.go index 3a9365c8..2d9bb00f 100644 --- a/launchdarkly/resource_launchdarkly_team_member.go +++ b/launchdarkly/resource_launchdarkly_team_member.go @@ -5,8 +5,8 @@ import ( "log" "net/http" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) @@ -20,7 +20,7 @@ func resourceTeamMember() *schema.Resource { Exists: resourceTeamMemberExists, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ diff --git a/launchdarkly/resource_launchdarkly_team_member_test.go b/launchdarkly/resource_launchdarkly_team_member_test.go index e46f2d31..87acbdf5 100644 --- a/launchdarkly/resource_launchdarkly_team_member_test.go +++ b/launchdarkly/resource_launchdarkly_team_member_test.go @@ -4,10 +4,9 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) const ( @@ -165,7 +164,7 @@ func TestAccTeamMember_CreateWithCustomRole(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "first_name", "first"), resource.TestCheckResourceAttr(resourceName, "last_name", "last"), resource.TestCheckResourceAttr(resourceName, "custom_roles.#", "1"), - resource.TestCheckResourceAttr(resourceName, "custom_roles."+testAccMemberCustomRolePropertyKey(roleKey), roleKey), + resource.TestCheckResourceAttr(resourceName, "custom_roles.0", roleKey), ), }, { @@ -199,7 +198,7 @@ func TestAccTeamMember_UpdateWithCustomRole(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "first_name", "first"), resource.TestCheckResourceAttr(resourceName, "last_name", "last"), resource.TestCheckResourceAttr(resourceName, "custom_roles.#", "1"), - resource.TestCheckResourceAttr(resourceName, "custom_roles."+testAccMemberCustomRolePropertyKey(roleKey1), roleKey1), + resource.TestCheckResourceAttr(resourceName, "custom_roles.0", roleKey1), ), }, { @@ -216,7 +215,7 @@ func TestAccTeamMember_UpdateWithCustomRole(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "first_name", "first"), resource.TestCheckResourceAttr(resourceName, "last_name", "last"), resource.TestCheckResourceAttr(resourceName, "custom_roles.#", "1"), - resource.TestCheckResourceAttr(resourceName, "custom_roles."+testAccMemberCustomRolePropertyKey(roleKey2), roleKey2), + resource.TestCheckResourceAttr(resourceName, "custom_roles.0", roleKey2), ), }, { @@ -228,10 +227,6 @@ func TestAccTeamMember_UpdateWithCustomRole(t *testing.T) { }) } -func testAccMemberCustomRolePropertyKey(roleKey string) string { - return fmt.Sprintf("%d", hashcode.String(roleKey)) -} - func testAccCheckMemberExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] diff --git a/launchdarkly/resource_launchdarkly_webhook.go b/launchdarkly/resource_launchdarkly_webhook.go index 7488ba6d..32407630 100644 --- a/launchdarkly/resource_launchdarkly_webhook.go +++ b/launchdarkly/resource_launchdarkly_webhook.go @@ -1,11 +1,10 @@ package launchdarkly import ( - "errors" "fmt" "net/http" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" ) @@ -16,18 +15,11 @@ func resourceWebhook() *schema.Resource { Required: true, Description: "The URL of the remote webhook", } - schemaMap[ENABLED] = &schema.Schema{ - Type: schema.TypeBool, - Description: "Whether this webhook is enabled or not. This field has been deprecated in favor of 'on'", - Optional: true, - Deprecated: "'enabled' is deprecated in favor of 'on'", - ConflictsWith: []string{ON}, - } schemaMap[ON] = &schema.Schema{ - Type: schema.TypeBool, - Description: "Whether this webhook is enabled or not", - Optional: true, - ConflictsWith: []string{ENABLED}, + Type: schema.TypeBool, + Description: "Whether this webhook is enabled or not", + Optional: true, + Default: false, } return &schema.Resource{ Create: resourceWebhookCreate, @@ -37,7 +29,7 @@ func resourceWebhook() *schema.Resource { Exists: resourceWebhookExists, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + StateContext: schema.ImportStatePassthroughContext, }, Schema: schemaMap, @@ -49,15 +41,12 @@ func resourceWebhookCreate(d *schema.ResourceData, metaRaw interface{}) error { webhookURL := d.Get(URL).(string) webhookSecret := d.Get(SECRET).(string) webhookName := d.Get(NAME).(string) - statements, err := policyStatementsFromResourceData(getWebhookStatements(d)) + statements, err := policyStatementsFromResourceData(d.Get(STATEMENTS).([]interface{})) if err != nil { return err } - webhookOn, err := getWebhookOn(d) - if err != nil { - return err - } + webhookOn := d.Get(ON).(bool) webhookBody := ldapi.WebhookBody{ Url: webhookURL, @@ -103,11 +92,7 @@ func resourceWebhookUpdate(d *schema.ResourceData, metaRaw interface{}) error { webhookSecret := d.Get(SECRET).(string) webhookName := d.Get(NAME).(string) webhookTags := stringsFromResourceData(d, TAGS) - - webhookOn, err := getWebhookOn(d) - if err != nil { - return err - } + webhookOn := d.Get(ON).(bool) patch := []ldapi.PatchOperation{ patchReplace("/url", &webhookURL), @@ -117,12 +102,17 @@ func resourceWebhookUpdate(d *schema.ResourceData, metaRaw interface{}) error { patchReplace("/tags", &webhookTags), } - statements, err := policyStatementsFromResourceData(getWebhookStatements(d)) + statements, err := policyStatementsFromResourceData(d.Get(STATEMENTS).([]interface{})) if err != nil { return err } - if len(statements) > 0 { - patch = append(patch, patchReplace("/statements", &statements)) + + if d.HasChange(STATEMENTS) { + if len(statements) > 0 { + patch = append(patch, patchReplace("/statements", &statements)) + } else { + patch = append(patch, patchRemove("/statements")) + } } _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { @@ -170,31 +160,3 @@ func webhookExists(webhookID string, meta *Client) (bool, error) { return true, nil } - -// getWebhookOn is a helper function used for deprecating ENABLED in favor of ON to match -// LD's API response. -func getWebhookOn(d *schema.ResourceData) (bool, error) { - var webhookOn bool - enabled, enabledSet := d.GetOkExists(ENABLED) - on, onSet := d.GetOkExists(ON) - if !onSet && !enabledSet { - return false, errors.New("one of 'on' or 'enabled' must be configured") - } - if enabledSet { - webhookOn = enabled.(bool) - } else { - webhookOn = on.(bool) - } - return webhookOn, nil -} - -// getWebhookStatements is a helper function used for deprecating POLICY_STATEMENTS in favor of STATEMENTS -// to match LD's API response. -func getWebhookStatements(d *schema.ResourceData) []interface{} { - if v, ok := d.GetOk(POLICY_STATEMENTS); ok { - return v.([]interface{}) - } else if v, ok := d.GetOk(STATEMENTS); ok { - return v.([]interface{}) - } - return make([]interface{}, 0) -} diff --git a/launchdarkly/resource_launchdarkly_webhook_test.go b/launchdarkly/resource_launchdarkly_webhook_test.go index 919fffd3..34a12d9e 100644 --- a/launchdarkly/resource_launchdarkly_webhook_test.go +++ b/launchdarkly/resource_launchdarkly_webhook_test.go @@ -5,8 +5,8 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) const ( @@ -24,7 +24,7 @@ resource "launchdarkly_webhook" "test" { name = "example-webhook" url = "http://webhooks.com" tags = [ "terraform" ] - enabled = true + on = true } ` @@ -50,13 +50,21 @@ resource "launchdarkly_webhook" "with_statements" { } } ` - // policy_statements is deprecated but we will still support it in v1 + + testAccWebhookWithStatementsRemoved = ` +resource "launchdarkly_webhook" "with_statements" { + name = "Webhook without statements" + url = "http://webhooks.com" + on = true +} +` + testAccWebhookWithPolicyStatements = ` resource "launchdarkly_webhook" "with_statements" { name = "Webhook with policy statements" url = "http://webhooks.com" on = true - policy_statements { + statements { actions = ["*"] effect = "allow" resources = ["proj/*:env/production:flag/*"] @@ -113,8 +121,8 @@ func TestAccWebhook_Create(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "url", "http://webhooks.com"), resource.TestCheckResourceAttr(resourceName, "on", "true"), resource.TestCheckResourceAttr(resourceName, "tags.#", "1"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), - resource.TestCheckResourceAttr(resourceName, "policy_statements.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), + resource.TestCheckResourceAttr(resourceName, "statements.#", "0"), ), }, { @@ -140,10 +148,10 @@ func TestAccWebhook_CreateWithEnabled(t *testing.T) { testAccCheckWebhookExists(resourceName), resource.TestCheckResourceAttr(resourceName, "name", "example-webhook"), resource.TestCheckResourceAttr(resourceName, "url", "http://webhooks.com"), - resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "on", "true"), resource.TestCheckResourceAttr(resourceName, "tags.#", "1"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), - resource.TestCheckResourceAttr(resourceName, "policy_statements.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), + resource.TestCheckResourceAttr(resourceName, "statements.#", "0"), ), }, { @@ -168,10 +176,10 @@ func TestAccWebhook_Update(t *testing.T) { testAccCheckWebhookExists(resourceName), resource.TestCheckResourceAttr(resourceName, "name", "example-webhook"), resource.TestCheckResourceAttr(resourceName, "url", "http://webhooks.com"), - resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "on", "true"), resource.TestCheckResourceAttr(resourceName, "tags.#", "1"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), - resource.TestCheckResourceAttr(resourceName, "policy_statements.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), + resource.TestCheckResourceAttr(resourceName, "statements.#", "0"), ), }, { @@ -182,8 +190,8 @@ func TestAccWebhook_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "url", "http://webhooks.com"), resource.TestCheckResourceAttr(resourceName, "on", "true"), resource.TestCheckResourceAttr(resourceName, "tags.#", "1"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), - resource.TestCheckResourceAttr(resourceName, "policy_statements.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), + resource.TestCheckResourceAttr(resourceName, "statements.#", "0"), ), }, { @@ -194,10 +202,10 @@ func TestAccWebhook_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "url", "http://webhooks.com/updatedUrl"), resource.TestCheckResourceAttr(resourceName, "on", "false"), resource.TestCheckResourceAttr(resourceName, "tags.#", "2"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("terraform"), "terraform"), - resource.TestCheckResourceAttr(resourceName, testAccTagKey("updated"), "updated"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "updated"), resource.TestCheckResourceAttr(resourceName, SECRET, "SuperSecret"), - resource.TestCheckResourceAttr(resourceName, "policy_statements.#", "0"), + resource.TestCheckResourceAttr(resourceName, "statements.#", "0"), ), }, { @@ -255,12 +263,12 @@ func TestAccWebhook_CreateWithPolicyStatements(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", "Webhook with policy statements"), resource.TestCheckResourceAttr(resourceName, "url", "http://webhooks.com"), resource.TestCheckResourceAttr(resourceName, "on", "true"), - resource.TestCheckResourceAttr(resourceName, "policy_statements.#", "1"), - resource.TestCheckResourceAttr(resourceName, "policy_statements.0.effect", "allow"), - resource.TestCheckResourceAttr(resourceName, "policy_statements.0.actions.#", "1"), - resource.TestCheckResourceAttr(resourceName, "policy_statements.0.actions.0", "*"), - resource.TestCheckResourceAttr(resourceName, "policy_statements.0.resources.#", "1"), - resource.TestCheckResourceAttr(resourceName, "policy_statements.0.resources.0", "proj/*:env/production:flag/*"), + resource.TestCheckResourceAttr(resourceName, "statements.#", "1"), + resource.TestCheckResourceAttr(resourceName, "statements.0.effect", "allow"), + resource.TestCheckResourceAttr(resourceName, "statements.0.actions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "statements.0.actions.0", "*"), + resource.TestCheckResourceAttr(resourceName, "statements.0.resources.#", "1"), + resource.TestCheckResourceAttr(resourceName, "statements.0.resources.0", "proj/*:env/production:flag/*"), ), }, }, @@ -310,6 +318,16 @@ func TestAccWebhook_UpdateWithStatements(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "statements.1.resources.0", "proj/test:env/production:segment/*"), ), }, + { + Config: testAccWebhookWithStatementsRemoved, + Check: resource.ComposeTestCheckFunc( + testAccCheckWebhookExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "Webhook without statements"), + resource.TestCheckResourceAttr(resourceName, "url", "http://webhooks.com"), + resource.TestCheckResourceAttr(resourceName, "on", "true"), + resource.TestCheckResourceAttr(resourceName, "statements.#", "0"), + ), + }, { ResourceName: resourceName, ImportState: true, diff --git a/launchdarkly/rollout_helper.go b/launchdarkly/rollout_helper.go index aeb89775..09a30b33 100644 --- a/launchdarkly/rollout_helper.go +++ b/launchdarkly/rollout_helper.go @@ -3,8 +3,8 @@ package launchdarkly import ( "log" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) diff --git a/launchdarkly/rule_helper.go b/launchdarkly/rule_helper.go index 3b081c72..15256a6c 100644 --- a/launchdarkly/rule_helper.go +++ b/launchdarkly/rule_helper.go @@ -4,8 +4,8 @@ import ( "errors" "log" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) @@ -14,7 +14,6 @@ func rulesSchema() *schema.Schema { Type: schema.TypeList, Optional: true, Description: "List of logical targeting rules. You must specify either clauses or rollout weights", - Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ CLAUSES: clauseSchema(), diff --git a/launchdarkly/segment_rule_helper.go b/launchdarkly/segment_rule_helper.go index 8de293b4..232d4936 100644 --- a/launchdarkly/segment_rule_helper.go +++ b/launchdarkly/segment_rule_helper.go @@ -1,8 +1,8 @@ package launchdarkly import ( - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) diff --git a/launchdarkly/segments_helper.go b/launchdarkly/segments_helper.go index 4ab611eb..97d89926 100644 --- a/launchdarkly/segments_helper.go +++ b/launchdarkly/segments_helper.go @@ -5,7 +5,7 @@ import ( "log" "net/http" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" ) diff --git a/launchdarkly/tags_helper.go b/launchdarkly/tags_helper.go index 8361928d..8c681df5 100644 --- a/launchdarkly/tags_helper.go +++ b/launchdarkly/tags_helper.go @@ -1,7 +1,7 @@ package launchdarkly import ( - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func tagsSchema() *schema.Schema { diff --git a/launchdarkly/target_helper.go b/launchdarkly/target_helper.go index 9263105d..36f3e829 100644 --- a/launchdarkly/target_helper.go +++ b/launchdarkly/target_helper.go @@ -1,100 +1,72 @@ package launchdarkly import ( - "log" - - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ldapi "github.com/launchdarkly/api-client-go" ) func targetsSchema() *schema.Schema { return &schema.Schema{ - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, - Description: "List of nested blocks describing the individual user targets for each variation. The order of the user_targets blocks determines the index of the variation to serve if a user_target is matched", - Computed: true, + Description: "Set of nested blocks describing the individual user targets for each variation", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "values": { + VALUES: { Type: schema.TypeList, Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, + Required: true, Description: "List of user strings to target", }, + VARIATION: { + Type: schema.TypeInt, + Required: true, + Description: "Index of the variation to serve if a user_target is matched", + ValidateFunc: validation.IntAtLeast(0), + }, }, }, } } func targetsFromResourceData(d *schema.ResourceData) []ldapi.Target { - var schemaTargets []interface{} - targetsHasChange := d.HasChange(TARGETS) - userTargetsHasChange := d.HasChange(USER_TARGETS) - if targetsHasChange { - schemaTargets = d.Get(TARGETS).([]interface{}) - } else if userTargetsHasChange { - schemaTargets = d.Get(USER_TARGETS).([]interface{}) + tgts, ok := d.GetOk(TARGETS) + if !ok { + return []ldapi.Target{} } - targets := make([]ldapi.Target, len(schemaTargets)) - for i, target := range schemaTargets { - v := targetFromResourceData(i, target) - targets[i] = v + schemaTargets := tgts.(*schema.Set).List() + targets := make([]ldapi.Target, 0, len(schemaTargets)) + for _, target := range schemaTargets { + targetMap := target.(map[string]interface{}) + targets = append(targets, targetFromResourceData(targetMap)) } return targets } -func targetFromResourceData(variation int, val interface{}) ldapi.Target { - if val == nil { - return ldapi.Target{Variation: int32(variation)} - } - targetMap := val.(map[string]interface{}) - p := ldapi.Target{ - Variation: int32(variation), +func targetFromResourceData(targetMap map[string]interface{}) ldapi.Target { + resourceValues := targetMap[VALUES].([]interface{}) + values := make([]string, 0, len(resourceValues)) + for _, v := range resourceValues { + values = append(values, v.(string)) } - for _, v := range targetMap[VALUES].([]interface{}) { - p.Values = append(p.Values, v.(string)) + return ldapi.Target{ + Variation: int32(targetMap[VARIATION].(int)), + Values: values, } - - log.Printf("[DEBUG] %+v\n", p) - - return p } // targetToResourceData converts the `target` information returned // by the LaunchDarkly API into a format suitable for Terraform -// If no `targets` are specified for a given variation, LaunchDarkly may -// omit this information in the response. For example: -// "targets": [ -// { -// "values": [ -// "test" -// ], -// "variation": 1 -// } -// ], -// From this information, we must imply that variation 0 has no targets. func targetsToResourceData(targets []ldapi.Target) []interface{} { - targetMap := make(map[int32][]string, len(targets)) - maxVariationIndex := int32(-1) - - for _, p := range targets { - if p.Variation > maxVariationIndex { - maxVariationIndex = p.Variation + transformed := make([]interface{}, 0, len(targets)) + for _, target := range targets { + resourceTarget := map[string]interface{}{ + VALUES: target.Values, + VARIATION: int(target.Variation), } - targetMap[p.Variation] = p.Values + transformed = append(transformed, resourceTarget) } - transformed := make([]interface{}, maxVariationIndex+1) - - for i := int32(0); i <= maxVariationIndex; i++ { - values, found := targetMap[i] - if !found { - values = []string{} - } - transformed[i] = map[string]interface{}{ - VALUES: values, - } - } - return transformed } diff --git a/launchdarkly/target_helper_test.go b/launchdarkly/target_helper_test.go index 585e9902..d94c8268 100644 --- a/launchdarkly/target_helper_test.go +++ b/launchdarkly/target_helper_test.go @@ -27,10 +27,12 @@ func TestTargetsToResourceData(t *testing.T) { }, expected: []interface{}{ map[string]interface{}{ - "values": []string{"test1"}, + "values": []string{"test1"}, + "variation": 0, }, map[string]interface{}{ - "values": []string{"test2"}, + "values": []string{"test2"}, + "variation": 1, }, }, }, @@ -48,10 +50,12 @@ func TestTargetsToResourceData(t *testing.T) { }, expected: []interface{}{ map[string]interface{}{ - "values": []string{"test2"}, + "values": []string{"test1"}, + "variation": 1, }, map[string]interface{}{ - "values": []string{"test1"}, + "values": []string{"test2"}, + "variation": 0, }, }, }, @@ -65,10 +69,8 @@ func TestTargetsToResourceData(t *testing.T) { }, expected: []interface{}{ map[string]interface{}{ - "values": []string{}, - }, - map[string]interface{}{ - "values": []string{"test2"}, + "values": []string{"test2"}, + "variation": 1, }, }, }, diff --git a/launchdarkly/validation_helper.go b/launchdarkly/validation_helper.go index 2d6ac6a0..46ddf14e 100644 --- a/launchdarkly/validation_helper.go +++ b/launchdarkly/validation_helper.go @@ -3,8 +3,8 @@ package launchdarkly import ( "regexp" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) func validateKey() schema.SchemaValidateFunc { diff --git a/launchdarkly/variations_helper.go b/launchdarkly/variations_helper.go index 1b927202..59d03c5d 100644 --- a/launchdarkly/variations_helper.go +++ b/launchdarkly/variations_helper.go @@ -7,8 +7,8 @@ import ( "strconv" "strings" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/structure" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" ldapi "github.com/launchdarkly/api-client-go" ) diff --git a/launchdarkly/variations_helper_test.go b/launchdarkly/variations_helper_test.go index 4f277b6a..68f0dec4 100644 --- a/launchdarkly/variations_helper_test.go +++ b/launchdarkly/variations_helper_test.go @@ -3,7 +3,7 @@ package launchdarkly import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/launchdarkly/webhooks_helper.go b/launchdarkly/webhooks_helper.go index 34d9dbf5..85b74069 100644 --- a/launchdarkly/webhooks_helper.go +++ b/launchdarkly/webhooks_helper.go @@ -5,7 +5,7 @@ import ( "log" "net/http" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ldapi "github.com/launchdarkly/api-client-go" ) @@ -22,16 +22,8 @@ func baseWebhookSchema() map[string]*schema.Schema { Optional: true, Description: "A human-readable name for your webhook", }, - POLICY_STATEMENTS: policyStatementsSchema( - policyStatementSchemaOptions{ - deprecated: "'policy_statements' is deprecated in favor of 'statements'", - conflictsWith: []string{STATEMENTS}, - }, - ), - STATEMENTS: policyStatementsSchema(policyStatementSchemaOptions{ - conflictsWith: []string{POLICY_STATEMENTS}, - }), - TAGS: tagsSchema(), + STATEMENTS: policyStatementsSchema(policyStatementSchemaOptions{}), + TAGS: tagsSchema(), } } @@ -63,43 +55,12 @@ func webhookRead(d *schema.ResourceData, meta interface{}, isDataSource bool) er } _ = d.Set(URL, webhook.Url) _ = d.Set(SECRET, webhook.Secret) - - // "enabled" is deprecated in favor of "on". For data sources, set both, for resources only set the one being used. - if isDataSource { - _ = d.Set(ENABLED, webhook.On) - _ = d.Set(ON, webhook.On) - } else { - if _, ok := d.GetOkExists(ENABLED); ok { - _ = d.Set(ENABLED, webhook.On) - } else { - _ = d.Set(ON, webhook.On) - } - } - + _ = d.Set(ON, webhook.On) _ = d.Set(NAME, webhook.Name) - // // "policy_statements" is deprecated in favor of "statements". For data sources, set both, for resources only set the one being used. - if isDataSource { - err = d.Set(POLICY_STATEMENTS, statements) - if err != nil { - return fmt.Errorf("failed to set policy_statements on webhook with id %q: %v", webhookID, err) - } - err = d.Set(STATEMENTS, statements) - if err != nil { - return fmt.Errorf("failed to set statements on webhook with id %q: %v", webhookID, err) - } - } else { - if _, ok := d.GetOk(POLICY_STATEMENTS); ok { - err = d.Set(POLICY_STATEMENTS, statements) - if err != nil { - return fmt.Errorf("failed to set policy_statements on webhook with id %q: %v", webhookID, err) - } - } else { - err = d.Set(STATEMENTS, statements) - if err != nil { - return fmt.Errorf("failed to set statements on webhook with id %q: %v", webhookID, err) - } - } + err = d.Set(STATEMENTS, statements) + if err != nil { + return fmt.Errorf("failed to set statements on webhook with id %q: %v", webhookID, err) } err = d.Set(TAGS, webhook.Tags) diff --git a/main.go b/main.go index ea77e8df..6a28484f 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/hashicorp/terraform-plugin-sdk/plugin" + "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" "github.com/launchdarkly/terraform-provider-launchdarkly/launchdarkly" ) diff --git a/website/docs/d/feature_flag.html.markdown b/website/docs/d/feature_flag.html.markdown index e6c29a46..43eec85c 100644 --- a/website/docs/d/feature_flag.html.markdown +++ b/website/docs/d/feature_flag.html.markdown @@ -38,9 +38,7 @@ In addition to the arguments above, the resource exports the following attribute - `variations` - List of nested blocks describing the variations associated with the feature flag. To learn more, read [Nested Variations Blocks](#nested-variations-blocks). -- `default_on_variation` - The value of the variation served when the flag is on for new environments. - -- `default_off_variation` - The value of the variation served when the flag is off for new environments. +- `defaults` - A map describing the index of the variation served when the flag is on for new environments. To learn more, read [Nested Defaults Blocks](#nested-defaults-blocks). - `description` - The feature flag's description. @@ -58,12 +56,20 @@ In addition to the arguments above, the resource exports the following attribute Nested `variations` blocks have the following attributes: -- `value` - The variation value. +- `value` - The variation value. - `name` - The name of the variation. - `description` - The variation's description. +### Nested Defaults Blocks + +Nested `defaults` blocks have the following structure: + +- `on_variation` - (Required) The index of the variation the flag will default to in all new environments when on. + +- `off_variation` - (Required) The index of the variation the flag will default to in all new environments when off. + ### Nested Client-Side Availibility Block The nested `client_side_availability` block has the following attributes: @@ -81,4 +87,3 @@ Nested `custom_properties` have the following attributes: - `name` - The name of the custom property. - `value` - The list of custom property value strings. - diff --git a/website/docs/d/feature_flag_environment.html.markdown b/website/docs/d/feature_flag_environment.html.markdown index a6ff47aa..82940a72 100644 --- a/website/docs/d/feature_flag_environment.html.markdown +++ b/website/docs/d/feature_flag_environment.html.markdown @@ -30,7 +30,7 @@ data "launchdarkly_feature_flag_environment" "example" { In addition to the arguments above, the resource exports the following attributes: -- `targeting_enabled` - Whether targeting is enabled. +- `on` - Whether targeting is enabled. - `track_events` - Whether event data will be sent back to LaunchDarkly. @@ -38,7 +38,7 @@ In addition to the arguments above, the resource exports the following attribute - `prerequisites` - List of nested blocks describing prerequisite feature flags rules. To learn more, read [Nested Prequisites Blocks](#nested-prerequisites-blocks). -- `targets` (previously `user_targets`) - List of nested blocks describing the individual user targets for each variation. The order of the `targets` blocks determines the index of the variation to serve if a `target` is matched. To learn more, read [Nested Target Blocks](#nested-targets-blocks). +- `targets` (previously `user_targets`) - Set of nested blocks describing the individual user targets for each variation. To learn more, read [Nested Target Blocks](#nested-targets-blocks). - `rules` - List of logical targeting rules. To learn more, read [Nested Rules Blocks](#nested-rules-blocks). @@ -58,6 +58,8 @@ Nested `targets` blocks have the following structure: - `values` - List of `user` strings to target. +- `variation` - The index of the variation to serve is a user target value is matched. + ### Nested Fallthrough Block The nested `fallthrough` block has the following structure: diff --git a/website/docs/d/project.html.markdown b/website/docs/d/project.html.markdown index b7c4dc94..ca8cf701 100644 --- a/website/docs/d/project.html.markdown +++ b/website/docs/d/project.html.markdown @@ -11,6 +11,8 @@ Provides a LaunchDarkly project data source. This data source allows you to retrieve project information from your LaunchDarkly organization. +-> **Note:** LaunchDarkly data sources do not provide access to the project's environments. If you wish to import environment configurations as data sources you must use the [`launchdarkly_environment` data source](/docs/providers/launchdarkly/d/environment.html). + ## Example Usage ```hcl diff --git a/website/docs/d/webhook.html.markdown b/website/docs/d/webhook.html.markdown index 68a3a3f5..90e9c1ec 100644 --- a/website/docs/d/webhook.html.markdown +++ b/website/docs/d/webhook.html.markdown @@ -29,7 +29,7 @@ In addition to the arguments above, the resource exports following attributes: - `url` - The URL of the remote webhook. -- `enabled` - Whether the webhook is enabled. This attribute is **deprecated** in favor or `on`. Please update all references of `enabled` to `on` to maintain compatibility with future versions. +- `on` - Whether the webhook is enabled. - `name` - The webhook's human-readable name. @@ -39,11 +39,9 @@ In addition to the arguments above, the resource exports following attributes: - `statements` - List of policy statement blocks used to filter webhook events. For more information on webhook policy filters read [Adding a policy filter](https://docs.launchdarkly.com/integrations/webhooks#adding-a-policy-filter). To learn more, read [Policy Statement Blocks](#policy-statement-blocks). -- `policy_statements` - List of policy statement blocks used to filter webhook events. For more information on webhook policy filters read [Adding a policy filter](https://docs.launchdarkly.com/integrations/webhooks#adding-a-policy-filter). To learn more, read [Policy Statement Blocks](#policy-statement-blocks). This attribute is **deprecated** in favor or `statements`. Please update all references of `policy_statements` to `statements` to maintain compatibility with future versions. +### Statement Blocks -### Policy Statement Blocks - -Webhook `statements` (previously `policy_statements`) blocks are composed of the following arguments: +Webhook `statements` blocks are composed of the following arguments: - `effect` - Either `allow` or `deny`. This argument defines whether the statement allows or denies access to the named resources and actions. diff --git a/website/docs/r/destination.html.markdown b/website/docs/r/destination.html.markdown index 89e85f37..396515a5 100644 --- a/website/docs/r/destination.html.markdown +++ b/website/docs/r/destination.html.markdown @@ -108,9 +108,7 @@ resource "launchdarkly_destination" "example" { - `config` - (Required) - The destination-specific configuration. To learn more, read [Destination-Specific Configs](#destination-specific-configs). -- `enabled` - (Optional, **Deprecated**) - Whether the data export destination is on or not. This field argument is **deprecated** in favor of `on`. Please update your config to use to `on` to maintain compatibility with future versions. - -- `on` - (Optional) - Whether the data export destination is on or not. +- `on` - (Optional, previously `enabled`) - Whether the data export destination is on or not. ### Destination-Specific Configs diff --git a/website/docs/r/environment.html.markdown b/website/docs/r/environment.html.markdown index 9e60e6a3..6d9614a9 100644 --- a/website/docs/r/environment.html.markdown +++ b/website/docs/r/environment.html.markdown @@ -9,7 +9,7 @@ description: |- Provides a LaunchDarkly environment resource. -This resource allows you to create and manage environments in your LaunchDarkly organization. +This resource allows you to create and manage environments in your LaunchDarkly organization. This resource should _not_ be used if the encapsulated project is also managed via Terraform. In this case, you should _always_ use the nested environments config blocks on your[`launchdarkly_project`](/docs/providers/launchdarkly/r/project.html) resource to manage your environments. -> **Note:** Mixing the use of nested `environments` blocks in the [`launchdarkly_project`](/docs/providers/launchdarkly/r/project.html) resource and `launchdarkly_environment` resources is not recommended. @@ -38,15 +38,15 @@ resource "launchdarkly_environment" "staging" { - `tags` - (Optional) Set of tags associated with the environment. -- `secure_mode` - (Optional) Set to `true` to ensure a user of the client-side SDK cannot impersonate another user. +- `secure_mode` - (Optional) Set to `true` to ensure a user of the client-side SDK cannot impersonate another user. This field will default to `false` when not set. -- `default_track_events` - (Optional) Set to `true` to enable data export for every flag created in this environment after you configure this argument. To learn more, read [Data Export](https://docs.launchdarkly.com/docs/data-export). +- `default_track_events` - (Optional) Set to `true` to enable data export for every flag created in this environment after you configure this argument. This field will default to `false` when not set. To learn more, read [Data Export](https://docs.launchdarkly.com/docs/data-export). -- `default_ttl` - (Optional) The TTL for the environment. This must be between 0 and 60 minutes. The TTL setting only applies to environments using the PHP SDK. To learn more, read [TTL settings](https://docs.launchdarkly.com/docs/environments#section-ttl-settings). +- `default_ttl` - (Optional) The TTL for the environment. This must be between 0 and 60 minutes. The TTL setting only applies to environments using the PHP SDK. This field will default to `0` when not set. To learn more, read [TTL settings](https://docs.launchdarkly.com/docs/environments#section-ttl-settings). -- `require_comments` - (Optional) Set to `true` if this environment requires comments for flag and segment changes. +- `require_comments` - (Optional) Set to `true` if this environment requires comments for flag and segment changes. This field will default to `false` when not set. -- `confirm_changes` - (Optional) Set to `true` if this environment requires confirmation for flag and segment changes. +- `confirm_changes` - (Optional) Set to `true` if this environment requires confirmation for flag and segment changes. This field will default to `false` when not set. ## Attribute Reference diff --git a/website/docs/r/feature_flag.html.markdown b/website/docs/r/feature_flag.html.markdown index 644c95c3..d88b17c9 100644 --- a/website/docs/r/feature_flag.html.markdown +++ b/website/docs/r/feature_flag.html.markdown @@ -37,8 +37,10 @@ resource "launchdarkly_feature_flag" "building_materials" { description = "The strongest variation" } - default_on_variation = "bricks" - default_off_variation = "straw" + defaults { + on_variation = 2 + off_variation = 0 + } tags = [ "example", @@ -65,8 +67,10 @@ resource "launchdarkly_feature_flag" "json_example" { value = jsonencode({ "foos" : ["bar1", "bar2"] }) } - default_on_variation = jsonencode({ "foos" : ["bar1", "bar2"] }) - default_off_variation = jsonencode({ "foo" : "bar" }) + defaults { + on_variation = 1 + off_variation = 0 + } } ``` @@ -82,15 +86,13 @@ resource "launchdarkly_feature_flag" "json_example" { - `variations` - (Required) List of nested blocks describing the variations associated with the feature flag. You must specify at least two variations. To learn more, read [Nested Variations Blocks](#nested-variations-blocks). -- `default_on_variation` - (Optional) The value of the variation served when the flag is on for new environments. Required if `default_off_variation` is set. Flag configurations in existing environments will not be changed. The value used here must be an exact match (including whitespace) to the `value` set in the `variations` list. - -- `default_off_variation` - (Optional) The value of the variation served when the flag is off for new environments. Required if `default_on_variation` is set. Flag configurations in existing environments will not be changed. The value used here must be an exact match (including whitespace) to the `value` set in the `variations` list. +- `defaults` - (Optional) A block containing the indices of the variations to be used as the default on and off variations in all new environments. Flag configurations in existing environments will not be changed nor updated if the configuration block is removed. To learn more, read [Nested Defaults Blocks](#nested-defaults-blocks). - `description` - (Optional) The feature flag's description. - `tags` - (Optional) Set of feature flag tags. -- `maintainer_id` - (Optional) The feature flag maintainer's 24 character alphanumeric team member ID. +- `maintainer_id` - (Optional) The feature flag maintainer's 24 character alphanumeric team member ID. If not set, it will automatically be or stay set to the member ID associated with the API key used by your LaunchDarkly Terraform provider or the most recently-set maintainer. - `temporary` - (Optional) Specifies whether the flag is a temporary flag. @@ -116,6 +118,14 @@ variations { - `description` - (Optional) The variation's description. +### Nested Defaults Blocks + +Nested `defaults` blocks have the following structure: + +- `on_variation` - (Required) The index of the variation the flag will default to in all new environments when on. + +- `off_variation` - (Required) The index of the variation the flag will default to in all new environments when off. + ### Nested Custom Properties Nested `custom_properties` have the following structure: diff --git a/website/docs/r/feature_flag_environment.html.markdown b/website/docs/r/feature_flag_environment.html.markdown index b0bbd38b..59a0e4cd 100644 --- a/website/docs/r/feature_flag_environment.html.markdown +++ b/website/docs/r/feature_flag_environment.html.markdown @@ -25,14 +25,13 @@ resource "launchdarkly_feature_flag_environment" "number_env" { variation = 0 } - user_targets { - values = ["user0"] - } - user_targets { - values = ["user1", "user2"] + targets { + values = ["user0"] + variation = 0 } - user_targets { - values = [] + targets { + values = ["user1", "user2"] + variation = 1 } rules { @@ -54,6 +53,7 @@ resource "launchdarkly_feature_flag_environment" "number_env" { fallthrough { rollout_weights = [60000, 40000, 0] } + off_variation = 2 } ``` @@ -63,23 +63,19 @@ resource "launchdarkly_feature_flag_environment" "number_env" { - `env_key` - (Required) The environment key. -- `targeting_enabled` - (Optional, **Deprecated**) Whether targeting is enabled. This field argument is **deprecated** in favor of `on`. Please update your config to use to `on` to maintain compatibility with future versions. Either `on` or `targeting_enabled` must be specified. - -- `on` - (Optional) Whether targeting is enabled. +- `on` (previously `targeting_enabled`) - (Optional) Whether targeting is enabled. Defaults to `false` if not set. -- `track_events` - (Optional) Whether to send event data back to LaunchDarkly. +- `track_events` - (Optional) Whether to send event data back to LaunchDarkly. Defaults to `false` if not set. -- `off_variation` - (Optional) The index of the variation to serve if targeting is disabled. +- `off_variation` - (Required) The index of the variation to serve if targeting is disabled. - `prerequisites` - (Optional) List of nested blocks describing prerequisite feature flags rules. To learn more, read [Nested Prequisites Blocks](#nested-prerequisites-blocks). -- `user_targets` - (Optional) List of nested blocks describing the individual user targets for each variation. The order of the `user_targets` blocks determines the index of the variation to serve if a `user_target` is matched. To learn more, read [Nested User Target Blocks](#nested-user-targets-blocks). +- `targets` (previously `user_targets`) - (Optional) Set of nested blocks describing the individual user targets for each variation. To learn more, read [Nested Target Blocks](#nested-targets-blocks). - `rules` - (Optional) List of logical targeting rules. To learn more, read [Nested Rules Blocks](#nested-rules-blocks). -- `flag_fallthrough` - (Optional, **Deprecated**) Nested block describing the default variation to serve if no `prerequisites`, `user_target`, or `rules` apply. This attribute is **deprecated** in favor of `fallthrough`. Please update all references of `flag_fallthrough` to `fallthrough` to maintain compatibility with future versions. - -- `fallthrough` - (Optional) Nested block describing the default variation to serve if no `prerequisites`, `user_target`, or `rules` apply.To learn more, read [Nested Fallthrough Block](#nested-fallthrough-block). +- `fallthrough` (previously `flag_fallthrough`) - (Required) Nested block describing the default variation to serve if no `prerequisites`, `target`, or `rules` apply.To learn more, read [Nested Fallthrough Block](#nested-fallthrough-block). ### Nested Prerequisites Blocks @@ -89,19 +85,21 @@ Nested `prerequisites` blocks have the following structure: - `variation` - (Required) The index of the prerequisite feature flag's variation to target. -### Nested User Targets Blocks +### Nested Targets Blocks + +Nested `targets` blocks have the following structure: -Nested `user_targets` blocks have the following structure: +- `values` - (Required) List of `user` strings to target. -- `values` - (Optional) List of `user` strings to target. +- `variation` - (Required) The index of the variation to serve is a user target value is matched. ### Nested Fallthrough Block The nested `fallthrough` (previously `flag_fallthrough`) block has the following structure: -- `variation` - (Optional) The default integer variation index to serve if no `prerequisites`, `user_target`, or `rules` apply. You must specify either `variation` or `rollout_weights`. +- `variation` - (Optional) The default integer variation index to serve if no `prerequisites`, `target`, or `rules` apply. You must specify either `variation` or `rollout_weights`. -- `rollout_weights` - (Optional) List of integer percentage rollout weights (in thousandths of a percent) to apply to each variation if no `prerequisites`, `user_target`, or `rules` apply. The sum of the `rollout_weights` must equal 100000. You must specify either `variation` or `rollout_weights`. +- `rollout_weights` - (Optional) List of integer percentage rollout weights (in thousandths of a percent) to apply to each variation if no `prerequisites`, `target`, or `rules` apply. The sum of the `rollout_weights` must equal 100000. You must specify either `variation` or `rollout_weights`. - `bucket_by` - (Optional) Group percentage rollout by a custom attribute. This argument is only valid if `rollout_weights` is also specified. @@ -131,6 +129,12 @@ Nested `clauses` blocks have the following structure: - `negate` - (Required) Whether to negate the rule clause. +Nested `fallthrough` blocks have the following structure: + +- `variation` - (Optional) The integer variation index to serve if the rule clauses evaluate to `true`. You must specify either `variation` or `rollout_weights`. + +- `rollout_weights` - (Optional) List of integer percentage rollout weights (in thousandths of a percent) to apply to each variation if the rule clauses evaluates to `true`. The sum of the `rollout_weights` must equal 100000. You must specify either `variation` or `rollout_weights`. + ## Attributes Reference In addition to the arguments above, the resource exports the following attribute: diff --git a/website/docs/r/project.html.markdown b/website/docs/r/project.html.markdown index 2c6ce5dd..0d785c66 100644 --- a/website/docs/r/project.html.markdown +++ b/website/docs/r/project.html.markdown @@ -44,13 +44,13 @@ resource "launchdarkly_project" "example" { - `name` - (Required) The project's name. -- `include_in_snippet - (Optional) Whether feature flags created under the project should be available to client-side SDKs by default. +- `environments` - (Required) List of nested `environments` blocks describing LaunchDarkly environments that belong to the project. When managing LaunchDarkly projects in Terraform, you should always manage your environments as nested project resources. To learn more, read [Nested Environments Blocks](#nested-environments-blocks). -- `tags` - (Optional) The project's set of tags. +-> **Note:** Mixing the use of nested `environments` blocks and [`launchdarkly_environment`](/docs/providers/launchdarkly/r/environment.html) resources is not recommended. `launchdarkly_environment` resources should only be used when the encapsulating project is not managed in Terraform. -- `environments` - (Optional) List of nested `environments` blocks describing LaunchDarkly environments that belong to the project. Use the nested `environments` blocks instead of the `launchdarkly_environment` resource when you wish to override the default behavior of creating `Test` and `Production` environments during project creation. To learn more, read [Nested Environments Blocks](#nested-environments-blocks). +- `include_in_snippet - (Optional) Whether feature flags created under the project should be available to client-side SDKs by default. --> **Note:** Mixing the use of nested `environments` blocks and [`launchdarkly_environment`](/docs/providers/launchdarkly/r/environment.html) resources is not recommended. +- `tags` - (Optional) The project's set of tags. ### Nested Environments Blocks @@ -64,15 +64,15 @@ Nested `environments` blocks have the following structure: - `tags` - (Optional) Set of tags associated with the environment. -- `secure_mode` - (Optional) Set to `true` to ensure a user of the client-side SDK cannot impersonate another user. +- `secure_mode` - (Optional) Set to `true` to ensure a user of the client-side SDK cannot impersonate another user. This field will default to `false` when not set. -- `default_track_events` - (Optional) Set to `true` to enable data export for every flag created in this environment after you configure this argument. To learn more, read [Data Export](https://docs.launchdarkly.com/docs/data-export). +- `default_track_events` - (Optional) Set to `true` to enable data export for every flag created in this environment after you configure this argument. This field will default to `false` when not set. To learn more, read [Data Export](https://docs.launchdarkly.com/docs/data-export). -- `default_ttl` - (Optional) The TTL for the environment. This must be between 0 and 60 minutes. The TTL setting only applies to environments using the PHP SDK. To learn more, read [TTL settings](https://docs.launchdarkly.com/docs/environments#section-ttl-settings). +- `default_ttl` - (Optional) The TTL for the environment. This must be between 0 and 60 minutes. The TTL setting only applies to environments using the PHP SDK. This field will default to `0` when not set. To learn more, read [TTL settings](https://docs.launchdarkly.com/docs/environments#section-ttl-settings). -- `require_comments` - (Optional) Set to `true` if this environment requires comments for flag and segment changes. +- `require_comments` - (Optional) Set to `true` if this environment requires comments for flag and segment changes. This field will default to `false` when not set. -- `confirm_changes` - (Optional) Set to `true` if this environment requires confirmation for flag and segment changes. +- `confirm_changes` - (Optional) Set to `true` if this environment requires confirmation for flag and segment changes. This field will default to `false` when not set. ## Import @@ -81,3 +81,5 @@ LaunchDarkly projects can be imported using the project's key, e.g. ``` $ terraform import launchdarkly_project.example example-project ``` + +Please note that, in order to manage an imported project resource using Terraform, you will be required to include at least one environment in your configuration. Managing environment resources with Terraform should always be done on the project unless the project is not also managed with Terraform. diff --git a/website/docs/r/team_member.html.markdown b/website/docs/r/team_member.html.markdown index 6b219db1..85c1d571 100644 --- a/website/docs/r/team_member.html.markdown +++ b/website/docs/r/team_member.html.markdown @@ -28,9 +28,9 @@ resource "launchdarkly_team_member" "example" { - `email` - (Required) The unique email address associated with the team member. -- `first_name` - (Optional) The team member's given name. +- `first_name` - (Optional) The team member's given name. Please note that, once created, this cannot be updated except by the team member themself. -- `last_name` - (Optional) The team member's family name. +- `last_name` - (Optional) The team member's family name. Please note that, once created, this cannot be updated except by the team member themself. - `role` - (Optional) The role associated with team member. Supported roles are `reader`, `writer`, or `admin`. If you don't specify a role, `reader` is assigned by default. diff --git a/website/docs/r/webhook.html.markdown b/website/docs/r/webhook.html.markdown index 304c6703..47ef5d7c 100644 --- a/website/docs/r/webhook.html.markdown +++ b/website/docs/r/webhook.html.markdown @@ -20,12 +20,12 @@ resource "launchdarkly_webhook" "example" { tags = ["terraform"] on = true - policy_statements { + statements { actions = ["*"] effect = "allow" resources = ["proj/*:env/production:flag/*"] } - policy_statements { + statements { actions = ["*"] effect = "allow" resources = resources = ["proj/test:env/production:segment/*"] @@ -37,9 +37,7 @@ resource "launchdarkly_webhook" "example" { - `url` - (Required) The URL of the remote webhook. -- `on` - (Optional) Specifies whether the webhook is enabled. Either `on` or `enabled` must be specified. - -- `enabled` - (Optional, **Deprecated**) Specifies whether the webhook is enabled. This field argument is **deprecated** in favor of `on`. Please update your config to use to `on` to maintain compatibility with future versions. Either `on` or `enabled` must be specified. +- `on` - (Required, previously `enabled`) Specifies whether the webhook is enabled. - `name` - (Optional) The webhook's human-readable name. @@ -47,11 +45,9 @@ resource "launchdarkly_webhook" "example" { - `tags` - (Optional) Set of tags associated with the webhook. -`statements` - (Optional) List of policy statement blocks used to filter webhook events. For more information on webhook policy filters read [Adding a policy filter](https://docs.launchdarkly.com/integrations/webhooks#adding-a-policy-filter). - -- `policy_statements` - (Optional, **Deprecated**) List of policy statement blocks used to filter webhook events. For more information on webhook policy filters read [Adding a policy filter](https://docs.launchdarkly.com/integrations/webhooks#adding-a-policy-filter). This argument is **deprecated** in favor of `statements`. Please update your config to use `statements` to maintain compatibility with future versions. +`statements` - (Optional, previously `policy_statements`) List of policy statement blocks used to filter webhook events. For more information on webhook policy filters read [Adding a policy filter](https://docs.launchdarkly.com/integrations/webhooks#adding-a-policy-filter). -Webhook `statements` and `policy_statements` blocks are composed of the following arguments: +Webhook `statements` blocks are composed of the following arguments: - `effect` - (Required) Either `allow` or `deny`. This argument defines whether the statement allows or denies access to the named resources and actions. From 2762d147e18ee9e8b847f3278270f34cf67acc05 Mon Sep 17 00:00:00 2001 From: Henry Barrow Date: Wed, 15 Sep 2021 15:12:04 +0100 Subject: [PATCH 06/14] Fix import of launchdarkly_project nested environments (#153) * start importing all environments * Preserve environment order * Update changelog * Add ignore_changes to withRandomProject --- CHANGELOG.md | 6 ++++++ launchdarkly/project_helper.go | 14 ++++++++++++++ .../resource_launchdarkly_feature_flag_test.go | 3 +++ .../resource_launchdarkly_project_test.go | 18 +++++++++++------- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef2073ff..e314a27e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [Unreleased] () + +BUG FIXES: + +- Fixed [a bug](https://github.com/launchdarkly/terraform-provider-launchdarkly/issues/67) resulting in nested environments not being imported on the `launchdarkly_project` resource. As a result, _all_ of a project's environments will be saved to the Terraform state during an import of the `launchdarkly_project` resource. Please keep in mind if you have not added all of the existing environments to your Terraform config before importing a `launchdarkly_project` resource, Terraform will delete these environments from LaunchDarkly during the next `terraform apply`. If you wish to manage project properties with Terraform but not nested environments consider using Terraform's [ignore changes](https://www.terraform.io/docs/language/meta-arguments/lifecycle.html#ignore_changes) lifecycle meta-argument. + ## [2.0.0] (August 31, 2021) ENHANCEMENTS: diff --git a/launchdarkly/project_helper.go b/launchdarkly/project_helper.go index 439d6678..566305e2 100644 --- a/launchdarkly/project_helper.go +++ b/launchdarkly/project_helper.go @@ -44,10 +44,24 @@ func projectRead(d *schema.ResourceData, meta interface{}, isDataSource bool) er // iterate over the environment keys in the order defined by the config and look up the environment returned by // LD's API rawEnvs := d.Get(ENVIRONMENTS).([]interface{}) + envConfigKeys := rawEnvironmentConfigsToKeyList(rawEnvs) + envAddedMap := make(map[string]bool, len(project.Environments)) environments := make([]interface{}, 0, len(envConfigKeys)) for _, envKey := range envConfigKeys { environments = append(environments, envMap[envKey]) + envAddedMap[envKey] = true + } + + // Now add all environments that are not specified in the config. + // This is required in order to successfully import nested environments because rawEnvs is always an empty slice + // durning import, even if nested environments are defined in the config. + for _, env := range project.Environments { + alreadyAdded := envAddedMap[env.Key] + if !alreadyAdded { + environments = append(environments, envMap[env.Key]) + envAddedMap[env.Key] = true + } } err = d.Set(ENVIRONMENTS, environments) diff --git a/launchdarkly/resource_launchdarkly_feature_flag_test.go b/launchdarkly/resource_launchdarkly_feature_flag_test.go index 6faaf260..89dd286d 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag_test.go +++ b/launchdarkly/resource_launchdarkly_feature_flag_test.go @@ -408,6 +408,9 @@ resource "launchdarkly_feature_flag" "empty_string_variation" { func withRandomProject(randomProject, resource string) string { return fmt.Sprintf(` resource "launchdarkly_project" "test" { + lifecycle { + ignore_changes = [environments] + } name = "testProject" key = "%s" environments { diff --git a/launchdarkly/resource_launchdarkly_project_test.go b/launchdarkly/resource_launchdarkly_project_test.go index b1a96a90..b377d557 100644 --- a/launchdarkly/resource_launchdarkly_project_test.go +++ b/launchdarkly/resource_launchdarkly_project_test.go @@ -126,9 +126,6 @@ func TestAccProject_Create(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - // we currently do not set the environments attr in the importer function because - // we are not forcing a complete list of nested environments on imported resource - ImportStateVerifyIgnore: []string{ENVIRONMENTS}, }, }, }) @@ -185,6 +182,11 @@ func TestAccProject_Update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "false"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -218,8 +220,9 @@ func TestAccProject_WithEnvironments(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, { Config: fmt.Sprintf(testAccProjectWithEnvironmentUpdate, projectKey), @@ -274,8 +277,9 @@ func TestAccProject_WithEnvironments(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) From 966aa6de84e10c981b7726e57242aa5478e36aae Mon Sep 17 00:00:00 2001 From: Isabelle Miller Date: Mon, 20 Sep 2021 15:59:23 +0100 Subject: [PATCH 07/14] update doc (#154) --- website/docs/r/project.html.markdown | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/website/docs/r/project.html.markdown b/website/docs/r/project.html.markdown index 0d785c66..8d073dac 100644 --- a/website/docs/r/project.html.markdown +++ b/website/docs/r/project.html.markdown @@ -82,4 +82,17 @@ LaunchDarkly projects can be imported using the project's key, e.g. $ terraform import launchdarkly_project.example example-project ``` -Please note that, in order to manage an imported project resource using Terraform, you will be required to include at least one environment in your configuration. Managing environment resources with Terraform should always be done on the project unless the project is not also managed with Terraform. +**IMPORTANT:** Please note that, regardless of how many `environments` blocks you include on your import, _all_ of the project's environments will be saved to the Terraform state and will update with subsequent applies. This means that any environments not included in your import configuration will be torn down with any subsequent apply. If you wish to manage project properties with Terraform but not nested environments consider using Terraform's [ignore changes](https://www.terraform.io/docs/language/meta-arguments/lifecycle.html#ignore_changes) lifecycle meta-argument; see below for example. + +``` +resource "launchdarkly_project" "example" { + lifecycle { + ignore_changes = [environments] + } + name = "testProject" + key = "%s" + # environments not included on this configuration will not be affected by subsequent applies + } +``` + +Managing environment resources with Terraform should always be done on the project unless the project is not also managed with Terraform. From fe9668d9cb8c7fe673e11171a66309b980190030 Mon Sep 17 00:00:00 2001 From: Isabelle Miller Date: Mon, 27 Sep 2021 17:05:33 +0100 Subject: [PATCH 08/14] Imiller/sc 119920/add boolean archive attribute to launchdarkly (#155) * add archived to ff env schema * make it a global property * put it in the wrong schema doh * attempt archive before deleting so we can return an error if there are dependencies * prelim test * some syntax issues * gotta figure out error bit * remove failing test * forgot to set archived in read function * actually no need to archive first --- launchdarkly/feature_flags_helper.go | 7 +++++++ launchdarkly/keys.go | 1 + launchdarkly/resource_launchdarkly_feature_flag.go | 2 ++ launchdarkly/resource_launchdarkly_feature_flag_test.go | 7 +++---- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/launchdarkly/feature_flags_helper.go b/launchdarkly/feature_flags_helper.go index 354f0045..45f54449 100644 --- a/launchdarkly/feature_flags_helper.go +++ b/launchdarkly/feature_flags_helper.go @@ -77,6 +77,12 @@ func baseFeatureFlagSchema() map[string]*schema.Schema { }, }, }, + ARCHIVED: { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to archive the flag", + Default: false, + }, } } @@ -105,6 +111,7 @@ func featureFlagRead(d *schema.ResourceData, raw interface{}, isDataSource bool) _ = d.Set(DESCRIPTION, flag.Description) _ = d.Set(INCLUDE_IN_SNIPPET, flag.IncludeInSnippet) _ = d.Set(TEMPORARY, flag.Temporary) + _ = d.Set(ARCHIVED, flag.Archived) if isDataSource { CSA := *flag.ClientSideAvailability diff --git a/launchdarkly/keys.go b/launchdarkly/keys.go index 0429b811..3faaad95 100644 --- a/launchdarkly/keys.go +++ b/launchdarkly/keys.go @@ -77,4 +77,5 @@ const ( EXPIRE = "expire" ID = "id" CLIENT_SIDE_AVAILABILITY = "client_side_availability" + ARCHIVED = "archived" ) diff --git a/launchdarkly/resource_launchdarkly_feature_flag.go b/launchdarkly/resource_launchdarkly_feature_flag.go index 5449abc0..44480383 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag.go +++ b/launchdarkly/resource_launchdarkly_feature_flag.go @@ -108,6 +108,7 @@ func resourceFeatureFlagUpdate(d *schema.ResourceData, metaRaw interface{}) erro includeInSnippet := d.Get(INCLUDE_IN_SNIPPET).(bool) temporary := d.Get(TEMPORARY).(bool) customProperties := customPropertiesFromResourceData(d) + archived := d.Get(ARCHIVED).(bool) patch := ldapi.PatchComment{ Comment: "Terraform", @@ -118,6 +119,7 @@ func resourceFeatureFlagUpdate(d *schema.ResourceData, metaRaw interface{}) erro patchReplace("/includeInSnippet", includeInSnippet), patchReplace("/temporary", temporary), patchReplace("/customProperties", customProperties), + patchReplace("/archived", archived), }} variationPatches, err := variationPatchesFromResourceData(d) diff --git a/launchdarkly/resource_launchdarkly_feature_flag_test.go b/launchdarkly/resource_launchdarkly_feature_flag_test.go index 89dd286d..44768c8a 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag_test.go +++ b/launchdarkly/resource_launchdarkly_feature_flag_test.go @@ -874,10 +874,9 @@ func TestAccFeatureFlag_UpdateDefaults(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"default"}, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) From a298a44f7a1c9deb3736ee9c70d33f5ea33c765b Mon Sep 17 00:00:00 2001 From: Henry Barrow Date: Thu, 30 Sep 2021 16:24:23 +0100 Subject: [PATCH 09/14] Update index of docs with recommended version (#156) * Update index of docs with recommended version * Show how to configure the access token --- website/docs/index.html.markdown | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index d1054e63..6aaaed15 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -12,9 +12,17 @@ description: |- ## Example Usage ```hcl +terraform { + required_providers { + launchdarkly = { + source = "launchdarkly/launchdarkly" + version = "~> 2.0" + } + } +} + # Configure the LaunchDarkly provider provider "launchdarkly" { - version = "~> 1.0" access_token = var.launchdarkly_access_token } @@ -29,21 +37,6 @@ resource "launchdarkly_feature_flag" "terraform" { } ``` -If you are using Terraform 0.13 and above, the provider declaration should go directly in your `terraform` block. Additionally, the syntax will be slightly different: - -```hcl -# Configure the LaunchDarkly provider -terraform { - required_providers { - launchdarkly = { - source = "launchdarkly/launchdarkly" - version = "~> 1.0" - } - } - required_version = "~> 0.13.0" -} -``` - Please refer to [Terraform's documentation on upgrading to v0.13](https://www.terraform.io/upgrade-guides/0-13.html) for more information. ## Argument Reference From 358b5328c2248c60783cb46ca474df55385db50f Mon Sep 17 00:00:00 2001 From: Isabelle Miller Date: Fri, 8 Oct 2021 10:01:54 -0400 Subject: [PATCH 10/14] Imiller/sc 123568/add approval settings to environment resource (#157) * prelim schema * tweak approval schema * draft conversion functions * i forgot you can't use conflictswith with nested attributes * a mess of things * fixed up patches * handle patch remove * okay i think this should handle all cases * unused transformation function since no setting * forgot to handle most obvious case * need to set but only sometimes * min num approvals cannot exceed 5 * make min num approvals required * project test * fix helper function for project - first pass * make entire approvals schema computed * projet tests working * NOW project tests working * remove dead code * update docs * update changelog * simplify approval helper function * switch old and new afunc arguments --- CHANGELOG.md | 8 + launchdarkly/approvals_helper.go | 123 ++++++++++++++ launchdarkly/environments_helper.go | 21 ++- launchdarkly/keys.go | 156 +++++++++--------- .../resource_launchdarkly_environment.go | 22 ++- .../resource_launchdarkly_environment_test.go | 103 ++++++++++++ launchdarkly/resource_launchdarkly_project.go | 20 ++- .../resource_launchdarkly_project_test.go | 74 ++++++++- website/docs/r/environment.html.markdown | 34 ++++ website/docs/r/project.html.markdown | 22 +++ 10 files changed, 498 insertions(+), 85 deletions(-) create mode 100644 launchdarkly/approvals_helper.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e0e01d8..d3c89027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [Unreleased] + +FEATURES: + +- Added `approval_settings` blocks to the `launchdarkly_environment` resource and nested `environments` blocks on the `launchdarkly_project` resource. + +- Added a boolean `archive` attribute on the `launchdarkly_feature_flag` resource to allow archiving and unarchiving flags instead of deleting them. + ## [2.0.1] (September 20, 2021) BUG FIXES: diff --git a/launchdarkly/approvals_helper.go b/launchdarkly/approvals_helper.go new file mode 100644 index 00000000..e4120970 --- /dev/null +++ b/launchdarkly/approvals_helper.go @@ -0,0 +1,123 @@ +package launchdarkly + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + ldapi "github.com/launchdarkly/api-client-go" +) + +func approvalSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + REQUIRED: { + Type: schema.TypeBool, + Optional: true, + Description: "Whether any changes to flags in this environment will require approval. You may only set required or requiredApprovalTags, not both.", + Default: false, + }, + CAN_REVIEW_OWN_REQUEST: { + Type: schema.TypeBool, + Optional: true, + Description: "Whether requesters can approve or decline their own request. They may always comment.", + Default: false, + }, + MIN_NUM_APPROVALS: { + Type: schema.TypeInt, + Optional: true, + Description: "The number of approvals required before an approval request can be applied.", + ValidateFunc: validation.IntBetween(1, 5), + Default: 1, + }, + CAN_APPLY_DECLINED_CHANGES: { + Type: schema.TypeBool, + Optional: true, + Description: "Whether changes can be applied as long as minNumApprovals is met, regardless of whether any reviewers have declined a request.", + Default: false, + }, + REQUIRED_APPROVAL_TAGS: { + Type: schema.TypeList, + Optional: true, + Description: "An array of tags used to specify which flags with those tags require approval. You may only set requiredApprovalTags or required, not both.", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateTags(), + }, + }, + }, + }, + } +} + +func approvalSettingsFromResourceData(val interface{}) (ldapi.EnvironmentApprovalSettings, error) { + raw := val.([]interface{}) + if len(raw) == 0 { + return ldapi.EnvironmentApprovalSettings{}, nil + } + approvalSettingsMap := raw[0].(map[string]interface{}) + settings := ldapi.EnvironmentApprovalSettings{ + CanReviewOwnRequest: approvalSettingsMap[CAN_REVIEW_OWN_REQUEST].(bool), + MinNumApprovals: int64(approvalSettingsMap[MIN_NUM_APPROVALS].(int)), + CanApplyDeclinedChanges: approvalSettingsMap[CAN_APPLY_DECLINED_CHANGES].(bool), + } + // Required and RequiredApprovalTags should never be defined simultaneously + // unfortunately since they default to their null values and are nested we cannot tell if the + // user has put a value in their config, so we'll check this way + required := approvalSettingsMap[REQUIRED].(bool) + tags := approvalSettingsMap[REQUIRED_APPROVAL_TAGS].([]interface{}) + if len(tags) > 0 { + if required { + return ldapi.EnvironmentApprovalSettings{}, fmt.Errorf("invalid approval_settings config: required and required_approval_tags cannot be set simultaneously") + } + stringTags := make([]string, len(tags)) + for i := range tags { + stringTags[i] = tags[i].(string) + } + settings.RequiredApprovalTags = stringTags + } else { + settings.Required = required + } + return settings, nil +} + +func approvalSettingsToResourceData(settings ldapi.EnvironmentApprovalSettings) interface{} { + transformed := map[string]interface{}{ + CAN_REVIEW_OWN_REQUEST: settings.CanReviewOwnRequest, + MIN_NUM_APPROVALS: settings.MinNumApprovals, + CAN_APPLY_DECLINED_CHANGES: settings.CanApplyDeclinedChanges, + REQUIRED_APPROVAL_TAGS: settings.RequiredApprovalTags, + REQUIRED: settings.Required, + } + return []map[string]interface{}{transformed} +} + +func approvalPatchFromSettings(oldApprovalSettings, newApprovalSettings interface{}) ([]ldapi.PatchOperation, error) { + settings, err := approvalSettingsFromResourceData(newApprovalSettings) + if err != nil { + return []ldapi.PatchOperation{}, err + } + new := newApprovalSettings.([]interface{}) + old := oldApprovalSettings.([]interface{}) + if len(new) == 0 && len(old) == 0 { + return []ldapi.PatchOperation{}, nil + } + if len(new) == 0 && len(old) > 0 { + return []ldapi.PatchOperation{ + patchRemove("/approvalSettings/required"), + patchRemove("/approvalSettings/requiredApprovalTags"), + }, nil + } + patch := []ldapi.PatchOperation{ + patchReplace("/approvalSettings/required", settings.Required), + patchReplace("/approvalSettings/canReviewOwnRequest", settings.CanReviewOwnRequest), + patchReplace("/approvalSettings/minNumApprovals", settings.MinNumApprovals), + patchReplace("/approvalSettings/canApplyDeclinedChanges", settings.CanApplyDeclinedChanges), + patchReplace("/approvalSettings/requiredApprovalTags", settings.RequiredApprovalTags), + } + return patch, nil +} diff --git a/launchdarkly/environments_helper.go b/launchdarkly/environments_helper.go index ffb10871..4e022c99 100644 --- a/launchdarkly/environments_helper.go +++ b/launchdarkly/environments_helper.go @@ -70,11 +70,12 @@ func baseEnvironmentSchema(forProject bool) map[string]*schema.Schema { Optional: true, Description: "Whether or not to require confirmation for flag and segment changes in this environment", }, - TAGS: tagsSchema(), + TAGS: tagsSchema(), + APPROVAL_SETTINGS: approvalSchema(), } } -func getEnvironmentUpdatePatches(config map[string]interface{}) []ldapi.PatchOperation { +func getEnvironmentUpdatePatches(oldConfig, config map[string]interface{}) ([]ldapi.PatchOperation, error) { // Always include required fields name := config[NAME] color := config[COLOR] @@ -114,7 +115,18 @@ func getEnvironmentUpdatePatches(config map[string]interface{}) []ldapi.PatchOpe envTags := stringsFromSchemaSet(tags.(*schema.Set)) patches = append(patches, patchReplace("/tags", &envTags)) } - return patches + + var oldApprovalSettings []interface{} + if oldSettings, ok := oldConfig[APPROVAL_SETTINGS]; ok { + oldApprovalSettings = oldSettings.([]interface{}) + } + newApprovalSettings := config[APPROVAL_SETTINGS] + approvalPatches, err := approvalPatchFromSettings(oldApprovalSettings, newApprovalSettings) + if err != nil { + return []ldapi.PatchOperation{}, err + } + patches = append(patches, approvalPatches...) + return patches, nil } func environmentSchema(forProject bool) map[string]*schema.Schema { @@ -193,6 +205,7 @@ func environmentToResourceData(env ldapi.Environment) envResourceData { REQUIRE_COMMENTS: env.RequireComments, CONFIRM_CHANGES: env.ConfirmChanges, TAGS: env.Tags, + APPROVAL_SETTINGS: approvalSettingsToResourceData(*env.ApprovalSettings), } } @@ -237,5 +250,7 @@ func environmentRead(d *schema.ResourceData, meta interface{}, isDataSource bool _ = d.Set(TAGS, env.Tags) _ = d.Set(REQUIRE_COMMENTS, env.RequireComments) _ = d.Set(CONFIRM_CHANGES, env.ConfirmChanges) + _ = d.Set(APPROVAL_SETTINGS, approvalSettingsToResourceData(*env.ApprovalSettings)) + return nil } diff --git a/launchdarkly/keys.go b/launchdarkly/keys.go index 3faaad95..12890b49 100644 --- a/launchdarkly/keys.go +++ b/launchdarkly/keys.go @@ -3,79 +3,85 @@ package launchdarkly const ( // keys used in terraform files referencing keys in launchdarkly resource objects. // The name of each constant is the same as its value. - PROJECT_KEY = "project_key" - ENV_KEY = "env_key" - KEY = "key" - FLAG_ID = "flag_id" - NAME = "name" - TAGS = "tags" - ENVIRONMENTS = "environments" - API_KEY = "api_key" - MOBILE_KEY = "mobile_key" - CLIENT_SIDE_ID = "client_side_id" - COLOR = "color" - DEFAULT_TTL = "default_ttl" - SECURE_MODE = "secure_mode" - DEFAULT_TRACK_EVENTS = "default_track_events" - REQUIRE_COMMENTS = "require_comments" - CONFIRM_CHANGES = "confirm_changes" - DESCRIPTION = "description" - MAINTAINER_ID = "maintainer_id" - VARIATION_TYPE = "variation_type" - VARIATIONS = "variations" - TEMPORARY = "temporary" - INCLUDE_IN_SNIPPET = "include_in_snippet" - VALUE = "value" - URL = "url" - SECRET = "secret" - ENABLED = "enabled" - ON = "on" - RESOURCES = "resources" - NOT_RESOURCES = "not_resources" - ACTIONS = "actions" - NOT_ACTIONS = "not_actions" - EFFECT = "effect" - POLICY = "policy" - STATEMENTS = "statements" - POLICY_STATEMENTS = "policy_statements" - INLINE_ROLES = "inline_roles" - EXCLUDED = "excluded" - INCLUDED = "included" - CREATION_DATE = "creation_date" - CUSTOM_PROPERTIES = "custom_properties" - EMAIL = "email" - FIRST_NAME = "first_name" - LAST_NAME = "last_name" - ROLE = "role" - CUSTOM_ROLES = "custom_roles" - RULES = "rules" - ATTRIBUTE = "attribute" - OP = "op" - VALUES = "values" - VALUE_TYPE = "value_type" - NEGATE = "negate" - CLAUSES = "clauses" - WEIGHT = "weight" - BUCKET_BY = "bucket_by" - ROLLOUT_WEIGHTS = "rollout_weights" - VARIATION = "variation" - TARGETS = "targets" - PREREQUISITES = "prerequisites" - FLAG_KEY = "flag_key" - TRACK_EVENTS = "track_events" - FALLTHROUGH = "fallthrough" - KIND = "kind" - CONFIG = "config" - DEFAULT_ON_VARIATION = "default_on_variation" - DEFAULT_OFF_VARIATION = "default_off_variation" - DEFAULTS = "defaults" - ON_VARIATION = "on_variation" - OFF_VARIATION = "off_variation" - SERVICE_TOKEN = "service_token" - DEFAULT_API_VERSION = "default_api_version" - TOKEN = "token" - EXPIRE = "expire" - ID = "id" - CLIENT_SIDE_AVAILABILITY = "client_side_availability" - ARCHIVED = "archived" + PROJECT_KEY = "project_key" + ENV_KEY = "env_key" + KEY = "key" + FLAG_ID = "flag_id" + NAME = "name" + TAGS = "tags" + ENVIRONMENTS = "environments" + API_KEY = "api_key" + MOBILE_KEY = "mobile_key" + CLIENT_SIDE_ID = "client_side_id" + COLOR = "color" + DEFAULT_TTL = "default_ttl" + SECURE_MODE = "secure_mode" + DEFAULT_TRACK_EVENTS = "default_track_events" + REQUIRE_COMMENTS = "require_comments" + CONFIRM_CHANGES = "confirm_changes" + DESCRIPTION = "description" + MAINTAINER_ID = "maintainer_id" + VARIATION_TYPE = "variation_type" + VARIATIONS = "variations" + TEMPORARY = "temporary" + INCLUDE_IN_SNIPPET = "include_in_snippet" + VALUE = "value" + URL = "url" + SECRET = "secret" + ENABLED = "enabled" + ON = "on" + RESOURCES = "resources" + NOT_RESOURCES = "not_resources" + ACTIONS = "actions" + NOT_ACTIONS = "not_actions" + EFFECT = "effect" + POLICY = "policy" + STATEMENTS = "statements" + POLICY_STATEMENTS = "policy_statements" + INLINE_ROLES = "inline_roles" + EXCLUDED = "excluded" + INCLUDED = "included" + CREATION_DATE = "creation_date" + CUSTOM_PROPERTIES = "custom_properties" + EMAIL = "email" + FIRST_NAME = "first_name" + LAST_NAME = "last_name" + ROLE = "role" + CUSTOM_ROLES = "custom_roles" + RULES = "rules" + ATTRIBUTE = "attribute" + OP = "op" + VALUES = "values" + VALUE_TYPE = "value_type" + NEGATE = "negate" + CLAUSES = "clauses" + WEIGHT = "weight" + BUCKET_BY = "bucket_by" + ROLLOUT_WEIGHTS = "rollout_weights" + VARIATION = "variation" + TARGETS = "targets" + PREREQUISITES = "prerequisites" + FLAG_KEY = "flag_key" + TRACK_EVENTS = "track_events" + FALLTHROUGH = "fallthrough" + KIND = "kind" + CONFIG = "config" + DEFAULT_ON_VARIATION = "default_on_variation" + DEFAULT_OFF_VARIATION = "default_off_variation" + DEFAULTS = "defaults" + ON_VARIATION = "on_variation" + OFF_VARIATION = "off_variation" + SERVICE_TOKEN = "service_token" + DEFAULT_API_VERSION = "default_api_version" + TOKEN = "token" + EXPIRE = "expire" + ID = "id" + CLIENT_SIDE_AVAILABILITY = "client_side_availability" + ARCHIVED = "archived" + APPROVAL_SETTINGS = "approval_settings" + REQUIRED = "required" + CAN_REVIEW_OWN_REQUEST = "can_review_own_request" + MIN_NUM_APPROVALS = "min_num_approvals" + CAN_APPLY_DECLINED_CHANGES = "can_apply_declined_changes" + REQUIRED_APPROVAL_TAGS = "required_approval_tags" ) diff --git a/launchdarkly/resource_launchdarkly_environment.go b/launchdarkly/resource_launchdarkly_environment.go index d4a0ad2b..c85125dc 100644 --- a/launchdarkly/resource_launchdarkly_environment.go +++ b/launchdarkly/resource_launchdarkly_environment.go @@ -65,6 +65,20 @@ func resourceEnvironmentCreate(d *schema.ResourceData, metaRaw interface{}) erro return fmt.Errorf("failed to create environment: [%+v] for project key: %s: %s", envPost, projectKey, handleLdapiErr(err)) } + approvalSettings := d.Get(APPROVAL_SETTINGS) + if len(approvalSettings.([]interface{})) > 0 { + err = resourceEnvironmentUpdate(d, metaRaw) + if err != nil { + // if there was a problem in the update state, we need to clean up completely by deleting the env + _, deleteErr := client.ld.EnvironmentsApi.DeleteEnvironment(client.ctx, projectKey, key) + if deleteErr != nil { + return fmt.Errorf("failed to clean up environment %q from project %q: %s", key, projectKey, handleLdapiErr(err)) + } + return fmt.Errorf("failed to update environment with name %q key %q for projectKey %q: %s", + name, key, projectKey, handleLdapiErr(err)) + } + } + d.SetId(projectKey + "/" + key) return resourceEnvironmentRead(d, metaRaw) } @@ -96,7 +110,13 @@ func resourceEnvironmentUpdate(d *schema.ResourceData, metaRaw interface{}) erro patchReplace("/confirmChanges", &confirmChanges), } - _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { + oldApprovalSettings, newApprovalSettings := d.GetChange(APPROVAL_SETTINGS) + approvalPatch, err := approvalPatchFromSettings(oldApprovalSettings, newApprovalSettings) + if err != nil { + return err + } + patch = append(patch, approvalPatch...) + _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { return client.ld.EnvironmentsApi.PatchEnvironment(client.ctx, projectKey, key, patch) }) diff --git a/launchdarkly/resource_launchdarkly_environment_test.go b/launchdarkly/resource_launchdarkly_environment_test.go index 7672c493..04f6cc1c 100644 --- a/launchdarkly/resource_launchdarkly_environment_test.go +++ b/launchdarkly/resource_launchdarkly_environment_test.go @@ -61,6 +61,43 @@ resource "launchdarkly_environment" "staging" { require_comments = false confirm_changes = true } +` + + testAccEnvironmentWithApprovals = ` +resource "launchdarkly_environment" "approvals_test" { + name = "Approvals Test" + key = "approvals-test" + color = "ababab" + project_key = launchdarkly_project.test.key + approval_settings { + can_review_own_request = false + min_num_approvals = 2 + required_approval_tags = ["approvals_required"] + } +} +` + testAccEnvironmentWithApprovalsUpdate = ` +resource "launchdarkly_environment" "approvals_test" { + name = "Approvals Test 2.0" + key = "approvals-test" + color = "bababa" + project_key = launchdarkly_project.test.key + approval_settings { + required = true + can_review_own_request = true + min_num_approvals = 1 + can_apply_declined_changes = true + } +} +` + + testAccEnvironmentWithApprovalsRemoved = ` +resource "launchdarkly_environment" "approvals_test" { + name = "Approvals Test 2.1" + key = "approvals-test" + color = "bababa" + project_key = launchdarkly_project.test.key +} ` ) @@ -220,6 +257,72 @@ func TestAccEnvironment_Invalid(t *testing.T) { }) } +func TestAccEnvironmentWithApprovals(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "launchdarkly_environment.approvals_test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccEnvironmentWithApprovals), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckEnvironmentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "Approvals Test"), + resource.TestCheckResourceAttr(resourceName, "key", "approvals-test"), + resource.TestCheckResourceAttr(resourceName, "color", "ababab"), + resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), + resource.TestCheckResourceAttr(resourceName, "approval_settings.0.can_review_own_request", "false"), + resource.TestCheckResourceAttr(resourceName, "approval_settings.0.can_apply_declined_changes", "false"), // should default to false + resource.TestCheckResourceAttr(resourceName, "approval_settings.0.min_num_approvals", "2"), + resource.TestCheckResourceAttr(resourceName, "approval_settings.0.required_approval_tags.0", "approvals_required"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: withRandomProject(projectKey, testAccEnvironmentWithApprovalsUpdate), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckEnvironmentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "Approvals Test 2.0"), + resource.TestCheckResourceAttr(resourceName, "key", "approvals-test"), + resource.TestCheckResourceAttr(resourceName, "color", "bababa"), + resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), + resource.TestCheckResourceAttr(resourceName, "approval_settings.0.required", "true"), + resource.TestCheckResourceAttr(resourceName, "approval_settings.0.can_review_own_request", "true"), + resource.TestCheckResourceAttr(resourceName, "approval_settings.0.can_apply_declined_changes", "true"), + resource.TestCheckResourceAttr(resourceName, "approval_settings.0.min_num_approvals", "1"), + resource.TestCheckNoResourceAttr(resourceName, "approval_settings.0.required_approval_tags"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: withRandomProject(projectKey, testAccEnvironmentWithApprovalsRemoved), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckEnvironmentExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "Approvals Test 2.1"), + resource.TestCheckResourceAttr(resourceName, "key", "approvals-test"), + resource.TestCheckResourceAttr(resourceName, "color", "bababa"), + resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), + resource.TestCheckNoResourceAttr(resourceName, "approval_settings"), + ), + }, + }, + }) +} + func testAccCheckEnvironmentExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] diff --git a/launchdarkly/resource_launchdarkly_project.go b/launchdarkly/resource_launchdarkly_project.go index 5713cbc1..f3442a87 100644 --- a/launchdarkly/resource_launchdarkly_project.go +++ b/launchdarkly/resource_launchdarkly_project.go @@ -112,7 +112,7 @@ func resourceProjectUpdate(d *schema.ResourceData, metaRaw interface{}) error { return fmt.Errorf("failed to update project with key %q: %s", projectKey, handleLdapiErr(err)) } // Update environments if necessary - schemaEnvList := d.Get(ENVIRONMENTS) + oldSchemaEnvList, newSchemaEnvList := d.GetChange(ENVIRONMENTS) // Get the project so we can see if we need to create any environments or just update existing environments rawProject, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { return client.ld.ProjectsApi.GetProject(client.ctx, projectKey) @@ -122,7 +122,14 @@ func resourceProjectUpdate(d *schema.ResourceData, metaRaw interface{}) error { } project := rawProject.(ldapi.Project) - environmentConfigs := schemaEnvList.([]interface{}) + environmentConfigs := newSchemaEnvList.([]interface{}) + oldEnvironmentConfigs := oldSchemaEnvList.([]interface{}) + var oldEnvConfigsForCompare = make(map[string]map[string]interface{}, len(oldEnvironmentConfigs)) + for _, env := range oldEnvironmentConfigs { + envConfig := env.(map[string]interface{}) + envKey := envConfig[KEY].(string) + oldEnvConfigsForCompare[envKey] = envConfig + } // save envs in a key:config map so we can more easily figure out which need to be patchRemoved after var envConfigsForCompare = make(map[string]map[string]interface{}, len(environmentConfigs)) for _, env := range environmentConfigs { @@ -142,8 +149,15 @@ func resourceProjectUpdate(d *schema.ResourceData, metaRaw interface{}) error { } } + var oldEnvConfig map[string]interface{} + if rawOldConfig, ok := oldEnvConfigsForCompare[envKey]; ok { + oldEnvConfig = rawOldConfig + } // by default patching an env that was not recently tracked in the state will import it into the tf state - patches := getEnvironmentUpdatePatches(envConfig) + patches, err := getEnvironmentUpdatePatches(oldEnvConfig, envConfig) + if err != nil { + return err + } _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { return client.ld.EnvironmentsApi.PatchEnvironment(client.ctx, projectKey, envKey, patches) diff --git a/launchdarkly/resource_launchdarkly_project_test.go b/launchdarkly/resource_launchdarkly_project_test.go index b377d557..7254bec0 100644 --- a/launchdarkly/resource_launchdarkly_project_test.go +++ b/launchdarkly/resource_launchdarkly_project_test.go @@ -79,12 +79,46 @@ resource "launchdarkly_project" "env_test" { require_comments = true confirm_changes = true } + environments { + key = "new-approvals-env" + name = "New approvals environment" + color = "EEEEEE" + tags = ["new"] + approval_settings { + required = true + can_review_own_request = true + min_num_approvals = 2 + } + } +} +` + testAccProjectWithEnvironmentUpdateApprovalSettings = ` +resource "launchdarkly_project" "env_test" { + key = "%s" + name = "test project" + environments { + key = "test-env" + name = "test environment updated" + color = "AAAAAA" + tags = ["terraform", "test", "updated"] + default_ttl = 30 + secure_mode = true + default_track_events = true + require_comments = true + confirm_changes = true + } environments { - key = "new-env" - name = "New test environment" + key = "new-approvals-env" + name = "New approvals environment" color = "EEEEEE" tags = ["new"] + approval_settings { + required_approval_tags = ["approvals_required"] + can_review_own_request = false + min_num_approvals = 1 + can_apply_declined_changes = false + } } } ` @@ -243,7 +277,36 @@ func TestAccProject_WithEnvironments(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "environments.0.confirm_changes", "true"), // Check environment 1 is created - resource.TestCheckResourceAttr(resourceName, "environments.1.name", "New test environment"), + resource.TestCheckResourceAttr(resourceName, "environments.1.key", "new-approvals-env"), + resource.TestCheckResourceAttr(resourceName, "environments.1.name", "New approvals environment"), + resource.TestCheckResourceAttr(resourceName, "environments.1.tags.#", "1"), + resource.TestCheckResourceAttr(resourceName, "environments.1.color", "EEEEEE"), + resource.TestCheckResourceAttr(resourceName, "environments.1.default_ttl", "0"), + resource.TestCheckResourceAttr(resourceName, "environments.1.secure_mode", "false"), + resource.TestCheckResourceAttr(resourceName, "environments.1.default_track_events", "false"), + resource.TestCheckResourceAttr(resourceName, "environments.1.require_comments", "false"), + resource.TestCheckResourceAttr(resourceName, "environments.1.confirm_changes", "false"), + resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.required", "true"), + resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.can_review_own_request", "true"), + resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.min_num_approvals", "2"), + resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.can_apply_declined_changes", "false"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + }, + { + Config: fmt.Sprintf(testAccProjectWithEnvironmentUpdateApprovalSettings, projectKey), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "key", projectKey), + resource.TestCheckResourceAttr(resourceName, "name", "test project"), + resource.TestCheckResourceAttr(resourceName, "environments.#", "2"), + + // Check approval_settings have updated as expected + resource.TestCheckResourceAttr(resourceName, "environments.1.key", "new-approvals-env"), + resource.TestCheckResourceAttr(resourceName, "environments.1.name", "New approvals environment"), resource.TestCheckResourceAttr(resourceName, "environments.1.tags.#", "1"), resource.TestCheckResourceAttr(resourceName, "environments.1.color", "EEEEEE"), resource.TestCheckResourceAttr(resourceName, "environments.1.default_ttl", "0"), @@ -251,6 +314,11 @@ func TestAccProject_WithEnvironments(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "environments.1.default_track_events", "false"), resource.TestCheckResourceAttr(resourceName, "environments.1.require_comments", "false"), resource.TestCheckResourceAttr(resourceName, "environments.1.confirm_changes", "false"), + resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.required", "false"), + resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.required_approval_tags.0", "approvals_required"), + resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.can_review_own_request", "false"), + resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.min_num_approvals", "1"), + resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.can_apply_declined_changes", "false"), ), }, { diff --git a/website/docs/r/environment.html.markdown b/website/docs/r/environment.html.markdown index 6d9614a9..1a6b505f 100644 --- a/website/docs/r/environment.html.markdown +++ b/website/docs/r/environment.html.markdown @@ -26,6 +26,24 @@ resource "launchdarkly_environment" "staging" { } ``` +```hcl +resource "launchdarkly_environment" "approvals_example" { + name = "Approvals Example Environment" + key = "approvals-example" + color = "ff00ff" + tags = ["terraform", "staging"] + + approval_settings { + required = true + can_review_own_request = true + min_num_approvals = 2 + can_apply_declined_changes = true + } + + project_key = launchdarkly_project.example.key +} +``` + ## Argument Reference - `project_key` - (Required) - The environment's project key. @@ -48,6 +66,8 @@ resource "launchdarkly_environment" "staging" { - `confirm_changes` - (Optional) Set to `true` if this environment requires confirmation for flag and segment changes. This field will default to `false` when not set. +- `approval_settings` - (Optional) A nested block describing the environment approval settings. To learn more about this feature, read [Approvals](https://docs.launchdarkly.com/home/feature-workflows/approvals). To learn more about configuring them in Terraform, read [Nested Approval Settings Blocks](#nested-approval-settings-blocks). + ## Attribute Reference In addition to the arguments above, the resource exports the following attributes: @@ -60,6 +80,20 @@ In addition to the arguments above, the resource exports the following attribute - `client_side_id` - The environment's client-side ID. +### Nested Approval Settings Blocks + +Nested `approval_settings` blocks have the following structure: + +- `required` - Set to `true` for changes to flags in this environment to require approval. You may only set `required` to true if `required_approval_tags` is not set and vice versa. Defaults to `false`. + +- `can_review_own_request` - Set to `true` if requesters can approve or decline their own request. They may always comment. Defaults to `false`. + +- `min_num_approvals` - The number of approvals required before an approval request can be applied. This number must be between 1 and 5. Defaults to 1. + +- `can_apply_declined_changes` - Set to `true` if changes can be applied as long as the `min_num_approvals` is met, regardless of whether any reviewers have declined a request. Defaults to `false`. + +- `required_approval_tags` - An array of tags used to specify which flags with those tags require approval. You may only set `required_approval_tags` if `required` is not set to `true` and vice versa. + ## Import You can import a LaunchDarkly environment using this format: `project_key/environment_key`. diff --git a/website/docs/r/project.html.markdown b/website/docs/r/project.html.markdown index 8d073dac..142319c1 100644 --- a/website/docs/r/project.html.markdown +++ b/website/docs/r/project.html.markdown @@ -27,6 +27,12 @@ resource "launchdarkly_project" "example" { name = "Production" color = "EEEEEE" tags = ["terraform"] + approval_settings { + can_review_own_request = false + can_apply_declined_changes = false + min_num_approvals = 3 + required_approval_tags = ["approvals_required"] + } } environments { @@ -74,6 +80,22 @@ Nested `environments` blocks have the following structure: - `confirm_changes` - (Optional) Set to `true` if this environment requires confirmation for flag and segment changes. This field will default to `false` when not set. +- `approval_settings` - (Optional) A nested block describing the environment approval settings. To learn more about this feature, read [Approvals](https://docs.launchdarkly.com/home/feature-workflows/approvals). To learn more about configuring them in Terraform, read [Nested Environments Approval Settings Blocks](#nested-environments-approval-settings-blocks). + +### Nested Environments Approval Settings Blocks + +Nested environments `approval_settings` blocks have the following structure: + +- `required` - Set to `true` for changes to flags in this environment to require approval. You may only set `required` to true if `required_approval_tags` is not set and vice versa. Defaults to `false`. + +- `can_review_own_request` - Set to `true` if requesters can approve or decline their own request. They may always comment. Defaults to `false`. + +- `min_num_approvals` - The number of approvals required before an approval request can be applied. This number must be between 1 and 5. Defaults to 1. + +- `can_apply_declined_changes` - Set to `true` if changes can be applied as long as the `min_num_approvals` is met, regardless of whether any reviewers have declined a request. Defaults to `false`. + +- `required_approval_tags` - An array of tags used to specify which flags with those tags require approval. You may only set `required_approval_tags` if `required` is not set to `true` and vice versa. + ## Import LaunchDarkly projects can be imported using the project's key, e.g. From 5ee5b57900ec4d90a3a8410f34eb5fc4bb669d72 Mon Sep 17 00:00:00 2001 From: Isabelle Miller Date: Mon, 11 Oct 2021 16:17:21 -0400 Subject: [PATCH 11/14] Imiller/sc 126337/make can apply declined changes default to (#158) * update error message * change default in schema * update tests * update docs' * update changelog --- CHANGELOG.md | 6 ++++++ launchdarkly/approvals_helper.go | 4 ++-- launchdarkly/resource_launchdarkly_environment_test.go | 6 +++--- launchdarkly/resource_launchdarkly_project_test.go | 2 +- launchdarkly/variations_helper.go | 2 +- website/docs/r/environment.html.markdown | 2 +- website/docs/r/project.html.markdown | 2 +- 7 files changed, 15 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63c3e641..61562492 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## [Unreleased] +BUG FIXES: + +- Fixed an oversight in the approval settings where `can_apply_declined_changes` was defaulting to `false` where it should have been defaulting to `true` in alignment with the LaunchDarkly API. + +- Updated an error message. + ## [2.1.0] (October 8, 2021) FEATURES: diff --git a/launchdarkly/approvals_helper.go b/launchdarkly/approvals_helper.go index e4120970..e8f888c1 100644 --- a/launchdarkly/approvals_helper.go +++ b/launchdarkly/approvals_helper.go @@ -37,8 +37,8 @@ func approvalSchema() *schema.Schema { CAN_APPLY_DECLINED_CHANGES: { Type: schema.TypeBool, Optional: true, - Description: "Whether changes can be applied as long as minNumApprovals is met, regardless of whether any reviewers have declined a request.", - Default: false, + Description: "Whether changes can be applied as long as minNumApprovals is met, regardless of whether any reviewers have declined a request. Defaults to true", + Default: true, }, REQUIRED_APPROVAL_TAGS: { Type: schema.TypeList, diff --git a/launchdarkly/resource_launchdarkly_environment_test.go b/launchdarkly/resource_launchdarkly_environment_test.go index 04f6cc1c..20afd84c 100644 --- a/launchdarkly/resource_launchdarkly_environment_test.go +++ b/launchdarkly/resource_launchdarkly_environment_test.go @@ -86,7 +86,7 @@ resource "launchdarkly_environment" "approvals_test" { required = true can_review_own_request = true min_num_approvals = 1 - can_apply_declined_changes = true + can_apply_declined_changes = false } } ` @@ -276,7 +276,7 @@ func TestAccEnvironmentWithApprovals(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "color", "ababab"), resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), resource.TestCheckResourceAttr(resourceName, "approval_settings.0.can_review_own_request", "false"), - resource.TestCheckResourceAttr(resourceName, "approval_settings.0.can_apply_declined_changes", "false"), // should default to false + resource.TestCheckResourceAttr(resourceName, "approval_settings.0.can_apply_declined_changes", "true"), // should default to true resource.TestCheckResourceAttr(resourceName, "approval_settings.0.min_num_approvals", "2"), resource.TestCheckResourceAttr(resourceName, "approval_settings.0.required_approval_tags.0", "approvals_required"), ), @@ -297,7 +297,7 @@ func TestAccEnvironmentWithApprovals(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), resource.TestCheckResourceAttr(resourceName, "approval_settings.0.required", "true"), resource.TestCheckResourceAttr(resourceName, "approval_settings.0.can_review_own_request", "true"), - resource.TestCheckResourceAttr(resourceName, "approval_settings.0.can_apply_declined_changes", "true"), + resource.TestCheckResourceAttr(resourceName, "approval_settings.0.can_apply_declined_changes", "false"), resource.TestCheckResourceAttr(resourceName, "approval_settings.0.min_num_approvals", "1"), resource.TestCheckNoResourceAttr(resourceName, "approval_settings.0.required_approval_tags"), ), diff --git a/launchdarkly/resource_launchdarkly_project_test.go b/launchdarkly/resource_launchdarkly_project_test.go index 7254bec0..5daceaff 100644 --- a/launchdarkly/resource_launchdarkly_project_test.go +++ b/launchdarkly/resource_launchdarkly_project_test.go @@ -289,7 +289,7 @@ func TestAccProject_WithEnvironments(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.required", "true"), resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.can_review_own_request", "true"), resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.min_num_approvals", "2"), - resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.can_apply_declined_changes", "false"), + resource.TestCheckResourceAttr(resourceName, "environments.1.approval_settings.0.can_apply_declined_changes", "true"), // defaults to true ), }, { diff --git a/launchdarkly/variations_helper.go b/launchdarkly/variations_helper.go index 59d03c5d..f5d9c75e 100644 --- a/launchdarkly/variations_helper.go +++ b/launchdarkly/variations_helper.go @@ -74,7 +74,7 @@ func validateVariationType(val interface{}, key string) (warns []string, errs [] case BOOL_VARIATION, STRING_VARIATION, NUMBER_VARIATION, JSON_VARIATION: break default: - errs = append(errs, fmt.Errorf("%q contains an invalid value %q. Valid values are `boolean` and `string`", key, value)) + errs = append(errs, fmt.Errorf("%q contains an invalid value %q. Valid values are `boolean`, `string`, `number`, and `json`", key, value)) } return warns, errs } diff --git a/website/docs/r/environment.html.markdown b/website/docs/r/environment.html.markdown index 1a6b505f..1d6782e6 100644 --- a/website/docs/r/environment.html.markdown +++ b/website/docs/r/environment.html.markdown @@ -90,7 +90,7 @@ Nested `approval_settings` blocks have the following structure: - `min_num_approvals` - The number of approvals required before an approval request can be applied. This number must be between 1 and 5. Defaults to 1. -- `can_apply_declined_changes` - Set to `true` if changes can be applied as long as the `min_num_approvals` is met, regardless of whether any reviewers have declined a request. Defaults to `false`. +- `can_apply_declined_changes` - Set to `true` if changes can be applied as long as the `min_num_approvals` is met, regardless of whether any reviewers have declined a request. Defaults to `true`. - `required_approval_tags` - An array of tags used to specify which flags with those tags require approval. You may only set `required_approval_tags` if `required` is not set to `true` and vice versa. diff --git a/website/docs/r/project.html.markdown b/website/docs/r/project.html.markdown index 142319c1..9c896d0e 100644 --- a/website/docs/r/project.html.markdown +++ b/website/docs/r/project.html.markdown @@ -92,7 +92,7 @@ Nested environments `approval_settings` blocks have the following structure: - `min_num_approvals` - The number of approvals required before an approval request can be applied. This number must be between 1 and 5. Defaults to 1. -- `can_apply_declined_changes` - Set to `true` if changes can be applied as long as the `min_num_approvals` is met, regardless of whether any reviewers have declined a request. Defaults to `false`. +- `can_apply_declined_changes` - Set to `true` if changes can be applied as long as the `min_num_approvals` is met, regardless of whether any reviewers have declined a request. Defaults to `true`. - `required_approval_tags` - An array of tags used to specify which flags with those tags require approval. You may only set `required_approval_tags` if `required` is not set to `true` and vice versa. From 9b2a2880cee4486b7b8288960d83ae35bf0f30d9 Mon Sep 17 00:00:00 2001 From: Isabelle Miller Date: Fri, 29 Oct 2021 14:51:12 +0200 Subject: [PATCH 12/14] clarify rollout weights (#159) --- website/docs/r/feature_flag_environment.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/r/feature_flag_environment.html.markdown b/website/docs/r/feature_flag_environment.html.markdown index 59a0e4cd..6a907177 100644 --- a/website/docs/r/feature_flag_environment.html.markdown +++ b/website/docs/r/feature_flag_environment.html.markdown @@ -99,7 +99,7 @@ The nested `fallthrough` (previously `flag_fallthrough`) block has the following - `variation` - (Optional) The default integer variation index to serve if no `prerequisites`, `target`, or `rules` apply. You must specify either `variation` or `rollout_weights`. -- `rollout_weights` - (Optional) List of integer percentage rollout weights (in thousandths of a percent) to apply to each variation if no `prerequisites`, `target`, or `rules` apply. The sum of the `rollout_weights` must equal 100000. You must specify either `variation` or `rollout_weights`. +- `rollout_weights` - (Optional) List of integer percentage rollout weights (in thousandths of a percent) to apply to each variation if no `prerequisites`, `target`, or `rules` apply. The sum of the `rollout_weights` must equal 100000 and the number of rollout weights specified in the array must match the number of flag variations. You must specify either `variation` or `rollout_weights`. - `bucket_by` - (Optional) Group percentage rollout by a custom attribute. This argument is only valid if `rollout_weights` is also specified. @@ -111,7 +111,7 @@ Nested `rules` blocks have the following structure: - `variation` - (Optional) The integer variation index to serve if the rule clauses evaluate to `true`. You must specify either `variation` or `rollout_weights`. -- `rollout_weights` - (Optional) List of integer percentage rollout weights (in thousandths of a percent) to apply to each variation if the rule clauses evaluates to `true`. The sum of the `rollout_weights` must equal 100000. You must specify either `variation` or `rollout_weights`. +- `rollout_weights` - (Optional) List of integer percentage rollout weights (in thousandths of a percent) to apply to each variation if the rule clauses evaluates to `true`. The sum of the `rollout_weights` must equal 100000 and the number of rollout weights specified in the array must match the number of flag variations. You must specify either `variation` or `rollout_weights`. - `bucket_by` - (Optional) Group percentage rollout by a custom attribute. This argument is only valid if `rollout_weights` is also specified. @@ -133,7 +133,7 @@ Nested `fallthrough` blocks have the following structure: - `variation` - (Optional) The integer variation index to serve if the rule clauses evaluate to `true`. You must specify either `variation` or `rollout_weights`. -- `rollout_weights` - (Optional) List of integer percentage rollout weights (in thousandths of a percent) to apply to each variation if the rule clauses evaluates to `true`. The sum of the `rollout_weights` must equal 100000. You must specify either `variation` or `rollout_weights`. +- `rollout_weights` - (Optional) List of integer percentage rollout weights (in thousandths of a percent) to apply to each variation if the rule clauses evaluates to `true`. The sum of the `rollout_weights` must equal 100000 and the number of rollout weights specified in the array must match the number of flag variations. You must specify either `variation` or `rollout_weights`. ## Attributes Reference From aabe026cc66bf29ebf018aa508e9edd837da1484 Mon Sep 17 00:00:00 2001 From: Isabelle Miller Date: Thu, 23 Dec 2021 14:50:02 +0100 Subject: [PATCH 13/14] Upgrade Terraform provider to use api-client-go 7 (#160) * Start updating go client * Continue updating resources * feature flag resource * feature flag resource test * approval settings and custom role policies * destination resource * environment * feature flag environment * project * segment * team member * webhook * rule, segment rule, segments, and team member helpers * variations helper * data source tests * webhooks helper * resource tests * variations helper test * policy helpers and tests probably will all fail * custom role and access token probably also broken * policy statements helper update * instantiate config prior to setting other values on it * fix 401 * vendor v7 * update all imports to v7 * missed one * fix nil pointer dereference * instantiate with var instead of make to fix non-empty slice bu * custom roles now complains if you try to set more than one of Role, InlineRole, or CustomRoleIds * fix webhook statemetns * webhook read check if statements are nil & webhook create only set secret if defined * update changelog * fix webhook data source tests * new pointers in client FeatureFlagConfig breaking ff env data source tests * fix negative expiry thing (apparently the api no longer handles it so we have to) * move reset into helper function * hackily handle token reset requests until client bug is fixed * fix variation transformation - can no longer point to empty string * fix notResource and notActions * fix policy helper tests * fix the weird empty string value in variations issue * strip protocol from host if provided * go mod tidy && vendor * update all go packages * clean up commentsg * clean up comment * Ffeldberg/sc 130551/add the ability to adjust the sdks using (#161) * chore: first attempts * feat: updating flag resources works, reading/creating plan is a bit iffy * chore: clean up comments a bit * chore: add TODO as reminder * chore: add more todos * chore: use ConflictsWith to ensure users dont use both includeInSnippet and clientSideAvailablity * chore: pr feedback - update deprecation message, always read both sdk client values * feat: set both includeInSnippet and clientSideAvailability to computed properties * wip: attempt to use computed and update/create logic blocks * add comment * use getOk instead of getOkExists for nested, rename vars * wip: use getOk instead of getOkExists for nested, test modified read * temp: temporary test changes for local debugging * chore: add some comments * feat: working apart from switching back to deprecated option in edge case * chore: remove commented out code * fix: fix issue with read * test: reset tests * fix: fix typo in read * fix: account for datasources in featureflag read * test: add tests for clientSideAvailability changes * docs: update example docs * docs: add include_in_snippet to website docs and add deprecation notice * docs: update docs * chore: update CHANGELOG * fix: fix most issues caused by go client v7 merge * fix: fix other merge issues * chore: update CHANGELOG * chore: apply codereview keys suggestions Co-authored-by: Isabelle Miller * chore: Update launchdarkly/feature_flags_helper.go keys Co-authored-by: Isabelle Miller * chore: update keys.go to reflect clientsideavailablity keys * chore: go fmt * test: add tests for switching between CSA and IIS, clean up * chore: go fmt * test: remove pure create tests * chore: add TODOs where required * test: add new test case for reverting to default CSA * feat: set clientSideAvailability according to project defaults for flags * test: update tests accordingly and add comments * chore: fmt * refactor project default get into its own function * test: account for default project CSA settings * feat: basic working implementation of resetting to defaults using customizeDiff * feat: attempted changes to customdiff to support CSA * chore: clean up flag resource read * chore: clean up comments and todos * docs: update docs to mention project level defaults for flag sdk settings * chore: remove prints to clean up test logs * chore: go mod vendor * chore: Update CHANGELOG.md Co-authored-by: Isabelle Miller * chore: address pr feedback * docs: update changelog Co-authored-by: Isabelle Miller Co-authored-by: Henry Barrow Co-authored-by: Fabian --- CHANGELOG.md | 13 + examples/v2/feature_flags/README.md | 35 +- go.mod | 26 +- go.sum | 189 ++++++++--- launchdarkly/account_cleaner_test.go | 114 ------- launchdarkly/approvals_helper.go | 14 +- launchdarkly/clause_helper.go | 2 +- launchdarkly/clause_helper_test.go | 2 +- launchdarkly/config.go | 47 +-- launchdarkly/custom_properties_helper.go | 2 +- launchdarkly/custom_properties_helper_test.go | 2 +- ...ta_source_launchdarkly_environment_test.go | 18 +- .../data_source_launchdarkly_feature_flag.go | 16 - ...nchdarkly_feature_flag_environment_test.go | 19 +- ...a_source_launchdarkly_feature_flag_test.go | 14 +- .../data_source_launchdarkly_project_test.go | 18 +- .../data_source_launchdarkly_segment_test.go | 26 +- .../data_source_launchdarkly_team_member.go | 16 +- ...ta_source_launchdarkly_team_member_test.go | 20 +- .../data_source_launchdarkly_webhook_test.go | 17 +- launchdarkly/default_variations_helper.go | 2 +- .../default_variations_helper_test.go | 2 +- launchdarkly/environments_helper.go | 6 +- launchdarkly/environments_helper_test.go | 4 +- launchdarkly/fallthrough_helper.go | 8 +- .../feature_flag_environment_helper.go | 9 +- launchdarkly/feature_flags_helper.go | 65 +++- launchdarkly/helper.go | 6 +- launchdarkly/keys.go | 2 + launchdarkly/policies_helper.go | 12 +- launchdarkly/policy_statements_helper.go | 74 +++-- launchdarkly/policy_statements_helper_test.go | 34 +- launchdarkly/prerequisite_helper.go | 2 +- launchdarkly/project_helper.go | 4 +- launchdarkly/provider.go | 8 +- .../resource_launchdarkly_access_token.go | 138 +++++--- ...resource_launchdarkly_access_token_test.go | 2 +- .../resource_launchdarkly_custom_role.go | 33 +- .../resource_launchdarkly_custom_role_test.go | 4 +- .../resource_launchdarkly_destination.go | 26 +- .../resource_launchdarkly_destination_test.go | 2 +- .../resource_launchdarkly_environment.go | 26 +- .../resource_launchdarkly_environment_test.go | 2 +- .../resource_launchdarkly_feature_flag.go | 139 +++++++- ...e_launchdarkly_feature_flag_environment.go | 25 +- ...nchdarkly_feature_flag_environment_test.go | 5 +- ...resource_launchdarkly_feature_flag_test.go | 309 +++++++++++++++++- launchdarkly/resource_launchdarkly_project.go | 24 +- .../resource_launchdarkly_project_test.go | 2 +- launchdarkly/resource_launchdarkly_segment.go | 37 ++- .../resource_launchdarkly_segment_test.go | 2 +- .../resource_launchdarkly_team_member.go | 22 +- .../resource_launchdarkly_team_member_test.go | 2 +- launchdarkly/resource_launchdarkly_webhook.go | 33 +- .../resource_launchdarkly_webhook_test.go | 2 +- launchdarkly/rollout_helper.go | 2 +- launchdarkly/rule_helper.go | 4 +- launchdarkly/segment_rule_helper.go | 8 +- launchdarkly/segments_helper.go | 4 +- launchdarkly/target_helper.go | 2 +- launchdarkly/target_helper_test.go | 2 +- launchdarkly/team_member_helper.go | 6 +- launchdarkly/test_utils.go | 12 +- launchdarkly/variations_helper.go | 81 +++-- launchdarkly/variations_helper_test.go | 8 +- launchdarkly/webhooks_helper.go | 17 +- website/docs/d/feature_flag.html.markdown | 2 + website/docs/r/feature_flag.html.markdown | 13 +- 68 files changed, 1208 insertions(+), 636 deletions(-) delete mode 100644 launchdarkly/account_cleaner_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c85bd21..544944cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ ## [Unreleased] +ENHANCEMENTS: + +- Upgraded the LaunchDarkly API client to version 7. +- Flag resource creation respects project level SDK availability defaults. + +FEATURES: + +- Added `client_side_availability` block to the `launchdarkly_feature_flag` resource to allow setting whether this flag should be made available to the client-side JavaScript SDK using the client-side ID, mobile key, or both. + +NOTES: + +- The `launchdarkly_feature_flag` resource's argument `include_in_snippet` has been deprecated in favor of `client_side_availability`. Please update your config to use `client_side_availability` in order to maintain compatibility with future versions. + ## [2.1.1] (October 11, 2021) BUG FIXES: diff --git a/examples/v2/feature_flags/README.md b/examples/v2/feature_flags/README.md index d11156c9..b71fafff 100644 --- a/examples/v2/feature_flags/README.md +++ b/examples/v2/feature_flags/README.md @@ -25,7 +25,7 @@ Terraform will perform the following actions: + resource "launchdarkly_feature_flag" "boolean_flag" { + description = "An example boolean feature flag that can be turned either on or off" + id = (known after apply) - + include_in_snippet = false + + include_in_snippet = (known after apply) + key = "boolean-flag" + maintainer_id = (known after apply) + name = "Bool feature flag" @@ -33,6 +33,11 @@ Terraform will perform the following actions: + temporary = false + variation_type = "boolean" + + client_side_availability { + + using_environment_id = (known after apply) + + using_mobile_key = (known after apply) + } + + defaults { + off_variation = (known after apply) + on_variation = (known after apply) @@ -49,7 +54,7 @@ Terraform will perform the following actions: + resource "launchdarkly_feature_flag" "json_flag" { + description = "An example of a multivariate feature flag with JSON variations" + id = (known after apply) - + include_in_snippet = false + + include_in_snippet = (known after apply) + key = "json-flag" + maintainer_id = (known after apply) + name = "JSON-based feature flag" @@ -60,6 +65,11 @@ Terraform will perform the following actions: + temporary = false + variation_type = "json" + + client_side_availability { + + using_environment_id = (known after apply) + + using_mobile_key = (known after apply) + } + + defaults { + off_variation = (known after apply) + on_variation = (known after apply) @@ -88,7 +98,7 @@ Terraform will perform the following actions: + resource "launchdarkly_feature_flag" "number_flag" { + description = "An example of a multivariate feature flag with numeric variations" + id = (known after apply) - + include_in_snippet = false + + include_in_snippet = (known after apply) + key = "number-flag" + maintainer_id = (known after apply) + name = "Number value-based feature flag" @@ -99,6 +109,11 @@ Terraform will perform the following actions: + temporary = false + variation_type = "number" + + client_side_availability { + + using_environment_id = (known after apply) + + using_mobile_key = (known after apply) + } + + defaults { + off_variation = (known after apply) + on_variation = (known after apply) @@ -122,7 +137,7 @@ Terraform will perform the following actions: + resource "launchdarkly_feature_flag" "string_flag" { + description = "An example of a multivariate feature flag with string variations" + id = (known after apply) - + include_in_snippet = false + + include_in_snippet = (known after apply) + key = "string-flag" + maintainer_id = (known after apply) + name = "String-based feature flag" @@ -133,6 +148,11 @@ Terraform will perform the following actions: + temporary = false + variation_type = "string" + + client_side_availability { + + using_environment_id = (known after apply) + + using_mobile_key = (known after apply) + } + + defaults { + off_variation = (known after apply) + on_variation = (known after apply) @@ -246,13 +266,18 @@ Terraform will perform the following actions: # launchdarkly_project.tf_flag_examples will be created + resource "launchdarkly_project" "tf_flag_examples" { + id = (known after apply) - + include_in_snippet = false + + include_in_snippet = (known after apply) + key = "tf-flag-examples" + name = "Terraform Project for Flag Examples" + tags = [ + "terraform-managed", ] + + client_side_availability { + + using_environment_id = (known after apply) + + using_mobile_key = (known after apply) + } + + environments { + api_key = (sensitive value) + client_side_id = (sensitive value) diff --git a/go.mod b/go.mod index 30f4c4b5..2842d7e6 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,28 @@ module github.com/launchdarkly/terraform-provider-launchdarkly go 1.16 require ( - github.com/antihax/optional v1.0.0 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.7.0 - github.com/launchdarkly/api-client-go v5.3.0+incompatible + github.com/agext/levenshtein v1.2.3 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect + github.com/hashicorp/go-hclog v1.0.0 // indirect + github.com/hashicorp/go-plugin v1.4.3 // indirect + github.com/hashicorp/hcl/v2 v2.11.1 // indirect + github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.0 + github.com/hashicorp/terraform-registry-address v0.0.0-20210816115301-cb2034eba045 // indirect + github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect + github.com/launchdarkly/api-client-go/v7 v7.0.0 + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/oklog/run v1.1.0 // indirect github.com/stretchr/testify v1.7.0 + github.com/zclconf/go-cty v1.10.0 // indirect + golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect + google.golang.org/grpc v1.42.0 // indirect ) diff --git a/go.sum b/go.sum index 167faefd..fe8b8993 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,10 @@ cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6 cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.61.0 h1:NLQf5e1OMspfNT1RAHOB3ublr1TW3YTXO8OiWwVjK2U= cloud.google.com/go v0.61.0/go.mod h1:XukKJg4Y7QsUu0Hxg3qQKUWR4VuWivmyMK2+rUyxAqw= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -40,16 +42,17 @@ github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuN github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+kXDxro6ErPXBRTJ/ro2mf0SsFG8s7doP9kJE= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-cidr v1.0.1 h1:NmIwLZ/KdsjIUlhf+/Np40atNXm/+lZ5txfTJ/SpF+U= github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= @@ -70,12 +73,20 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1U github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -85,10 +96,15 @@ github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= @@ -114,6 +130,7 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -127,9 +144,11 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -138,12 +157,11 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -157,11 +175,14 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= @@ -170,38 +191,53 @@ github.com/hashicorp/go-getter v1.5.3 h1:NF5+zOlQegim+w/EUhSLh6QhXHmZMEeHLQzllkQ github.com/hashicorp/go-getter v1.5.3/go.mod h1:BrrV/1clo8cCYu6mxvboYg+KutTiFnXjMEgDD8+i7ZI= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.15.0 h1:qMuK0wxsoW4D0ddCCYwPSTm4KQv1X1ke3WmPWZ0Mvsk= -github.com/hashicorp/go-hclog v0.15.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0 h1:bkKf0BeBXcSYa7f5Fyi9gMuQ8gNsxeiNpZjR6VxNZeo= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= -github.com/hashicorp/go-plugin v1.4.1 h1:6UltRQlLN9iZO513VveELp5xyaFxVD2+1OVylE+2E+w= github.com/hashicorp/go-plugin v1.4.1/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= +github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggUE= +github.com/hashicorp/hc-install v0.3.1 h1:VIjllE6KyAI1A244G8kTaHXy+TL5/XYzvrtFi8po/Yk= +github.com/hashicorp/hc-install v0.3.1/go.mod h1:3LCdWcCDS1gaHC9mhHCGbkYfoY6vdsKohGjugbZdZak= github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8= +github.com/hashicorp/hcl/v2 v2.11.1 h1:yTyWcXcm9XB0TEkyU/JCRU6rYy4K+mgLtzn2wlrJbcc= +github.com/hashicorp/hcl/v2 v2.11.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/terraform-exec v0.14.0 h1:UQoUcxKTZZXhyyK68Cwn4mApT4mnFPmEXPiqaHL9r+w= -github.com/hashicorp/terraform-exec v0.14.0/go.mod h1:qrAASDq28KZiMPDnQ02sFS9udcqEkRly002EA2izXTA= -github.com/hashicorp/terraform-json v0.12.0 h1:8czPgEEWWPROStjkWPUnTQDXmpmZPlkQAwYYLETaTvw= -github.com/hashicorp/terraform-json v0.12.0/go.mod h1:pmbq9o4EuL43db5+0ogX10Yofv1nozM+wskr/bGFJpI= -github.com/hashicorp/terraform-plugin-go v0.3.0 h1:AJqYzP52JFYl9NABRI7smXI1pNjgR5Q/y2WyVJ/BOZA= -github.com/hashicorp/terraform-plugin-go v0.3.0/go.mod h1:dFHsQMaTLpON2gWhVWT96fvtlc/MF1vSy3OdMhWBzdM= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.7.0 h1:SuI59MqNjYDrL7EfqHX9V6P/24isgqYx/FdglwVs9bg= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.7.0/go.mod h1:grseeRo9g3yNkYW09iFlV8LG78jTa1ssBgouogQg/RU= +github.com/hashicorp/terraform-exec v0.15.0 h1:cqjh4d8HYNQrDoEmlSGelHmg2DYDh5yayckvJ5bV18E= +github.com/hashicorp/terraform-exec v0.15.0/go.mod h1:H4IG8ZxanU+NW0ZpDRNsvh9f0ul7C0nHP+rUR/CHs7I= +github.com/hashicorp/terraform-json v0.13.0 h1:Li9L+lKD1FO5RVFRM1mMMIBDoUHslOniyEi5CM+FWGY= +github.com/hashicorp/terraform-json v0.13.0/go.mod h1:y5OdLBCT+rxbwnpxZs9kGL7R9ExU76+cpdY8zHwoazk= +github.com/hashicorp/terraform-plugin-go v0.5.0 h1:+gCDdF0hcYCm0YBTxrP4+K1NGIS5ZKZBKDORBewLJmg= +github.com/hashicorp/terraform-plugin-go v0.5.0/go.mod h1:PAVN26PNGpkkmsvva1qfriae5Arky3xl3NfzKa8XFVM= +github.com/hashicorp/terraform-plugin-log v0.2.0 h1:rjflRuBqCnSk3UHOR25MP1G5BDLKktTA6lNjjcAnBfI= +github.com/hashicorp/terraform-plugin-log v0.2.0/go.mod h1:E1kJmapEHzqu1x6M++gjvhzM2yMQNXPVWZRCB8sgYjg= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.0 h1:osXVmeDNoYGxPGnIFxrR//rxa47XIMwzOBL9/rX0iDM= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.0/go.mod h1:FjM9DXWfP0w/AeOtJoSKHBZ01LqmaO6uP4bXhv3fekw= +github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896/go.mod h1:bzBPnUIkI0RxauU8Dqo+2KrZZ28Cf48s8V6IHt3p4co= +github.com/hashicorp/terraform-registry-address v0.0.0-20210816115301-cb2034eba045 h1:R/I8ofvXuPcTNoc//N4ruvaHGZcShI/VuU2iXo875Lo= +github.com/hashicorp/terraform-registry-address v0.0.0-20210816115301-cb2034eba045/go.mod h1:anRyJbe12BZscpFgaeGu9gH12qfdBP094LYFtuAFzd4= +github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= +github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= +github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -234,17 +270,21 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/launchdarkly/api-client-go v5.3.0+incompatible h1:xB4QGNbNzAUm2UDdtlsHhmCrs6l7OQGWFgs1xB6s/u8= -github.com/launchdarkly/api-client-go v5.3.0+incompatible/go.mod h1:INGa7NUZYSwVozwPV7l6ikgD7pzSOpZvg9I5sqCZIWs= +github.com/launchdarkly/api-client-go/v7 v7.0.0 h1:mCVGV3adts81Gtq2YxwCi6lvS/V9hYGJlqilLGFKj98= +github.com/launchdarkly/api-client-go/v7 v7.0.0/go.mod h1:5FlSAYTMrNa4UOiuSSL1+85NOiJel6cZT2P86ihNR9s= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -255,28 +295,31 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.4 h1:ZU1VNC02qyufSZsjjs7+khruk2fKvbQ3TwRV/IBCeFA= -github.com/mitchellh/go-testing-interface v1.0.4/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -284,12 +327,14 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -305,10 +350,12 @@ github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6e github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.8.4 h1:pwhhz5P+Fjxse7S7UriBrMu6AUJSZM5pKqGem1PjGAs= -github.com/zclconf/go-cty v1.8.4/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.9.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= +github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -316,6 +363,7 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -325,8 +373,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -373,6 +422,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -386,15 +436,21 @@ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b h1:MWaHNqZy3KTpuTMAGvv+Kw+ylsEpmyJZizz1dqxnu28= +golang.org/x/net v0.0.0-20211208012354-db4efeb81f4b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -421,6 +477,7 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -432,11 +489,19 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79 h1:RX8C8PRZc2hTIod4ds8ij+/4RQX3AqhYj3uOHmyaz4E= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -444,8 +509,10 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -486,12 +553,16 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed h1:+qzWo37K31KxduIYaBeMqJ8MUOyTayOQKpH9aDPLMSY= golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -506,15 +577,17 @@ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -539,11 +612,16 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200711021454-869866162049 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc= google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -556,8 +634,13 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -567,8 +650,11 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -580,6 +666,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/launchdarkly/account_cleaner_test.go b/launchdarkly/account_cleaner_test.go deleted file mode 100644 index d74eb769..00000000 --- a/launchdarkly/account_cleaner_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package launchdarkly - -import ( - "fmt" - "os" - "testing" - "time" - - ldapi "github.com/launchdarkly/api-client-go" - "github.com/stretchr/testify/require" -) - -const ( - dummyProject = "dummy-project" -) - -func TestCleanAccount(t *testing.T) { - // Uncomment this if you really want to wipe the account. - t.SkipNow() - - fmt.Println("****** DANGER!!!! ******") - fmt.Println("We're about to clean your account!!! pausing 10 seconds so you can kill this in case it was run by mistake!!") - time.Sleep(10 * time.Second) - - require.NoError(t, cleanAccount()) -} - -func cleanAccount() error { - c, err := newClient(os.Getenv(LAUNCHDARKLY_ACCESS_TOKEN), os.Getenv(LAUNCHDARKLY_API_HOST), false) - if err != nil { - return err - } - - err = c.cleanProjects() - if err != nil { - return err - } - err = c.cleanTeamMembers() - if err != nil { - return err - } - - err = c.cleanCustomRoles() - if err != nil { - return err - } - return nil -} - -// cleanProjects ensures exactly one project with name and key 'dummy-project' exists for an account. -// LD requires at least one project in an account. -func (c *Client) cleanProjects() error { - // make sure we have a dummy project - _, response, err := c.ld.ProjectsApi.GetProject(c.ctx, dummyProject) - - if response.StatusCode == 404 { - _, _, err = c.ld.ProjectsApi.PostProject(c.ctx, ldapi.ProjectBody{Name: dummyProject, Key: dummyProject}) - if err != nil { - return handleLdapiErr(err) - } - } else { - if err != nil { - return err - } - } - projects, _, err := c.ld.ProjectsApi.GetProjects(c.ctx) - if err != nil { - return handleLdapiErr(err) - } - - // delete all but dummy project - for _, p := range projects.Items { - if p.Key != dummyProject { - _, err := c.ld.ProjectsApi.DeleteProject(c.ctx, p.Key) - if err != nil { - return handleLdapiErr(err) - } - } - } - return nil -} - -// cleanTeamMembers ensures the only team member is the account owner -func (c *Client) cleanTeamMembers() error { - members, _, err := c.ld.TeamMembersApi.GetMembers(c.ctx, &ldapi.TeamMembersApiGetMembersOpts{}) - if err != nil { - return handleLdapiErr(err) - } - for _, m := range members.Items { - if *m.Role != ldapi.OWNER_Role && m.PendingInvite == true { - _, err := c.ld.TeamMembersApi.DeleteMember(c.ctx, m.Id) - if err != nil { - return handleLdapiErr(err) - } - } - } - return nil -} - -// cleanCustomRoles deletes all custom roles -func (c *Client) cleanCustomRoles() error { - roles, _, err := c.ld.CustomRolesApi.GetCustomRoles(c.ctx) - if err != nil { - return handleLdapiErr(err) - } - - for _, r := range roles.Items { - _, err := c.ld.CustomRolesApi.DeleteCustomRole(c.ctx, r.Id) - if err != nil { - return handleLdapiErr(err) - } - } - return nil -} diff --git a/launchdarkly/approvals_helper.go b/launchdarkly/approvals_helper.go index e8f888c1..42197ada 100644 --- a/launchdarkly/approvals_helper.go +++ b/launchdarkly/approvals_helper.go @@ -5,7 +5,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func approvalSchema() *schema.Schema { @@ -54,15 +54,15 @@ func approvalSchema() *schema.Schema { } } -func approvalSettingsFromResourceData(val interface{}) (ldapi.EnvironmentApprovalSettings, error) { +func approvalSettingsFromResourceData(val interface{}) (ldapi.ApprovalSettings, error) { raw := val.([]interface{}) if len(raw) == 0 { - return ldapi.EnvironmentApprovalSettings{}, nil + return ldapi.ApprovalSettings{}, nil } approvalSettingsMap := raw[0].(map[string]interface{}) - settings := ldapi.EnvironmentApprovalSettings{ + settings := ldapi.ApprovalSettings{ CanReviewOwnRequest: approvalSettingsMap[CAN_REVIEW_OWN_REQUEST].(bool), - MinNumApprovals: int64(approvalSettingsMap[MIN_NUM_APPROVALS].(int)), + MinNumApprovals: int32(approvalSettingsMap[MIN_NUM_APPROVALS].(int)), CanApplyDeclinedChanges: approvalSettingsMap[CAN_APPLY_DECLINED_CHANGES].(bool), } // Required and RequiredApprovalTags should never be defined simultaneously @@ -72,7 +72,7 @@ func approvalSettingsFromResourceData(val interface{}) (ldapi.EnvironmentApprova tags := approvalSettingsMap[REQUIRED_APPROVAL_TAGS].([]interface{}) if len(tags) > 0 { if required { - return ldapi.EnvironmentApprovalSettings{}, fmt.Errorf("invalid approval_settings config: required and required_approval_tags cannot be set simultaneously") + return ldapi.ApprovalSettings{}, fmt.Errorf("invalid approval_settings config: required and required_approval_tags cannot be set simultaneously") } stringTags := make([]string, len(tags)) for i := range tags { @@ -85,7 +85,7 @@ func approvalSettingsFromResourceData(val interface{}) (ldapi.EnvironmentApprova return settings, nil } -func approvalSettingsToResourceData(settings ldapi.EnvironmentApprovalSettings) interface{} { +func approvalSettingsToResourceData(settings ldapi.ApprovalSettings) interface{} { transformed := map[string]interface{}{ CAN_REVIEW_OWN_REQUEST: settings.CanReviewOwnRequest, MIN_NUM_APPROVALS: settings.MinNumApprovals, diff --git a/launchdarkly/clause_helper.go b/launchdarkly/clause_helper.go index 8bd19762..a128d2b3 100644 --- a/launchdarkly/clause_helper.go +++ b/launchdarkly/clause_helper.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) const ( diff --git a/launchdarkly/clause_helper_test.go b/launchdarkly/clause_helper_test.go index d7bdd60d..ec9ec026 100644 --- a/launchdarkly/clause_helper_test.go +++ b/launchdarkly/clause_helper_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/launchdarkly/config.go b/launchdarkly/config.go index e24fca80..a69c0f9b 100644 --- a/launchdarkly/config.go +++ b/launchdarkly/config.go @@ -4,8 +4,10 @@ import ( "context" "errors" "fmt" + "net/http" + "time" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) // The version string gets updated at build time using -ldflags @@ -17,40 +19,43 @@ const ( // Client is used by the provider to access the ld API. type Client struct { - apiKey string - apiHost string - ld *ldapi.APIClient - ctx context.Context + apiKey string + apiHost string + ld *ldapi.APIClient + ctx context.Context + fallbackClient *http.Client } func newClient(token string, apiHost string, oauth bool) (*Client, error) { if token == "" { return nil, errors.New("token cannot be empty") } - basePath := "https://app.launchdarkly.com/api/v2" - if apiHost != "" { - basePath = fmt.Sprintf("%s/api/v2", apiHost) - } - cfg := &ldapi.Configuration{ - BasePath: basePath, - DefaultHeader: make(map[string]string), - UserAgent: fmt.Sprintf("launchdarkly-terraform-provider/%s", version), - } + cfg := ldapi.NewConfiguration() + cfg.Host = apiHost + cfg.DefaultHeader = make(map[string]string) + cfg.UserAgent = fmt.Sprintf("launchdarkly-terraform-provider/%s", version) cfg.AddDefaultHeader("LD-API-Version", APIVersion) - ctx := context.WithValue(context.Background(), ldapi.ContextAPIKey, ldapi.APIKey{ - Key: token, - }) + ctx := context.WithValue(context.Background(), ldapi.ContextAPIKeys, map[string]ldapi.APIKey{ + "ApiKey": { + Key: token, + }}) if oauth { ctx = context.WithValue(context.Background(), ldapi.ContextAccessToken, token) } + // TODO: remove this once we get the go client reset endpoint fixed + fallbackClient := http.Client{ + Timeout: time.Duration(5 * time.Second), + } + return &Client{ - apiKey: token, - apiHost: apiHost, - ld: ldapi.NewAPIClient(cfg), - ctx: ctx, + apiKey: token, + apiHost: apiHost, + ld: ldapi.NewAPIClient(cfg), + ctx: ctx, + fallbackClient: &fallbackClient, }, nil } diff --git a/launchdarkly/custom_properties_helper.go b/launchdarkly/custom_properties_helper.go index 6e251f77..1abbc32f 100644 --- a/launchdarkly/custom_properties_helper.go +++ b/launchdarkly/custom_properties_helper.go @@ -6,7 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) // https://docs.launchdarkly.com/docs/custom-properties diff --git a/launchdarkly/custom_properties_helper_test.go b/launchdarkly/custom_properties_helper_test.go index 6a3cc38d..db6eaa3f 100644 --- a/launchdarkly/custom_properties_helper_test.go +++ b/launchdarkly/custom_properties_helper_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/require" ) diff --git a/launchdarkly/data_source_launchdarkly_environment_test.go b/launchdarkly/data_source_launchdarkly_environment_test.go index 770042d7..ece7017f 100644 --- a/launchdarkly/data_source_launchdarkly_environment_test.go +++ b/launchdarkly/data_source_launchdarkly_environment_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/require" ) @@ -25,12 +25,10 @@ data "launchdarkly_environment" "test" { // for environment data source tests func testAccDataSourceEnvironmentScaffold(client *Client, projectKey string, envBody ldapi.EnvironmentPost) (*ldapi.Environment, error) { // create project - projectBody := ldapi.ProjectBody{ - Name: "Env Test Project", - Key: projectKey, - Environments: []ldapi.EnvironmentPost{ - envBody, - }, + projectBody := ldapi.ProjectPost{ + Name: "Env Test Project", + Key: projectKey, + Environments: &[]ldapi.EnvironmentPost{envBody}, } project, err := testAccDataSourceProjectCreate(client, projectBody) if err != nil { @@ -52,7 +50,7 @@ func TestAccDataSourceEnvironment_noMatchReturnsError(t *testing.T) { client, err := newClient(os.Getenv(LAUNCHDARKLY_ACCESS_TOKEN), os.Getenv(LAUNCHDARKLY_API_HOST), false) require.NoError(t, err) projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) - projectBody := ldapi.ProjectBody{ + projectBody := ldapi.ProjectPost{ Name: "Terraform Env Test Project", Key: projectKey, } @@ -96,8 +94,8 @@ func TestAccDataSourceEnv_exists(t *testing.T) { Name: envName, Key: envKey, Color: envColor, - SecureMode: true, - Tags: []string{ + SecureMode: ldapi.PtrBool(true), + Tags: &[]string{ "some", "tag", }, } diff --git a/launchdarkly/data_source_launchdarkly_feature_flag.go b/launchdarkly/data_source_launchdarkly_feature_flag.go index 8971a9c2..66c4bac2 100644 --- a/launchdarkly/data_source_launchdarkly_feature_flag.go +++ b/launchdarkly/data_source_launchdarkly_feature_flag.go @@ -19,22 +19,6 @@ func dataSourceFeatureFlag() *schema.Resource { Description: fmt.Sprintf("The uniform type for all variations. Can be either %q, %q, %q, or %q.", BOOL_VARIATION, STRING_VARIATION, NUMBER_VARIATION, JSON_VARIATION), } - schemaMap[CLIENT_SIDE_AVAILABILITY] = &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "using_environment_id": { - Type: schema.TypeBool, - Optional: true, - }, - "using_mobile_key": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - } return &schema.Resource{ Read: dataSourceFeatureFlagRead, Schema: schemaMap, diff --git a/launchdarkly/data_source_launchdarkly_feature_flag_environment_test.go b/launchdarkly/data_source_launchdarkly_feature_flag_environment_test.go index 525e7338..b5ce2f78 100644 --- a/launchdarkly/data_source_launchdarkly_feature_flag_environment_test.go +++ b/launchdarkly/data_source_launchdarkly_feature_flag_environment_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -27,7 +27,7 @@ func testAccDataSourceFeatureFlagEnvironmentScaffold(client *Client, projectKey, flagBody := ldapi.FeatureFlagBody{ Name: "Feature Flag Env Data Source Test", Key: flagKey, - Variations: []ldapi.Variation{ + Variations: &[]ldapi.Variation{ {Value: intfPtr(true)}, {Value: intfPtr(false)}, }, @@ -38,13 +38,12 @@ func testAccDataSourceFeatureFlagEnvironmentScaffold(client *Client, projectKey, } // patch feature flag with env-specific config - patch := ldapi.PatchComment{ - Comment: "Terraform feature flag env data source test", - Patch: envConfigPatches, - } + patch := ldapi.NewPatchWithComment(envConfigPatches) + patch.SetComment("Terraform feature flag env data source test") + _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.FeatureFlagsApi.PatchFeatureFlag(client.ctx, projectKey, flagKey, patch) + return client.ld.FeatureFlagsApi.PatchFeatureFlag(client.ctx, projectKey, flagKey).PatchWithComment(*patch).Execute() }) }) if err != nil { @@ -54,7 +53,7 @@ func testAccDataSourceFeatureFlagEnvironmentScaffold(client *Client, projectKey, return nil, fmt.Errorf("failed to create feature flag env config: %s", err.Error()) } flagRaw, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projectKey, flagKey, nil) + return client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projectKey, flagKey).Execute() }) if err != nil { _ = testAccDataSourceProjectDelete(client, projectKey) @@ -166,13 +165,13 @@ func TestAccDataSourceFeatureFlagEnvironment_exists(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "env_key", envKey), resource.TestCheckResourceAttr(resourceName, "on", fmt.Sprint(thisConfig.On)), resource.TestCheckResourceAttr(resourceName, "track_events", fmt.Sprint(thisConfig.TrackEvents)), - resource.TestCheckResourceAttr(resourceName, "rules.0.variation", fmt.Sprint(thisConfig.Rules[0].Variation)), + resource.TestCheckResourceAttr(resourceName, "rules.0.variation", fmt.Sprint(*thisConfig.Rules[0].Variation)), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.attribute", thisConfig.Rules[0].Clauses[0].Attribute), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.op", thisConfig.Rules[0].Clauses[0].Op), resource.TestCheckResourceAttr(resourceName, "rules.0.clauses.0.values.0", fmt.Sprint(thisConfig.Rules[0].Clauses[0].Values[0])), resource.TestCheckResourceAttr(resourceName, "prerequisites.0.flag_key", thisConfig.Prerequisites[0].Key), resource.TestCheckResourceAttr(resourceName, "prerequisites.0.variation", fmt.Sprint(thisConfig.Prerequisites[0].Variation)), - resource.TestCheckResourceAttr(resourceName, "off_variation", fmt.Sprint(thisConfig.OffVariation)), + resource.TestCheckResourceAttr(resourceName, "off_variation", fmt.Sprint(*thisConfig.OffVariation)), resource.TestCheckResourceAttr(resourceName, "targets.0.values.#", fmt.Sprint(len(thisConfig.Targets[0].Values))), resource.TestCheckResourceAttr(resourceName, "targets.0.variation", "1"), ), diff --git a/launchdarkly/data_source_launchdarkly_feature_flag_test.go b/launchdarkly/data_source_launchdarkly_feature_flag_test.go index b95d6a4b..5371d023 100644 --- a/launchdarkly/data_source_launchdarkly_feature_flag_test.go +++ b/launchdarkly/data_source_launchdarkly_feature_flag_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/require" ) @@ -29,7 +29,7 @@ func TestAccDataSourceFeatureFlag_noMatchReturnsError(t *testing.T) { client, err := newClient(os.Getenv(LAUNCHDARKLY_ACCESS_TOKEN), os.Getenv(LAUNCHDARKLY_API_HOST), false) require.NoError(t, err) projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) - projectBody := ldapi.ProjectBody{ + projectBody := ldapi.ProjectPost{ Name: "Terraform Flag Test Project", Key: projectKey, } @@ -71,13 +71,13 @@ func TestAccDataSourceFeatureFlag_exists(t *testing.T) { flagBody := ldapi.FeatureFlagBody{ Name: flagName, Key: flagKey, - Variations: []ldapi.Variation{ + Variations: &[]ldapi.Variation{ {Value: intfPtr(true)}, {Value: intfPtr(false)}, }, - Description: "a flag to test the terraform flag data source", - Temporary: true, - ClientSideAvailability: &ldapi.ClientSideAvailability{ + Description: ldapi.PtrString("a flag to test the terraform flag data source"), + Temporary: ldapi.PtrBool(true), + ClientSideAvailability: &ldapi.ClientSideAvailabilityPost{ UsingEnvironmentId: true, UsingMobileKey: false, }, @@ -105,7 +105,7 @@ func TestAccDataSourceFeatureFlag_exists(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "project_key"), resource.TestCheckResourceAttr(resourceName, "key", flag.Key), resource.TestCheckResourceAttr(resourceName, "name", flag.Name), - resource.TestCheckResourceAttr(resourceName, "description", flag.Description), + resource.TestCheckResourceAttr(resourceName, "description", *flag.Description), resource.TestCheckResourceAttr(resourceName, "temporary", "true"), resource.TestCheckResourceAttr(resourceName, "variations.#", "2"), resource.TestCheckResourceAttr(resourceName, "variations.0.value", "true"), diff --git a/launchdarkly/data_source_launchdarkly_project_test.go b/launchdarkly/data_source_launchdarkly_project_test.go index 95a5a1c0..a83522cc 100644 --- a/launchdarkly/data_source_launchdarkly_project_test.go +++ b/launchdarkly/data_source_launchdarkly_project_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/require" ) @@ -57,25 +57,25 @@ func TestAccDataSourceProject_exists(t *testing.T) { client, err := newClient(os.Getenv(LAUNCHDARKLY_ACCESS_TOKEN), os.Getenv(LAUNCHDARKLY_API_HOST), false) require.NoError(t, err) - projectBody := ldapi.ProjectBody{ + projectBody := ldapi.ProjectPost{ Name: projectName, Key: projectKey, - DefaultClientSideAvailability: &ldapi.ClientSideAvailability{ + DefaultClientSideAvailability: &ldapi.DefaultClientSideAvailabilityPost{ UsingEnvironmentId: false, UsingMobileKey: false, }, - Tags: []string{ + Tags: &[]string{ tag, }, - Environments: []ldapi.EnvironmentPost{ + Environments: &[]ldapi.EnvironmentPost{ { Name: envName, Key: envKey, Color: envColor, - SecureMode: true, - ConfirmChanges: true, - RequireComments: true, - Tags: []string{ + SecureMode: ldapi.PtrBool(true), + ConfirmChanges: ldapi.PtrBool(true), + RequireComments: ldapi.PtrBool(true), + Tags: &[]string{ tag, }, }, diff --git a/launchdarkly/data_source_launchdarkly_segment_test.go b/launchdarkly/data_source_launchdarkly_segment_test.go index 36d949cc..37b4db8c 100644 --- a/launchdarkly/data_source_launchdarkly_segment_test.go +++ b/launchdarkly/data_source_launchdarkly_segment_test.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/require" ) @@ -31,7 +31,7 @@ type testSegmentUpdate struct { func testAccDataSourceSegmentCreate(client *Client, projectKey, segmentKey string, properties testSegmentUpdate) (*ldapi.UserSegment, error) { envKey := "test" - projectBody := ldapi.ProjectBody{ + projectBody := ldapi.ProjectPost{ Name: "Terraform Segment DS Test", Key: projectKey, } @@ -40,27 +40,29 @@ func testAccDataSourceSegmentCreate(client *Client, projectKey, segmentKey strin return nil, err } - segmentBody := ldapi.UserSegmentBody{ + segmentBody := ldapi.SegmentBody{ Name: "Data Source Test Segment", Key: segmentKey, - Description: "test description", - Tags: []string{"terraform"}, + Description: ldapi.PtrString("test description"), + Tags: &[]string{"terraform"}, } _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.UserSegmentsApi.PostUserSegment(client.ctx, project.Key, envKey, segmentBody) + return client.ld.SegmentsApi.PostSegment(client.ctx, project.Key, envKey).SegmentBody(segmentBody).Execute() }) if err != nil { return nil, fmt.Errorf("failed to create segment %q in project %q: %s", segmentKey, projectKey, handleLdapiErr(err)) } - patch := []ldapi.PatchOperation{ - patchReplace("/included", properties.Included), - patchReplace("/excluded", properties.Excluded), - patchReplace("/rules", properties.Rules), + patch := ldapi.PatchWithComment{ + Patch: []ldapi.PatchOperation{ + patchReplace("/included", properties.Included), + patchReplace("/excluded", properties.Excluded), + patchReplace("/rules", properties.Rules), + }, } rawSegment, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.UserSegmentsApi.PatchUserSegment(client.ctx, projectKey, envKey, segmentKey, patch) + return client.ld.SegmentsApi.PatchSegment(client.ctx, projectKey, envKey, segmentKey).PatchWithComment(patch).Execute() }) }) if err != nil { @@ -83,7 +85,7 @@ func TestAccDataSourceSegment_noMatchReturnsError(t *testing.T) { segmentKey := "bad-segment-key" client, err := newClient(os.Getenv(LAUNCHDARKLY_ACCESS_TOKEN), os.Getenv(LAUNCHDARKLY_API_HOST), false) require.NoError(t, err) - _, err = testAccDataSourceProjectCreate(client, ldapi.ProjectBody{Name: "Segment DS No Match Test", Key: projectKey}) + _, err = testAccDataSourceProjectCreate(client, ldapi.ProjectPost{Name: "Segment DS No Match Test", Key: projectKey}) require.NoError(t, err) defer func() { diff --git a/launchdarkly/data_source_launchdarkly_team_member.go b/launchdarkly/data_source_launchdarkly_team_member.go index a809e67d..821d88ba 100644 --- a/launchdarkly/data_source_launchdarkly_team_member.go +++ b/launchdarkly/data_source_launchdarkly_team_member.go @@ -4,9 +4,8 @@ import ( "fmt" "net/http" - "github.com/antihax/optional" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func dataSourceTeamMember() *schema.Resource { @@ -41,26 +40,25 @@ func dataSourceTeamMember() *schema.Resource { } func getTeamMemberByEmail(client *Client, memberEmail string) (*ldapi.Member, error) { - apiOpts := ldapi.TeamMembersApiGetMembersOpts{ - Limit: optional.NewFloat32(1000), // this should be the max limit allowed when the member-list-max-limit flag is on - } + // this should be the max limit allowed when the member-list-max-limit flag is on + teamMemberLimit := int64(1000) membersRaw, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.TeamMembersApi.GetMembers(client.ctx, &apiOpts) + return client.ld.AccountMembersApi.GetMembers(client.ctx).Limit(teamMemberLimit).Execute() }) if err != nil { return nil, fmt.Errorf("failed to read team member with email: %s: %v", memberEmail, handleLdapiErr(err)) } members := membersRaw.(ldapi.Members) - totalMemberCount := int(members.TotalCount) + totalMemberCount := int(*members.TotalCount) memberItems := members.Items membersPulled := len(memberItems) for membersPulled < totalMemberCount { - apiOpts.Offset = optional.NewFloat32(float32(membersPulled)) + offset := int64(membersPulled) newRawMembers, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.TeamMembersApi.GetMembers(client.ctx, &apiOpts) + return client.ld.AccountMembersApi.GetMembers(client.ctx).Limit(teamMemberLimit).Offset(offset).Execute() }) if err != nil { return nil, fmt.Errorf("failed to read team member with email: %s: %v", memberEmail, handleLdapiErr(err)) diff --git a/launchdarkly/data_source_launchdarkly_team_member_test.go b/launchdarkly/data_source_launchdarkly_team_member_test.go index 65d46172..0cd29d03 100644 --- a/launchdarkly/data_source_launchdarkly_team_member_test.go +++ b/launchdarkly/data_source_launchdarkly_team_member_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/require" ) @@ -21,14 +21,12 @@ data "launchdarkly_team_member" "test" { } func testAccDataSourceTeamMemberCreate(client *Client, email string) (*ldapi.Member, error) { - membersBody := ldapi.MembersBody{ + membersBody := []ldapi.NewMemberForm{{ Email: email, - FirstName: "Test", - LastName: "Account", - } - members, _, err := client.ld.TeamMembersApi.PostMembers(client.ctx, []ldapi.MembersBody{ - membersBody, - }) + FirstName: ldapi.PtrString("Test"), + LastName: ldapi.PtrString("Account"), + }} + members, _, err := client.ld.AccountMembersApi.PostMembers(client.ctx).NewMemberForm(membersBody).Execute() if err != nil { return nil, err } @@ -36,7 +34,7 @@ func testAccDataSourceTeamMemberCreate(client *Client, email string) (*ldapi.Mem } func testAccDataSourceTeamMemberDelete(client *Client, id string) error { - _, err := client.ld.TeamMembersApi.DeleteMember(client.ctx, id) + _, err := client.ld.AccountMembersApi.DeleteMember(client.ctx, id).Execute() if err != nil { return err } @@ -91,8 +89,8 @@ func TestAccDataSourceTeamMember_exists(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet(resourceName, "email"), resource.TestCheckResourceAttr(resourceName, "email", testMember.Email), - resource.TestCheckResourceAttr(resourceName, "first_name", testMember.FirstName), - resource.TestCheckResourceAttr(resourceName, "last_name", testMember.LastName), + resource.TestCheckResourceAttr(resourceName, "first_name", *testMember.FirstName), + resource.TestCheckResourceAttr(resourceName, "last_name", *testMember.LastName), resource.TestCheckResourceAttr(resourceName, "id", testMember.Id), ), }, diff --git a/launchdarkly/data_source_launchdarkly_webhook_test.go b/launchdarkly/data_source_launchdarkly_webhook_test.go index 4fd66906..5d177c0f 100644 --- a/launchdarkly/data_source_launchdarkly_webhook_test.go +++ b/launchdarkly/data_source_launchdarkly_webhook_test.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/require" ) @@ -22,13 +22,13 @@ data "launchdarkly_webhook" "test" { ) func testAccDataSourceWebhookCreate(client *Client, webhookName string) (*ldapi.Webhook, error) { - webhookBody := ldapi.WebhookBody{ + webhookBody := ldapi.WebhookPost{ Url: "https://www.example.com", Sign: false, On: true, - Name: webhookName, - Tags: []string{"terraform"}, - Statements: []ldapi.Statement{ + Name: ldapi.PtrString(webhookName), + Tags: &[]string{"terraform"}, + Statements: &[]ldapi.StatementPost{ { Resources: []string{"proj/*"}, Actions: []string{"turnFlagOn"}, @@ -37,7 +37,7 @@ func testAccDataSourceWebhookCreate(client *Client, webhookName string) (*ldapi. }, } webhookRaw, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.WebhooksApi.PostWebhook(client.ctx, webhookBody) + return client.ld.WebhooksApi.PostWebhook(client.ctx).WebhookPost(webhookBody).Execute() }) if err != nil { return nil, fmt.Errorf("failed to create webhook with name %q: %s", webhookName, handleLdapiErr(err)) @@ -51,7 +51,7 @@ func testAccDataSourceWebhookCreate(client *Client, webhookName string) (*ldapi. func testAccDataSourceWebhookDelete(client *Client, webhookId string) error { _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - res, err := client.ld.WebhooksApi.DeleteWebhook(client.ctx, webhookId) + res, err := client.ld.WebhooksApi.DeleteWebhook(client.ctx, webhookId).Execute() return nil, res, err }) if err != nil { @@ -112,12 +112,13 @@ func TestAccDataSourceWebhook_exists(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", webhookName), resource.TestCheckResourceAttr(resourceName, "url", webhook.Url), resource.TestCheckResourceAttr(resourceName, "on", "true"), - resource.TestCheckResourceAttr(resourceName, "secret", webhook.Secret), resource.TestCheckResourceAttr(resourceName, "tags.#", "1"), resource.TestCheckResourceAttr(resourceName, "statements.#", "1"), resource.TestCheckResourceAttr(resourceName, "statements.0.resources.0", "proj/*"), resource.TestCheckResourceAttr(resourceName, "statements.0.actions.0", "turnFlagOn"), resource.TestCheckResourceAttr(resourceName, "statements.0.effect", "allow"), + resource.TestCheckResourceAttr(resourceName, "secret", ""), // since we set Sign to false + ), }, }, diff --git a/launchdarkly/default_variations_helper.go b/launchdarkly/default_variations_helper.go index cbd15b68..5c58732c 100644 --- a/launchdarkly/default_variations_helper.go +++ b/launchdarkly/default_variations_helper.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func defaultVariationsFromResourceData(d *schema.ResourceData) (*ldapi.Defaults, error) { diff --git a/launchdarkly/default_variations_helper_test.go b/launchdarkly/default_variations_helper_test.go index 4ea95467..051e6685 100644 --- a/launchdarkly/default_variations_helper_test.go +++ b/launchdarkly/default_variations_helper_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/launchdarkly/environments_helper.go b/launchdarkly/environments_helper.go index 4e022c99..a7ca3ba5 100644 --- a/launchdarkly/environments_helper.go +++ b/launchdarkly/environments_helper.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) // baseEnvironmentSchema covers the overlap between the data source and resource schemas @@ -175,7 +175,7 @@ func environmentPostFromResourceData(env interface{}) ldapi.EnvironmentPost { } if defaultTTL, ok := envMap[DEFAULT_TTL]; ok { - envPost.DefaultTtl = float32(defaultTTL.(int)) + envPost.DefaultTtl = ldapi.PtrInt32(int32(defaultTTL.(int))) } return envPost } @@ -225,7 +225,7 @@ func environmentRead(d *schema.ResourceData, meta interface{}, isDataSource bool key := d.Get(KEY).(string) envRaw, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.EnvironmentsApi.GetEnvironment(client.ctx, projectKey, key) + return client.ld.EnvironmentsApi.GetEnvironment(client.ctx, projectKey, key).Execute() }) if isStatusNotFound(res) && !isDataSource { log.Printf("[WARN] failed to find environment with key %q in project %q, removing from state", key, projectKey) diff --git a/launchdarkly/environments_helper_test.go b/launchdarkly/environments_helper_test.go index b2490e6f..b877d7b1 100644 --- a/launchdarkly/environments_helper_test.go +++ b/launchdarkly/environments_helper_test.go @@ -3,7 +3,7 @@ package launchdarkly import ( "testing" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/require" ) @@ -25,7 +25,7 @@ func TestEnvironmentPostFromResourceData(t *testing.T) { Name: "envName", Key: "envKey", Color: "000000", - DefaultTtl: 50, + DefaultTtl: ldapi.PtrInt32(50), }, }, { diff --git a/launchdarkly/fallthrough_helper.go b/launchdarkly/fallthrough_helper.go index 986f10dc..7b866a7c 100644 --- a/launchdarkly/fallthrough_helper.go +++ b/launchdarkly/fallthrough_helper.go @@ -6,7 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func fallthroughSchema(forDataSource bool) *schema.Schema { @@ -75,7 +75,7 @@ func fallthroughFromResourceData(d *schema.ResourceData) (fallthroughModel, erro rollout := fallthroughModel{Rollout: rolloutFromResourceData(fall[ROLLOUT_WEIGHTS])} bucketBy, ok := fall[BUCKET_BY] if ok { - rollout.Rollout.BucketBy = bucketBy.(string) + rollout.Rollout.BucketBy = ldapi.PtrString(bucketBy.(string)) } return rollout, nil @@ -84,13 +84,13 @@ func fallthroughFromResourceData(d *schema.ResourceData) (fallthroughModel, erro return fallthroughModel{Variation: &val}, nil } -func fallthroughToResourceData(fallThrough *ldapi.ModelFallthrough) interface{} { +func fallthroughToResourceData(fallThrough ldapi.VariationOrRolloutRep) interface{} { transformed := make([]interface{}, 1) if fallThrough.Rollout != nil { rollout := map[string]interface{}{ ROLLOUT_WEIGHTS: rolloutsToResourceData(fallThrough.Rollout), } - if fallThrough.Rollout.BucketBy != "" { + if fallThrough.Rollout.BucketBy != nil { rollout[BUCKET_BY] = fallThrough.Rollout.BucketBy } transformed[0] = rollout diff --git a/launchdarkly/feature_flag_environment_helper.go b/launchdarkly/feature_flag_environment_helper.go index 8129da65..21dbd0ab 100644 --- a/launchdarkly/feature_flag_environment_helper.go +++ b/launchdarkly/feature_flag_environment_helper.go @@ -6,10 +6,9 @@ import ( "net/http" "strings" - "github.com/antihax/optional" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func baseFeatureFlagEnvironmentSchema(forDataSource bool) map[string]*schema.Schema { @@ -57,9 +56,7 @@ func baseFeatureFlagEnvironmentSchema(forDataSource bool) map[string]*schema.Sch // get FeatureFlagEnvironment uses a query parameter to get the ldapi.FeatureFlag with only a single environment. func getFeatureFlagEnvironment(client *Client, projectKey, flagKey, environmentKey string) (ldapi.FeatureFlag, *http.Response, error) { flagRaw, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projectKey, flagKey, &ldapi.FeatureFlagsApiGetFeatureFlagOpts{ - Env: optional.NewInterface(environmentKey), - }) + return client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projectKey, flagKey).Env(environmentKey).Execute() }) flag := flagRaw.(ldapi.FeatureFlag) return flag, res, err @@ -116,7 +113,7 @@ func featureFlagEnvironmentRead(d *schema.ResourceData, raw interface{}, isDataS return fmt.Errorf("failed to set targets on flag with key %q: %v", flagKey, err) } - err = d.Set(FALLTHROUGH, fallthroughToResourceData(environment.Fallthrough_)) + err = d.Set(FALLTHROUGH, fallthroughToResourceData(environment.Fallthrough)) if err != nil { return fmt.Errorf("failed to set flag fallthrough on flag with key %q: %v", flagKey, err) } diff --git a/launchdarkly/feature_flags_helper.go b/launchdarkly/feature_flags_helper.go index 45f54449..2122d5b4 100644 --- a/launchdarkly/feature_flags_helper.go +++ b/launchdarkly/feature_flags_helper.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func baseFeatureFlagSchema() map[string]*schema.Schema { @@ -47,10 +47,33 @@ func baseFeatureFlagSchema() map[string]*schema.Schema { Default: false, }, INCLUDE_IN_SNIPPET: { - Type: schema.TypeBool, - Optional: true, - Description: "Whether or not this flag should be made available to the client-side JavaScript SDK", - Default: false, + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "Whether or not this flag should be made available to the client-side JavaScript SDK", + Deprecated: "'include_in_snippet' is now deprecated. Please migrate to 'client_side_availability' to maintain future compatability.", + ConflictsWith: []string{CLIENT_SIDE_AVAILABILITY}, + }, + // Annoying that we can't define a typemap to have specific keys https://www.terraform.io/docs/extend/schemas/schema-types.html#typemap + CLIENT_SIDE_AVAILABILITY: { + Type: schema.TypeList, + Optional: true, + Computed: true, + ConflictsWith: []string{INCLUDE_IN_SNIPPET}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + USING_ENVIRONMENT_ID: { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + USING_MOBILE_KEY: { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + }, }, TAGS: tagsSchema(), CUSTOM_PROPERTIES: customPropertiesSchema(), @@ -92,7 +115,7 @@ func featureFlagRead(d *schema.ResourceData, raw interface{}, isDataSource bool) key := d.Get(KEY).(string) flagRaw, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projectKey, key, nil) + return client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projectKey, key).Execute() }) flag := flagRaw.(ldapi.FeatureFlag) if isStatusNotFound(res) && !isDataSource { @@ -109,20 +132,17 @@ func featureFlagRead(d *schema.ResourceData, raw interface{}, isDataSource bool) _ = d.Set(KEY, flag.Key) _ = d.Set(NAME, flag.Name) _ = d.Set(DESCRIPTION, flag.Description) - _ = d.Set(INCLUDE_IN_SNIPPET, flag.IncludeInSnippet) _ = d.Set(TEMPORARY, flag.Temporary) _ = d.Set(ARCHIVED, flag.Archived) - if isDataSource { - CSA := *flag.ClientSideAvailability - clientSideAvailability := []map[string]interface{}{{ - "using_environment_id": CSA.UsingEnvironmentId, - "using_mobile_key": CSA.UsingMobileKey, - }} - _ = d.Set(CLIENT_SIDE_AVAILABILITY, clientSideAvailability) - } else { - _ = d.Set(INCLUDE_IN_SNIPPET, flag.IncludeInSnippet) - } + CSA := *flag.ClientSideAvailability + clientSideAvailability := []map[string]interface{}{{ + USING_ENVIRONMENT_ID: CSA.UsingEnvironmentId, + USING_MOBILE_KEY: CSA.UsingMobileKey, + }} + // Always set both CSA and IIS to state in order to correctly represent the flag resource as it exists in LD + _ = d.Set(CLIENT_SIDE_AVAILABILITY, clientSideAvailability) + _ = d.Set(INCLUDE_IN_SNIPPET, CSA.UsingEnvironmentId) // Only set the maintainer ID if is specified in the schema _, ok := d.GetOk(MAINTAINER_ID) @@ -184,3 +204,14 @@ func flagIdToKeys(id string) (projectKey string, flagKey string, err error) { projectKey, flagKey = parts[0], parts[1] return projectKey, flagKey, nil } + +func getProjectDefaultCSAandIncludeInSnippet(client *Client, projectKey string) (ldapi.ClientSideAvailability, bool, error) { + rawProject, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { + return client.ld.ProjectsApi.GetProject(client.ctx, projectKey).Execute() + }) + if err != nil { + return ldapi.ClientSideAvailability{}, false, err + } + project := rawProject.(ldapi.Project) + return *project.DefaultClientSideAvailability, project.IncludeInSnippetByDefault, nil +} diff --git a/launchdarkly/helper.go b/launchdarkly/helper.go index cdce1cd1..390583f5 100644 --- a/launchdarkly/helper.go +++ b/launchdarkly/helper.go @@ -8,7 +8,7 @@ import ( "strconv" "time" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) const ( @@ -80,6 +80,8 @@ func intPtr(i int) *int { func strPtr(v string) *string { return &v } +func strArrayPtr(v []string) *[]string { return &v } + func patchReplace(path string, value interface{}) ldapi.PatchOperation { return ldapi.PatchOperation{ Op: "replace", @@ -109,7 +111,7 @@ func handleLdapiErr(err error) error { if err == nil { return nil } - if swaggerErr, ok := err.(ldapi.GenericSwaggerError); ok { + if swaggerErr, ok := err.(ldapi.GenericOpenAPIError); ok { return fmt.Errorf("%s: %s", swaggerErr.Error(), string(swaggerErr.Body())) } return err diff --git a/launchdarkly/keys.go b/launchdarkly/keys.go index 12890b49..f7ebc1bc 100644 --- a/launchdarkly/keys.go +++ b/launchdarkly/keys.go @@ -84,4 +84,6 @@ const ( MIN_NUM_APPROVALS = "min_num_approvals" CAN_APPLY_DECLINED_CHANGES = "can_apply_declined_changes" REQUIRED_APPROVAL_TAGS = "required_approval_tags" + USING_ENVIRONMENT_ID = "using_environment_id" + USING_MOBILE_KEY = "using_mobile_key" ) diff --git a/launchdarkly/policies_helper.go b/launchdarkly/policies_helper.go index 6cb1b606..5105427e 100644 --- a/launchdarkly/policies_helper.go +++ b/launchdarkly/policies_helper.go @@ -5,7 +5,7 @@ import ( "sort" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func policyArraySchema() *schema.Schema { @@ -39,10 +39,10 @@ func policyArraySchema() *schema.Schema { } } -func policiesFromResourceData(d *schema.ResourceData) []ldapi.Policy { +func policiesFromResourceData(d *schema.ResourceData) []ldapi.StatementPost { schemaPolicies := d.Get(POLICY).(*schema.Set) - policies := make([]ldapi.Policy, schemaPolicies.Len()) + policies := make([]ldapi.StatementPost, schemaPolicies.Len()) list := schemaPolicies.List() for i, policy := range list { v := policyFromResourceData(policy) @@ -51,9 +51,9 @@ func policiesFromResourceData(d *schema.ResourceData) []ldapi.Policy { return policies } -func policyFromResourceData(val interface{}) ldapi.Policy { +func policyFromResourceData(val interface{}) ldapi.StatementPost { policyMap := val.(map[string]interface{}) - p := ldapi.Policy{ + p := ldapi.StatementPost{ Resources: []string{}, Actions: []string{}, Effect: policyMap[EFFECT].(string), @@ -70,7 +70,7 @@ func policyFromResourceData(val interface{}) ldapi.Policy { return p } -func policiesToResourceData(policies []ldapi.Policy) interface{} { +func policiesToResourceData(policies []ldapi.Statement) interface{} { transformed := make([]interface{}, len(policies)) for i, p := range policies { diff --git a/launchdarkly/policy_statements_helper.go b/launchdarkly/policy_statements_helper.go index ad3df49e..36b6a50b 100644 --- a/launchdarkly/policy_statements_helper.go +++ b/launchdarkly/policy_statements_helper.go @@ -5,7 +5,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) // policyStatementSchemaOptions is used to help with renaming 'policy_statements' to statements for the launchdarkly_webhook resource. @@ -93,8 +93,8 @@ func validatePolicyStatement(statement map[string]interface{}) error { return nil } -func policyStatementsFromResourceData(schemaStatements []interface{}) ([]ldapi.Statement, error) { - statements := make([]ldapi.Statement, 0, len(schemaStatements)) +func policyStatementsFromResourceData(schemaStatements []interface{}) ([]ldapi.StatementPost, error) { + statements := make([]ldapi.StatementPost, 0, len(schemaStatements)) for _, stmt := range schemaStatements { statement := stmt.(map[string]interface{}) err := validatePolicyStatement(statement) @@ -107,60 +107,74 @@ func policyStatementsFromResourceData(schemaStatements []interface{}) ([]ldapi.S return statements, nil } -func policyStatementFromResourceData(statement map[string]interface{}) ldapi.Statement { - ret := ldapi.Statement{ +func policyStatementFromResourceData(statement map[string]interface{}) ldapi.StatementPost { + ret := ldapi.StatementPost{ Effect: statement[EFFECT].(string), } for _, r := range statement[RESOURCES].([]interface{}) { ret.Resources = append(ret.Resources, r.(string)) } - for _, n := range statement[NOT_RESOURCES].([]interface{}) { - ret.NotResources = append(ret.NotResources, n.(string)) - } for _, a := range statement[ACTIONS].([]interface{}) { ret.Actions = append(ret.Actions, a.(string)) } - for _, n := range statement[NOT_ACTIONS].([]interface{}) { - ret.NotActions = append(ret.NotActions, n.(string)) + // optional fields + rawNotResources := statement[NOT_RESOURCES].([]interface{}) + var notResources []string + for _, n := range rawNotResources { + notResources = append(notResources, n.(string)) + ret.NotResources = ¬Resources + } + rawNotActions := statement[NOT_ACTIONS].([]interface{}) + var notActions []string + for _, n := range rawNotActions { + notActions = append(notActions, n.(string)) + ret.NotActions = ¬Actions } return ret } -func policyStatementsToResourceData(statements []ldapi.Statement) []interface{} { +func policyStatementsToResourceData(statements []ldapi.StatementRep) []interface{} { transformed := make([]interface{}, 0, len(statements)) for _, s := range statements { t := map[string]interface{}{ EFFECT: s.Effect, } - if len(s.Resources) > 0 { - t[RESOURCES] = stringSliceToInterfaceSlice(s.Resources) + if s.Resources != nil && len(*s.Resources) > 0 { + var resources []interface{} + for _, v := range *s.Resources { + resources = append(resources, v) + } + t[RESOURCES] = resources } - if len(s.NotResources) > 0 { - t[NOT_RESOURCES] = stringSliceToInterfaceSlice(s.NotResources) + if s.NotResources != nil && len(*s.NotResources) > 0 { + var notResources []interface{} + for _, v := range *s.NotResources { + notResources = append(notResources, v) + } + t[NOT_RESOURCES] = notResources } - if len(s.Actions) > 0 { - t[ACTIONS] = stringSliceToInterfaceSlice(s.Actions) + if s.Actions != nil && len(*s.Actions) > 0 { + t[ACTIONS] = stringSliceToInterfaceSlice(*s.Actions) } - if len(s.NotActions) > 0 { - t[NOT_ACTIONS] = stringSliceToInterfaceSlice(s.NotActions) + if s.NotActions != nil && len(*s.NotActions) > 0 { + t[NOT_ACTIONS] = stringSliceToInterfaceSlice(*s.NotActions) } transformed = append(transformed, t) } return transformed } -func statementsToPolicies(statements []ldapi.Statement) []ldapi.Policy { - policies := make([]ldapi.Policy, 0, len(statements)) - for _, s := range statements { - policies = append(policies, ldapi.Policy(s)) - } - return policies -} - -func policiesToStatements(policies []ldapi.Policy) []ldapi.Statement { - statements := make([]ldapi.Statement, 0, len(policies)) +func statementsToStatementReps(policies []ldapi.Statement) []ldapi.StatementRep { + statements := make([]ldapi.StatementRep, 0, len(policies)) for _, p := range policies { - statements = append(statements, ldapi.Statement(p)) + rep := ldapi.StatementRep{ + Resources: p.Resources, + Actions: p.Actions, + NotResources: p.NotResources, + NotActions: p.NotActions, + Effect: p.Effect, + } + statements = append(statements, rep) } return statements } diff --git a/launchdarkly/policy_statements_helper_test.go b/launchdarkly/policy_statements_helper_test.go index a8404606..2efddfe1 100644 --- a/launchdarkly/policy_statements_helper_test.go +++ b/launchdarkly/policy_statements_helper_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -13,7 +13,7 @@ func TestPolicyStatementsRoundTripConversion(t *testing.T) { testCases := []struct { name string policyStatements map[string]interface{} - expected []ldapi.Statement + expected []ldapi.StatementPost }{ { name: "basic policy statement", @@ -26,7 +26,7 @@ func TestPolicyStatementsRoundTripConversion(t *testing.T) { }, }, }, - expected: []ldapi.Statement{ + expected: []ldapi.StatementPost{ { Resources: []string{"proj/*"}, Actions: []string{"*"}, @@ -50,7 +50,7 @@ func TestPolicyStatementsRoundTripConversion(t *testing.T) { }, }, }, - expected: []ldapi.Statement{ + expected: []ldapi.StatementPost{ { Resources: []string{"proj/*:env/*;qa_*"}, Actions: []string{"*"}, @@ -74,9 +74,9 @@ func TestPolicyStatementsRoundTripConversion(t *testing.T) { }, }, }, - expected: []ldapi.Statement{ + expected: []ldapi.StatementPost{ { - NotResources: []string{"proj/*:env/production:flag/*"}, + NotResources: strArrayPtr([]string{"proj/*:env/production:flag/*"}), Actions: []string{"*"}, Effect: "allow", }, @@ -97,7 +97,9 @@ func TestPolicyStatementsRoundTripConversion(t *testing.T) { require.NoError(t, err) require.Equal(t, tc.expected, actual) - actualRaw := policyStatementsToResourceData(actual) + // with v7 of the go client there is an accidental duplicate type, so it returns a Statement type + // even though it takes a StatementPost type + actualRaw := policyStatementsToResourceData(statementsToStatementReps(statementPostsToStatements(actual))) require.Equal(t, tc.policyStatements[POLICY_STATEMENTS], actualRaw) }) } @@ -161,3 +163,21 @@ func TestPolicyStatementValidation(t *testing.T) { }) } } + +// statementPostToStatement is a helper function just for these tests +// since v7 of the go client passes and returns two differing types +func statementPostsToStatements(posts []ldapi.StatementPost) []ldapi.Statement { + var statements []ldapi.Statement + for _, p := range posts { + p := p + statement := ldapi.Statement{ + Resources: &p.Resources, + NotResources: p.NotResources, + Actions: &p.Actions, + NotActions: p.NotActions, + Effect: p.Effect, + } + statements = append(statements, statement) + } + return statements +} diff --git a/launchdarkly/prerequisite_helper.go b/launchdarkly/prerequisite_helper.go index 812f4c0c..682da0a0 100644 --- a/launchdarkly/prerequisite_helper.go +++ b/launchdarkly/prerequisite_helper.go @@ -5,7 +5,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func prerequisitesSchema() *schema.Schema { diff --git a/launchdarkly/project_helper.go b/launchdarkly/project_helper.go index 566305e2..78779578 100644 --- a/launchdarkly/project_helper.go +++ b/launchdarkly/project_helper.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func projectRead(d *schema.ResourceData, meta interface{}, isDataSource bool) error { @@ -14,7 +14,7 @@ func projectRead(d *schema.ResourceData, meta interface{}, isDataSource bool) er projectKey := d.Get(KEY).(string) rawProject, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.ProjectsApi.GetProject(client.ctx, projectKey) + return client.ld.ProjectsApi.GetProject(client.ctx, projectKey).Execute() }) // return nil error for resource reads but 404 for data source reads if isStatusNotFound(res) && !isDataSource { diff --git a/launchdarkly/provider.go b/launchdarkly/provider.go index 4041863b..9cf1b6d7 100644 --- a/launchdarkly/provider.go +++ b/launchdarkly/provider.go @@ -2,6 +2,8 @@ package launchdarkly import ( "fmt" + "net/url" + "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -70,11 +72,15 @@ func Provider() *schema.Provider { func providerConfigure(d *schema.ResourceData) (interface{}, error) { host := d.Get(api_host).(string) + if strings.HasPrefix(host, "http") { + u, _ := url.Parse(host) + host = u.Host + } accessToken := d.Get(access_token).(string) oauthToken := d.Get(oauth_token).(string) if oauthToken == "" && accessToken == "" { - return nil, fmt.Errorf("either an %q or %q must be specified.", access_token, oauth_token) + return nil, fmt.Errorf("either an %q or %q must be specified", access_token, oauth_token) } if oauthToken != "" { diff --git a/launchdarkly/resource_launchdarkly_access_token.go b/launchdarkly/resource_launchdarkly_access_token.go index 156bbfbc..34df7b7e 100644 --- a/launchdarkly/resource_launchdarkly_access_token.go +++ b/launchdarkly/resource_launchdarkly_access_token.go @@ -1,15 +1,18 @@ package launchdarkly import ( + "bytes" + "encoding/json" "fmt" + "io" "log" "net/http" + "strings" - "github.com/antihax/optional" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func resourceAccessToken() *schema.Resource { @@ -49,7 +52,7 @@ func resourceAccessToken() *schema.Resource { Set: schema.HashString, Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, - ConflictsWith: []string{ROLE, POLICY_STATEMENTS}, + ConflictsWith: []string{ROLE, POLICY_STATEMENTS, INLINE_ROLES}, }, POLICY_STATEMENTS: deprecatedTokenPolicySchema, INLINE_ROLES: tokenPolicySchema, @@ -75,10 +78,11 @@ func resourceAccessToken() *schema.Resource { Sensitive: true, }, EXPIRE: { - Deprecated: "'expire' is deprecated and will be removed in the next major release of the LaunchDarly provider", - Type: schema.TypeInt, - Description: "Replace the computed token secret with a new value. The expired secret will no longer be able to authorize usage of the LaunchDarkly API. Should be an expiration time for the current token secret, expressed as a Unix epoch time in milliseconds. Setting this to a negative value will expire the existing token immediately. To reset the token value again, change 'expire' to a new value. Setting this field at resource creation time WILL NOT set an expiration time for the token.", - Optional: true, + Deprecated: "'expire' is deprecated and will be removed in the next major release of the LaunchDarkly provider", + Type: schema.TypeInt, + Description: "Replace the computed token secret with a new value. The expired secret will no longer be able to authorize usage of the LaunchDarkly API. Should be an expiration time for the current token secret, expressed as a Unix epoch time in milliseconds. Setting this to a negative value will expire the existing token immediately. To reset the token value again, change 'expire' to a new value. Setting this field at resource creation time WILL NOT set an expiration time for the token.", + Optional: true, + ValidateFunc: validation.NoZeroValues, }, }, } @@ -123,31 +127,37 @@ func resourceAccessTokenCreate(d *schema.ResourceData, metaRaw interface{}) erro client := metaRaw.(*Client) accessTokenName := d.Get(NAME).(string) - accessTokenRole := d.Get(ROLE).(string) serviceToken := d.Get(SERVICE_TOKEN).(bool) - defaultApiVersion := d.Get(DEFAULT_API_VERSION).(int) - customRolesRaw := d.Get(CUSTOM_ROLES).(*schema.Set).List() + + accessTokenBody := ldapi.AccessTokenPost{ + Name: ldapi.PtrString(accessTokenName), + ServiceToken: ldapi.PtrBool(serviceToken), + } + + if defaultApiVersion, ok := d.GetOk(DEFAULT_API_VERSION); ok { + accessTokenBody.DefaultApiVersion = ldapi.PtrInt32(int32(defaultApiVersion.(int))) + } + inlineRoles, _ := policyStatementsFromResourceData(d.Get(POLICY_STATEMENTS).([]interface{})) if len(inlineRoles) == 0 { inlineRoles, _ = policyStatementsFromResourceData(d.Get(INLINE_ROLES).([]interface{})) } - customRoles := make([]string, len(customRolesRaw)) - for i, cr := range customRolesRaw { - customRoles[i] = cr.(string) - } - - accessTokenBody := ldapi.TokenBody{ - Name: accessTokenName, - Role: accessTokenRole, - CustomRoleIds: customRoles, - InlineRole: inlineRoles, - ServiceToken: serviceToken, - DefaultApiVersion: int32(defaultApiVersion), + customRolesRaw := d.Get(CUSTOM_ROLES).(*schema.Set).List() + if len(inlineRoles) == 0 && len(customRolesRaw) > 0 { + customRoles := make([]string, len(customRolesRaw)) + for i, cr := range customRolesRaw { + customRoles[i] = cr.(string) + } + accessTokenBody.CustomRoleIds = &customRoles + } else if len(inlineRoles) > 0 { + accessTokenBody.InlineRole = &inlineRoles + } else if accessTokenRole, ok := d.GetOk(ROLE); ok { + accessTokenBody.Role = ldapi.PtrString(accessTokenRole.(string)) } tokenRaw, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.AccessTokensApi.PostToken(client.ctx, accessTokenBody) + return client.ld.AccessTokensApi.PostToken(client.ctx).AccessTokenPost(accessTokenBody).Execute() }) token := tokenRaw.(ldapi.Token) if err != nil { @@ -164,7 +174,7 @@ func resourceAccessTokenRead(d *schema.ResourceData, metaRaw interface{}) error accessTokenID := d.Id() accessTokenRaw, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.AccessTokensApi.GetToken(client.ctx, accessTokenID) + return client.ld.AccessTokensApi.GetToken(client.ctx, accessTokenID).Execute() }) accessToken := accessTokenRaw.(ldapi.Token) if isStatusNotFound(res) { @@ -177,11 +187,11 @@ func resourceAccessTokenRead(d *schema.ResourceData, metaRaw interface{}) error } _ = d.Set(NAME, accessToken.Name) - if accessToken.Role != "" { - _ = d.Set(ROLE, accessToken.Role) + if accessToken.Role != nil { + _ = d.Set(ROLE, *accessToken.Role) } - if len(accessToken.CustomRoleIds) > 0 { - customRoleKeys, err := customRoleIDsToKeys(client, accessToken.CustomRoleIds) + if accessToken.CustomRoleIds != nil && len(*accessToken.CustomRoleIds) > 0 { + customRoleKeys, err := customRoleIDsToKeys(client, *accessToken.CustomRoleIds) if err != nil { return err } @@ -191,12 +201,12 @@ func resourceAccessTokenRead(d *schema.ResourceData, metaRaw interface{}) error _ = d.Set(DEFAULT_API_VERSION, accessToken.DefaultApiVersion) policies := accessToken.InlineRole - if len(policies) > 0 { + if policies != nil && len(*policies) > 0 { policyStatements, _ := policyStatementsFromResourceData(d.Get(POLICY_STATEMENTS).([]interface{})) if len(policyStatements) > 0 { - err = d.Set(POLICY_STATEMENTS, policyStatementsToResourceData(policies)) + err = d.Set(POLICY_STATEMENTS, policyStatementsToResourceData(*policies)) } else { - err = d.Set(INLINE_ROLES, policyStatementsToResourceData(policies)) + err = d.Set(INLINE_ROLES, policyStatementsToResourceData(*policies)) } if err != nil { return fmt.Errorf("could not set policy on access token with id %q: %v", accessTokenID, err) @@ -231,7 +241,7 @@ func resourceAccessTokenUpdate(d *schema.ResourceData, metaRaw interface{}) erro if len(inlineRoles) == 0 { inlineRoles, _ = policyStatementsFromResourceData(d.Get(INLINE_ROLES).([]interface{})) } - iRoles := statementsToPolicies(inlineRoles) + iRoles := inlineRoles patch := []ldapi.PatchOperation{ patchReplace("/name", &accessTokenName), @@ -266,7 +276,7 @@ func resourceAccessTokenUpdate(d *schema.ResourceData, metaRaw interface{}) erro } _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.AccessTokensApi.PatchToken(client.ctx, accessTokenID, patch) + return client.ld.AccessTokensApi.PatchToken(client.ctx, accessTokenID).PatchOperation(patch).Execute() }) if err != nil { return fmt.Errorf("failed to update access token with id %q: %s", accessTokenID, handleLdapiErr(err)) @@ -277,12 +287,8 @@ func resourceAccessTokenUpdate(d *schema.ResourceData, metaRaw interface{}) erro oldExpireRaw, newExpireRaw := d.GetChange(EXPIRE) oldExpire := oldExpireRaw.(int) newExpire := newExpireRaw.(int) - opts := ldapi.AccessTokensApiResetTokenOpts{} if oldExpire != newExpire && newExpire != 0 { - if newExpire > 0 { - opts.Expiry = optional.NewInt64(int64(newExpire)) - } - token, _, err := client.ld.AccessTokensApi.ResetToken(client.ctx, accessTokenID, &opts) + token, err := resetAccessToken(client, accessTokenID, newExpire) if err != nil { return fmt.Errorf("failed to reset access token with id %q: %s", accessTokenID, handleLdapiErr(err)) } @@ -299,7 +305,7 @@ func resourceAccessTokenDelete(d *schema.ResourceData, metaRaw interface{}) erro accessTokenID := d.Id() _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - res, err := client.ld.AccessTokensApi.DeleteToken(client.ctx, accessTokenID) + res, err := client.ld.AccessTokensApi.DeleteToken(client.ctx, accessTokenID).Execute() return nil, res, err }) if err != nil { @@ -314,7 +320,7 @@ func resourceAccessTokenExists(d *schema.ResourceData, metaRaw interface{}) (boo } func accessTokenExists(accessTokenID string, meta *Client) (bool, error) { - _, res, err := meta.ld.AccessTokensApi.GetToken(meta.ctx, accessTokenID) + _, res, err := meta.ld.AccessTokensApi.GetToken(meta.ctx, accessTokenID).Execute() if isStatusNotFound(res) { return false, nil } @@ -324,3 +330,55 @@ func accessTokenExists(accessTokenID string, meta *Client) (bool, error) { return true, nil } + +func resetAccessToken(client *Client, accessTokenID string, expiry int) (ldapi.Token, error) { + var token ldapi.Token + // var err error + // // Terraform validation will ensure we do not get a zero value + // if expiry > 0 { + // token, _, err = client.ld.AccessTokensApi.ResetToken(client.ctx, accessTokenID).Expiry(int64(expiry)).Execute() + // } else if expiry < 0 { + // token, _, err = client.ld.AccessTokensApi.ResetToken(client.ctx, accessTokenID).Execute() + // } + // if err != nil { + // return token, fmt.Errorf("failed to reset access token with id %q: %s", accessTokenID, handleLdapiErr(err)) + // } + // return token, nil + endpoint := fmt.Sprintf("%s/api/v2/tokens/%s/reset", client.apiHost, accessTokenID) + if !strings.HasPrefix(endpoint, "http") { + endpoint = "https://" + endpoint + } + var body io.Reader + if expiry > 0 { + rawBody, err := json.Marshal(map[string]int{ + "expiry": expiry, + }) + if err != nil { + return token, err + } + body = bytes.NewBuffer(rawBody) + } + req, err := http.NewRequest("POST", endpoint, body) + if err != nil { + return token, err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", client.apiKey) + + resp, err := client.fallbackClient.Do(req) + if err != nil { + return token, err + } + + rawBody, err := io.ReadAll(resp.Body) + if err != nil { + return token, err + } + + err = json.Unmarshal(rawBody, &token) + if err != nil { + return token, err + } + + return token, nil +} diff --git a/launchdarkly/resource_launchdarkly_access_token_test.go b/launchdarkly/resource_launchdarkly_access_token_test.go index 76744ce7..cf14544d 100644 --- a/launchdarkly/resource_launchdarkly_access_token_test.go +++ b/launchdarkly/resource_launchdarkly_access_token_test.go @@ -386,7 +386,7 @@ func testAccCheckAccessTokenExists(resourceName string) resource.TestCheckFunc { return fmt.Errorf("access token ID is not set") } client := testAccProvider.Meta().(*Client) - _, _, err := client.ld.AccessTokensApi.GetToken(client.ctx, rs.Primary.ID) + _, _, err := client.ld.AccessTokensApi.GetToken(client.ctx, rs.Primary.ID).Execute() if err != nil { return fmt.Errorf("received an error getting access token. %s", err) } diff --git a/launchdarkly/resource_launchdarkly_custom_role.go b/launchdarkly/resource_launchdarkly_custom_role.go index 4b32f21b..6447372b 100644 --- a/launchdarkly/resource_launchdarkly_custom_role.go +++ b/launchdarkly/resource_launchdarkly_custom_role.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func resourceCustomRole() *schema.Resource { @@ -57,18 +57,18 @@ func resourceCustomRoleCreate(d *schema.ResourceData, metaRaw interface{}) error return err } if len(policyStatements) > 0 { - customRolePolicies = statementsToPolicies(policyStatements) + customRolePolicies = policyStatements } - customRoleBody := ldapi.CustomRoleBody{ + customRoleBody := ldapi.CustomRolePost{ Key: customRoleKey, Name: customRoleName, - Description: customRoleDescription, + Description: ldapi.PtrString(customRoleDescription), Policy: customRolePolicies, } _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.CustomRolesApi.PostCustomRole(client.ctx, customRoleBody) + return client.ld.CustomRolesApi.PostCustomRole(client.ctx).CustomRolePost(customRoleBody).Execute() }) if err != nil { return fmt.Errorf("failed to create custom role with name %q: %s", customRoleName, handleLdapiErr(err)) @@ -83,7 +83,7 @@ func resourceCustomRoleRead(d *schema.ResourceData, metaRaw interface{}) error { customRoleID := d.Id() customRoleRaw, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.CustomRolesApi.GetCustomRole(client.ctx, customRoleID) + return client.ld.CustomRolesApi.GetCustomRole(client.ctx, customRoleID).Execute() }) customRole := customRoleRaw.(ldapi.CustomRole) if isStatusNotFound(res) { @@ -104,7 +104,7 @@ func resourceCustomRoleRead(d *schema.ResourceData, metaRaw interface{}) error { if _, ok := d.GetOk(POLICY); ok { err = d.Set(POLICY, policiesToResourceData(customRole.Policy)) } else { - err = d.Set(POLICY_STATEMENTS, policyStatementsToResourceData(policiesToStatements(customRole.Policy))) + err = d.Set(POLICY_STATEMENTS, policyStatementsToResourceData(statementsToStatementReps(customRole.Policy))) } if err != nil { @@ -124,18 +124,19 @@ func resourceCustomRoleUpdate(d *schema.ResourceData, metaRaw interface{}) error return err } if len(policyStatements) > 0 { - customRolePolicies = statementsToPolicies(policyStatements) + customRolePolicies = policyStatements } - patch := []ldapi.PatchOperation{ - patchReplace("/name", &customRoleName), - patchReplace("/description", &customRoleDescription), - patchReplace("/policy", &customRolePolicies), - } + patch := ldapi.PatchWithComment{ + Patch: []ldapi.PatchOperation{ + patchReplace("/name", &customRoleName), + patchReplace("/description", &customRoleDescription), + patchReplace("/policy", &customRolePolicies), + }} _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.CustomRolesApi.PatchCustomRole(client.ctx, customRoleKey, patch) + return client.ld.CustomRolesApi.PatchCustomRole(client.ctx, customRoleKey).PatchWithComment(patch).Execute() }) }) if err != nil { @@ -150,7 +151,7 @@ func resourceCustomRoleDelete(d *schema.ResourceData, metaRaw interface{}) error customRoleKey := d.Id() _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - res, err := client.ld.CustomRolesApi.DeleteCustomRole(client.ctx, customRoleKey) + res, err := client.ld.CustomRolesApi.DeleteCustomRole(client.ctx, customRoleKey).Execute() return nil, res, err }) @@ -166,7 +167,7 @@ func resourceCustomRoleExists(d *schema.ResourceData, metaRaw interface{}) (bool } func customRoleExists(customRoleKey string, meta *Client) (bool, error) { - _, res, err := meta.ld.CustomRolesApi.GetCustomRole(meta.ctx, customRoleKey) + _, res, err := meta.ld.CustomRolesApi.GetCustomRole(meta.ctx, customRoleKey).Execute() if isStatusNotFound(res) { return false, nil } diff --git a/launchdarkly/resource_launchdarkly_custom_role_test.go b/launchdarkly/resource_launchdarkly_custom_role_test.go index f5b073d7..06e1ec94 100644 --- a/launchdarkly/resource_launchdarkly_custom_role_test.go +++ b/launchdarkly/resource_launchdarkly_custom_role_test.go @@ -37,7 +37,7 @@ resource "launchdarkly_custom_role" "test" { resource "launchdarkly_custom_role" "test" { key = "%s" name = "Custom role - %s" - description= "Allow all actions on staging environments" + description = "Allow all actions on staging environments" policy_statements { actions = ["*"] effect = "allow" @@ -204,7 +204,7 @@ func testAccCheckCustomRoleExists(resourceName string) resource.TestCheckFunc { return fmt.Errorf("custom role ID is not set") } client := testAccProvider.Meta().(*Client) - _, _, err := client.ld.CustomRolesApi.GetCustomRole(client.ctx, rs.Primary.ID) + _, _, err := client.ld.CustomRolesApi.GetCustomRole(client.ctx, rs.Primary.ID).Execute() if err != nil { return fmt.Errorf("received an error getting custom role. %s", err) } diff --git a/launchdarkly/resource_launchdarkly_destination.go b/launchdarkly/resource_launchdarkly_destination.go index f904aa2d..2a8561eb 100644 --- a/launchdarkly/resource_launchdarkly_destination.go +++ b/launchdarkly/resource_launchdarkly_destination.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func resourceDestination() *schema.Resource { @@ -79,15 +79,15 @@ func resourceDestinationCreate(d *schema.ResourceData, metaRaw interface{}) erro return err } - destinationBody := ldapi.DestinationBody{ - Name: destinationName, - Kind: destinationKind, + destinationBody := ldapi.DestinationPost{ + Name: &destinationName, + Kind: &destinationKind, Config: &destinationConfig, - On: destinationOn, + On: &destinationOn, } destinationRaw, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.DataExportDestinationsApi.PostDestination(client.ctx, destinationProjKey, destinationEnvKey, destinationBody) + return client.ld.DataExportDestinationsApi.PostDestination(client.ctx, destinationProjKey, destinationEnvKey).DestinationPost(destinationBody).Execute() }) destination := destinationRaw.(ldapi.Destination) if err != nil { @@ -96,7 +96,7 @@ func resourceDestinationCreate(d *schema.ResourceData, metaRaw interface{}) erro } // destination defined in api-client-go/model_destination.go - d.SetId(strings.Join([]string{destinationProjKey, destinationEnvKey, destination.Id}, "/")) + d.SetId(strings.Join([]string{destinationProjKey, destinationEnvKey, *destination.Id}, "/")) return resourceDestinationRead(d, metaRaw) } @@ -112,7 +112,7 @@ func resourceDestinationRead(d *schema.ResourceData, metaRaw interface{}) error destinationEnvKey := d.Get(ENV_KEY).(string) destinationRaw, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.DataExportDestinationsApi.GetDestination(client.ctx, destinationProjKey, destinationEnvKey, destinationID) + return client.ld.DataExportDestinationsApi.GetDestination(client.ctx, destinationProjKey, destinationEnvKey, destinationID).Execute() }) destination := destinationRaw.(ldapi.Destination) if isStatusNotFound(res) { @@ -124,7 +124,7 @@ func resourceDestinationRead(d *schema.ResourceData, metaRaw interface{}) error return fmt.Errorf("failed to get destination with id %q: %s", destinationID, handleLdapiErr(err)) } - cfg := destinationConfigToResourceData(destination.Kind, *destination.Config) + cfg := destinationConfigToResourceData(*destination.Kind, destination.Config) preservedCfg := preserveObfuscatedConfigAttributes(d.Get(CONFIG).(map[string]interface{}), cfg) _ = d.Set(NAME, destination.Name) @@ -132,7 +132,7 @@ func resourceDestinationRead(d *schema.ResourceData, metaRaw interface{}) error _ = d.Set(CONFIG, preservedCfg) _ = d.Set(ON, destination.On) - d.SetId(strings.Join([]string{destinationProjKey, destinationEnvKey, destination.Id}, "/")) + d.SetId(strings.Join([]string{destinationProjKey, destinationEnvKey, *destination.Id}, "/")) return nil } @@ -161,7 +161,7 @@ func resourceDestinationUpdate(d *schema.ResourceData, metaRaw interface{}) erro _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict((func() (interface{}, *http.Response, error) { - return client.ld.DataExportDestinationsApi.PatchDestination(client.ctx, destinationProjKey, destinationEnvKey, destinationID, patch) + return client.ld.DataExportDestinationsApi.PatchDestination(client.ctx, destinationProjKey, destinationEnvKey, destinationID).PatchOperation(patch).Execute() })) }) if err != nil { @@ -181,7 +181,7 @@ func resourceDestinationDelete(d *schema.ResourceData, metaRaw interface{}) erro destinationEnvKey := d.Get(ENV_KEY).(string) _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { - res, err := client.ld.DataExportDestinationsApi.DeleteDestination(client.ctx, destinationProjKey, destinationEnvKey, destinationID) + res, err := client.ld.DataExportDestinationsApi.DeleteDestination(client.ctx, destinationProjKey, destinationEnvKey, destinationID).Execute() return nil, res, err }) @@ -202,7 +202,7 @@ func resourceDestinationExists(d *schema.ResourceData, metaRaw interface{}) (boo destinationEnvKey := d.Get(ENV_KEY).(string) _, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.DataExportDestinationsApi.GetDestination(client.ctx, destinationProjKey, destinationEnvKey, destinationID) + return client.ld.DataExportDestinationsApi.GetDestination(client.ctx, destinationProjKey, destinationEnvKey, destinationID).Execute() }) if isStatusNotFound(res) { return false, nil diff --git a/launchdarkly/resource_launchdarkly_destination_test.go b/launchdarkly/resource_launchdarkly_destination_test.go index 97309f42..0a9a45c3 100644 --- a/launchdarkly/resource_launchdarkly_destination_test.go +++ b/launchdarkly/resource_launchdarkly_destination_test.go @@ -521,7 +521,7 @@ func testAccCheckDestinationExists(resourceName string) resource.TestCheckFunc { if err != nil { return err } - _, _, err = client.ld.DataExportDestinationsApi.GetDestination(client.ctx, projKey, envKey, destID) + _, _, err = client.ld.DataExportDestinationsApi.GetDestination(client.ctx, projKey, envKey, destID).Execute() if err != nil { return fmt.Errorf("error getting destination: %s", err) } diff --git a/launchdarkly/resource_launchdarkly_environment.go b/launchdarkly/resource_launchdarkly_environment.go index c85125dc..31665b33 100644 --- a/launchdarkly/resource_launchdarkly_environment.go +++ b/launchdarkly/resource_launchdarkly_environment.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func resourceEnvironment() *schema.Resource { @@ -39,7 +39,7 @@ func resourceEnvironmentCreate(d *schema.ResourceData, metaRaw interface{}) erro key := d.Get(KEY).(string) name := d.Get(NAME).(string) color := d.Get(COLOR).(string) - defaultTTL := float32(d.Get(DEFAULT_TTL).(int)) + defaultTTL := int32(d.Get(DEFAULT_TTL).(int)) secureMode := d.Get(SECURE_MODE).(bool) defaultTrackEvents := d.Get(DEFAULT_TRACK_EVENTS).(bool) tags := stringsFromSchemaSet(d.Get(TAGS).(*schema.Set)) @@ -50,16 +50,16 @@ func resourceEnvironmentCreate(d *schema.ResourceData, metaRaw interface{}) erro Name: name, Key: key, Color: color, - DefaultTtl: defaultTTL, - SecureMode: secureMode, - DefaultTrackEvents: defaultTrackEvents, - Tags: tags, - RequireComments: requireComments, - ConfirmChanges: confirmChanges, + DefaultTtl: &defaultTTL, + SecureMode: &secureMode, + DefaultTrackEvents: &defaultTrackEvents, + Tags: &tags, + RequireComments: &requireComments, + ConfirmChanges: &confirmChanges, } _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.EnvironmentsApi.PostEnvironment(client.ctx, projectKey, envPost) + return client.ld.EnvironmentsApi.PostEnvironment(client.ctx, projectKey).EnvironmentPost(envPost).Execute() }) if err != nil { return fmt.Errorf("failed to create environment: [%+v] for project key: %s: %s", envPost, projectKey, handleLdapiErr(err)) @@ -70,7 +70,7 @@ func resourceEnvironmentCreate(d *schema.ResourceData, metaRaw interface{}) erro err = resourceEnvironmentUpdate(d, metaRaw) if err != nil { // if there was a problem in the update state, we need to clean up completely by deleting the env - _, deleteErr := client.ld.EnvironmentsApi.DeleteEnvironment(client.ctx, projectKey, key) + _, deleteErr := client.ld.EnvironmentsApi.DeleteEnvironment(client.ctx, projectKey, key).Execute() if deleteErr != nil { return fmt.Errorf("failed to clean up environment %q from project %q: %s", key, projectKey, handleLdapiErr(err)) } @@ -118,7 +118,7 @@ func resourceEnvironmentUpdate(d *schema.ResourceData, metaRaw interface{}) erro patch = append(patch, approvalPatch...) _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.EnvironmentsApi.PatchEnvironment(client.ctx, projectKey, key, patch) + return client.ld.EnvironmentsApi.PatchEnvironment(client.ctx, projectKey, key).PatchOperation(patch).Execute() }) }) if err != nil { @@ -134,7 +134,7 @@ func resourceEnvironmentDelete(d *schema.ResourceData, metaRaw interface{}) erro key := d.Get(KEY).(string) _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - res, err := client.ld.EnvironmentsApi.DeleteEnvironment(client.ctx, projectKey, key) + res, err := client.ld.EnvironmentsApi.DeleteEnvironment(client.ctx, projectKey, key).Execute() return nil, res, err }) @@ -151,7 +151,7 @@ func resourceEnvironmentExists(d *schema.ResourceData, metaRaw interface{}) (boo func environmentExists(projectKey string, key string, meta *Client) (bool, error) { _, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return meta.ld.EnvironmentsApi.GetEnvironment(meta.ctx, projectKey, key) + return meta.ld.EnvironmentsApi.GetEnvironment(meta.ctx, projectKey, key).Execute() }) if isStatusNotFound(res) { return false, nil diff --git a/launchdarkly/resource_launchdarkly_environment_test.go b/launchdarkly/resource_launchdarkly_environment_test.go index 20afd84c..e269f4f6 100644 --- a/launchdarkly/resource_launchdarkly_environment_test.go +++ b/launchdarkly/resource_launchdarkly_environment_test.go @@ -338,7 +338,7 @@ func testAccCheckEnvironmentExists(resourceName string) resource.TestCheckFunc { return fmt.Errorf("project key not found: %s", resourceName) } client := testAccProvider.Meta().(*Client) - _, _, err := client.ld.EnvironmentsApi.GetEnvironment(client.ctx, projKey, envKey) + _, _, err := client.ld.EnvironmentsApi.GetEnvironment(client.ctx, projKey, envKey).Execute() if err != nil { return fmt.Errorf("received an error getting environment. %s", err) } diff --git a/launchdarkly/resource_launchdarkly_feature_flag.go b/launchdarkly/resource_launchdarkly_feature_flag.go index 44480383..2420b5fe 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag.go +++ b/launchdarkly/resource_launchdarkly_feature_flag.go @@ -1,13 +1,56 @@ package launchdarkly import ( + "context" "fmt" "net/http" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) +// We assign a custom diff in cases where the customer has not assigned CSA or IIS in config for a flag in order to respect project level defaults +func customizeDiff(ctx context.Context, diff *schema.ResourceDiff, v interface{}) error { + config := diff.GetRawConfig() + client := v.(*Client) + projectKey := diff.Get(PROJECT_KEY).(string) + + // Below values will exist due to the schema, we need to check if they are all null + snippetInConfig := config.GetAttr(INCLUDE_IN_SNIPPET) + csaInConfig := config.GetAttr(CLIENT_SIDE_AVAILABILITY) + + // If we have no keys in the CSA block in the config (length is 0) we know the customer hasn't set any CSA values + csaKeys := csaInConfig.AsValueSlice() + if len(csaKeys) == 0 { + // When we have no values for either clienSideAvailability or includeInSnippet + // Force an UPDATE call by setting a new value for INCLUDE_IN_SNIPPET in the diff according to project defaults + if snippetInConfig.IsNull() { + defaultCSA, includeInSnippetByDefault, err := getProjectDefaultCSAandIncludeInSnippet(client, projectKey) + // We will fall into this block during the first config read when a user creates a flag at the same time they create the parent project + // (and during our tests) + // We can ignore the error here, as it is correctly handled during update/create (and doesn't occur then as the project will have been created) + if err != nil { + } else { + // We set our values to the project defaults in order to guarantee an update call happening + // If we don't do this, we can run into an edge case described below + // IF previous value of INCLUDE_IN_SNIPPET was false + // AND the project default value for INCLUDE_IN_SNIPPET is true + // AND the customer removes the INCLUDE_IN_SNIPPET key from the config without replacing with defaultCSA + // The read would assume no changes are needed, HOWEVER we need to jump back to project level set defaults + // Hence the setting below + diff.SetNew(INCLUDE_IN_SNIPPET, includeInSnippetByDefault) + diff.SetNew(CLIENT_SIDE_AVAILABILITY, []map[string]interface{}{{ + USING_ENVIRONMENT_ID: defaultCSA.UsingEnvironmentId, + USING_MOBILE_KEY: defaultCSA.UsingMobileKey, + }}) + } + } + + } + + return nil +} + func resourceFeatureFlag() *schema.Resource { schemaMap := baseFeatureFlagSchema() schemaMap[NAME] = &schema.Schema{ @@ -26,7 +69,8 @@ func resourceFeatureFlag() *schema.Resource { Importer: &schema.ResourceImporter{ State: resourceFeatureFlagImport, }, - Schema: schemaMap, + Schema: schemaMap, + CustomizeDiff: customizeDiff, } } @@ -46,6 +90,14 @@ func resourceFeatureFlagCreate(d *schema.ResourceData, metaRaw interface{}) erro flagName := d.Get(NAME).(string) tags := stringsFromResourceData(d, TAGS) includeInSnippet := d.Get(INCLUDE_IN_SNIPPET).(bool) + // GetOkExists is 'deprecated', but needed as optional booleans set to false return a 'false' ok value from GetOk + // Also not really deprecated as they are keeping it around pending a replacement https://github.com/hashicorp/terraform-plugin-sdk/pull/350#issuecomment-597888969 + _, includeInSnippetOk := d.GetOkExists(INCLUDE_IN_SNIPPET) + _, clientSideAvailabilityOk := d.GetOk(CLIENT_SIDE_AVAILABILITY) + clientSideAvailability := &ldapi.ClientSideAvailabilityPost{ + UsingEnvironmentId: d.Get("client_side_availability.0.using_environment_id").(bool), + UsingMobileKey: d.Get("client_side_availability.0.using_mobile_key").(bool), + } temporary := d.Get(TEMPORARY).(bool) variations, err := variationsFromResourceData(d) @@ -59,18 +111,37 @@ func resourceFeatureFlagCreate(d *schema.ResourceData, metaRaw interface{}) erro } flag := ldapi.FeatureFlagBody{ - Name: flagName, - Key: key, - Description: description, - Variations: variations, - Temporary: temporary, - Tags: tags, - IncludeInSnippet: includeInSnippet, - Defaults: defaults, + Name: flagName, + Key: key, + Description: &description, + Variations: &variations, + Temporary: &temporary, + Tags: &tags, + Defaults: defaults, } + if clientSideAvailabilityOk { + flag.ClientSideAvailability = clientSideAvailability + } else if includeInSnippetOk { + // If includeInSnippet is set, still use clientSideAvailability behind the scenes in order to switch UsingMobileKey to false if needed + flag.ClientSideAvailability = &ldapi.ClientSideAvailabilityPost{ + UsingEnvironmentId: includeInSnippet, + UsingMobileKey: false, + } + } else { + // If neither value is set, we should get the default from the project level and apply that + // IncludeInSnippetdefault is the same as defaultCSA.UsingEnvironmentId, so we can _ it + defaultCSA, _, err := getProjectDefaultCSAandIncludeInSnippet(client, projectKey) + if err != nil { + return fmt.Errorf("failed to get project level client side availability defaults. %v", err) + } + flag.ClientSideAvailability = &ldapi.ClientSideAvailabilityPost{ + UsingEnvironmentId: *defaultCSA.UsingEnvironmentId, + UsingMobileKey: *defaultCSA.UsingMobileKey, + } + } _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.FeatureFlagsApi.PostFeatureFlag(client.ctx, projectKey, flag, nil) + return client.ld.FeatureFlagsApi.PostFeatureFlag(client.ctx, projectKey).FeatureFlagBody(flag).Execute() }) if err != nil { @@ -82,7 +153,7 @@ func resourceFeatureFlagCreate(d *schema.ResourceData, metaRaw interface{}) erro err = resourceFeatureFlagUpdate(d, metaRaw) if err != nil { // if there was a problem in the update state, we need to clean up completely by deleting the flag - _, deleteErr := client.ld.FeatureFlagsApi.DeleteFeatureFlag(client.ctx, projectKey, key) + _, deleteErr := client.ld.FeatureFlagsApi.DeleteFeatureFlag(client.ctx, projectKey, key).Execute() if deleteErr != nil { return fmt.Errorf("failed to delete flag %q from project %q: %s", key, projectKey, handleLdapiErr(err)) } @@ -106,22 +177,54 @@ func resourceFeatureFlagUpdate(d *schema.ResourceData, metaRaw interface{}) erro name := d.Get(NAME).(string) tags := stringsFromResourceData(d, TAGS) includeInSnippet := d.Get(INCLUDE_IN_SNIPPET).(bool) + + snippetHasChange := d.HasChange(INCLUDE_IN_SNIPPET) + clientSideHasChange := d.HasChange(CLIENT_SIDE_AVAILABILITY) + // GetOkExists is 'deprecated', but needed as optional booleans set to false return a 'false' ok value from GetOk + // Also not really deprecated as they are keeping it around pending a replacement https://github.com/hashicorp/terraform-plugin-sdk/pull/350#issuecomment-597888969 + _, includeInSnippetOk := d.GetOkExists(INCLUDE_IN_SNIPPET) + _, clientSideAvailabilityOk := d.GetOk(CLIENT_SIDE_AVAILABILITY) temporary := d.Get(TEMPORARY).(bool) customProperties := customPropertiesFromResourceData(d) archived := d.Get(ARCHIVED).(bool) + clientSideAvailability := &ldapi.ClientSideAvailabilityPost{ + UsingEnvironmentId: d.Get("client_side_availability.0.using_environment_id").(bool), + UsingMobileKey: d.Get("client_side_availability.0.using_mobile_key").(bool), + } - patch := ldapi.PatchComment{ - Comment: "Terraform", + comment := "Terraform" + patch := ldapi.PatchWithComment{ + Comment: &comment, Patch: []ldapi.PatchOperation{ patchReplace("/name", name), patchReplace("/description", description), patchReplace("/tags", tags), - patchReplace("/includeInSnippet", includeInSnippet), patchReplace("/temporary", temporary), patchReplace("/customProperties", customProperties), patchReplace("/archived", archived), }} + if clientSideAvailabilityOk && clientSideHasChange { + patch.Patch = append(patch.Patch, patchReplace("/clientSideAvailability", clientSideAvailability)) + } else if includeInSnippetOk && snippetHasChange { + // If includeInSnippet is set, still use clientSideAvailability behind the scenes in order to switch UsingMobileKey to false if needed + patch.Patch = append(patch.Patch, patchReplace("/clientSideAvailability", &ldapi.ClientSideAvailabilityPost{ + UsingEnvironmentId: includeInSnippet, + UsingMobileKey: false, + })) + } else { + // If the user doesn't set either CSA or IIS in config, we pull the defaults from their Project level settings and apply those + // IncludeInSnippetdefault is the same as defaultCSA.UsingEnvironmentId, so we can _ it + defaultCSA, _, err := getProjectDefaultCSAandIncludeInSnippet(client, projectKey) + if err != nil { + return fmt.Errorf("failed to get project level client side availability defaults. %v", err) + } + patch.Patch = append(patch.Patch, patchReplace("/clientSideAvailability", &ldapi.ClientSideAvailabilityPost{ + UsingEnvironmentId: *defaultCSA.UsingEnvironmentId, + UsingMobileKey: *defaultCSA.UsingMobileKey, + })) + } + variationPatches, err := variationPatchesFromResourceData(d) if err != nil { return fmt.Errorf("failed to build variation patches. %v", err) @@ -145,7 +248,7 @@ func resourceFeatureFlagUpdate(d *schema.ResourceData, metaRaw interface{}) erro _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.FeatureFlagsApi.PatchFeatureFlag(client.ctx, projectKey, key, patch) + return client.ld.FeatureFlagsApi.PatchFeatureFlag(client.ctx, projectKey, key).PatchWithComment(*&patch).Execute() }) }) @@ -162,7 +265,7 @@ func resourceFeatureFlagDelete(d *schema.ResourceData, metaRaw interface{}) erro key := d.Get(KEY).(string) _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - res, err := client.ld.FeatureFlagsApi.DeleteFeatureFlag(client.ctx, projectKey, key) + res, err := client.ld.FeatureFlagsApi.DeleteFeatureFlag(client.ctx, projectKey, key).Execute() return nil, res, err }) if err != nil { @@ -177,7 +280,7 @@ func resourceFeatureFlagExists(d *schema.ResourceData, metaRaw interface{}) (boo projectKey := d.Get(PROJECT_KEY).(string) key := d.Get(KEY).(string) - _, res, err := client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projectKey, key, nil) + _, res, err := client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projectKey, key).Execute() if isStatusNotFound(res) { return false, nil } diff --git a/launchdarkly/resource_launchdarkly_feature_flag_environment.go b/launchdarkly/resource_launchdarkly_feature_flag_environment.go index a42d37f1..c5a34fcf 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag_environment.go +++ b/launchdarkly/resource_launchdarkly_feature_flag_environment.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func resourceFeatureFlagEnvironment() *schema.Resource { @@ -105,15 +105,16 @@ func resourceFeatureFlagEnvironmentCreate(d *schema.ResourceData, metaRaw interf patches = append(patches, patchReplace(patchFlagEnvPath(d, "fallthrough"), fall)) if len(patches) > 0 { - patch := ldapi.PatchComment{ - Comment: "Terraform", + comment := "Terraform" + patch := ldapi.PatchWithComment{ + Comment: &comment, Patch: patches, } log.Printf("[DEBUG] %+v\n", patch) _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.FeatureFlagsApi.PatchFeatureFlag(client.ctx, projectKey, flagKey, patch) + return client.ld.FeatureFlagsApi.PatchFeatureFlag(client.ctx, projectKey, flagKey).PatchWithComment(patch).Execute() }) }) if err != nil { @@ -167,8 +168,9 @@ func resourceFeatureFlagEnvironmentUpdate(d *schema.ResourceData, metaRaw interf } offVariation := d.Get(OFF_VARIATION) - patch := ldapi.PatchComment{ - Comment: "Terraform", + comment := "Terraform" + patch := ldapi.PatchWithComment{ + Comment: &comment, Patch: []ldapi.PatchOperation{ patchReplace(patchFlagEnvPath(d, "on"), on), patchReplace(patchFlagEnvPath(d, "rules"), rules), @@ -182,7 +184,7 @@ func resourceFeatureFlagEnvironmentUpdate(d *schema.ResourceData, metaRaw interf log.Printf("[DEBUG] %+v\n", patch) _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.FeatureFlagsApi.PatchFeatureFlag(client.ctx, projectKey, flagKey, patch) + return client.ld.FeatureFlagsApi.PatchFeatureFlag(client.ctx, projectKey, flagKey).PatchWithComment(patch).Execute() }) }) if err != nil { @@ -214,7 +216,7 @@ func resourceFeatureFlagEnvironmentDelete(d *schema.ResourceData, metaRaw interf return fmt.Errorf("failed to find environment with key %q", envKey) } - flag, _, err := client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projectKey, flagKey, nil) + flag, _, err := client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projectKey, flagKey).Execute() if err != nil { return fmt.Errorf("failed to update flag %q in project %q, environment %q: %s", flagKey, projectKey, envKey, handleLdapiErr(err)) } @@ -222,8 +224,9 @@ func resourceFeatureFlagEnvironmentDelete(d *schema.ResourceData, metaRaw interf // Set off variation to match default with how a rule is created offVariation := len(flag.Variations) - 1 - patch := ldapi.PatchComment{ - Comment: "Terraform", + comment := "Terraform" + patch := ldapi.PatchWithComment{ + Comment: &comment, Patch: []ldapi.PatchOperation{ patchReplace(patchFlagEnvPath(d, "on"), false), patchReplace(patchFlagEnvPath(d, "rules"), []ldapi.Rule{}), @@ -237,7 +240,7 @@ func resourceFeatureFlagEnvironmentDelete(d *schema.ResourceData, metaRaw interf _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.FeatureFlagsApi.PatchFeatureFlag(client.ctx, projectKey, flagKey, patch) + return client.ld.FeatureFlagsApi.PatchFeatureFlag(client.ctx, projectKey, flagKey).PatchWithComment(patch).Execute() }) }) if err != nil { diff --git a/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go b/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go index 28be97b8..cdeaf12f 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go +++ b/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go @@ -5,12 +5,9 @@ import ( "regexp" "testing" - "github.com/antihax/optional" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - - ldapi "github.com/launchdarkly/api-client-go" ) const ( @@ -677,7 +674,7 @@ func testAccCheckFeatureFlagEnvironmentExists(resourceName string) resource.Test return fmt.Errorf("environent key not found: %s", resourceName) } client := testAccProvider.Meta().(*Client) - _, _, err = client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projKey, flagKey, &ldapi.FeatureFlagsApiGetFeatureFlagOpts{Env: optional.NewInterface(envKey)}) + _, _, err = client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projKey, flagKey).Env(envKey).Execute() if err != nil { return fmt.Errorf("received an error getting feature flag environment. %s", err) } diff --git a/launchdarkly/resource_launchdarkly_feature_flag_test.go b/launchdarkly/resource_launchdarkly_feature_flag_test.go index 44768c8a..92ba1c86 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag_test.go +++ b/launchdarkly/resource_launchdarkly_feature_flag_test.go @@ -402,6 +402,56 @@ resource "launchdarkly_feature_flag" "empty_string_variation" { value = "non-empty" } } +` + testAccFeatureFlagIncludeInSnippet = ` +resource "launchdarkly_feature_flag" "sdk_settings" { + project_key = launchdarkly_project.test.key + key = "basic-flag-sdk-settings" + name = "Basic feature flag" + variation_type = "boolean" + include_in_snippet = true +} +` + testAccFeatureFlagIncludeInSnippetUpdate = ` +resource "launchdarkly_feature_flag" "sdk_settings" { + project_key = launchdarkly_project.test.key + key = "basic-flag-sdk-settings" + name = "Basic feature flag" + variation_type = "boolean" + include_in_snippet = false +} +` + testAccFeatureFlagIncludeInSnippetEmpty = ` +resource "launchdarkly_feature_flag" "sdk_settings" { + project_key = launchdarkly_project.test.key + key = "basic-flag-sdk-settings" + name = "Basic feature flag" + variation_type = "boolean" +} +` + testAccFeatureFlagClientSideAvailability = ` +resource "launchdarkly_feature_flag" "sdk_settings" { + project_key = launchdarkly_project.test.key + key = "basic-flag-sdk-settings" + name = "Basic feature flag" + variation_type = "boolean" + client_side_availability { + using_environment_id = true + using_mobile_key = true + } +} +` + testAccFeatureFlagClientSideAvailabilityUpdate = ` +resource "launchdarkly_feature_flag" "sdk_settings" { + project_key = launchdarkly_project.test.key + key = "basic-flag-sdk-settings" + name = "Basic feature flag" + variation_type = "boolean" + client_side_availability { + using_environment_id = false + using_mobile_key = false + } +} ` ) @@ -423,6 +473,25 @@ func withRandomProject(randomProject, resource string) string { %s`, randomProject, resource) } +func withRandomProjectIncludeInSnippetTrue(randomProject, resource string) string { + return fmt.Sprintf(` + resource "launchdarkly_project" "test" { + lifecycle { + ignore_changes = [environments] + } + include_in_snippet = true + name = "testProject" + key = "%s" + environments { + name = "testEnvironment" + key = "test" + color = "000000" + } + } + + %s`, randomProject, resource) +} + func TestAccFeatureFlag_Basic(t *testing.T) { projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resourceName := "launchdarkly_feature_flag.basic" @@ -448,9 +517,10 @@ func TestAccFeatureFlag_Basic(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + // TODO: While we have to account for usingMobileKey being set to true by default, we cant use importStateVerify + // ImportStateVerify: true, }, }, }) @@ -521,9 +591,10 @@ func TestAccFeatureFlag_Number(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + // TODO: While we have to account for usingMobileKey being set to true by default, we cant use importStateVerify + // ImportStateVerify: true, }, }, }) @@ -874,9 +945,10 @@ func TestAccFeatureFlag_UpdateDefaults(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + // TODO: While we have to account for usingMobileKey being set to true by default, we cant use importStateVerify + // ImportStateVerify: true, }, }, }) @@ -919,9 +991,10 @@ func TestAccFeatureFlag_UpdateMultivariateDefaults(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + // TODO: While we have to account for usingMobileKey being set to true by default, we cant use importStateVerify + // ImportStateVerify: true, }, }, }) @@ -958,6 +1031,216 @@ func TestAccFeatureFlag_EmptyStringVariation(t *testing.T) { }) } +func TestAccFeatureFlag_ClientSideAvailabilityUpdate(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "launchdarkly_feature_flag.sdk_settings" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccFeatureFlagClientSideAvailability), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckFeatureFlagExists(resourceName), + resource.TestCheckResourceAttr(resourceName, NAME, "Basic feature flag"), + resource.TestCheckResourceAttr(resourceName, KEY, "basic-flag-sdk-settings"), + resource.TestCheckResourceAttr(resourceName, PROJECT_KEY, projectKey), + resource.TestCheckResourceAttr(resourceName, VARIATION_TYPE, "boolean"), + resource.TestCheckResourceAttr(resourceName, "variations.#", "2"), + resource.TestCheckResourceAttr(resourceName, "variations.0.value", "true"), + resource.TestCheckResourceAttr(resourceName, "variations.1.value", "false"), + resource.TestCheckNoResourceAttr(resourceName, "maintainer_id"), + resource.TestCheckResourceAttr(resourceName, "client_side_availability.0.using_environment_id", "true"), + resource.TestCheckResourceAttr(resourceName, "client_side_availability.0.using_mobile_key", "true"), + ), + }, + { + Config: withRandomProject(projectKey, testAccFeatureFlagClientSideAvailabilityUpdate), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckFeatureFlagExists(resourceName), + resource.TestCheckResourceAttr(resourceName, NAME, "Basic feature flag"), + resource.TestCheckResourceAttr(resourceName, KEY, "basic-flag-sdk-settings"), + resource.TestCheckResourceAttr(resourceName, PROJECT_KEY, projectKey), + resource.TestCheckResourceAttr(resourceName, VARIATION_TYPE, "boolean"), + resource.TestCheckResourceAttr(resourceName, "variations.#", "2"), + resource.TestCheckResourceAttr(resourceName, "variations.0.value", "true"), + resource.TestCheckResourceAttr(resourceName, "variations.1.value", "false"), + resource.TestCheckNoResourceAttr(resourceName, "maintainer_id"), + resource.TestCheckResourceAttr(resourceName, "client_side_availability.0.using_environment_id", "false"), + resource.TestCheckResourceAttr(resourceName, "client_side_availability.0.using_mobile_key", "false"), + ), + }, + }, + }) +} + +func TestAccFeatureFlag_IncludeInSnippetToClientSide(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "launchdarkly_feature_flag.sdk_settings" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccFeatureFlagIncludeInSnippet), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckFeatureFlagExists(resourceName), + resource.TestCheckResourceAttr(resourceName, NAME, "Basic feature flag"), + resource.TestCheckResourceAttr(resourceName, KEY, "basic-flag-sdk-settings"), + resource.TestCheckResourceAttr(resourceName, PROJECT_KEY, projectKey), + resource.TestCheckResourceAttr(resourceName, VARIATION_TYPE, "boolean"), + resource.TestCheckResourceAttr(resourceName, "variations.#", "2"), + resource.TestCheckResourceAttr(resourceName, "variations.0.value", "true"), + resource.TestCheckResourceAttr(resourceName, "variations.1.value", "false"), + resource.TestCheckNoResourceAttr(resourceName, "maintainer_id"), + resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "true"), + ), + }, + { + Config: withRandomProject(projectKey, testAccFeatureFlagClientSideAvailability), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckFeatureFlagExists(resourceName), + resource.TestCheckResourceAttr(resourceName, NAME, "Basic feature flag"), + resource.TestCheckResourceAttr(resourceName, KEY, "basic-flag-sdk-settings"), + resource.TestCheckResourceAttr(resourceName, PROJECT_KEY, projectKey), + resource.TestCheckResourceAttr(resourceName, VARIATION_TYPE, "boolean"), + resource.TestCheckResourceAttr(resourceName, "variations.#", "2"), + resource.TestCheckResourceAttr(resourceName, "variations.0.value", "true"), + resource.TestCheckResourceAttr(resourceName, "variations.1.value", "false"), + resource.TestCheckNoResourceAttr(resourceName, "maintainer_id"), + resource.TestCheckResourceAttr(resourceName, "client_side_availability.0.using_environment_id", "true"), + resource.TestCheckResourceAttr(resourceName, "client_side_availability.0.using_mobile_key", "true"), + resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "true"), + ), + }, + { + Config: withRandomProject(projectKey, testAccFeatureFlagClientSideAvailabilityUpdate), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckFeatureFlagExists(resourceName), + resource.TestCheckResourceAttr(resourceName, NAME, "Basic feature flag"), + resource.TestCheckResourceAttr(resourceName, KEY, "basic-flag-sdk-settings"), + resource.TestCheckResourceAttr(resourceName, PROJECT_KEY, projectKey), + resource.TestCheckResourceAttr(resourceName, VARIATION_TYPE, "boolean"), + resource.TestCheckResourceAttr(resourceName, "variations.#", "2"), + resource.TestCheckResourceAttr(resourceName, "variations.0.value", "true"), + resource.TestCheckResourceAttr(resourceName, "variations.1.value", "false"), + resource.TestCheckNoResourceAttr(resourceName, "maintainer_id"), + resource.TestCheckResourceAttr(resourceName, "client_side_availability.0.using_environment_id", "false"), + resource.TestCheckResourceAttr(resourceName, "client_side_availability.0.using_mobile_key", "false"), + resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "false"), + ), + }, + }, + }) +} + +func TestAccFeatureFlag_ClientSideToIncludeInSnippet(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "launchdarkly_feature_flag.sdk_settings" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccFeatureFlagClientSideAvailability), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckFeatureFlagExists(resourceName), + resource.TestCheckResourceAttr(resourceName, NAME, "Basic feature flag"), + resource.TestCheckResourceAttr(resourceName, KEY, "basic-flag-sdk-settings"), + resource.TestCheckResourceAttr(resourceName, PROJECT_KEY, projectKey), + resource.TestCheckResourceAttr(resourceName, VARIATION_TYPE, "boolean"), + resource.TestCheckResourceAttr(resourceName, "variations.#", "2"), + resource.TestCheckResourceAttr(resourceName, "variations.0.value", "true"), + resource.TestCheckResourceAttr(resourceName, "variations.1.value", "false"), + resource.TestCheckNoResourceAttr(resourceName, "maintainer_id"), + resource.TestCheckResourceAttr(resourceName, "client_side_availability.0.using_environment_id", "true"), + resource.TestCheckResourceAttr(resourceName, "client_side_availability.0.using_mobile_key", "true"), + resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "true"), + ), + }, + { + Config: withRandomProject(projectKey, testAccFeatureFlagIncludeInSnippetUpdate), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckFeatureFlagExists(resourceName), + resource.TestCheckResourceAttr(resourceName, NAME, "Basic feature flag"), + resource.TestCheckResourceAttr(resourceName, KEY, "basic-flag-sdk-settings"), + resource.TestCheckResourceAttr(resourceName, PROJECT_KEY, projectKey), + resource.TestCheckResourceAttr(resourceName, VARIATION_TYPE, "boolean"), + resource.TestCheckResourceAttr(resourceName, "variations.#", "2"), + resource.TestCheckResourceAttr(resourceName, "variations.0.value", "true"), + resource.TestCheckResourceAttr(resourceName, "variations.1.value", "false"), + resource.TestCheckNoResourceAttr(resourceName, "maintainer_id"), + resource.TestCheckResourceAttr(resourceName, "client_side_availability.0.using_environment_id", "false"), + resource.TestCheckResourceAttr(resourceName, "client_side_availability.0.using_mobile_key", "false"), + resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "false"), + ), + }, + }, + }) +} + +func TestAccFeatureFlag_IncludeInSnippetRevertToDefault(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "launchdarkly_feature_flag.sdk_settings" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + // Create without value set and check for default value + { + Config: withRandomProjectIncludeInSnippetTrue(projectKey, testAccFeatureFlagIncludeInSnippetEmpty), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckFeatureFlagExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "Basic feature flag"), + resource.TestCheckResourceAttr(resourceName, "key", "basic-flag-sdk-settings"), + resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), + resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "true"), + ), + }, + // Replace default value with specific value + { + Config: withRandomProjectIncludeInSnippetTrue(projectKey, testAccFeatureFlagIncludeInSnippetUpdate), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckFeatureFlagExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "Basic feature flag"), + resource.TestCheckResourceAttr(resourceName, "key", "basic-flag-sdk-settings"), + resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), + resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "false"), + ), + }, + // Clear specific value, check for default + { + Config: withRandomProjectIncludeInSnippetTrue(projectKey, testAccFeatureFlagIncludeInSnippetEmpty), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckFeatureFlagExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "Basic feature flag"), + resource.TestCheckResourceAttr(resourceName, "key", "basic-flag-sdk-settings"), + resource.TestCheckResourceAttr(resourceName, "project_key", projectKey), + resource.TestCheckResourceAttr(resourceName, "include_in_snippet", "true"), + ), + }, + }, + }) +} + func testAccCheckFeatureFlagExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] @@ -973,7 +1256,7 @@ func testAccCheckFeatureFlagExists(resourceName string) resource.TestCheckFunc { return fmt.Errorf("project key not found: %s", resourceName) } client := testAccProvider.Meta().(*Client) - _, _, err := client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projKey, flagKey, nil) + _, _, err := client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projKey, flagKey).Execute() if err != nil { return fmt.Errorf("received an error getting feature flag. %s", err) } diff --git a/launchdarkly/resource_launchdarkly_project.go b/launchdarkly/resource_launchdarkly_project.go index f3442a87..c2a8bcef 100644 --- a/launchdarkly/resource_launchdarkly_project.go +++ b/launchdarkly/resource_launchdarkly_project.go @@ -7,7 +7,7 @@ import ( "net/http" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func resourceProject() *schema.Resource { @@ -62,17 +62,17 @@ func resourceProjectCreate(d *schema.ResourceData, metaRaw interface{}) error { envs := environmentPostsFromResourceData(d) d.SetId(projectKey) - projectBody := ldapi.ProjectBody{ + projectBody := ldapi.ProjectPost{ Name: name, Key: projectKey, } if len(envs) > 0 { - projectBody.Environments = envs + projectBody.Environments = &envs } _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.ProjectsApi.PostProject(client.ctx, projectBody) + return client.ld.ProjectsApi.PostProject(client.ctx).ProjectPost(projectBody).Execute() }) if err != nil { return fmt.Errorf("failed to create project with name %s and projectKey %s: %v", name, projectKey, handleLdapiErr(err)) @@ -105,7 +105,7 @@ func resourceProjectUpdate(d *schema.ResourceData, metaRaw interface{}) error { _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.ProjectsApi.PatchProject(client.ctx, projectKey, patch) + return client.ld.ProjectsApi.PatchProject(client.ctx, projectKey).PatchOperation(patch).Execute() }) }) if err != nil { @@ -115,7 +115,7 @@ func resourceProjectUpdate(d *schema.ResourceData, metaRaw interface{}) error { oldSchemaEnvList, newSchemaEnvList := d.GetChange(ENVIRONMENTS) // Get the project so we can see if we need to create any environments or just update existing environments rawProject, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.ProjectsApi.GetProject(client.ctx, projectKey) + return client.ld.ProjectsApi.GetProject(client.ctx, projectKey).Execute() }) if err != nil { return fmt.Errorf("failed to load project %q before updating environments: %s", projectKey, handleLdapiErr(err)) @@ -142,7 +142,7 @@ func resourceProjectUpdate(d *schema.ResourceData, metaRaw interface{}) error { if !exists { envPost := environmentPostFromResourceData(env) _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.EnvironmentsApi.PostEnvironment(client.ctx, projectKey, envPost) + return client.ld.EnvironmentsApi.PostEnvironment(client.ctx, projectKey).EnvironmentPost(envPost).Execute() }) if err != nil { return fmt.Errorf("failed to create environment %q in project %q: %s", envKey, projectKey, handleLdapiErr(err)) @@ -154,13 +154,13 @@ func resourceProjectUpdate(d *schema.ResourceData, metaRaw interface{}) error { oldEnvConfig = rawOldConfig } // by default patching an env that was not recently tracked in the state will import it into the tf state - patches, err := getEnvironmentUpdatePatches(oldEnvConfig, envConfig) + patch, err := getEnvironmentUpdatePatches(oldEnvConfig, envConfig) if err != nil { return err } _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.EnvironmentsApi.PatchEnvironment(client.ctx, projectKey, envKey, patches) + return client.ld.EnvironmentsApi.PatchEnvironment(client.ctx, projectKey, envKey).PatchOperation(patch).Execute() }) }) if err != nil { @@ -175,7 +175,7 @@ func resourceProjectUpdate(d *schema.ResourceData, metaRaw interface{}) error { envKey := envConfig[KEY].(string) if _, persists := envConfigsForCompare[envKey]; !persists { _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { - res, err := client.ld.EnvironmentsApi.DeleteEnvironment(client.ctx, projectKey, envKey) + res, err := client.ld.EnvironmentsApi.DeleteEnvironment(client.ctx, projectKey, envKey).Execute() return nil, res, err }) if err != nil { @@ -192,7 +192,7 @@ func resourceProjectDelete(d *schema.ResourceData, metaRaw interface{}) error { projectKey := d.Get(KEY).(string) _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - res, err := client.ld.ProjectsApi.DeleteProject(client.ctx, projectKey) + res, err := client.ld.ProjectsApi.DeleteProject(client.ctx, projectKey).Execute() return nil, res, err }) @@ -209,7 +209,7 @@ func resourceProjectExists(d *schema.ResourceData, metaRaw interface{}) (bool, e func projectExists(projectKey string, meta *Client) (bool, error) { _, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return meta.ld.ProjectsApi.GetProject(meta.ctx, projectKey) + return meta.ld.ProjectsApi.GetProject(meta.ctx, projectKey).Execute() }) if isStatusNotFound(res) { log.Println("got 404 when getting project. returning false.") diff --git a/launchdarkly/resource_launchdarkly_project_test.go b/launchdarkly/resource_launchdarkly_project_test.go index 5daceaff..b98ff972 100644 --- a/launchdarkly/resource_launchdarkly_project_test.go +++ b/launchdarkly/resource_launchdarkly_project_test.go @@ -364,7 +364,7 @@ func testAccCheckProjectExists(resourceName string) resource.TestCheckFunc { } client := testAccProvider.Meta().(*Client) - _, _, err := client.ld.ProjectsApi.GetProject(client.ctx, rs.Primary.ID) + _, _, err := client.ld.ProjectsApi.GetProject(client.ctx, rs.Primary.ID).Execute() if err != nil { return fmt.Errorf("received an error getting project. %s", err) } diff --git a/launchdarkly/resource_launchdarkly_segment.go b/launchdarkly/resource_launchdarkly_segment.go index 274ccf56..d8a3bd2d 100644 --- a/launchdarkly/resource_launchdarkly_segment.go +++ b/launchdarkly/resource_launchdarkly_segment.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func resourceSegment() *schema.Resource { @@ -62,15 +62,15 @@ func resourceSegmentCreate(d *schema.ResourceData, metaRaw interface{}) error { segmentName := d.Get(NAME).(string) tags := stringsFromResourceData(d, TAGS) - segment := ldapi.UserSegmentBody{ + segment := ldapi.SegmentBody{ Name: segmentName, Key: key, - Description: description, - Tags: tags, + Description: &description, + Tags: &tags, } _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.UserSegmentsApi.PostUserSegment(client.ctx, projectKey, envKey, segment) + return client.ld.SegmentsApi.PostSegment(client.ctx, projectKey, envKey).SegmentBody(segment).Execute() }) if err != nil { @@ -107,19 +107,22 @@ func resourceSegmentUpdate(d *schema.ResourceData, metaRaw interface{}) error { if err != nil { return err } - patch := []ldapi.PatchOperation{ - patchReplace("/name", name), - patchReplace("/description", description), - patchReplace("/tags", tags), - patchReplace("/temporary", TEMPORARY), - patchReplace("/included", included), - patchReplace("/excluded", excluded), - patchReplace("/rules", rules), - } + comment := "Terraform" + patch := ldapi.PatchWithComment{ + Comment: &comment, + Patch: []ldapi.PatchOperation{ + patchReplace("/name", name), + patchReplace("/description", description), + patchReplace("/tags", tags), + patchReplace("/temporary", TEMPORARY), + patchReplace("/included", included), + patchReplace("/excluded", excluded), + patchReplace("/rules", rules), + }} _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.UserSegmentsApi.PatchUserSegment(client.ctx, projectKey, envKey, key, patch) + return client.ld.SegmentsApi.PatchSegment(client.ctx, projectKey, envKey, key).PatchWithComment(patch).Execute() }) }) if err != nil { @@ -136,7 +139,7 @@ func resourceSegmentDelete(d *schema.ResourceData, metaRaw interface{}) error { key := d.Get(KEY).(string) _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - res, err := client.ld.UserSegmentsApi.DeleteUserSegment(client.ctx, projectKey, envKey, key) + res, err := client.ld.SegmentsApi.DeleteSegment(client.ctx, projectKey, envKey, key).Execute() return nil, res, err }) @@ -154,7 +157,7 @@ func resourceSegmentExists(d *schema.ResourceData, metaRaw interface{}) (bool, e key := d.Get(KEY).(string) _, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.UserSegmentsApi.GetUserSegment(client.ctx, projectKey, envKey, key) + return client.ld.SegmentsApi.GetSegment(client.ctx, projectKey, envKey, key).Execute() }) if isStatusNotFound(res) { return false, nil diff --git a/launchdarkly/resource_launchdarkly_segment_test.go b/launchdarkly/resource_launchdarkly_segment_test.go index 2d3cde1f..4db7d684 100644 --- a/launchdarkly/resource_launchdarkly_segment_test.go +++ b/launchdarkly/resource_launchdarkly_segment_test.go @@ -291,7 +291,7 @@ func testAccCheckSegmentExists(resourceName string) resource.TestCheckFunc { return fmt.Errorf("project key not found: %s", resourceName) } client := testAccProvider.Meta().(*Client) - _, _, err := client.ld.UserSegmentsApi.GetUserSegment(client.ctx, projKey, envKey, segmentKey) + _, _, err := client.ld.SegmentsApi.GetSegment(client.ctx, projKey, envKey, segmentKey).Execute() if err != nil { return fmt.Errorf("received an error getting environment. %s", err) } diff --git a/launchdarkly/resource_launchdarkly_team_member.go b/launchdarkly/resource_launchdarkly_team_member.go index 2d9bb00f..9fd93b15 100644 --- a/launchdarkly/resource_launchdarkly_team_member.go +++ b/launchdarkly/resource_launchdarkly_team_member.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func resourceTeamMember() *schema.Resource { @@ -65,7 +65,7 @@ func resourceTeamMemberCreate(d *schema.ResourceData, metaRaw interface{}) error memberEmail := d.Get(EMAIL).(string) firstName := d.Get(FIRST_NAME).(string) lastName := d.Get(LAST_NAME).(string) - memberRole := ldapi.Role(d.Get(ROLE).(string)) + memberRole := d.Get(ROLE).(string) customRolesRaw := d.Get(CUSTOM_ROLES).(*schema.Set).List() customRoles := make([]string, len(customRolesRaw)) @@ -73,16 +73,16 @@ func resourceTeamMemberCreate(d *schema.ResourceData, metaRaw interface{}) error customRoles[i] = cr.(string) } - membersBody := ldapi.MembersBody{ + membersBody := ldapi.NewMemberForm{ Email: memberEmail, - FirstName: firstName, - LastName: lastName, + FirstName: &firstName, + LastName: &lastName, Role: &memberRole, - CustomRoles: customRoles, + CustomRoles: &customRoles, } membersRaw, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.TeamMembersApi.PostMembers(client.ctx, []ldapi.MembersBody{membersBody}) + return client.ld.AccountMembersApi.PostMembers(client.ctx).NewMemberForm([]ldapi.NewMemberForm{membersBody}).Execute() }) members := membersRaw.(ldapi.Members) if err != nil { @@ -98,7 +98,7 @@ func resourceTeamMemberRead(d *schema.ResourceData, metaRaw interface{}) error { memberID := d.Id() memberRaw, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.TeamMembersApi.GetMember(client.ctx, memberID) + return client.ld.AccountMembersApi.GetMember(client.ctx, memberID).Execute() }) member := memberRaw.(ldapi.Member) if isStatusNotFound(res) { @@ -150,7 +150,7 @@ func resourceTeamMemberUpdate(d *schema.ResourceData, metaRaw interface{}) error _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.TeamMembersApi.PatchMember(client.ctx, memberID, patch) + return client.ld.AccountMembersApi.PatchMember(client.ctx, memberID).PatchOperation(patch).Execute() }) }) if err != nil { @@ -164,7 +164,7 @@ func resourceTeamMemberDelete(d *schema.ResourceData, metaRaw interface{}) error client := metaRaw.(*Client) _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - res, err := client.ld.TeamMembersApi.DeleteMember(client.ctx, d.Id()) + res, err := client.ld.AccountMembersApi.DeleteMember(client.ctx, d.Id()).Execute() return nil, res, err }) if err != nil { @@ -179,7 +179,7 @@ func resourceTeamMemberExists(d *schema.ResourceData, metaRaw interface{}) (bool } func teamMemberExists(memberID string, meta *Client) (bool, error) { - _, res, err := meta.ld.TeamMembersApi.GetMember(meta.ctx, memberID) + _, res, err := meta.ld.AccountMembersApi.GetMember(meta.ctx, memberID).Execute() if isStatusNotFound(res) { return false, nil } diff --git a/launchdarkly/resource_launchdarkly_team_member_test.go b/launchdarkly/resource_launchdarkly_team_member_test.go index 87acbdf5..8bfec216 100644 --- a/launchdarkly/resource_launchdarkly_team_member_test.go +++ b/launchdarkly/resource_launchdarkly_team_member_test.go @@ -237,7 +237,7 @@ func testAccCheckMemberExists(resourceName string) resource.TestCheckFunc { return fmt.Errorf("team member ID is not set") } client := testAccProvider.Meta().(*Client) - _, _, err := client.ld.TeamMembersApi.GetMember(client.ctx, rs.Primary.ID) + _, _, err := client.ld.AccountMembersApi.GetMember(client.ctx, rs.Primary.ID).Execute() if err != nil { return fmt.Errorf("received an error getting team member. %s", err) } diff --git a/launchdarkly/resource_launchdarkly_webhook.go b/launchdarkly/resource_launchdarkly_webhook.go index 32407630..06067fd3 100644 --- a/launchdarkly/resource_launchdarkly_webhook.go +++ b/launchdarkly/resource_launchdarkly_webhook.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func resourceWebhook() *schema.Resource { @@ -41,29 +41,32 @@ func resourceWebhookCreate(d *schema.ResourceData, metaRaw interface{}) error { webhookURL := d.Get(URL).(string) webhookSecret := d.Get(SECRET).(string) webhookName := d.Get(NAME).(string) - statements, err := policyStatementsFromResourceData(d.Get(STATEMENTS).([]interface{})) - if err != nil { - return err - } webhookOn := d.Get(ON).(bool) - webhookBody := ldapi.WebhookBody{ - Url: webhookURL, - Secret: webhookSecret, - On: webhookOn, - Name: webhookName, - Statements: statements, + webhookBody := ldapi.WebhookPost{ + Url: webhookURL, + On: webhookOn, + Name: &webhookName, + } + + if rawStatements, ok := d.GetOk(STATEMENTS); ok { + statements, err := policyStatementsFromResourceData(rawStatements.([]interface{})) + if err != nil { + return err + } + webhookBody.Statements = &statements } // The sign field isn't returned when GETting a webhook so terraform can't import it properly. // We hide the field from terraform to avoid import problems. if webhookSecret != "" { + webhookBody.Secret = &webhookSecret webhookBody.Sign = true } webhookRaw, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.WebhooksApi.PostWebhook(client.ctx, webhookBody) + return client.ld.WebhooksApi.PostWebhook(client.ctx).WebhookPost(webhookBody).Execute() }) webhook := webhookRaw.(ldapi.Webhook) if err != nil { @@ -117,7 +120,7 @@ func resourceWebhookUpdate(d *schema.ResourceData, metaRaw interface{}) error { _, _, err = handleRateLimit(func() (interface{}, *http.Response, error) { return handleNoConflict(func() (interface{}, *http.Response, error) { - return client.ld.WebhooksApi.PatchWebhook(client.ctx, webhookID, patch) + return client.ld.WebhooksApi.PatchWebhook(client.ctx, webhookID).PatchOperation(patch).Execute() }) }) if err != nil { @@ -132,7 +135,7 @@ func resourceWebhookDelete(d *schema.ResourceData, metaRaw interface{}) error { webhookID := d.Id() _, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - res, err := client.ld.WebhooksApi.DeleteWebhook(client.ctx, webhookID) + res, err := client.ld.WebhooksApi.DeleteWebhook(client.ctx, webhookID).Execute() return nil, res, err }) @@ -149,7 +152,7 @@ func resourceWebhookExists(d *schema.ResourceData, metaRaw interface{}) (bool, e func webhookExists(webhookID string, meta *Client) (bool, error) { _, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return meta.ld.WebhooksApi.GetWebhook(meta.ctx, webhookID) + return meta.ld.WebhooksApi.GetWebhook(meta.ctx, webhookID).Execute() }) if isStatusNotFound(res) { return false, nil diff --git a/launchdarkly/resource_launchdarkly_webhook_test.go b/launchdarkly/resource_launchdarkly_webhook_test.go index 34a12d9e..b88783a3 100644 --- a/launchdarkly/resource_launchdarkly_webhook_test.go +++ b/launchdarkly/resource_launchdarkly_webhook_test.go @@ -362,7 +362,7 @@ func testAccCheckWebhookExists(resourceName string) resource.TestCheckFunc { return fmt.Errorf("webhook ID is not set") } client := testAccProvider.Meta().(*Client) - _, _, err := client.ld.WebhooksApi.GetWebhook(client.ctx, rs.Primary.ID) + _, _, err := client.ld.WebhooksApi.GetWebhook(client.ctx, rs.Primary.ID).Execute() if err != nil { return fmt.Errorf("received an error getting webhook. %s", err) } diff --git a/launchdarkly/rollout_helper.go b/launchdarkly/rollout_helper.go index 09a30b33..69f577f3 100644 --- a/launchdarkly/rollout_helper.go +++ b/launchdarkly/rollout_helper.go @@ -6,7 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func rolloutSchema() *schema.Schema { diff --git a/launchdarkly/rule_helper.go b/launchdarkly/rule_helper.go index 15256a6c..702ad918 100644 --- a/launchdarkly/rule_helper.go +++ b/launchdarkly/rule_helper.go @@ -6,7 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func rulesSchema() *schema.Schema { @@ -69,7 +69,7 @@ func ruleFromResourceData(val interface{}) (rule, error) { if len(rolloutFromResourceData(ruleMap[ROLLOUT_WEIGHTS]).Variations) > 0 { r.Rollout = rolloutFromResourceData(ruleMap[ROLLOUT_WEIGHTS]) if bucketByFound { - r.Rollout.BucketBy = bucketBy + r.Rollout.BucketBy = &bucketBy } } else { if bucketByFound && bucketBy != "" { diff --git a/launchdarkly/segment_rule_helper.go b/launchdarkly/segment_rule_helper.go index 232d4936..55c2d04e 100644 --- a/launchdarkly/segment_rule_helper.go +++ b/launchdarkly/segment_rule_helper.go @@ -3,7 +3,7 @@ package launchdarkly import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func segmentRulesSchema() *schema.Schema { @@ -47,9 +47,11 @@ func segmentRulesFromResourceData(d *schema.ResourceData, metaRaw interface{}) ( func segmentRuleFromResourceData(val interface{}) (ldapi.UserSegmentRule, error) { ruleMap := val.(map[string]interface{}) + weight := int32(ruleMap[WEIGHT].(int)) + bucketBy := ruleMap[BUCKET_BY].(string) r := ldapi.UserSegmentRule{ - Weight: int32(ruleMap[WEIGHT].(int)), - BucketBy: ruleMap[BUCKET_BY].(string), + Weight: &weight, + BucketBy: &bucketBy, } for _, c := range ruleMap[CLAUSES].([]interface{}) { clause, err := clauseFromResourceData(c) diff --git a/launchdarkly/segments_helper.go b/launchdarkly/segments_helper.go index 97d89926..289aa89c 100644 --- a/launchdarkly/segments_helper.go +++ b/launchdarkly/segments_helper.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func baseSegmentSchema() map[string]*schema.Schema { @@ -45,7 +45,7 @@ func segmentRead(d *schema.ResourceData, raw interface{}, isDataSource bool) err segmentKey := d.Get(KEY).(string) segmentRaw, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.UserSegmentsApi.GetUserSegment(client.ctx, projectKey, envKey, segmentKey) + return client.ld.SegmentsApi.GetSegment(client.ctx, projectKey, envKey, segmentKey).Execute() }) segment := segmentRaw.(ldapi.UserSegment) if isStatusNotFound(res) && !isDataSource { diff --git a/launchdarkly/target_helper.go b/launchdarkly/target_helper.go index 36f3e829..c973a6cc 100644 --- a/launchdarkly/target_helper.go +++ b/launchdarkly/target_helper.go @@ -4,7 +4,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func targetsSchema() *schema.Schema { diff --git a/launchdarkly/target_helper_test.go b/launchdarkly/target_helper_test.go index d94c8268..24802ab1 100644 --- a/launchdarkly/target_helper_test.go +++ b/launchdarkly/target_helper_test.go @@ -3,7 +3,7 @@ package launchdarkly import ( "testing" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/assert" ) diff --git a/launchdarkly/team_member_helper.go b/launchdarkly/team_member_helper.go index 80a38c32..45485c2d 100644 --- a/launchdarkly/team_member_helper.go +++ b/launchdarkly/team_member_helper.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) // The LD api returns custom role IDs (not keys). Since we want to set custom_roles with keys, we need to look up their IDs @@ -12,7 +12,7 @@ func customRoleIDsToKeys(client *Client, ids []string) ([]string, error) { customRoleKeys := make([]string, 0, len(ids)) for _, customRoleID := range ids { roleRaw, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.CustomRolesApi.GetCustomRole(client.ctx, customRoleID) + return client.ld.CustomRolesApi.GetCustomRole(client.ctx, customRoleID).Execute() }) role := roleRaw.(ldapi.CustomRole) if isStatusNotFound(res) { @@ -31,7 +31,7 @@ func customRoleKeysToIDs(client *Client, keys []string) ([]string, error) { customRoleIds := make([]string, 0, len(keys)) for _, key := range keys { roleRaw, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.CustomRolesApi.GetCustomRole(client.ctx, key) + return client.ld.CustomRolesApi.GetCustomRole(client.ctx, key).Execute() }) role := roleRaw.(ldapi.CustomRole) if isStatusNotFound(res) { diff --git a/launchdarkly/test_utils.go b/launchdarkly/test_utils.go index 17cd73cb..38b6ca3b 100644 --- a/launchdarkly/test_utils.go +++ b/launchdarkly/test_utils.go @@ -4,13 +4,13 @@ import ( "fmt" "net/http" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) // testAccDataSourceProjectCreate creates a project with the given project parameters -func testAccDataSourceProjectCreate(client *Client, projectBody ldapi.ProjectBody) (*ldapi.Project, error) { +func testAccDataSourceProjectCreate(client *Client, projectBody ldapi.ProjectPost) (*ldapi.Project, error) { project, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.ProjectsApi.PostProject(client.ctx, projectBody) + return client.ld.ProjectsApi.PostProject(client.ctx).ProjectPost(projectBody).Execute() }) if err != nil { return nil, err @@ -22,7 +22,7 @@ func testAccDataSourceProjectCreate(client *Client, projectBody ldapi.ProjectBod } func testAccDataSourceProjectDelete(client *Client, projectKey string) error { - _, err := client.ld.ProjectsApi.DeleteProject(client.ctx, projectKey) + _, err := client.ld.ProjectsApi.DeleteProject(client.ctx, projectKey).Execute() if err != nil { return err } @@ -30,7 +30,7 @@ func testAccDataSourceProjectDelete(client *Client, projectKey string) error { } func testAccDataSourceFeatureFlagScaffold(client *Client, projectKey string, flagBody ldapi.FeatureFlagBody) (*ldapi.FeatureFlag, error) { - projectBody := ldapi.ProjectBody{ + projectBody := ldapi.ProjectPost{ Name: "Flag Test Project", Key: projectKey, } @@ -40,7 +40,7 @@ func testAccDataSourceFeatureFlagScaffold(client *Client, projectKey string, fla } flag, _, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.FeatureFlagsApi.PostFeatureFlag(client.ctx, project.Key, flagBody, nil) + return client.ld.FeatureFlagsApi.PostFeatureFlag(client.ctx, project.Key).FeatureFlagBody(flagBody).Execute() }) if err != nil { return nil, err diff --git a/launchdarkly/variations_helper.go b/launchdarkly/variations_helper.go index f5d9c75e..5fcf6e27 100644 --- a/launchdarkly/variations_helper.go +++ b/launchdarkly/variations_helper.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) const ( @@ -168,30 +168,38 @@ func variationsFromResourceData(d *schema.ResourceData) ([]ldapi.Variation, erro func boolVariationFromResourceData(variation interface{}) ldapi.Variation { variationMap := variation.(map[string]interface{}) v := variationMap[VALUE].(string) == "true" - return ldapi.Variation{ - Name: variationMap[NAME].(string), - Description: variationMap[DESCRIPTION].(string), - Value: ptr(v), + transformed := ldapi.Variation{ + Value: ptr(v), } + name := variationMap[NAME].(string) + if name != "" { + transformed.Name = &name + } + description := variationMap[DESCRIPTION].(string) + if description != "" { + transformed.Description = &description + } + return transformed } func stringVariationFromResourceData(variation interface{}) ldapi.Variation { - var v interface{} + var transformed ldapi.Variation if variation == nil { // handle empty string value - v = "" - return ldapi.Variation{ - Name: "", - Description: "", - Value: &v, - } + transformed.Value = strPtr("") + return transformed } variationMap := variation.(map[string]interface{}) - v = variationMap[VALUE] - return ldapi.Variation{ - Name: variationMap[NAME].(string), - Description: variationMap[DESCRIPTION].(string), - Value: &v, + v := variationMap[VALUE] + transformed.Value = &v + name := variationMap[NAME].(string) + if name != "" { + transformed.Name = &name + } + description := variationMap[DESCRIPTION].(string) + if description != "" { + transformed.Description = &description } + return transformed } func numberVariationFromResourceData(variation interface{}) (ldapi.Variation, error) { @@ -201,11 +209,16 @@ func numberVariationFromResourceData(variation interface{}) (ldapi.Variation, er if err != nil { return ldapi.Variation{}, fmt.Errorf("%q is an invalid number variation value. %v", stringValue, err) } - return ldapi.Variation{ - Name: variationMap[NAME].(string), - Description: variationMap[DESCRIPTION].(string), - Value: ptr(v), - }, nil + transformed := ldapi.Variation{Value: ptr(v)} + name := variationMap[NAME].(string) + if name != "" { + transformed.Name = &name + } + description := variationMap[DESCRIPTION].(string) + if description != "" { + transformed.Description = &description + } + return transformed, nil } func jsonVariationFromResourceData(variation interface{}) (ldapi.Variation, error) { @@ -216,11 +229,16 @@ func jsonVariationFromResourceData(variation interface{}) (ldapi.Variation, erro if err != nil { return ldapi.Variation{}, fmt.Errorf("%q is an invalid json variation value. %v", stringValue, err) } - return ldapi.Variation{ - Name: variationMap[NAME].(string), - Description: variationMap[DESCRIPTION].(string), - Value: ptr(v), - }, nil + transformed := ldapi.Variation{Value: ptr(v)} + name := variationMap[NAME].(string) + if name != "" { + transformed.Name = &name + } + description := variationMap[DESCRIPTION].(string) + if description != "" { + transformed.Description = &description + } + return transformed, nil } func stringifyValue(value interface{}) string { @@ -257,7 +275,7 @@ func variationsToResourceData(variations []ldapi.Variation, variationType string transformed := make([]interface{}, 0, len(variations)) for _, variation := range variations { - v, err := variationValueToString(variation.Value, variationType) + v, err := variationValueToString(&variation.Value, variationType) if err != nil { return nil, err } @@ -273,11 +291,10 @@ func variationsToResourceData(variations []ldapi.Variation, variationType string func variationsToVariationType(variations []ldapi.Variation) (string, error) { // since all variations have a uniform type, checking the first variation is sufficient - valPtr := variations[0].Value - if valPtr == nil { - return "", fmt.Errorf("nil variation value: %v", valPtr) + variationValue := variations[0].Value + if variationValue == nil { + return "", fmt.Errorf("nil variation value: %v", variationValue) } - variationValue := *valPtr var variationType string switch variationValue.(type) { case bool: diff --git a/launchdarkly/variations_helper_test.go b/launchdarkly/variations_helper_test.go index 68f0dec4..9546dd24 100644 --- a/launchdarkly/variations_helper_test.go +++ b/launchdarkly/variations_helper_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -32,8 +32,8 @@ func TestVariationsFromResourceData(t *testing.T) { }, }}, expected: []ldapi.Variation{ - {Name: "nameValue", Description: "descValue", Value: ptr("a string value")}, - {Name: "nameValue2", Description: "descValue2", Value: ptr("another string value")}, + {Name: strPtr("nameValue"), Description: strPtr("descValue"), Value: ptr("a string value")}, + {Name: strPtr("nameValue2"), Description: strPtr("descValue2"), Value: ptr("another string value")}, }, }, { @@ -110,7 +110,7 @@ func TestVariationsFromResourceData(t *testing.T) { for idx, expected := range tc.expected { assert.Equal(t, expected.Name, actualVariations[idx].Name) assert.Equal(t, expected.Description, actualVariations[idx].Description) - assert.Equal(t, *expected.Value, *actualVariations[idx].Value) + assert.Equal(t, expected.Value, actualVariations[idx].Value) } }) } diff --git a/launchdarkly/webhooks_helper.go b/launchdarkly/webhooks_helper.go index 85b74069..02a28b07 100644 --- a/launchdarkly/webhooks_helper.go +++ b/launchdarkly/webhooks_helper.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - ldapi "github.com/launchdarkly/api-client-go" + ldapi "github.com/launchdarkly/api-client-go/v7" ) func baseWebhookSchema() map[string]*schema.Schema { @@ -37,7 +37,7 @@ func webhookRead(d *schema.ResourceData, meta interface{}, isDataSource bool) er } webhookRaw, res, err := handleRateLimit(func() (interface{}, *http.Response, error) { - return client.ld.WebhooksApi.GetWebhook(client.ctx, webhookID) + return client.ld.WebhooksApi.GetWebhook(client.ctx, webhookID).Execute() }) webhook := webhookRaw.(ldapi.Webhook) if isStatusNotFound(res) && !isDataSource { @@ -48,7 +48,13 @@ func webhookRead(d *schema.ResourceData, meta interface{}, isDataSource bool) er if err != nil { return fmt.Errorf("failed to get webhook with id %q: %s", webhookID, handleLdapiErr(err)) } - statements := policyStatementsToResourceData(webhook.Statements) + if webhook.Statements != nil { + statements := policyStatementsToResourceData(*webhook.Statements) + err = d.Set(STATEMENTS, statements) + if err != nil { + return fmt.Errorf("failed to set statements on webhook with id %q: %v", webhookID, err) + } + } if isDataSource { d.SetId(webhook.Id) @@ -58,11 +64,6 @@ func webhookRead(d *schema.ResourceData, meta interface{}, isDataSource bool) er _ = d.Set(ON, webhook.On) _ = d.Set(NAME, webhook.Name) - err = d.Set(STATEMENTS, statements) - if err != nil { - return fmt.Errorf("failed to set statements on webhook with id %q: %v", webhookID, err) - } - err = d.Set(TAGS, webhook.Tags) if err != nil { return fmt.Errorf("failed to set tags on webhook with id %q: %v", webhookID, err) diff --git a/website/docs/d/feature_flag.html.markdown b/website/docs/d/feature_flag.html.markdown index 43eec85c..f362c55d 100644 --- a/website/docs/d/feature_flag.html.markdown +++ b/website/docs/d/feature_flag.html.markdown @@ -48,6 +48,8 @@ In addition to the arguments above, the resource exports the following attribute - `temporary` - Whether the flag is a temporary flag. +- `include_in_snippet` - **Deprecated** A boolean describing whether this flag has been made available to the client-side Javescript SDK using the client-side ID only. `include_in_snippet` is now deprecated. Please retrieve information from `client_side_availability.using_environment_id` to maintain future compatability. + - `client_side_availability` - A map describing whether this flag has been made available to the client-side JavaScript SDK. To learn more, read [Nested Client-Side Availability Block](#nested-client-side-availability-block). - `custom_properties` - List of nested blocks describing the feature flag's [custom properties](https://docs.launchdarkly.com/docs/custom-properties). To learn more, read [Nested Custom Properties](#nested-custom-properties). diff --git a/website/docs/r/feature_flag.html.markdown b/website/docs/r/feature_flag.html.markdown index d88b17c9..7e4bdd0f 100644 --- a/website/docs/r/feature_flag.html.markdown +++ b/website/docs/r/feature_flag.html.markdown @@ -96,10 +96,13 @@ resource "launchdarkly_feature_flag" "json_example" { - `temporary` - (Optional) Specifies whether the flag is a temporary flag. -- `include_in_snippet` - (Optional) Specifies whether this flag should be made available to the client-side JavaScript SDK. +- `include_in_snippet` - **Deprecated** (Optional) Specifies whether this flag should be made available to the client-side JavaScript SDK using the client-side Id. This value gets its default from your project configuration if not set. `include_in_snippet` is now deprecated. Please migrate to `client_side_availability.using_environment_id` to maintain future compatability. + +- `client_side_availability` - (Optional) A block describing whether this flag should be made available to the client-side JavaScript SDK using the client-side Id, mobile key, or both. This value gets its default from your project configuration if not set. To learn more, read [Nested Client-Side Availability Block](#nested-client-side-availability-block). - `custom_properties` - (Optional) List of nested blocks describing the feature flag's [custom properties](https://docs.launchdarkly.com/docs/custom-properties). To learn more, read [Nested Custom Properties](#nested-custom-properties). + ### Nested Variations Blocks Nested `variations` blocks have the following structure: @@ -126,6 +129,14 @@ Nested `defaults` blocks have the following structure: - `off_variation` - (Required) The index of the variation the flag will default to in all new environments when off. +### Nested Client-Side Availibility Block + +The nested `client_side_availability` block has the following structure: + +- `using_environment_id` - (Optional) Whether this flag is available to SDKs using the client-side ID. + +- `using_mobile_key` - (Optional) Whether this flag is available to SDKs using a mobile key. + ### Nested Custom Properties Nested `custom_properties` have the following structure: From f0b2c60d620074c32a8e8f0f34315fb267fb4a18 Mon Sep 17 00:00:00 2001 From: Fabian Feldberg Date: Thu, 23 Dec 2021 14:54:30 +0100 Subject: [PATCH 14/14] 2.2.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 544944cc..96c2fc46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## [Unreleased] +## [2.2.0] (December 23, 2021) ENHANCEMENTS: