diff --git a/aws/resource_aws_sfn_state_machine.go b/aws/resource_aws_sfn_state_machine.go index bbeb5ed6e34d..15c44621b235 100644 --- a/aws/resource_aws_sfn_state_machine.go +++ b/aws/resource_aws_sfn_state_machine.go @@ -31,6 +31,37 @@ func resourceAwsSfnStateMachine() *schema.Resource { ValidateFunc: validation.StringLenBetween(0, 1024*1024), // 1048576 }, + "logging_configuration": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "log_destination": { + Type: schema.TypeString, + Optional: true, + }, + "include_execution_data": { + Type: schema.TypeBool, + Optional: true, + // Default: false, + }, + "level": { + Type: schema.TypeString, + Optional: true, + // Default: sfn.LogLevelOff, + ValidateFunc: validation.StringInSlice([]string{ + sfn.LogLevelAll, + sfn.LogLevelError, + sfn.LogLevelFatal, + sfn.LogLevelOff, + }, false), + }, + }, + }, + }, + "name": { Type: schema.TypeString, Required: true, @@ -53,7 +84,18 @@ func resourceAwsSfnStateMachine() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "tags": tagsSchema(), + + "type": { + Type: schema.TypeString, + Optional: true, + Default: sfn.StateMachineTypeStandard, + ValidateFunc: validation.StringInSlice([]string{ + sfn.StateMachineTypeStandard, + sfn.StateMachineTypeExpress, + }, false), + }, }, } } @@ -61,12 +103,13 @@ func resourceAwsSfnStateMachine() *schema.Resource { func resourceAwsSfnStateMachineCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).sfnconn log.Print("[DEBUG] Creating Step Function State Machine") - params := &sfn.CreateStateMachineInput{ - Definition: aws.String(d.Get("definition").(string)), - Name: aws.String(d.Get("name").(string)), - RoleArn: aws.String(d.Get("role_arn").(string)), - Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().SfnTags(), + Definition: aws.String(d.Get("definition").(string)), + LoggingConfiguration: expandAwsSfnLoggingConfiguration(d.Get("logging_configuration").([]interface{})), + Name: aws.String(d.Get("name").(string)), + RoleArn: aws.String(d.Get("role_arn").(string)), + Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().SfnTags(), + Type: aws.String(d.Get("type").(string)), } var activity *sfn.CreateStateMachineOutput @@ -104,7 +147,6 @@ func resourceAwsSfnStateMachineCreate(d *schema.ResourceData, meta interface{}) func resourceAwsSfnStateMachineRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).sfnconn log.Printf("[DEBUG] Reading Step Function State Machine: %s", d.Id()) - sm, err := conn.DescribeStateMachine(&sfn.DescribeStateMachineInput{ StateMachineArn: aws.String(d.Id()), }) @@ -122,8 +164,18 @@ func resourceAwsSfnStateMachineRead(d *schema.ResourceData, meta interface{}) er d.Set("definition", sm.Definition) d.Set("name", sm.Name) d.Set("role_arn", sm.RoleArn) + d.Set("type", sm.Type) d.Set("status", sm.Status) + loggingConfiguration := flattenAwsSfnLoggingConfiguration(sm.LoggingConfiguration) + + if loggingConfiguration != nil { + err := d.Set("logging_configuration", loggingConfiguration) + if err != nil { + log.Printf("[DEBUG] Error setting logging_configuration %s \n", err) + } + } + if err := d.Set("creation_date", sm.CreationDate.Format(time.RFC3339)); err != nil { log.Printf("[DEBUG] Error setting creation_date: %s", err) } @@ -150,10 +202,14 @@ func resourceAwsSfnStateMachineUpdate(d *schema.ResourceData, meta interface{}) RoleArn: aws.String(d.Get("role_arn").(string)), } - _, err := conn.UpdateStateMachine(params) - log.Printf("[DEBUG] Updating Step Function State Machine: %#v", params) + if d.HasChange("logging_configuration") && d.Get("type").(string) == sfn.StateMachineTypeExpress { + params.LoggingConfiguration = expandAwsSfnLoggingConfiguration(d.Get("logging_configuration").([]interface{})) + } + + _, err := conn.UpdateStateMachine(params) + if err != nil { if isAWSErr(err, "StateMachineDoesNotExist", "State Machine Does Not Exist") { return fmt.Errorf("Error updating Step Function State Machine: %s", err) @@ -168,7 +224,7 @@ func resourceAwsSfnStateMachineUpdate(d *schema.ResourceData, meta interface{}) } } - return resourceAwsSfnStateMachineRead(d, meta) + return nil } func resourceAwsSfnStateMachineDelete(d *schema.ResourceData, meta interface{}) error { @@ -195,3 +251,45 @@ func resourceAwsSfnStateMachineDelete(d *schema.ResourceData, meta interface{}) } return nil } + +func expandAwsSfnLoggingConfiguration(l []interface{}) *sfn.LoggingConfiguration { + + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + loggingConfiguration := &sfn.LoggingConfiguration{ + Destinations: []*sfn.LogDestination{ + { + CloudWatchLogsLogGroup: &sfn.CloudWatchLogsLogGroup{ + LogGroupArn: aws.String(m["log_destination"].(string)), + }, + }, + }, + IncludeExecutionData: aws.Bool(m["include_execution_data"].(bool)), + Level: aws.String(m["level"].(string)), + } + + return loggingConfiguration +} + +func flattenAwsSfnLoggingConfiguration(loggingConfiguration *sfn.LoggingConfiguration) []interface{} { + + if loggingConfiguration == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "log_destination": "", + "include_execution_data": aws.BoolValue(loggingConfiguration.IncludeExecutionData), + "level": aws.StringValue(loggingConfiguration.Level), + } + + if len(loggingConfiguration.Destinations) > 0 { + m["log_destination"] = aws.StringValue(loggingConfiguration.Destinations[0].CloudWatchLogsLogGroup.LogGroupArn) + } + + return []interface{}{m} +} diff --git a/aws/resource_aws_sfn_state_machine_test.go b/aws/resource_aws_sfn_state_machine_test.go index beec17c2dbdd..4101203475d1 100644 --- a/aws/resource_aws_sfn_state_machine_test.go +++ b/aws/resource_aws_sfn_state_machine_test.go @@ -27,6 +27,7 @@ func TestAccAWSSfnStateMachine_createUpdate(t *testing.T) { testAccCheckAWSSfnExists("aws_sfn_state_machine.foo"), resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "status", sfn.StateMachineStatusActive), resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "name"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "name", fmt.Sprintf("test_sfn_%s", name)), resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "creation_date"), resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "definition"), resource.TestMatchResourceAttr("aws_sfn_state_machine.foo", "definition", regexp.MustCompile(`.*\"MaxAttempts\": 5.*`)), @@ -35,6 +36,83 @@ func TestAccAWSSfnStateMachine_createUpdate(t *testing.T) { }, { Config: testAccAWSSfnStateMachineConfig(name, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSfnExists("aws_sfn_state_machine.foo"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "status", sfn.StateMachineStatusActive), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "name"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "name", fmt.Sprintf("test_sfn_%s", name)), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "creation_date"), + resource.TestMatchResourceAttr("aws_sfn_state_machine.foo", "definition", regexp.MustCompile(`.*\"MaxAttempts\": 10.*`)), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "role_arn"), + ), + }, + }, + }) +} + +func TestAccAWSSfnStateMachine_express_createUpdate(t *testing.T) { + name := acctest.RandString(10) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSfnStateMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSfnStateMachineTypedConfig(sfn.StateMachineTypeExpress, name, 5), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSfnExists("aws_sfn_state_machine.foo"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "status", sfn.StateMachineStatusActive), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "name"), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "creation_date"), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "definition"), + resource.TestMatchResourceAttr("aws_sfn_state_machine.foo", "definition", regexp.MustCompile(`.*\"MaxAttempts\": 5.*`)), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "role_arn"), + // resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "logging_configuration"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "logging_configuration.#", "1"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "logging_configuration.0.level", sfn.LogLevelOff), + ), + }, + { + Config: testAccAWSSfnStateMachineTypedConfig(sfn.StateMachineTypeExpress, name, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSfnExists("aws_sfn_state_machine.foo"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "status", sfn.StateMachineStatusActive), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "name"), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "creation_date"), + resource.TestMatchResourceAttr("aws_sfn_state_machine.foo", "definition", regexp.MustCompile(`.*\"MaxAttempts\": 10.*`)), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "role_arn"), + // resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "logging_configuration"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "logging_configuration.#", "1"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "logging_configuration.0.level", sfn.LogLevelOff), + ), + }, + }, + }) +} + +func TestAccAWSSfnStateMachine_standard_createUpdate(t *testing.T) { + name := acctest.RandString(10) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSfnStateMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSfnStateMachineTypedConfig(sfn.StateMachineTypeStandard, name, 5), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSfnExists("aws_sfn_state_machine.foo"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "status", sfn.StateMachineStatusActive), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "name"), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "creation_date"), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "definition"), + resource.TestMatchResourceAttr("aws_sfn_state_machine.foo", "definition", regexp.MustCompile(`.*\"MaxAttempts\": 5.*`)), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "role_arn"), + ), + }, + { + Config: testAccAWSSfnStateMachineTypedConfig(sfn.StateMachineTypeStandard, name, 10), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSfnExists("aws_sfn_state_machine.foo"), resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "status", sfn.StateMachineStatusActive), @@ -85,6 +163,45 @@ func TestAccAWSSfnStateMachine_Tags(t *testing.T) { }) } +func TestAccAWSSfnStateMachine_express_LoggingConfiguration(t *testing.T) { + name := acctest.RandString(10) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSfnStateMachineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSfnStateMachineExpressConfigLogConfiguration1(sfn.StateMachineTypeExpress, name, sfn.LogLevelError), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSfnExists("aws_sfn_state_machine.foo"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "status", sfn.StateMachineStatusActive), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "name"), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "creation_date"), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "definition"), + resource.TestMatchResourceAttr("aws_sfn_state_machine.foo", "definition", regexp.MustCompile(`.*\"MaxAttempts\": 5.*`)), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "role_arn"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "logging_configuration.#", "1"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "logging_configuration.0.level", sfn.LogLevelError), + ), + }, + { + Config: testAccAWSSfnStateMachineExpressConfigLogConfiguration1(sfn.StateMachineTypeExpress, name, sfn.LogLevelAll), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSfnExists("aws_sfn_state_machine.foo"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "status", sfn.StateMachineStatusActive), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "name"), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "creation_date"), + resource.TestMatchResourceAttr("aws_sfn_state_machine.foo", "definition", regexp.MustCompile(`.*\"MaxAttempts\": 5.*`)), + resource.TestCheckResourceAttrSet("aws_sfn_state_machine.foo", "role_arn"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "logging_configuration.#", "1"), + resource.TestCheckResourceAttr("aws_sfn_state_machine.foo", "logging_configuration.0.level", sfn.LogLevelAll), + ), + }, + }, + }) +} + func testAccCheckAWSSfnExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -499,3 +616,271 @@ tags = { } `, rName, rName, rName, rName, rName, rName, tag1Key, tag1Value, tag2Key, tag2Value) } + +func testAccAWSSfnStateMachineTypedConfig(rType string, rName string, rMaxAttempts int) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +data "aws_region" "current" {} + +resource "aws_iam_role_policy" "iam_policy_for_lambda" { + name = "iam_policy_for_lambda_%s" + role = "${aws_iam_role.iam_for_lambda.id}" + + policy = < *NOTE:* Logging is only accepted for EXPRESS Workflows. See the [AWS Step Functions Developer Guide](https://docs.aws.amazon.com/step-functions/latest/dg/welcome.html) for more information about enabling Step Function logging. ```hcl # ... @@ -18,6 +70,7 @@ Provides a Step Function State Machine resource resource "aws_sfn_state_machine" "sfn_state_machine" { name = "my-state-machine" role_arn = "${aws_iam_role.iam_for_sfn.arn}" + type = "EXPRESS" definition = <