diff --git a/aws/resource_aws_api_gateway_stage.go b/aws/resource_aws_api_gateway_stage.go index 2735e736f5c6..3cd18ed72d32 100644 --- a/aws/resource_aws_api_gateway_stage.go +++ b/aws/resource_aws_api_gateway_stage.go @@ -65,6 +65,29 @@ func resourceAwsApiGatewayStage() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "canary_settings": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "percent_traffic": { + Type: schema.TypeFloat, + Optional: true, + Default: 0.0, + }, + "stage_variable_overrides": { + Type: schema.TypeMap, + Elem: schema.TypeString, + Optional: true, + }, + "use_stage_cache": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, "client_certificate_id": { Type: schema.TypeString, Optional: true, @@ -112,6 +135,62 @@ func resourceAwsApiGatewayStage() *schema.Resource { } } +func appendCanarySettingsPatchOperations(operations []*apigateway.PatchOperation, oldCanarySettingsRaw, newCanarySettingsRaw []interface{}) []*apigateway.PatchOperation { + if len(newCanarySettingsRaw) == 0 { // Schema guarantees either 0 or 1 + return append(operations, &apigateway.PatchOperation{ + Op: aws.String("remove"), + Path: aws.String("/canarySettings"), + }) + } + newSettings := newCanarySettingsRaw[0].(map[string]interface{}) + + var oldSettings map[string]interface{} + if len(oldCanarySettingsRaw) == 1 { // Schema guarantees either 0 or 1 + oldSettings = oldCanarySettingsRaw[0].(map[string]interface{}) + } else { + oldSettings = map[string]interface{}{ + "percent_traffic": 0.0, + "stage_variable_overrides": make(map[string]interface{}), + "use_stage_cache": false, + } + } + + oldOverrides := oldSettings["stage_variable_overrides"].(map[string]interface{}) + newOverrides := newSettings["stage_variable_overrides"].(map[string]interface{}) + operations = append(operations, diffVariablesOps(oldOverrides, newOverrides)...) + + oldPercentTraffic := oldSettings["percent_traffic"].(float64) + newPercentTraffic := newSettings["percent_traffic"].(float64) + if oldPercentTraffic != newPercentTraffic { + operations = append(operations, &apigateway.PatchOperation{ + Op: aws.String("replace"), + Path: aws.String("/canarySettings/percentTraffic"), + Value: aws.String(fmt.Sprintf("%f", newPercentTraffic)), + }) + } + + oldUseStageCache := oldSettings["use_stage_cache"].(bool) + newUseStageCache := newSettings["use_stage_cache"].(bool) + if oldUseStageCache != newUseStageCache { + operations = append(operations, &apigateway.PatchOperation{ + Op: aws.String("replace"), + Path: aws.String("/canarySettings/useStageCache"), + Value: aws.String(fmt.Sprintf("%t", newUseStageCache)), + }) + } + + return operations +} + +func readStageVariableOverrides(overrides map[string]interface{}) map[string]string { + result := make(map[string]string) + for k, v := range overrides { + result[k] = v.(string) + } + + return result +} + func resourceAwsApiGatewayStageCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).apigateway @@ -132,6 +211,20 @@ func resourceAwsApiGatewayStageCreate(d *schema.ResourceData, meta interface{}) input.CacheClusterSize = aws.String(v.(string)) waitForCache = true } + if v, ok := d.GetOk("canary_settings"); ok { + canarySettings := v.([]interface{}) + canarySetting := canarySettings[0].(map[string]interface{}) + canarySettingVariables := canarySetting["stage_variable_overrides"] + stageVariableOverrides := readStageVariableOverrides(canarySettingVariables.(map[string]interface{})) + percentTraffic := canarySetting["percent_traffic"] + useStageCache := canarySetting["use_stage_cache"] + input.CanarySettings = &apigateway.CanarySettings{ + DeploymentId: aws.String(d.Get("deployment_id").(string)), + StageVariableOverrides: aws.StringMap(stageVariableOverrides), + PercentTraffic: aws.Float64(percentTraffic.(float64)), + UseStageCache: aws.Bool(useStageCache.(bool)), + } + } if v, ok := d.GetOk("description"); ok { input.Description = aws.String(v.(string)) } @@ -192,6 +285,7 @@ func resourceAwsApiGatewayStageCreate(d *schema.ResourceData, meta interface{}) d.SetPartial("cache_cluster_enabled") d.SetPartial("cache_cluster_size") + d.SetPartial("canary_settings") d.Partial(false) if _, ok := d.GetOk("client_certificate_id"); ok { @@ -238,6 +332,10 @@ func resourceAwsApiGatewayStageRead(d *schema.ResourceData, meta interface{}) er d.Set("cache_cluster_size", stage.CacheClusterSize) } + if err := d.Set("canary_settings", flattenApiGatewayStageCanarySettings(stage.CanarySettings)); err != nil { + return fmt.Errorf("error setting canary_settings: %s", err) + } + d.Set("deployment_id", stage.DeploymentId) d.Set("description", stage.Description) d.Set("documentation_version", stage.DocumentationVersion) @@ -300,6 +398,13 @@ func resourceAwsApiGatewayStageUpdate(d *schema.ResourceData, meta interface{}) }) waitForCache = true } + if d.HasChange("canary_settings") { + oldCanarySettingsRaw, newCanarySettingsRaw := d.GetChange("canary_settings") + operations = appendCanarySettingsPatchOperations(operations, + oldCanarySettingsRaw.([]interface{}), + newCanarySettingsRaw.([]interface{}), + ) + } if d.HasChange("client_certificate_id") { operations = append(operations, &apigateway.PatchOperation{ Op: aws.String("replace"), @@ -313,6 +418,13 @@ func resourceAwsApiGatewayStageUpdate(d *schema.ResourceData, meta interface{}) Path: aws.String("/deploymentId"), Value: aws.String(d.Get("deployment_id").(string)), }) + if _, ok := d.GetOk("canary_settings"); ok { + operations = append(operations, &apigateway.PatchOperation{ + Op: aws.String("replace"), + Path: aws.String("/canarySettings/deploymentId"), + Value: aws.String(d.Get("deployment_id").(string)), + }) + } } if d.HasChange("description") { operations = append(operations, &apigateway.PatchOperation{ @@ -374,6 +486,7 @@ func resourceAwsApiGatewayStageUpdate(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Updating API Gateway Stage failed: %s", err) } + d.SetPartial("canary_settings") d.SetPartial("client_certificate_id") d.SetPartial("deployment_id") d.SetPartial("description") diff --git a/aws/resource_aws_api_gateway_stage_test.go b/aws/resource_aws_api_gateway_stage_test.go index 9ea0ba46cb65..7360c9665f33 100644 --- a/aws/resource_aws_api_gateway_stage_test.go +++ b/aws/resource_aws_api_gateway_stage_test.go @@ -30,6 +30,7 @@ func TestAccAWSAPIGatewayStage_basic(t *testing.T) { resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "cache_cluster_enabled", "true"), resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "cache_cluster_size", "0.5"), resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "tags.%", "1"), + resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "canary_settings.#", "0"), resource.TestCheckResourceAttrSet("aws_api_gateway_stage.test", "execution_arn"), resource.TestCheckResourceAttrSet("aws_api_gateway_stage.test", "invoke_url"), resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "xray_tracing_enabled", "true"), @@ -59,6 +60,7 @@ func TestAccAWSAPIGatewayStage_basic(t *testing.T) { resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "cache_cluster_enabled", "true"), resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "cache_cluster_size", "0.5"), resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "tags.%", "1"), + resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "canary_settings.#", "0"), resource.TestCheckResourceAttrSet("aws_api_gateway_stage.test", "execution_arn"), resource.TestCheckResourceAttrSet("aws_api_gateway_stage.test", "invoke_url"), resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "xray_tracing_enabled", "true"), @@ -130,6 +132,35 @@ func TestAccAWSAPIGatewayStage_accessLogSettings(t *testing.T) { }) } +func TestAccAWSAPIGatewayStage_canarySettings(t *testing.T) { + var conf apigateway.Stage + rName := acctest.RandString(5) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAPIGatewayStageDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSAPIGatewayStageConfig_canarySettings(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayStageExists("aws_api_gateway_stage.test", &conf), + resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "canary_settings.0.percent_traffic", "33.33"), + resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "canary_settings.0.stage_variable_overrides.four", "4"), + resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "canary_settings.0.use_stage_cache", "true"), + ), + }, + { + Config: testAccAWSAPIGatewayStageConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAPIGatewayStageExists("aws_api_gateway_stage.test", &conf), + resource.TestCheckResourceAttr("aws_api_gateway_stage.test", "canary_settings.#", "0"), + ), + }, + }, + }) +} + func testAccCheckAWSAPIGatewayStageExists(n string, res *apigateway.Stage) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -324,3 +355,30 @@ resource "aws_api_gateway_stage" "test" { } `, rName, format) } + +func testAccAWSAPIGatewayStageConfig_canarySettings(rName string) string { + return testAccAWSAPIGatewayStageConfig_base(rName) + ` +resource "aws_api_gateway_stage" "test" { + rest_api_id = "${aws_api_gateway_rest_api.test.id}" + stage_name = "prod" + deployment_id = "${aws_api_gateway_deployment.dev.id}" + cache_cluster_enabled = true + cache_cluster_size = "0.5" + xray_tracing_enabled = true + canary_settings { + percent_traffic = "33.33" + stage_variable_overrides = { + four = "4" + } + use_stage_cache = "true" + } + variables = { + one = "1" + two = "2" + } + tags = { + Name = "tf-test" + } +} +` +} diff --git a/aws/structure.go b/aws/structure.go index 52eaa1dce597..27c465af61ab 100644 --- a/aws/structure.go +++ b/aws/structure.go @@ -2349,6 +2349,25 @@ func normalizeCloudFormationTemplate(templateString interface{}) (string, error) return checkYamlString(templateString) } +func flattenApiGatewayStageCanarySettings(canarySettings *apigateway.CanarySettings) []interface{} { + settings := make(map[string]interface{}) + + if canarySettings == nil { + return nil + } + + overrides := aws.StringValueMap(canarySettings.StageVariableOverrides) + + if len(overrides) > 0 { + settings["stage_variable_overrides"] = overrides + } + + settings["percent_traffic"] = canarySettings.PercentTraffic + settings["use_stage_cache"] = canarySettings.UseStageCache + + return []interface{}{settings} +} + func flattenApiGatewayUsageApiStages(s []*apigateway.ApiStage) []map[string]interface{} { stages := make([]map[string]interface{}, 0) diff --git a/website/docs/r/api_gateway_stage.html.markdown b/website/docs/r/api_gateway_stage.html.markdown index 206545091c00..790b13c57553 100644 --- a/website/docs/r/api_gateway_stage.html.markdown +++ b/website/docs/r/api_gateway_stage.html.markdown @@ -16,6 +16,18 @@ resource "aws_api_gateway_stage" "test" { stage_name = "prod" rest_api_id = "${aws_api_gateway_rest_api.test.id}" deployment_id = "${aws_api_gateway_deployment.test.id}" + + variables = { + my_var = "normal value" + } + + canary_settings { + percent_traffic = 33.33 + stage_variable_overrides = { + my_var = "overridden value" + my_new_var = "true" + } + } } resource "aws_api_gateway_rest_api" "test" { @@ -104,6 +116,7 @@ The following arguments are supported: * `cache_cluster_enabled` - (Optional) Specifies whether a cache cluster is enabled for the stage * `cache_cluster_size` - (Optional) The size of the cache cluster for the stage, if enabled. Allowed values include `0.5`, `1.6`, `6.1`, `13.5`, `28.4`, `58.2`, `118` and `237`. +* `canary_settings` - (Optional) A map of settings for a canary deployment. Detailed below. * `client_certificate_id` - (Optional) The identifier of a client certificate for the stage. * `description` - (Optional) The description of the stage * `documentation_version` - (Optional) The version of the associated API documentation @@ -119,6 +132,13 @@ The following arguments are supported: * `format` - (Required) The formatting and values recorded in the logs. For more information on configuring the log format rules visit the AWS [documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html) +#### `canary_settings` + +* `percent_traffic` - (Optional) The percent `0.0` - `100.0` of traffic to divert to the canary deployment. +* `stage_variable_overrides` - (Optional) A map of overridden stage `variables` (including new variables) for the canary deployment. +* `use_stage_cache` - (Optional) Whether the canary deployment uses the stage cache. Defaults to false. +For more information on configuring the canary settings visit the AWS [documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/canary-release.html) + ## Attribute Reference In addition to all arguments above, the following attributes are exported: