From 5d7fbf3d3a500713e53cb44d2ec8ff6ca05db35f Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Tue, 7 Sep 2021 15:35:11 +0300 Subject: [PATCH 01/10] sagemaker flow definition resource --- .../service/sagemaker/finder/finder.go | 28 + aws/provider.go | 1 + aws/resource_aws_sagemaker_flow_definition.go | 494 ++++++++++++++++++ 3 files changed, 523 insertions(+) create mode 100644 aws/resource_aws_sagemaker_flow_definition.go diff --git a/aws/internal/service/sagemaker/finder/finder.go b/aws/internal/service/sagemaker/finder/finder.go index 9f2775eb7197..dcd51bca3aa5 100644 --- a/aws/internal/service/sagemaker/finder/finder.go +++ b/aws/internal/service/sagemaker/finder/finder.go @@ -314,3 +314,31 @@ func EndpointConfigByName(conn *sagemaker.SageMaker, name string) (*sagemaker.De return output, nil } + +func FlowDefinitionByName(conn *sagemaker.SageMaker, name string) (*sagemaker.DescribeFlowDefinitionOutput, error) { + input := &sagemaker.DescribeFlowDefinitionInput{ + FlowDefinitionName: aws.String(name), + } + + output, err := conn.DescribeFlowDefinition(input) + + if tfawserr.ErrCodeEquals(err, sagemaker.ErrCodeResourceNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output, nil +} diff --git a/aws/provider.go b/aws/provider.go index 549af60dd13d..27710d8edc12 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -1037,6 +1037,7 @@ func Provider() *schema.Provider { "aws_sagemaker_endpoint": resourceAwsSagemakerEndpoint(), "aws_sagemaker_endpoint_configuration": resourceAwsSagemakerEndpointConfiguration(), "aws_sagemaker_feature_group": resourceAwsSagemakerFeatureGroup(), + "aws_sagemaker_flow_definition": resourceAwsSagemakerFlowDefinition(), "aws_sagemaker_image": resourceAwsSagemakerImage(), "aws_sagemaker_image_version": resourceAwsSagemakerImageVersion(), "aws_sagemaker_human_task_ui": resourceAwsSagemakerHumanTaskUi(), diff --git a/aws/resource_aws_sagemaker_flow_definition.go b/aws/resource_aws_sagemaker_flow_definition.go new file mode 100644 index 000000000000..c3ec20c82fb9 --- /dev/null +++ b/aws/resource_aws_sagemaker_flow_definition.go @@ -0,0 +1,494 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/private/protocol" + "github.com/aws/aws-sdk-go/service/sagemaker" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sagemaker/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func resourceAwsSagemakerFlowDefinition() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSagemakerFlowDefinitionCreate, + Read: resourceAwsSagemakerFlowDefinitionRead, + Update: resourceAwsSagemakerFlowDefinitionUpdate, + Delete: resourceAwsSagemakerFlowDefinitionDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "flow_definition_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 63), + validation.StringMatch(regexp.MustCompile(`^[a-z0-9](-*[a-z0-9])*$`), "Valid characters are a-z, 0-9, and - (hyphen)."), + ), + }, + "human_loop_activation_config": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "human_loop_activation_conditions_config": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "human_loop_activation_conditions": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 10240), + }, + }, + }, + }, + }, + }, + }, + "human_loop_config": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "human_task_ui_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArn, + }, + "task_availability_lifetime_in_seconds": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 864000), + }, + "task_count": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(1, 3), + }, + "task_description": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "task_keywords": { + Type: schema.TypeSet, + Optional: true, + MinItems: 1, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 30), + validation.StringMatch(regexp.MustCompile(`^[A-Za-z0-9]+( [A-Za-z0-9]+)*$`), ""), + ), + }, + }, + "task_time_limit_in_seconds": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Default: 3600, + ValidateFunc: validation.IntBetween(30, 28800), + }, + "task_title": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + "workteam_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArn, + }, + }, + }, + }, + "human_loop_request_source": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "aws_managed_human_loop_request_source": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(sagemaker.AwsManagedHumanLoopRequestSource_Values(), false), + }, + }, + }, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArn, + }, + + "output_config": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kms_key_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateArn, + }, + "s3_output_path": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.All( + validation.StringMatch(regexp.MustCompile(`^(https|s3)://([^/])/?(.*)$`), ""), + validation.StringLenBetween(1, 512), + ), + }, + }, + }, + }, + "tags": tagsSchema(), + "tags_all": tagsSchemaComputed(), + }, + + CustomizeDiff: SetTagsDiff, + } +} + +func resourceAwsSagemakerFlowDefinitionCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) + + name := d.Get("flow_definition_name").(string) + input := &sagemaker.CreateFlowDefinitionInput{ + FlowDefinitionName: aws.String(name), + HumanLoopConfig: expandSagemakerFlowDefinitionHumanLoopConfig(d.Get("human_loop_config").([]interface{})), + RoleArn: aws.String(d.Get("role_arn").(string)), + OutputConfig: expandSagemakerFlowDefinitionOutputConfig(d.Get("output_config").([]interface{})), + } + + if v, ok := d.GetOk("human_loop_activation_config"); ok && (len(v.([]interface{})) > 0) { + input.HumanLoopActivationConfig = expandSagemakerFlowDefinitionHumanLoopActivationConfig(v.([]interface{})) + } + + if v, ok := d.GetOk("human_loop_request_source"); ok && (len(v.([]interface{})) > 0) { + input.HumanLoopRequestSource = expandSagemakerFlowDefinitionHumanLoopRequestSource(v.([]interface{})) + } + + if len(tags) > 0 { + input.Tags = tags.IgnoreAws().SagemakerTags() + } + + log.Printf("[DEBUG] Creating SageMaker Flow Definition: %s", input) + _, err := conn.CreateFlowDefinition(input) + if err != nil { + return fmt.Errorf("error creating SageMaker Flow Definition (%s): %w", name, err) + } + + d.SetId(name) + + return resourceAwsSagemakerFlowDefinitionRead(d, meta) +} + +func resourceAwsSagemakerFlowDefinitionRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + flowDefinition, err := finder.FlowDefinitionByName(conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SageMaker Flow Definition (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading SageMaker Flow Definition (%s): %w", d.Id(), err) + } + + arn := aws.StringValue(flowDefinition.FlowDefinitionArn) + d.Set("arn", arn) + d.Set("role_arn", flowDefinition.RoleArn) + d.Set("flow_definition_name", flowDefinition.FlowDefinitionName) + + if err := d.Set("human_loop_activation_config", flattenSagemakerFlowDefinitionHumanLoopActivationConfig(flowDefinition.HumanLoopActivationConfig)); err != nil { + return fmt.Errorf("error setting human_loop_activation_config: %w", err) + } + + if err := d.Set("human_loop_config", flattenSagemakerFlowDefinitionHumanLoopConfig(flowDefinition.HumanLoopConfig)); err != nil { + return fmt.Errorf("error setting human_loop_config: %w", err) + } + + if err := d.Set("human_loop_request_source", flattenSagemakerFlowDefinitionHumanLoopRequestSource(flowDefinition.HumanLoopRequestSource)); err != nil { + return fmt.Errorf("error setting human_loop_request_source: %w", err) + } + + if err := d.Set("output_config", flattenSagemakerFlowDefinitionOutputConfig(flowDefinition.OutputConfig)); err != nil { + return fmt.Errorf("error setting output_config: %w", err) + } + + tags, err := keyvaluetags.SagemakerListTags(conn, arn) + + if err != nil { + return fmt.Errorf("error listing tags for SageMaker Flow Definition (%s): %w", d.Id(), err) + } + + tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) + } + + return nil +} + +func resourceAwsSagemakerFlowDefinitionUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := keyvaluetags.SagemakerUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating SageMaker Flow Definition (%s) tags: %w", d.Id(), err) + } + } + + return resourceAwsSagemakerFlowDefinitionRead(d, meta) +} + +func resourceAwsSagemakerFlowDefinitionDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + log.Printf("[DEBUG] Deleting SageMaker Flow Definition: %s", d.Id()) + _, err := conn.DeleteFlowDefinition(&sagemaker.DeleteFlowDefinitionInput{ + FlowDefinitionName: aws.String(d.Id()), + }) + + if tfawserr.ErrMessageContains(err, "ValidationException", "The work team") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting SageMaker Flow Definition (%s): %w", d.Id(), err) + } + + return nil +} + +func expandSagemakerFlowDefinitionHumanLoopActivationConfig(l []interface{}) *sagemaker.HumanLoopActivationConfig { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.HumanLoopActivationConfig{ + HumanLoopActivationConditionsConfig: expandSagemakerFlowDefinitionHumanLoopActivationConditionsConfig(m["human_loop_activation_conditions_config"].([]interface{})), + } + + return config +} + +func flattenSagemakerFlowDefinitionHumanLoopActivationConfig(config *sagemaker.HumanLoopActivationConfig) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{ + "human_loop_activation_conditions_config": flattenSagemakerFlowDefinitionHumanLoopActivationConditionsConfig(config.HumanLoopActivationConditionsConfig), + } + + return []map[string]interface{}{m} +} + +func expandSagemakerFlowDefinitionHumanLoopActivationConditionsConfig(l []interface{}) *sagemaker.HumanLoopActivationConditionsConfig { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + v, _ := protocol.DecodeJSONValue(m["human_loop_activation_conditions"].(string), protocol.NoEscape) + // if err != nil { + // return err + // } + + config := &sagemaker.HumanLoopActivationConditionsConfig{ + HumanLoopActivationConditions: aws.JSONValue(v), + } + + return config +} + +func flattenSagemakerFlowDefinitionHumanLoopActivationConditionsConfig(config *sagemaker.HumanLoopActivationConditionsConfig) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + v, _ := protocol.EncodeJSONValue(config.HumanLoopActivationConditions, protocol.NoEscape) + // if err != nil { + // return err + // } + + m := map[string]interface{}{ + "human_loop_activation_conditions": v, + } + + return []map[string]interface{}{m} +} + +func expandSagemakerFlowDefinitionOutputConfig(l []interface{}) *sagemaker.FlowDefinitionOutputConfig { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.FlowDefinitionOutputConfig{ + S3OutputPath: aws.String(m["s3_output_path"].(string)), + } + + if v, ok := m["kms_key_id"].(string); ok && v != "" { + config.KmsKeyId = aws.String(v) + } + + return config +} + +func flattenSagemakerFlowDefinitionOutputConfig(config *sagemaker.FlowDefinitionOutputConfig) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{ + "kms_key_id": aws.StringValue(config.KmsKeyId), + "s3_output_path": aws.StringValue(config.S3OutputPath), + } + + return []map[string]interface{}{m} +} + +func expandSagemakerFlowDefinitionHumanLoopRequestSource(l []interface{}) *sagemaker.HumanLoopRequestSource { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.HumanLoopRequestSource{ + AwsManagedHumanLoopRequestSource: aws.String(m["aws_managed_human_loop_request_source"].(string)), + } + + return config +} + +func flattenSagemakerFlowDefinitionHumanLoopRequestSource(config *sagemaker.HumanLoopRequestSource) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{ + "aws_managed_human_loop_request_source": aws.StringValue(config.AwsManagedHumanLoopRequestSource), + } + + return []map[string]interface{}{m} +} + +func expandSagemakerFlowDefinitionHumanLoopConfig(l []interface{}) *sagemaker.HumanLoopConfig { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.HumanLoopConfig{ + HumanTaskUiArn: aws.String(m["human_task_ui_arn"].(string)), + TaskCount: aws.Int64(int64(m["task_count"].(int))), + TaskDescription: aws.String(m["task_description"].(string)), + TaskTitle: aws.String(m["task_title"].(string)), + WorkteamArn: aws.String(m["workteam_arn"].(string)), + } + + if v, ok := m["task_keywords"].(*schema.Set); ok && v.Len() > 0 { + config.TaskKeywords = expandStringSet(v) + } + + if v, ok := m["task_availability_lifetime_in_seconds"].(int); ok { + config.TaskAvailabilityLifetimeInSeconds = aws.Int64(int64(v)) + } + + if v, ok := m["task_time_limit_in_seconds"].(int); ok { + config.TaskTimeLimitInSeconds = aws.Int64(int64(v)) + } + + return config +} + +func flattenSagemakerFlowDefinitionHumanLoopConfig(config *sagemaker.HumanLoopConfig) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{ + "human_task_ui_arn": aws.StringValue(config.HumanTaskUiArn), + "task_count": aws.Int64Value(config.TaskCount), + "task_description": aws.StringValue(config.TaskDescription), + "task_title": aws.StringValue(config.TaskTitle), + "workteam_arn": aws.StringValue(config.WorkteamArn), + } + + if config.TaskKeywords != nil { + m["task_keywords"] = flattenStringSet(config.TaskKeywords) + } + + if config.TaskAvailabilityLifetimeInSeconds != nil { + m["task_availability_lifetime_in_seconds"] = aws.Int64Value(config.TaskAvailabilityLifetimeInSeconds) + } + + if config.TaskTimeLimitInSeconds != nil { + m["task_time_limit_in_seconds"] = aws.Int64Value(config.TaskTimeLimitInSeconds) + } + + return []map[string]interface{}{m} +} From bf17766d50373f95cb2767f262c92161a8a88351 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Tue, 7 Sep 2021 17:33:22 +0300 Subject: [PATCH 02/10] sagemaker flow def - basic tests --- aws/resource_aws_sagemaker_flow_definition.go | 7 +- ...urce_aws_sagemaker_flow_definition_test.go | 362 ++++++++++++++++++ 2 files changed, 367 insertions(+), 2 deletions(-) create mode 100644 aws/resource_aws_sagemaker_flow_definition_test.go diff --git a/aws/resource_aws_sagemaker_flow_definition.go b/aws/resource_aws_sagemaker_flow_definition.go index c3ec20c82fb9..d3bfdbf0ee07 100644 --- a/aws/resource_aws_sagemaker_flow_definition.go +++ b/aws/resource_aws_sagemaker_flow_definition.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sagemaker/finder" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) @@ -215,7 +216,9 @@ func resourceAwsSagemakerFlowDefinitionCreate(d *schema.ResourceData, meta inter } log.Printf("[DEBUG] Creating SageMaker Flow Definition: %s", input) - _, err := conn.CreateFlowDefinition(input) + _, err := tfresource.RetryWhenAwsErrCodeEquals(iamwaiter.PropagationTimeout, func() (interface{}, error) { + return conn.CreateFlowDefinition(input) + }, "ValidationException") if err != nil { return fmt.Errorf("error creating SageMaker Flow Definition (%s): %w", name, err) } @@ -305,7 +308,7 @@ func resourceAwsSagemakerFlowDefinitionDelete(d *schema.ResourceData, meta inter FlowDefinitionName: aws.String(d.Id()), }) - if tfawserr.ErrMessageContains(err, "ValidationException", "The work team") { + if tfawserr.ErrCodeEquals(err, sagemaker.ErrCodeResourceNotFound) { return nil } diff --git a/aws/resource_aws_sagemaker_flow_definition_test.go b/aws/resource_aws_sagemaker_flow_definition_test.go new file mode 100644 index 000000000000..cbda68c67b26 --- /dev/null +++ b/aws/resource_aws_sagemaker_flow_definition_test.go @@ -0,0 +1,362 @@ +package aws + +import ( + "fmt" + "log" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/sagemaker" + "github.com/hashicorp/go-multierror" + "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" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sagemaker/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func init() { + resource.AddTestSweepers("aws_sagemaker_flow_definition", &resource.Sweeper{ + Name: "aws_sagemaker_flow_definition", + F: testSweepSagemakerFlowDefinitions, + }) +} + +func testSweepSagemakerFlowDefinitions(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.(*AWSClient).sagemakerconn + var sweeperErrs *multierror.Error + + err = conn.ListFlowDefinitionsPages(&sagemaker.ListFlowDefinitionsInput{}, func(page *sagemaker.ListFlowDefinitionsOutput, lastPage bool) bool { + for _, flowDefinition := range page.FlowDefinitionSummaries { + + r := resourceAwsSagemakerFlowDefinition() + d := r.Data(nil) + d.SetId(aws.StringValue(flowDefinition.FlowDefinitionName)) + err := r.Delete(d, client) + if err != nil { + log.Printf("[ERROR] %s", err) + sweeperErrs = multierror.Append(sweeperErrs, err) + continue + } + } + + return !lastPage + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping SageMaker Flow Definition sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Sagemaker Flow Definitions: %w", err)) + } + + return sweeperErrs.ErrorOrNil() +} + +func TestAccAWSSagemakerFlowDefinition_basic(t *testing.T) { + var flowDefinition sagemaker.DescribeFlowDefinitionOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_flow_definition.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, sagemaker.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerFlowDefinitionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerFlowDefinitionBasicConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerFlowDefinitionExists(resourceName, &flowDefinition), + resource.TestCheckResourceAttr(resourceName, "flow_definition_name", rName), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "sagemaker", fmt.Sprintf("flow-definition/%s", rName)), + resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "human_loop_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "human_loop_config.0.human_task_ui_arn", "aws_sagemaker_human_task_ui.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "human_loop_config.0.task_availability_lifetime_in_seconds", "1"), + resource.TestCheckResourceAttr(resourceName, "human_loop_config.0.task_count", "1"), + resource.TestCheckResourceAttr(resourceName, "human_loop_config.0.task_description", rName), + resource.TestCheckResourceAttr(resourceName, "human_loop_config.0.task_title", rName), + resource.TestCheckResourceAttrPair(resourceName, "human_loop_config.0.workteam_arn", "aws_sagemaker_workteam.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "output_config.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "output_config.0.s3_output_path"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSSagemakerFlowDefinition_tags(t *testing.T) { + var flowDefinition sagemaker.DescribeFlowDefinitionOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_flow_definition.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, sagemaker.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerFlowDefinitionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerFlowDefinitionTagsConfig1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerFlowDefinitionExists(resourceName, &flowDefinition), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSSagemakerFlowDefinitionTagsConfig2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerFlowDefinitionExists(resourceName, &flowDefinition), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSSagemakerFlowDefinitionTagsConfig1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerFlowDefinitionExists(resourceName, &flowDefinition), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func TestAccAWSSagemakerFlowDefinition_disappears(t *testing.T) { + var flowDefinition sagemaker.DescribeFlowDefinitionOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_flow_definition.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, sagemaker.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerFlowDefinitionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerFlowDefinitionBasicConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerFlowDefinitionExists(resourceName, &flowDefinition), + testAccCheckResourceDisappears(testAccProvider, resourceAwsSagemakerFlowDefinition(), resourceName), + testAccCheckResourceDisappears(testAccProvider, resourceAwsSagemakerFlowDefinition(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAWSSagemakerFlowDefinitionDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).sagemakerconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_sagemaker_flow_definition" { + continue + } + + _, err := finder.FlowDefinitionByName(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("SageMaker Flow Definition %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckAWSSagemakerFlowDefinitionExists(n string, flowDefinition *sagemaker.DescribeFlowDefinitionOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No SageMaker Flow Definition ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).sagemakerconn + + output, err := finder.FlowDefinitionByName(conn, rs.Primary.ID) + + if err != nil { + return err + } + + *flowDefinition = *output + + return nil + } +} + +func testAccAWSSagemakerFlowDefinitionBaseConfig(rName string) string { + return composeConfig(testAccAWSSagemakerWorkteamCognitoConfig(rName), + fmt.Sprintf(` +resource "aws_sagemaker_human_task_ui" "test" { + human_task_ui_name = %[1]q + + ui_template { + content = file("test-fixtures/sagemaker-human-task-ui-tmpl.html") + } +} + +resource "aws_s3_bucket" "test" { + bucket = %[1]q + acl = "private" + force_destroy = true +} + +resource "aws_iam_role" "test" { + name = %[1]q + path = "/" + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +data "aws_iam_policy_document" "assume_role" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["sagemaker.amazonaws.com"] + } + } +} + +resource "aws_iam_role_policy" "test" { + name = %[1]q + role = aws_iam_role.test.id + + policy = < Date: Tue, 7 Sep 2021 20:14:39 +0300 Subject: [PATCH 03/10] sagemaker flow def - public wf + tests --- aws/resource_aws_sagemaker_flow_definition.go | 197 +++++++++++++++--- ...urce_aws_sagemaker_flow_definition_test.go | 158 +++++++++++++- 2 files changed, 321 insertions(+), 34 deletions(-) diff --git a/aws/resource_aws_sagemaker_flow_definition.go b/aws/resource_aws_sagemaker_flow_definition.go index d3bfdbf0ee07..98729c8e9f31 100644 --- a/aws/resource_aws_sagemaker_flow_definition.go +++ b/aws/resource_aws_sagemaker_flow_definition.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/service/sagemaker" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" @@ -43,10 +44,11 @@ func resourceAwsSagemakerFlowDefinition() *schema.Resource { ), }, "human_loop_activation_config": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + RequiredWith: []string{"human_loop_request_source", "human_loop_activation_config"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "human_loop_activation_conditions_config": { @@ -57,10 +59,18 @@ func resourceAwsSagemakerFlowDefinition() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "human_loop_activation_conditions": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringLenBetween(1, 10240), + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 10240), + validation.StringIsJSON, + ), + StateFunc: func(v interface{}) string { + json, _ := structure.NormalizeJsonString(v) + return json + }, + DiffSuppressFunc: suppressEquivalentJsonDiffs, }, }, }, @@ -81,6 +91,44 @@ func resourceAwsSagemakerFlowDefinition() *schema.Resource { ForceNew: true, ValidateFunc: validateArn, }, + "public_workforce_task_price": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "amount_in_usd": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cents": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(0, 99), + }, + "dollars": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(0, 2), + }, + "tenth_fractions_of_a_cent": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntBetween(0, 9), + }, + }, + }, + }, + }, + }, + }, "task_availability_lifetime_in_seconds": { Type: schema.TypeInt, Optional: true, @@ -135,10 +183,11 @@ func resourceAwsSagemakerFlowDefinition() *schema.Resource { }, }, "human_loop_request_source": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + RequiredWith: []string{"human_loop_request_source", "human_loop_activation_config"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "aws_managed_human_loop_request_source": { @@ -204,7 +253,11 @@ func resourceAwsSagemakerFlowDefinitionCreate(d *schema.ResourceData, meta inter } if v, ok := d.GetOk("human_loop_activation_config"); ok && (len(v.([]interface{})) > 0) { - input.HumanLoopActivationConfig = expandSagemakerFlowDefinitionHumanLoopActivationConfig(v.([]interface{})) + loopConfig, err := expandSagemakerFlowDefinitionHumanLoopActivationConfig(v.([]interface{})) + if err != nil { + return fmt.Errorf("error creating SageMaker Flow Definition Human Loop Activation Config (%s): %w", name, err) + } + input.HumanLoopActivationConfig = loopConfig } if v, ok := d.GetOk("human_loop_request_source"); ok && (len(v.([]interface{})) > 0) { @@ -319,18 +372,22 @@ func resourceAwsSagemakerFlowDefinitionDelete(d *schema.ResourceData, meta inter return nil } -func expandSagemakerFlowDefinitionHumanLoopActivationConfig(l []interface{}) *sagemaker.HumanLoopActivationConfig { +func expandSagemakerFlowDefinitionHumanLoopActivationConfig(l []interface{}) (*sagemaker.HumanLoopActivationConfig, error) { if len(l) == 0 || l[0] == nil { - return nil + return nil, nil } m := l[0].(map[string]interface{}) + loopConfig, err := expandSagemakerFlowDefinitionHumanLoopActivationConditionsConfig(m["human_loop_activation_conditions_config"].([]interface{})) + if err != nil { + return nil, err + } config := &sagemaker.HumanLoopActivationConfig{ - HumanLoopActivationConditionsConfig: expandSagemakerFlowDefinitionHumanLoopActivationConditionsConfig(m["human_loop_activation_conditions_config"].([]interface{})), + HumanLoopActivationConditionsConfig: loopConfig, } - return config + return config, nil } func flattenSagemakerFlowDefinitionHumanLoopActivationConfig(config *sagemaker.HumanLoopActivationConfig) []map[string]interface{} { @@ -345,23 +402,23 @@ func flattenSagemakerFlowDefinitionHumanLoopActivationConfig(config *sagemaker.H return []map[string]interface{}{m} } -func expandSagemakerFlowDefinitionHumanLoopActivationConditionsConfig(l []interface{}) *sagemaker.HumanLoopActivationConditionsConfig { +func expandSagemakerFlowDefinitionHumanLoopActivationConditionsConfig(l []interface{}) (*sagemaker.HumanLoopActivationConditionsConfig, error) { if len(l) == 0 || l[0] == nil { - return nil + return nil, nil } m := l[0].(map[string]interface{}) - v, _ := protocol.DecodeJSONValue(m["human_loop_activation_conditions"].(string), protocol.NoEscape) - // if err != nil { - // return err - // } + v, err := protocol.DecodeJSONValue(m["human_loop_activation_conditions"].(string), protocol.NoEscape) + if err != nil { + return nil, err + } config := &sagemaker.HumanLoopActivationConditionsConfig{ HumanLoopActivationConditions: aws.JSONValue(v), } - return config + return config, nil } func flattenSagemakerFlowDefinitionHumanLoopActivationConditionsConfig(config *sagemaker.HumanLoopActivationConditionsConfig) []map[string]interface{} { @@ -369,10 +426,10 @@ func flattenSagemakerFlowDefinitionHumanLoopActivationConditionsConfig(config *s return []map[string]interface{}{} } - v, _ := protocol.EncodeJSONValue(config.HumanLoopActivationConditions, protocol.NoEscape) - // if err != nil { - // return err - // } + v, err := protocol.EncodeJSONValue(config.HumanLoopActivationConditions, protocol.NoEscape) + if err != nil { + return []map[string]interface{}{} + } m := map[string]interface{}{ "human_loop_activation_conditions": v, @@ -453,6 +510,10 @@ func expandSagemakerFlowDefinitionHumanLoopConfig(l []interface{}) *sagemaker.Hu WorkteamArn: aws.String(m["workteam_arn"].(string)), } + if v, ok := m["public_workforce_task_price"].([]interface{}); ok && len(v) > 0 { + config.PublicWorkforceTaskPrice = expandSagemakerFlowDefinitionPublicWorkforceTaskPrice(v) + } + if v, ok := m["task_keywords"].(*schema.Set); ok && v.Len() > 0 { config.TaskKeywords = expandStringSet(v) } @@ -481,6 +542,10 @@ func flattenSagemakerFlowDefinitionHumanLoopConfig(config *sagemaker.HumanLoopCo "workteam_arn": aws.StringValue(config.WorkteamArn), } + if config.PublicWorkforceTaskPrice != nil { + m["public_workforce_task_price"] = flattenSagemakerFlowDefinitionPublicWorkforceTaskPrice(config.PublicWorkforceTaskPrice) + } + if config.TaskKeywords != nil { m["task_keywords"] = flattenStringSet(config.TaskKeywords) } @@ -495,3 +560,79 @@ func flattenSagemakerFlowDefinitionHumanLoopConfig(config *sagemaker.HumanLoopCo return []map[string]interface{}{m} } + +func expandSagemakerFlowDefinitionPublicWorkforceTaskPrice(l []interface{}) *sagemaker.PublicWorkforceTaskPrice { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.PublicWorkforceTaskPrice{} + + if v, ok := m["amount_in_usd"].([]interface{}); ok && len(v) > 0 { + config.AmountInUsd = expandSagemakerFlowDefinitionAmountInUsd(v) + } + + return config +} + +func expandSagemakerFlowDefinitionAmountInUsd(l []interface{}) *sagemaker.USD { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.USD{} + + if v, ok := m["cents"].(int); ok { + config.Cents = aws.Int64(int64(v)) + } + + if v, ok := m["dollars"].(int); ok { + config.Dollars = aws.Int64(int64(v)) + } + + if v, ok := m["tenth_fractions_of_a_cent"].(int); ok { + config.TenthFractionsOfACent = aws.Int64(int64(v)) + } + + return config +} + +func flattenSagemakerFlowDefinitionAmountInUsd(config *sagemaker.USD) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{} + + if config.Cents != nil { + m["cents"] = aws.Int64Value(config.Cents) + } + + if config.Dollars != nil { + m["dollars"] = aws.Int64Value(config.Dollars) + } + + if config.TenthFractionsOfACent != nil { + m["tenth_fractions_of_a_cent"] = aws.Int64Value(config.TenthFractionsOfACent) + } + + return []map[string]interface{}{m} +} + +func flattenSagemakerFlowDefinitionPublicWorkforceTaskPrice(config *sagemaker.PublicWorkforceTaskPrice) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{} + + if config.AmountInUsd != nil { + m["amount_in_usd"] = flattenSagemakerFlowDefinitionAmountInUsd(config.AmountInUsd) + } + + return []map[string]interface{}{m} +} diff --git a/aws/resource_aws_sagemaker_flow_definition_test.go b/aws/resource_aws_sagemaker_flow_definition_test.go index cbda68c67b26..583bacc4990d 100644 --- a/aws/resource_aws_sagemaker_flow_definition_test.go +++ b/aws/resource_aws_sagemaker_flow_definition_test.go @@ -77,7 +77,10 @@ func TestAccAWSSagemakerFlowDefinition_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "flow_definition_name", rName), testAccCheckResourceAttrRegionalARN(resourceName, "arn", "sagemaker", fmt.Sprintf("flow-definition/%s", rName)), resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "human_loop_request_source.#", "0"), + resource.TestCheckResourceAttr(resourceName, "human_loop_activation_config.#", "0"), resource.TestCheckResourceAttr(resourceName, "human_loop_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "human_loop_config.0.public_workforce_task_price.#", "0"), resource.TestCheckResourceAttrPair(resourceName, "human_loop_config.0.human_task_ui_arn", "aws_sagemaker_human_task_ui.test", "arn"), resource.TestCheckResourceAttr(resourceName, "human_loop_config.0.task_availability_lifetime_in_seconds", "1"), resource.TestCheckResourceAttr(resourceName, "human_loop_config.0.task_count", "1"), @@ -98,6 +101,70 @@ func TestAccAWSSagemakerFlowDefinition_basic(t *testing.T) { }) } +func TestAccAWSSagemakerFlowDefinition_humanLoopConfig_publicWorkforce(t *testing.T) { + var flowDefinition sagemaker.DescribeFlowDefinitionOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_flow_definition.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, sagemaker.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerFlowDefinitionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerFlowDefinitionPublicWorkforceConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerFlowDefinitionExists(resourceName, &flowDefinition), + resource.TestCheckResourceAttr(resourceName, "flow_definition_name", rName), + resource.TestCheckResourceAttr(resourceName, "human_loop_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "human_loop_config.0.public_workforce_task_price.#", "1"), + resource.TestCheckResourceAttr(resourceName, "human_loop_config.0.public_workforce_task_price.0.amount_in_usd.#", "1"), + resource.TestCheckResourceAttr(resourceName, "human_loop_config.0.public_workforce_task_price.0.amount_in_usd.0.cents", "1"), + resource.TestCheckResourceAttr(resourceName, "human_loop_config.0.public_workforce_task_price.0.amount_in_usd.0.tenth_fractions_of_a_cent", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSSagemakerFlowDefinition_humanLoopRequestSource(t *testing.T) { + var flowDefinition sagemaker.DescribeFlowDefinitionOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_flow_definition.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, sagemaker.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerFlowDefinitionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerFlowDefinitionHumanLoopRequestSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerFlowDefinitionExists(resourceName, &flowDefinition), + resource.TestCheckResourceAttr(resourceName, "flow_definition_name", rName), + resource.TestCheckResourceAttr(resourceName, "human_loop_request_source.#", "1"), + resource.TestCheckResourceAttr(resourceName, "human_loop_request_source.0.aws_managed_human_loop_request_source", "AWS/Textract/AnalyzeDocument/Forms/V1"), + resource.TestCheckResourceAttr(resourceName, "human_loop_activation_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "human_loop_activation_config.0.human_loop_activation_conditions_config.#", "1"), + resource.TestCheckResourceAttrSet(resourceName, "human_loop_activation_config.0.human_loop_activation_conditions_config.0.human_loop_activation_conditions"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAWSSagemakerFlowDefinition_tags(t *testing.T) { var flowDefinition sagemaker.DescribeFlowDefinitionOutput rName := acctest.RandomWithPrefix("tf-acc-test") @@ -217,8 +284,7 @@ func testAccCheckAWSSagemakerFlowDefinitionExists(n string, flowDefinition *sage } func testAccAWSSagemakerFlowDefinitionBaseConfig(rName string) string { - return composeConfig(testAccAWSSagemakerWorkteamCognitoConfig(rName), - fmt.Sprintf(` + return fmt.Sprintf(` resource "aws_sagemaker_human_task_ui" "test" { human_task_ui_name = %[1]q @@ -280,15 +346,16 @@ resource "aws_iam_role_policy" "test" { } EOF } -`, rName)) +`, rName) } func testAccAWSSagemakerFlowDefinitionBasicConfig(rName string) string { return composeConfig(testAccAWSSagemakerFlowDefinitionBaseConfig(rName), + testAccAWSSagemakerWorkteamCognitoConfig(rName), fmt.Sprintf(` resource "aws_sagemaker_flow_definition" "test" { flow_definition_name = %[1]q - role_arn = aws_iam_role.test.arn + role_arn = aws_iam_role.test.arn human_loop_config { human_task_ui_arn = aws_sagemaker_human_task_ui.test.arn @@ -306,12 +373,90 @@ resource "aws_sagemaker_flow_definition" "test" { `, rName)) } +func testAccAWSSagemakerFlowDefinitionPublicWorkforceConfig(rName string) string { + return composeConfig(testAccAWSSagemakerFlowDefinitionBaseConfig(rName), + fmt.Sprintf(` +data "aws_region" "current" {} + +resource "aws_sagemaker_flow_definition" "test" { + flow_definition_name = %[1]q + role_arn = aws_iam_role.test.arn + + human_loop_config { + human_task_ui_arn = aws_sagemaker_human_task_ui.test.arn + task_availability_lifetime_in_seconds = 1 + task_count = 1 + task_description = %[1]q + task_title = %[1]q + workteam_arn = "arn:aws:sagemaker:${data.aws_region.current.name}:394669845002:workteam/public-crowd/default" + + public_workforce_task_price { + amount_in_usd { + cents = 1 + tenth_fractions_of_a_cent = 2 + } + } + } + + output_config { + s3_output_path = "s3://${aws_s3_bucket.test.bucket}/" + } +} +`, rName)) +} + +func testAccAWSSagemakerFlowDefinitionHumanLoopRequestSourceConfig(rName string) string { + return composeConfig(testAccAWSSagemakerFlowDefinitionBaseConfig(rName), + testAccAWSSagemakerWorkteamCognitoConfig(rName), + fmt.Sprintf(` +resource "aws_sagemaker_flow_definition" "test" { + flow_definition_name = %[1]q + role_arn = aws_iam_role.test.arn + + human_loop_config { + human_task_ui_arn = aws_sagemaker_human_task_ui.test.arn + task_availability_lifetime_in_seconds = 1 + task_count = 1 + task_description = %[1]q + task_title = %[1]q + workteam_arn = aws_sagemaker_workteam.test.arn + } + + human_loop_request_source { + aws_managed_human_loop_request_source = "AWS/Textract/AnalyzeDocument/Forms/V1" + } + + human_loop_activation_config { + human_loop_activation_conditions_config { + human_loop_activation_conditions = < Date: Tue, 7 Sep 2021 21:43:44 +0300 Subject: [PATCH 04/10] docs --- ...urce_aws_sagemaker_flow_definition_test.go | 6 +- .../r/sagemaker_flow_definition.html.markdown | 175 ++++++++++++++++++ 2 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 website/docs/r/sagemaker_flow_definition.html.markdown diff --git a/aws/resource_aws_sagemaker_flow_definition_test.go b/aws/resource_aws_sagemaker_flow_definition_test.go index 583bacc4990d..8cb92e8e6fbb 100644 --- a/aws/resource_aws_sagemaker_flow_definition_test.go +++ b/aws/resource_aws_sagemaker_flow_definition_test.go @@ -390,12 +390,12 @@ resource "aws_sagemaker_flow_definition" "test" { task_title = %[1]q workteam_arn = "arn:aws:sagemaker:${data.aws_region.current.name}:394669845002:workteam/public-crowd/default" - public_workforce_task_price { + public_workforce_task_price { amount_in_usd { cents = 1 tenth_fractions_of_a_cent = 2 - } - } + } + } } output_config { diff --git a/website/docs/r/sagemaker_flow_definition.html.markdown b/website/docs/r/sagemaker_flow_definition.html.markdown new file mode 100644 index 000000000000..ab8184bd37d8 --- /dev/null +++ b/website/docs/r/sagemaker_flow_definition.html.markdown @@ -0,0 +1,175 @@ +--- +subcategory: "Sagemaker" +layout: "aws" +page_title: "AWS: aws_sagemaker_flow_definition" +description: |- + Provides a Sagemaker Flow Definition resource. +--- + +# Resource: aws_sagemaker_flow_definition + +Provides a Sagemaker Flow Definition resource. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_sagemaker_flow_definition" "example" { + flow_definition_name = "example" + role_arn = aws_iam_role.example.arn + + human_loop_config { + human_task_ui_arn = aws_sagemaker_human_task_ui.example.arn + task_availability_lifetime_in_seconds = 1 + task_count = 1 + task_description = "example" + task_title = "example" + workteam_arn = aws_sagemaker_workteam.example.arn + } + + output_config { + s3_output_path = "s3://${aws_s3_bucket.example.bucket}/" + } +} +``` + +### Public Workteam Usage + +```terraform +resource "aws_sagemaker_flow_definition" "example" { + flow_definition_name = "example" + role_arn = aws_iam_role.example.arn + + human_loop_config { + human_task_ui_arn = aws_sagemaker_human_task_ui.example.arn + task_availability_lifetime_in_seconds = 1 + task_count = 1 + task_description = "example" + task_title = "example" + workteam_arn = "arn:aws:sagemaker:${data.aws_region.current.name}:394669845002:workteam/public-crowd/default" + + public_workforce_task_price { + amount_in_usd { + cents = 1 + tenth_fractions_of_a_cent = 2 + } + } + } + + output_config { + s3_output_path = "s3://${aws_s3_bucket.example.bucket}/" + } +} +``` + +### Human Loop Activation Config Usage + +```terraform +resource "aws_sagemaker_flow_definition" "example" { + flow_definition_name = "example" + role_arn = aws_iam_role.example.arn + + human_loop_config { + human_task_ui_arn = aws_sagemaker_human_task_ui.example.arn + task_availability_lifetime_in_seconds = 1 + task_count = 1 + task_description = "example" + task_title = "example" + workteam_arn = aws_sagemaker_workteam.example.arn + } + + human_loop_request_source { + aws_managed_human_loop_request_source = "AWS/Textract/AnalyzeDocument/Forms/V1" + } + + human_loop_activation_config { + human_loop_activation_conditions_config { + human_loop_activation_conditions = < Date: Tue, 7 Sep 2021 21:45:17 +0300 Subject: [PATCH 05/10] changelog --- .changelog/20825.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/20825.txt diff --git a/.changelog/20825.txt b/.changelog/20825.txt new file mode 100644 index 000000000000..e3cd4fd39e4f --- /dev/null +++ b/.changelog/20825.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_sagemaker_flow_definition +``` \ No newline at end of file From 9fb31f2894a896857b2f6467669dc48e5bbf692c Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Tue, 7 Sep 2021 21:50:19 +0300 Subject: [PATCH 06/10] fmt --- aws/resource_aws_sagemaker_flow_definition_test.go | 4 ++-- website/docs/r/sagemaker_flow_definition.html.markdown | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aws/resource_aws_sagemaker_flow_definition_test.go b/aws/resource_aws_sagemaker_flow_definition_test.go index 8cb92e8e6fbb..2ac6462d0092 100644 --- a/aws/resource_aws_sagemaker_flow_definition_test.go +++ b/aws/resource_aws_sagemaker_flow_definition_test.go @@ -428,7 +428,7 @@ resource "aws_sagemaker_flow_definition" "test" { human_loop_activation_config { human_loop_activation_conditions_config { - human_loop_activation_conditions = < Date: Tue, 7 Sep 2021 23:40:59 +0300 Subject: [PATCH 07/10] add waiter --- .../service/sagemaker/waiter/status.go | 20 +++++++++ .../service/sagemaker/waiter/waiter.go | 44 +++++++++++++++++++ aws/resource_aws_sagemaker_flow_definition.go | 12 +++++ ...urce_aws_sagemaker_flow_definition_test.go | 2 +- 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/aws/internal/service/sagemaker/waiter/status.go b/aws/internal/service/sagemaker/waiter/status.go index 231c8440b837..7dd8484da029 100644 --- a/aws/internal/service/sagemaker/waiter/status.go +++ b/aws/internal/service/sagemaker/waiter/status.go @@ -172,6 +172,26 @@ func FeatureGroupStatus(conn *sagemaker.SageMaker, name string) resource.StateRe } } +func FlowDefinitionStatus(conn *sagemaker.SageMaker, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := finder.FlowDefinitionByName(conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + if aws.StringValue(output.FlowDefinitionStatus) == sagemaker.FlowDefinitionStatusFailed { + return output, sagemaker.FlowDefinitionStatusFailed, fmt.Errorf("%s", aws.StringValue(output.FailureReason)) + } + + return output, aws.StringValue(output.FlowDefinitionStatus), nil + } +} + // UserProfileStatus fetches the UserProfile and its Status func UserProfileStatus(conn *sagemaker.SageMaker, domainID, userProfileName string) resource.StateRefreshFunc { return func() (interface{}, string, error) { diff --git a/aws/internal/service/sagemaker/waiter/waiter.go b/aws/internal/service/sagemaker/waiter/waiter.go index 1423f652f852..4a9cf6d7a25d 100644 --- a/aws/internal/service/sagemaker/waiter/waiter.go +++ b/aws/internal/service/sagemaker/waiter/waiter.go @@ -28,6 +28,8 @@ const ( UserProfileDeletedTimeout = 10 * time.Minute AppInServiceTimeout = 10 * time.Minute AppDeletedTimeout = 10 * time.Minute + FlowDefinitionActiveTimeout = 2 * time.Minute + FlowDefinitionDeletedTimeout = 2 * time.Minute ) // NotebookInstanceInService waits for a NotebookInstance to return InService @@ -382,3 +384,45 @@ func AppDeleted(conn *sagemaker.SageMaker, domainID, userProfileName, appType, a return nil, err } + +// FlowDefinitionActive waits for a FlowDefinition to return Active +func FlowDefinitionActive(conn *sagemaker.SageMaker, name string) (*sagemaker.DescribeFlowDefinitionOutput, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + sagemaker.FlowDefinitionStatusInitializing, + }, + Target: []string{ + sagemaker.FlowDefinitionStatusActive, + }, + Refresh: FlowDefinitionStatus(conn, name), + Timeout: FlowDefinitionActiveTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*sagemaker.DescribeFlowDefinitionOutput); ok { + return output, err + } + + return nil, err +} + +// FlowDefinitionDeleted waits for a FlowDefinition to return Deleted +func FlowDefinitionDeleted(conn *sagemaker.SageMaker, name string) (*sagemaker.DescribeFlowDefinitionOutput, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + sagemaker.FlowDefinitionStatusDeleting, + }, + Target: []string{}, + Refresh: FlowDefinitionStatus(conn, name), + Timeout: FlowDefinitionDeletedTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*sagemaker.DescribeFlowDefinitionOutput); ok { + return output, err + } + + return nil, err +} diff --git a/aws/resource_aws_sagemaker_flow_definition.go b/aws/resource_aws_sagemaker_flow_definition.go index 98729c8e9f31..dc894c3d45d4 100644 --- a/aws/resource_aws_sagemaker_flow_definition.go +++ b/aws/resource_aws_sagemaker_flow_definition.go @@ -15,6 +15,7 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sagemaker/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sagemaker/waiter" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) @@ -278,6 +279,10 @@ func resourceAwsSagemakerFlowDefinitionCreate(d *schema.ResourceData, meta inter d.SetId(name) + if _, err := waiter.FlowDefinitionActive(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for SageMaker Flow Definition (%s) to Active: %w", d.Id(), err) + } + return resourceAwsSagemakerFlowDefinitionRead(d, meta) } @@ -369,6 +374,13 @@ func resourceAwsSagemakerFlowDefinitionDelete(d *schema.ResourceData, meta inter return fmt.Errorf("error deleting SageMaker Flow Definition (%s): %w", d.Id(), err) } + if _, err := waiter.FlowDefinitionDeleted(conn, d.Id()); err != nil { + if tfawserr.ErrCodeEquals(err, sagemaker.ErrCodeResourceNotFound) { + return nil + } + return fmt.Errorf("error waiting for SageMaker Flow Definition (%s) to be deleted: %w", d.Id(), err) + } + return nil } diff --git a/aws/resource_aws_sagemaker_flow_definition_test.go b/aws/resource_aws_sagemaker_flow_definition_test.go index 2ac6462d0092..6c0b8047272a 100644 --- a/aws/resource_aws_sagemaker_flow_definition_test.go +++ b/aws/resource_aws_sagemaker_flow_definition_test.go @@ -393,7 +393,7 @@ resource "aws_sagemaker_flow_definition" "test" { public_workforce_task_price { amount_in_usd { cents = 1 - tenth_fractions_of_a_cent = 2 + tenth_fractions_of_a_cent = 2 } } } From 5f92bb11bcf7408ded800a95ad5f411c9c57626c Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Wed, 8 Sep 2021 00:08:52 +0300 Subject: [PATCH 08/10] fmt --- aws/resource_aws_sagemaker_flow_definition.go | 2 +- aws/resource_aws_sagemaker_flow_definition_test.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_sagemaker_flow_definition.go b/aws/resource_aws_sagemaker_flow_definition.go index dc894c3d45d4..2d5532686b9d 100644 --- a/aws/resource_aws_sagemaker_flow_definition.go +++ b/aws/resource_aws_sagemaker_flow_definition.go @@ -427,7 +427,7 @@ func expandSagemakerFlowDefinitionHumanLoopActivationConditionsConfig(l []interf } config := &sagemaker.HumanLoopActivationConditionsConfig{ - HumanLoopActivationConditions: aws.JSONValue(v), + HumanLoopActivationConditions: v, } return config, nil diff --git a/aws/resource_aws_sagemaker_flow_definition_test.go b/aws/resource_aws_sagemaker_flow_definition_test.go index 6c0b8047272a..2a1e95fdf824 100644 --- a/aws/resource_aws_sagemaker_flow_definition_test.go +++ b/aws/resource_aws_sagemaker_flow_definition_test.go @@ -378,6 +378,8 @@ func testAccAWSSagemakerFlowDefinitionPublicWorkforceConfig(rName string) string fmt.Sprintf(` data "aws_region" "current" {} +data "aws_partition" "current" {} + resource "aws_sagemaker_flow_definition" "test" { flow_definition_name = %[1]q role_arn = aws_iam_role.test.arn @@ -388,7 +390,7 @@ resource "aws_sagemaker_flow_definition" "test" { task_count = 1 task_description = %[1]q task_title = %[1]q - workteam_arn = "arn:aws:sagemaker:${data.aws_region.current.name}:394669845002:workteam/public-crowd/default" + workteam_arn = "arn:${data.aws_partition.current.partition}:sagemaker:${data.aws_region.current.name}:394669845002:workteam/public-crowd/default" public_workforce_task_price { amount_in_usd { From 12c6a83be67fc986fbaaa76d832d636d0cda3039 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 21 Sep 2021 15:40:12 -0400 Subject: [PATCH 09/10] Tweak SageMaker finders. --- .../service/sagemaker/finder/finder.go | 23 ++++++++++++------- .../service/sagemaker/waiter/status.go | 4 ---- .../service/sagemaker/waiter/waiter.go | 20 ++++++++-------- aws/resource_aws_sagemaker_flow_definition.go | 21 ++++++++--------- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/aws/internal/service/sagemaker/finder/finder.go b/aws/internal/service/sagemaker/finder/finder.go index dcd51bca3aa5..90bcb4362d2d 100644 --- a/aws/internal/service/sagemaker/finder/finder.go +++ b/aws/internal/service/sagemaker/finder/finder.go @@ -5,6 +5,7 @@ import ( "github.com/aws/aws-sdk-go/service/sagemaker" "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + tfsagemaker "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sagemaker" "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) @@ -16,6 +17,7 @@ func CodeRepositoryByName(conn *sagemaker.SageMaker, name string) (*sagemaker.De } output, err := conn.DescribeCodeRepository(input) + if err != nil { return nil, err } @@ -35,6 +37,7 @@ func ModelPackageGroupByName(conn *sagemaker.SageMaker, name string) (*sagemaker } output, err := conn.DescribeModelPackageGroup(input) + if err != nil { return nil, err } @@ -54,6 +57,7 @@ func ImageByName(conn *sagemaker.SageMaker, name string) (*sagemaker.DescribeIma } output, err := conn.DescribeImage(input) + if err != nil { return nil, err } @@ -73,6 +77,7 @@ func ImageVersionByName(conn *sagemaker.SageMaker, name string) (*sagemaker.Desc } output, err := conn.DescribeImageVersion(input) + if err != nil { return nil, err } @@ -92,7 +97,8 @@ func DeviceFleetByName(conn *sagemaker.SageMaker, id string) (*sagemaker.Describ } output, err := conn.DescribeDeviceFleet(input) - if tfawserr.ErrMessageContains(err, "ValidationException", "No devicefleet with name") { + + if tfawserr.ErrMessageContains(err, tfsagemaker.ErrCodeValidationException, "No devicefleet with name") { return nil, &resource.NotFoundError{ LastError: err, LastRequest: input, @@ -118,6 +124,7 @@ func DomainByName(conn *sagemaker.SageMaker, domainID string) (*sagemaker.Descri } output, err := conn.DescribeDomain(input) + if err != nil { return nil, err } @@ -163,6 +170,7 @@ func UserProfileByName(conn *sagemaker.SageMaker, domainID, userProfileName stri } output, err := conn.DescribeUserProfile(input) + if err != nil { return nil, err } @@ -182,6 +190,7 @@ func AppImageConfigByName(conn *sagemaker.SageMaker, appImageConfigID string) (* } output, err := conn.DescribeAppImageConfig(input) + if err != nil { return nil, err } @@ -204,6 +213,7 @@ func AppByName(conn *sagemaker.SageMaker, domainID, userProfileName, appType, ap } output, err := conn.DescribeApp(input) + if err != nil { return nil, err } @@ -222,7 +232,7 @@ func WorkforceByName(conn *sagemaker.SageMaker, name string) (*sagemaker.Workfor output, err := conn.DescribeWorkforce(input) - if tfawserr.ErrMessageContains(err, "ValidationException", "No workforce") { + if tfawserr.ErrMessageContains(err, tfsagemaker.ErrCodeValidationException, "No workforce") { return nil, &resource.NotFoundError{ LastError: err, LastRequest: input, @@ -247,7 +257,7 @@ func WorkteamByName(conn *sagemaker.SageMaker, name string) (*sagemaker.Workteam output, err := conn.DescribeWorkteam(input) - if tfawserr.ErrMessageContains(err, "ValidationException", "The work team") { + if tfawserr.ErrMessageContains(err, tfsagemaker.ErrCodeValidationException, "The work team") { return nil, &resource.NotFoundError{ LastError: err, LastRequest: input, @@ -297,7 +307,7 @@ func EndpointConfigByName(conn *sagemaker.SageMaker, name string) (*sagemaker.De output, err := conn.DescribeEndpointConfig(input) - if tfawserr.ErrMessageContains(err, "ValidationException", "Could not find endpoint configuration") { + if tfawserr.ErrMessageContains(err, tfsagemaker.ErrCodeValidationException, "Could not find endpoint configuration") { return nil, &resource.NotFoundError{ LastError: err, LastRequest: input, @@ -334,10 +344,7 @@ func FlowDefinitionByName(conn *sagemaker.SageMaker, name string) (*sagemaker.De } if output == nil { - return nil, &resource.NotFoundError{ - Message: "Empty result", - LastRequest: input, - } + return nil, tfresource.NewEmptyResultError(input) } return output, nil diff --git a/aws/internal/service/sagemaker/waiter/status.go b/aws/internal/service/sagemaker/waiter/status.go index 7dd8484da029..9e92faf87aaf 100644 --- a/aws/internal/service/sagemaker/waiter/status.go +++ b/aws/internal/service/sagemaker/waiter/status.go @@ -184,10 +184,6 @@ func FlowDefinitionStatus(conn *sagemaker.SageMaker, name string) resource.State return nil, "", err } - if aws.StringValue(output.FlowDefinitionStatus) == sagemaker.FlowDefinitionStatusFailed { - return output, sagemaker.FlowDefinitionStatusFailed, fmt.Errorf("%s", aws.StringValue(output.FailureReason)) - } - return output, aws.StringValue(output.FlowDefinitionStatus), nil } } diff --git a/aws/internal/service/sagemaker/waiter/waiter.go b/aws/internal/service/sagemaker/waiter/waiter.go index 4a9cf6d7a25d..1323356bd603 100644 --- a/aws/internal/service/sagemaker/waiter/waiter.go +++ b/aws/internal/service/sagemaker/waiter/waiter.go @@ -388,12 +388,8 @@ func AppDeleted(conn *sagemaker.SageMaker, domainID, userProfileName, appType, a // FlowDefinitionActive waits for a FlowDefinition to return Active func FlowDefinitionActive(conn *sagemaker.SageMaker, name string) (*sagemaker.DescribeFlowDefinitionOutput, error) { stateConf := &resource.StateChangeConf{ - Pending: []string{ - sagemaker.FlowDefinitionStatusInitializing, - }, - Target: []string{ - sagemaker.FlowDefinitionStatusActive, - }, + Pending: []string{sagemaker.FlowDefinitionStatusInitializing}, + Target: []string{sagemaker.FlowDefinitionStatusActive}, Refresh: FlowDefinitionStatus(conn, name), Timeout: FlowDefinitionActiveTimeout, } @@ -401,6 +397,10 @@ func FlowDefinitionActive(conn *sagemaker.SageMaker, name string) (*sagemaker.De outputRaw, err := stateConf.WaitForState() if output, ok := outputRaw.(*sagemaker.DescribeFlowDefinitionOutput); ok { + if status, reason := aws.StringValue(output.FlowDefinitionStatus), aws.StringValue(output.FailureReason); status == sagemaker.FlowDefinitionStatusFailed && reason != "" { + tfresource.SetLastError(err, errors.New(reason)) + } + return output, err } @@ -410,9 +410,7 @@ func FlowDefinitionActive(conn *sagemaker.SageMaker, name string) (*sagemaker.De // FlowDefinitionDeleted waits for a FlowDefinition to return Deleted func FlowDefinitionDeleted(conn *sagemaker.SageMaker, name string) (*sagemaker.DescribeFlowDefinitionOutput, error) { stateConf := &resource.StateChangeConf{ - Pending: []string{ - sagemaker.FlowDefinitionStatusDeleting, - }, + Pending: []string{sagemaker.FlowDefinitionStatusDeleting}, Target: []string{}, Refresh: FlowDefinitionStatus(conn, name), Timeout: FlowDefinitionDeletedTimeout, @@ -421,6 +419,10 @@ func FlowDefinitionDeleted(conn *sagemaker.SageMaker, name string) (*sagemaker.D outputRaw, err := stateConf.WaitForState() if output, ok := outputRaw.(*sagemaker.DescribeFlowDefinitionOutput); ok { + if status, reason := aws.StringValue(output.FlowDefinitionStatus), aws.StringValue(output.FailureReason); status == sagemaker.FlowDefinitionStatusFailed && reason != "" { + tfresource.SetLastError(err, errors.New(reason)) + } + return output, err } diff --git a/aws/resource_aws_sagemaker_flow_definition.go b/aws/resource_aws_sagemaker_flow_definition.go index 2d5532686b9d..f5dc99159255 100644 --- a/aws/resource_aws_sagemaker_flow_definition.go +++ b/aws/resource_aws_sagemaker_flow_definition.go @@ -200,13 +200,6 @@ func resourceAwsSagemakerFlowDefinition() *schema.Resource { }, }, }, - "role_arn": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validateArn, - }, - "output_config": { Type: schema.TypeList, Required: true, @@ -232,6 +225,12 @@ func resourceAwsSagemakerFlowDefinition() *schema.Resource { }, }, }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArn, + }, "tags": tagsSchema(), "tags_all": tagsSchemaComputed(), }, @@ -273,6 +272,7 @@ func resourceAwsSagemakerFlowDefinitionCreate(d *schema.ResourceData, meta inter _, err := tfresource.RetryWhenAwsErrCodeEquals(iamwaiter.PropagationTimeout, func() (interface{}, error) { return conn.CreateFlowDefinition(input) }, "ValidationException") + if err != nil { return fmt.Errorf("error creating SageMaker Flow Definition (%s): %w", name, err) } @@ -280,7 +280,7 @@ func resourceAwsSagemakerFlowDefinitionCreate(d *schema.ResourceData, meta inter d.SetId(name) if _, err := waiter.FlowDefinitionActive(conn, d.Id()); err != nil { - return fmt.Errorf("error waiting for SageMaker Flow Definition (%s) to Active: %w", d.Id(), err) + return fmt.Errorf("error waiting for SageMaker Flow Definition (%s) to become active: %w", d.Id(), err) } return resourceAwsSagemakerFlowDefinitionRead(d, meta) @@ -375,10 +375,7 @@ func resourceAwsSagemakerFlowDefinitionDelete(d *schema.ResourceData, meta inter } if _, err := waiter.FlowDefinitionDeleted(conn, d.Id()); err != nil { - if tfawserr.ErrCodeEquals(err, sagemaker.ErrCodeResourceNotFound) { - return nil - } - return fmt.Errorf("error waiting for SageMaker Flow Definition (%s) to be deleted: %w", d.Id(), err) + return fmt.Errorf("error waiting for SageMaker Flow Definition (%s) to delete: %w", d.Id(), err) } return nil From d0fc1adef397b188ff344ee67ffd964e98fb2c23 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 21 Sep 2021 16:29:24 -0400 Subject: [PATCH 10/10] r/aws_sagemaker_flow_definition: Serialize tests with others. --- aws/resource_aws_sagemaker_flow_definition_test.go | 10 +++++----- aws/resource_aws_sagemaker_test.go | 7 +++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_sagemaker_flow_definition_test.go b/aws/resource_aws_sagemaker_flow_definition_test.go index 2a1e95fdf824..921cc605cd0f 100644 --- a/aws/resource_aws_sagemaker_flow_definition_test.go +++ b/aws/resource_aws_sagemaker_flow_definition_test.go @@ -59,7 +59,7 @@ func testSweepSagemakerFlowDefinitions(region string) error { return sweeperErrs.ErrorOrNil() } -func TestAccAWSSagemakerFlowDefinition_basic(t *testing.T) { +func testAccAWSSagemakerFlowDefinition_basic(t *testing.T) { var flowDefinition sagemaker.DescribeFlowDefinitionOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_flow_definition.test" @@ -101,7 +101,7 @@ func TestAccAWSSagemakerFlowDefinition_basic(t *testing.T) { }) } -func TestAccAWSSagemakerFlowDefinition_humanLoopConfig_publicWorkforce(t *testing.T) { +func testAccAWSSagemakerFlowDefinition_humanLoopConfig_publicWorkforce(t *testing.T) { var flowDefinition sagemaker.DescribeFlowDefinitionOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_flow_definition.test" @@ -133,7 +133,7 @@ func TestAccAWSSagemakerFlowDefinition_humanLoopConfig_publicWorkforce(t *testin }) } -func TestAccAWSSagemakerFlowDefinition_humanLoopRequestSource(t *testing.T) { +func testAccAWSSagemakerFlowDefinition_humanLoopRequestSource(t *testing.T) { var flowDefinition sagemaker.DescribeFlowDefinitionOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_flow_definition.test" @@ -165,7 +165,7 @@ func TestAccAWSSagemakerFlowDefinition_humanLoopRequestSource(t *testing.T) { }) } -func TestAccAWSSagemakerFlowDefinition_tags(t *testing.T) { +func testAccAWSSagemakerFlowDefinition_tags(t *testing.T) { var flowDefinition sagemaker.DescribeFlowDefinitionOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_flow_definition.test" @@ -210,7 +210,7 @@ func TestAccAWSSagemakerFlowDefinition_tags(t *testing.T) { }) } -func TestAccAWSSagemakerFlowDefinition_disappears(t *testing.T) { +func testAccAWSSagemakerFlowDefinition_disappears(t *testing.T) { var flowDefinition sagemaker.DescribeFlowDefinitionOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_flow_definition.test" diff --git a/aws/resource_aws_sagemaker_test.go b/aws/resource_aws_sagemaker_test.go index 5c15b16016b3..c451029b102a 100644 --- a/aws/resource_aws_sagemaker_test.go +++ b/aws/resource_aws_sagemaker_test.go @@ -42,6 +42,13 @@ func TestAccAWSSagemaker_serial(t *testing.T) { "securityGroup": testAccAWSSagemakerDomain_securityGroup, "sharingSettings": testAccAWSSagemakerDomain_sharingSettings, }, + "FlowDefinition": { + "basic": testAccAWSSagemakerFlowDefinition_basic, + "disappears": testAccAWSSagemakerFlowDefinition_disappears, + "HumanLoopConfigPublicWorkforce": testAccAWSSagemakerFlowDefinition_humanLoopConfig_publicWorkforce, + "HumanLoopRequestSource": testAccAWSSagemakerFlowDefinition_humanLoopRequestSource, + "Tags": testAccAWSSagemakerFlowDefinition_tags, + }, "UserProfile": { "basic": testAccAWSSagemakerUserProfile_basic, "disappears": testAccAWSSagemakerUserProfile_tags,