From 0919b7db75e4ee5af53da77eab67d8a712ce2abd Mon Sep 17 00:00:00 2001 From: Jan-Christoph Kuester Date: Mon, 27 Nov 2017 21:37:02 +0100 Subject: [PATCH 1/3] Add resource_aws_sagemaker_endpoint_configuration --- aws/provider.go | 1 + ...ce_aws_sagemaker_endpoint_configuration.go | 264 ++++++++++ ...s_sagemaker_endpoint_configuration_test.go | 454 ++++++++++++++++++ ...maker_endpoint_configuration.html.markdown | 67 +++ 4 files changed, 786 insertions(+) create mode 100644 aws/resource_aws_sagemaker_endpoint_configuration.go create mode 100644 aws/resource_aws_sagemaker_endpoint_configuration_test.go create mode 100644 website/docs/r/sagemaker_endpoint_configuration.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 9a98b74da4b..e31b82b4f63 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -616,6 +616,7 @@ func Provider() terraform.ResourceProvider { "aws_default_route_table": resourceAwsDefaultRouteTable(), "aws_route_table_association": resourceAwsRouteTableAssociation(), "aws_sagemaker_model": resourceAwsSagemakerModel(), + "aws_sagemaker_endpoint_configuration": resourceAwsSagemakerEndpointConfiguration(), "aws_secretsmanager_secret": resourceAwsSecretsManagerSecret(), "aws_secretsmanager_secret_version": resourceAwsSecretsManagerSecretVersion(), "aws_ses_active_receipt_rule_set": resourceAwsSesActiveReceiptRuleSet(), diff --git a/aws/resource_aws_sagemaker_endpoint_configuration.go b/aws/resource_aws_sagemaker_endpoint_configuration.go new file mode 100644 index 00000000000..df5b9ebde12 --- /dev/null +++ b/aws/resource_aws_sagemaker_endpoint_configuration.go @@ -0,0 +1,264 @@ +package aws + +import ( + "bytes" + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/sagemaker" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "log" + "time" +) + +func resourceAwsSagemakerEndpointConfiguration() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSagemakerEndpointConfigurationCreate, + Read: resourceAwsSagemakerEndpointConfigurationRead, + Update: resourceAwsSagemakerEndpointConfigurationUpdate, + Delete: resourceAwsSagemakerEndpointConfigurationDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validateSagemakerName, + }, + + "production_variants": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "variant_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "model_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "initial_instance_count": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "instance_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "initial_variant_weight": { + Type: schema.TypeFloat, + Required: true, + ForceNew: true, + }, + }, + }, + Set: resourceAwsSagmakerEndpointConfigEntryHash, + }, + + "kms_key_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "creation_time": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceAwsSagemakerEndpointConfigurationCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + var name string + if v, ok := d.GetOk("name"); ok { + name = v.(string) + } else { + name = resource.UniqueId() + } + + createOpts := &sagemaker.CreateEndpointConfigInput{ + EndpointConfigName: aws.String(name), + } + + prodVariants, err := expandProductionVariants(d.Get("production_variants").(*schema.Set).List()) + if err != nil { + return err + } + createOpts.ProductionVariants = prodVariants + + if v, ok := d.GetOk("kms_key_id"); ok { + createOpts.KmsKeyId = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Sagemaker endpoint configuration create config: %#v", *createOpts) + resp, err := conn.CreateEndpointConfig(createOpts) + if err != nil { + return fmt.Errorf("Error creating Sagemaker endpoint configuration: %s", err) + } + + d.SetId(name) + if err := d.Set("arn", resp.EndpointConfigArn); err != nil { + return err + } + + return resourceAwsSagemakerEndpointConfigurationUpdate(d, meta) +} + +func resourceAwsSagemakerEndpointConfigurationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + request := &sagemaker.DescribeEndpointConfigInput{ + EndpointConfigName: aws.String(d.Id()), + } + + endpointConfig, err := conn.DescribeEndpointConfig(request) + if err != nil { + if sagemakerErr, ok := err.(awserr.Error); ok && sagemakerErr.Code() == "ResourceNotFound" { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading Sagemaker endpoint configuration %s: %s", d.Id(), err) + } + + if err := d.Set("arn", endpointConfig.EndpointConfigArn); err != nil { + return err + } + if err := d.Set("name", endpointConfig.EndpointConfigName); err != nil { + return err + } + if err := d.Set("production_variants", flattenProductionVariants(endpointConfig.ProductionVariants)); err != nil { + return err + } + + if err := d.Set("kms_key_id", endpointConfig.KmsKeyId); err != nil { + return err + } + if err := d.Set("creation_time", endpointConfig.CreationTime.Format(time.RFC3339)); err != nil { + return err + } + + return nil +} + +func resourceAwsSagemakerEndpointConfigurationUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + d.Partial(true) + + if err := setSagemakerTags(conn, d); err != nil { + return err + } else { + d.SetPartial("tags") + } + + d.Partial(false) + + return resourceAwsSagemakerEndpointConfigurationRead(d, meta) +} + +func resourceAwsSagemakerEndpointConfigurationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + deleteOpts := &sagemaker.DeleteEndpointConfigInput{ + EndpointConfigName: aws.String(d.Id()), + } + log.Printf("[INFO] Deleting Sagemaker endpoint configuration: %s", d.Id()) + + return resource.Retry(5*time.Minute, func() *resource.RetryError { + _, err := conn.DeleteEndpointConfig(deleteOpts) + if err == nil { + return nil + } + + sagemakerErr, ok := err.(awserr.Error) + if !ok { + return resource.NonRetryableError(err) + } + + if sagemakerErr.Code() == "ResourceNotFound" { + return resource.RetryableError(err) + } + + return resource.NonRetryableError(fmt.Errorf("Error deleting Sagemaker endpoint configuration: %s", err)) + }) +} + +func expandProductionVariants(configured []interface{}) ([]*sagemaker.ProductionVariant, error) { + containers := make([]*sagemaker.ProductionVariant, 0, len(configured)) + + for _, lRaw := range configured { + data := lRaw.(map[string]interface{}) + + var name string + if v, ok := data["variant_name"]; ok { + name = v.(string) + } else { + name = resource.UniqueId() + } + + l := &sagemaker.ProductionVariant{ + VariantName: aws.String(name), + InstanceType: aws.String(data["instance_type"].(string)), + ModelName: aws.String(data["model_name"].(string)), + InitialVariantWeight: aws.Float64(float64(data["initial_variant_weight"].(float64))), + InitialInstanceCount: aws.Int64(int64(data["initial_instance_count"].(int))), + } + containers = append(containers, l) + } + + return containers, nil +} + +func flattenProductionVariants(list []*sagemaker.ProductionVariant) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(list)) + for _, i := range list { + l := map[string]interface{}{ + "variant_name": *i.VariantName, + "instance_type": *i.InstanceType, + "model_name": *i.ModelName, + "initial_variant_weight": *i.InitialVariantWeight, + "initial_instance_count": *i.InitialInstanceCount, + } + result = append(result, l) + } + return result +} + +func resourceAwsSagmakerEndpointConfigEntryHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["variant_name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["model_name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["instance_type"].(string))) + buf.WriteString(fmt.Sprintf("%f-", m["initial_variant_weight"].(float64))) + buf.WriteString(fmt.Sprintf("%d-", m["initial_instance_count"].(int))) + + return hashcode.String(buf.String()) +} diff --git a/aws/resource_aws_sagemaker_endpoint_configuration_test.go b/aws/resource_aws_sagemaker_endpoint_configuration_test.go new file mode 100644 index 00000000000..b32e67777a4 --- /dev/null +++ b/aws/resource_aws_sagemaker_endpoint_configuration_test.go @@ -0,0 +1,454 @@ +package aws + +import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/sagemaker" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "log" + "testing" +) + +func init() { + resource.AddTestSweepers("aws_sagemaker_endpoint_configuration", &resource.Sweeper{ + Name: "aws_sagemaker_endpoint_configuration", + Dependencies: []string{ + "aws_sagemaker_model", + "aws_iam_role", + }, + F: testSweepSagemakerEndpointConfigs, + }) +} + +func testSweepSagemakerEndpointConfigs(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).sagemakerconn + + req := &sagemaker.ListEndpointConfigsInput{ + NameContains: aws.String("terraform-testacc-sagemaker-endpoint-config"), + } + resp, err := conn.ListEndpointConfigs(req) + if err != nil { + return fmt.Errorf("Error listing endpoint configs: %s", err) + } + + if len(resp.EndpointConfigs) == 0 { + log.Print("[DEBUG] No SageMaker endpoint config to sweep") + return nil + } + + for _, endpointConfig := range resp.EndpointConfigs { + _, err := conn.DeleteEndpointConfig(&sagemaker.DeleteEndpointConfigInput{ + EndpointConfigName: endpointConfig.EndpointConfigName, + }) + if err != nil { + return fmt.Errorf( + "failed to delete SageMaker endpoint config (%s): %s", + *endpointConfig.EndpointConfigName, err) + } + } + + return nil +} + +func TestAccAWSSagemakerEndpointConfig_basic(t *testing.T) { + var endpointConfig sagemaker.DescribeEndpointConfigOutput + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSagemakerEndpointConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSagemakerEndpointConfigConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo", + &endpointConfig), + testAccCheckSagemakerEndpointConfigName(&endpointConfig, + "terraform-testacc-sagemaker-endpoint-config-foo"), + + resource.TestCheckResourceAttr( + "aws_sagemaker_endpoint_configuration.foo", "name", + "terraform-testacc-sagemaker-endpoint-config-foo"), + resource.TestCheckResourceAttr( + "aws_sagemaker_endpoint_configuration.foo", + "production_variants.#", "1"), + resource.TestCheckResourceAttr( + "aws_sagemaker_endpoint_configuration.foo", + "production_variants.2891507008.variant_name", + "variant-1"), + resource.TestCheckResourceAttr( + "aws_sagemaker_endpoint_configuration.foo", + "production_variants.2891507008.model_name", + "terraform-testacc-sagemaker-model-foo"), + resource.TestCheckResourceAttr( + "aws_sagemaker_endpoint_configuration.foo", + "production_variants.2891507008.initial_instance_count", + "1"), + resource.TestCheckResourceAttr( + "aws_sagemaker_endpoint_configuration.foo", + "production_variants.2891507008.instance_type", + "ml.t2.medium"), + resource.TestCheckResourceAttr( + "aws_sagemaker_endpoint_configuration.foo", + "production_variants.2891507008.initial_variant_weight", + "1"), + ), + }, + }, + }) +} + +func TestAccAWSSagemakerEndpointConfig_kmsKeyId(t *testing.T) { + var endpointConfig sagemaker.DescribeEndpointConfigOutput + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSagemakerEndpointConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSagemakerEndpointConfigKmsKeyIdConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo", + &endpointConfig), + testAccCheckSagemakerEndpointConfigKmsKeyId(&endpointConfig), + + resource.TestCheckResourceAttrSet("aws_sagemaker_endpoint_configuration.foo", "kms_key_id"), + ), + }, + }, + }) +} + +func TestAccAWSSagemakerEndpointConfig_tags(t *testing.T) { + var endpointConfig sagemaker.DescribeEndpointConfigOutput + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSagemakerEndpointConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSagemakerEndpointConfigConfigTags, + Check: resource.ComposeTestCheckFunc( + testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo", + &endpointConfig), + testAccCheckSagemakerEndpointConfigTags(&endpointConfig, "foo", "bar"), + + resource.TestCheckResourceAttr( + "aws_sagemaker_endpoint_configuration.foo", "name", + "terraform-testacc-sagemaker-endpoint-config-foo"), + resource.TestCheckResourceAttr("aws_sagemaker_endpoint_configuration.foo", + "tags.%", "1"), + resource.TestCheckResourceAttr("aws_sagemaker_endpoint_configuration.foo", + "tags.foo", "bar"), + ), + }, + + { + Config: testAccSagemakerEndpointConfigConfigTagsUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo", + &endpointConfig), + testAccCheckSagemakerEndpointConfigTags(&endpointConfig, "foo", ""), + testAccCheckSagemakerEndpointConfigTags(&endpointConfig, "bar", "baz"), + + resource.TestCheckResourceAttr("aws_sagemaker_endpoint_configuration.foo", + "tags.%", "1"), + resource.TestCheckResourceAttr("aws_sagemaker_endpoint_configuration.foo", + "tags.bar", "baz"), + ), + }, + }, + }) +} + +func testAccCheckSagemakerEndpointConfigDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).sagemakerconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_sagemaker_endpoint_configuration" { + continue + } + + resp, err := conn.ListEndpointConfigs(&sagemaker.ListEndpointConfigsInput{ + NameContains: aws.String(rs.Primary.ID), + }) + if err == nil { + if len(resp.EndpointConfigs) > 0 { + return fmt.Errorf("SageMaker endpoint configs still exists") + } + + return nil + } + + sagemakerErr, ok := err.(awserr.Error) + if !ok { + return err + } + if sagemakerErr.Code() != "ResourceNotFound" { + return err + } + } + + return nil +} + +func testAccCheckSagemakerEndpointConfigExists(n string, + endpointConfig *sagemaker.DescribeEndpointConfigOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("SageMaker endpoint config not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("no SageMaker endpoint config ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).sagemakerconn + opts := &sagemaker.DescribeEndpointConfigInput{ + EndpointConfigName: aws.String(rs.Primary.ID), + } + resp, err := conn.DescribeEndpointConfig(opts) + if err != nil { + return err + } + + *endpointConfig = *resp + return nil + } +} + +func testAccCheckSagemakerEndpointConfigName(endpointConfig *sagemaker.DescribeEndpointConfigOutput, + expected string) resource.TestCheckFunc { + return func(s *terraform.State) error { + name := endpointConfig.EndpointConfigName + if *name != expected { + return fmt.Errorf("bad name: %s", *name) + } + + return nil + } +} + +func testAccCheckSagemakerEndpointConfigKmsKeyId(endpointConfig *sagemaker.DescribeEndpointConfigOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + id := endpointConfig.KmsKeyId + if id == nil || len(*id) < 20 { + return fmt.Errorf("bad KMS key ID: %s", *id) + } + + return nil + } +} + +func testAccCheckSagemakerEndpointConfigTags(endpointConfig *sagemaker.DescribeEndpointConfigOutput, + key string, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).sagemakerconn + + ts, err := conn.ListTags(&sagemaker.ListTagsInput{ + ResourceArn: endpointConfig.EndpointConfigArn, + }) + if err != nil { + return fmt.Errorf("failed to list tags: %s", err) + } + + m := tagsToMapSagemaker(ts.Tags) + v, ok := m[key] + if value != "" && !ok { + return fmt.Errorf("missing tag: %s", key) + } else if value == "" && ok { + return fmt.Errorf("extra tag: %s", key) + } + if value == "" { + return nil + } + + if v != value { + return fmt.Errorf("%s: bad value: %s", key, v) + } + + return nil + } +} + +const testAccSagemakerEndpointConfigConfig = ` +resource "aws_sagemaker_endpoint_configuration" "foo" { + name = "terraform-testacc-sagemaker-endpoint-config-foo" + + production_variants { + variant_name = "variant-1" + model_name = "${aws_sagemaker_model.foo.name}" + initial_instance_count = 1 + instance_type = "ml.t2.medium" + initial_variant_weight = 1 + } +} + +resource "aws_sagemaker_model" "foo" { + name = "terraform-testacc-sagemaker-model-foo" + execution_role_arn = "${aws_iam_role.foo.arn}" + + + primary_container { + image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + } +} + +resource "aws_iam_role" "foo" { + name = "terraform-testacc-sagemaker-model-foo" + 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" ] + } + } +} +` + +const testAccSagemakerEndpointConfigKmsKeyIdConfig = ` +resource "aws_sagemaker_endpoint_configuration" "foo" { + name = "terraform-testacc-sagemaker-endpoint-config-foo" + kms_key_id = "${aws_kms_key.foo.arn}" + + production_variants { + variant_name = "variant-1" + model_name = "${aws_sagemaker_model.foo.name}" + initial_instance_count = 1 + instance_type = "ml.t2.medium" + initial_variant_weight = 1 + } +} + +resource "aws_sagemaker_model" "foo" { + name = "terraform-testacc-sagemaker-model-foo" + execution_role_arn = "${aws_iam_role.foo.arn}" + + + primary_container { + image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + } +} + +resource "aws_iam_role" "foo" { + name = "terraform-testacc-sagemaker-model-foo" + 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_kms_key" "foo" { + description = "terraform-testacc-sagemaker-model-foo" + deletion_window_in_days = 10 +} +` + +const testAccSagemakerEndpointConfigConfigTags = ` +resource "aws_sagemaker_endpoint_configuration" "foo" { + name = "terraform-testacc-sagemaker-endpoint-config-foo" + + production_variants { + variant_name = "variant-1" + model_name = "${aws_sagemaker_model.foo.name}" + initial_instance_count = 1 + instance_type = "ml.t2.medium" + initial_variant_weight = 1 + } + + tags { + foo = "bar" + } +} + +resource "aws_sagemaker_model" "foo" { + name = "terraform-testacc-sagemaker-model-foo" + execution_role_arn = "${aws_iam_role.foo.arn}" + + + primary_container { + image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + } +} + +resource "aws_iam_role" "foo" { + name = "terraform-testacc-sagemaker-model-foo" + 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" ] + } + } +} +` + +const testAccSagemakerEndpointConfigConfigTagsUpdate = ` +resource "aws_sagemaker_endpoint_configuration" "foo" { + name = "terraform-testacc-sagemaker-endpoint-config-foo" + + production_variants { + variant_name = "variant-1" + model_name = "${aws_sagemaker_model.foo.name}" + initial_instance_count = 1 + instance_type = "ml.t2.medium" + initial_variant_weight = 1 + } + + tags { + bar = "baz" + } +} + +resource "aws_sagemaker_model" "foo" { + name = "terraform-testacc-sagemaker-model-foo" + execution_role_arn = "${aws_iam_role.foo.arn}" + + primary_container { + image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + } +} + +resource "aws_iam_role" "foo" { + name = "terraform-testacc-sagemaker-model-foo" + 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" ] + } + } +} +` diff --git a/website/docs/r/sagemaker_endpoint_configuration.html.markdown b/website/docs/r/sagemaker_endpoint_configuration.html.markdown new file mode 100644 index 00000000000..a7f53eda227 --- /dev/null +++ b/website/docs/r/sagemaker_endpoint_configuration.html.markdown @@ -0,0 +1,67 @@ +--- +layout: "aws" +page_title: "AWS: sagemaker_endpoint" +sidebar_current: "docs-aws-resource-sagemaker-endpoint-configuration" +description: |- + Provides a Sagemaker endpoint configuration resource. +--- + +# aws\_sagemaker\_endpoint\_configuration + +Provides a Sagemaker endpoint configuration resource. + +## Example Usage + + +Basic usage: + +```hcl +resource "aws_sagemaker_endpoint_configuration" "ec" { + name = "my-endpoint-config" + + production_variant { + variant_name = "variant-1" + model_name = "my-model" + initial_instance_count = 1 + instance_type = "" + initial_variant_weight = 1 + } + + tags { + Name = "foo" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Optional) The name of the endpoint configuration. If omitted, Terraform will assign a random, unique name. +* `production_variants` - (Required) Fields are documented below. +* `kms_key_id` - (Optional) KMS key to encrypt the model data. +* `tags` - (Optional) A mapping of tags to assign to the resource. + +The `production_variant` block supports: + +* `variant_name` - (Optional) The name of the variant. If omitted, Terraform will assign a random, unique name. +* `model_name` - (Required) The name of the model to use. +* `initial_instance_count` - (Required) Initial number of instances used for auto-scaling. +* `instance_type` (Required) - The type of instance to start. +* `initial_variant_weight` - (Required) + +## Attributes Reference + +The following attributes are exported: + +* `name` - The name of the endpoint configuration. +* `arn` - The Amazon Resource Name (ARN) assigned by AWS to this endpoint configuration. +* `creation_time` - The creation timestamp of this endpoint configuration. + +## Import + +Endpoint configurations can be imported using the `name`, e.g. + +``` +$ terraform import aws_sagemaker_endpoint_configuration.test_endpoint_config endpoint-config-foo +``` From 077f20d9c073d890b6df172a941a7600acd48ed3 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Kuester Date: Wed, 9 Jan 2019 22:42:16 +0100 Subject: [PATCH 2/3] Address feedback --- ...ce_aws_sagemaker_endpoint_configuration.go | 118 ++++--- ...s_sagemaker_endpoint_configuration_test.go | 319 ++++++++++++------ aws/validators.go | 19 ++ website/aws.erb | 4 + ...maker_endpoint_configuration.html.markdown | 18 +- 5 files changed, 305 insertions(+), 173 deletions(-) diff --git a/aws/resource_aws_sagemaker_endpoint_configuration.go b/aws/resource_aws_sagemaker_endpoint_configuration.go index df5b9ebde12..19685be5236 100644 --- a/aws/resource_aws_sagemaker_endpoint_configuration.go +++ b/aws/resource_aws_sagemaker_endpoint_configuration.go @@ -1,16 +1,17 @@ package aws import ( - "bytes" "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/validation" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/sagemaker" - "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "log" - "time" ) func resourceAwsSagemakerEndpointConfiguration() *schema.Resource { @@ -38,7 +39,7 @@ func resourceAwsSagemakerEndpointConfiguration() *schema.Resource { }, "production_variants": { - Type: schema.TypeSet, + Type: schema.TypeList, Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -56,9 +57,10 @@ func resourceAwsSagemakerEndpointConfiguration() *schema.Resource { }, "initial_instance_count": { - Type: schema.TypeInt, - Required: true, - ForceNew: true, + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), }, "instance_type": { @@ -68,13 +70,19 @@ func resourceAwsSagemakerEndpointConfiguration() *schema.Resource { }, "initial_variant_weight": { - Type: schema.TypeFloat, - Required: true, + Type: schema.TypeFloat, + Optional: true, + ForceNew: true, + ValidateFunc: FloatAtLeast(0), + }, + + "accelerator_type": { + Type: schema.TypeString, + Optional: true, ForceNew: true, }, }, }, - Set: resourceAwsSagmakerEndpointConfigEntryHash, }, "kms_key_id": { @@ -83,11 +91,6 @@ func resourceAwsSagemakerEndpointConfiguration() *schema.Resource { ForceNew: true, }, - "creation_time": { - Type: schema.TypeString, - Computed: true, - }, - "tags": tagsSchema(), }, } @@ -107,7 +110,7 @@ func resourceAwsSagemakerEndpointConfigurationCreate(d *schema.ResourceData, met EndpointConfigName: aws.String(name), } - prodVariants, err := expandProductionVariants(d.Get("production_variants").(*schema.Set).List()) + prodVariants, err := expandProductionVariants(d.Get("production_variants").([]interface{})) if err != nil { return err } @@ -117,18 +120,18 @@ func resourceAwsSagemakerEndpointConfigurationCreate(d *schema.ResourceData, met createOpts.KmsKeyId = aws.String(v.(string)) } + if v, ok := d.GetOk("tags"); ok { + createOpts.Tags = tagsFromMapSagemaker(v.(map[string]interface{})) + } + log.Printf("[DEBUG] Sagemaker endpoint configuration create config: %#v", *createOpts) - resp, err := conn.CreateEndpointConfig(createOpts) + _, err = conn.CreateEndpointConfig(createOpts) if err != nil { - return fmt.Errorf("Error creating Sagemaker endpoint configuration: %s", err) + return fmt.Errorf("error creating Sagemaker endpoint configuration: %s", err) } - d.SetId(name) - if err := d.Set("arn", resp.EndpointConfigArn); err != nil { - return err - } - return resourceAwsSagemakerEndpointConfigurationUpdate(d, meta) + return resourceAwsSagemakerEndpointConfigurationRead(d, meta) } func resourceAwsSagemakerEndpointConfigurationRead(d *schema.ResourceData, meta interface{}) error { @@ -140,11 +143,12 @@ func resourceAwsSagemakerEndpointConfigurationRead(d *schema.ResourceData, meta endpointConfig, err := conn.DescribeEndpointConfig(request) if err != nil { - if sagemakerErr, ok := err.(awserr.Error); ok && sagemakerErr.Code() == "ResourceNotFound" { + if sagemakerErr, ok := err.(awserr.Error); ok && sagemakerErr.Code() == "ValidationException" { + log.Printf("[INFO] unable to find the sagemaker endpoint configuration resource and therefore it is removed from the state: %s", d.Id()) d.SetId("") return nil } - return fmt.Errorf("Error reading Sagemaker endpoint configuration %s: %s", d.Id(), err) + return fmt.Errorf("error reading Sagemaker endpoint configuration %s: %s", d.Id(), err) } if err := d.Set("arn", endpointConfig.EndpointConfigArn); err != nil { @@ -156,14 +160,19 @@ func resourceAwsSagemakerEndpointConfigurationRead(d *schema.ResourceData, meta if err := d.Set("production_variants", flattenProductionVariants(endpointConfig.ProductionVariants)); err != nil { return err } - if err := d.Set("kms_key_id", endpointConfig.KmsKeyId); err != nil { return err } - if err := d.Set("creation_time", endpointConfig.CreationTime.Format(time.RFC3339)); err != nil { + + tagsOutput, err := conn.ListTags(&sagemaker.ListTagsInput{ + ResourceArn: endpointConfig.EndpointConfigArn, + }) + if err != nil { + return fmt.Errorf("error listing tags of Sagemaker endpoint configuration %s: %s", d.Id(), err) + } + if err := d.Set("tags", tagsToMapSagemaker(tagsOutput.Tags)); err != nil { return err } - return nil } @@ -216,20 +225,28 @@ func expandProductionVariants(configured []interface{}) ([]*sagemaker.Production for _, lRaw := range configured { data := lRaw.(map[string]interface{}) - var name string - if v, ok := data["variant_name"]; ok { - name = v.(string) - } else { - name = resource.UniqueId() - } - l := &sagemaker.ProductionVariant{ - VariantName: aws.String(name), InstanceType: aws.String(data["instance_type"].(string)), ModelName: aws.String(data["model_name"].(string)), - InitialVariantWeight: aws.Float64(float64(data["initial_variant_weight"].(float64))), InitialInstanceCount: aws.Int64(int64(data["initial_instance_count"].(int))), } + + if v, ok := data["variant_name"]; ok { + l.VariantName = aws.String(v.(string)) + } else { + l.VariantName = aws.String(resource.UniqueId()) + } + + if v, ok := data["initial_variant_weight"]; ok { + l.InitialVariantWeight = aws.Float64(v.(float64)) + } else { + l.InitialVariantWeight = aws.Float64(1) + } + + if v, ok := data["accelerator_type"]; ok && v.(string) != "" { + l.AcceleratorType = aws.String(data["accelerator_type"].(string)) + } + containers = append(containers, l) } @@ -238,27 +255,24 @@ func expandProductionVariants(configured []interface{}) ([]*sagemaker.Production func flattenProductionVariants(list []*sagemaker.ProductionVariant) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(list)) + for _, i := range list { l := map[string]interface{}{ - "variant_name": *i.VariantName, "instance_type": *i.InstanceType, "model_name": *i.ModelName, - "initial_variant_weight": *i.InitialVariantWeight, "initial_instance_count": *i.InitialInstanceCount, } + if i.VariantName != nil { + l["variant_name"] = *i.VariantName + } + if i.InitialVariantWeight != nil { + l["initial_variant_weight"] = *i.InitialVariantWeight + } + if i.AcceleratorType != nil { + l["accelerator_type"] = *i.AcceleratorType + } + result = append(result, l) } return result } - -func resourceAwsSagmakerEndpointConfigEntryHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["variant_name"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["model_name"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["instance_type"].(string))) - buf.WriteString(fmt.Sprintf("%f-", m["initial_variant_weight"].(float64))) - buf.WriteString(fmt.Sprintf("%d-", m["initial_instance_count"].(int))) - - return hashcode.String(buf.String()) -} diff --git a/aws/resource_aws_sagemaker_endpoint_configuration_test.go b/aws/resource_aws_sagemaker_endpoint_configuration_test.go index b32e67777a4..2b4da7261d7 100644 --- a/aws/resource_aws_sagemaker_endpoint_configuration_test.go +++ b/aws/resource_aws_sagemaker_endpoint_configuration_test.go @@ -2,13 +2,16 @@ package aws import ( "fmt" + "log" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/sagemaker" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "log" - "testing" ) func init() { @@ -16,7 +19,6 @@ func init() { Name: "aws_sagemaker_endpoint_configuration", Dependencies: []string{ "aws_sagemaker_model", - "aws_iam_role", }, F: testSweepSagemakerEndpointConfigs, }) @@ -34,7 +36,7 @@ func testSweepSagemakerEndpointConfigs(region string) error { } resp, err := conn.ListEndpointConfigs(req) if err != nil { - return fmt.Errorf("Error listing endpoint configs: %s", err) + return fmt.Errorf("error listing endpoint configs: %s", err) } if len(resp.EndpointConfigs) == 0 { @@ -57,114 +59,165 @@ func testSweepSagemakerEndpointConfigs(region string) error { } func TestAccAWSSagemakerEndpointConfig_basic(t *testing.T) { - var endpointConfig sagemaker.DescribeEndpointConfigOutput + rName := acctest.RandString(10) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckSagemakerEndpointConfigDestroy, Steps: []resource.TestStep{ { - Config: testAccSagemakerEndpointConfigConfig, + Config: testAccSagemakerEndpointConfigConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo", - &endpointConfig), - testAccCheckSagemakerEndpointConfigName(&endpointConfig, - "terraform-testacc-sagemaker-endpoint-config-foo"), - + testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo"), resource.TestCheckResourceAttr( "aws_sagemaker_endpoint_configuration.foo", "name", - "terraform-testacc-sagemaker-endpoint-config-foo"), + fmt.Sprintf("terraform-testacc-sagemaker-endpoint-config-%s", rName)), resource.TestCheckResourceAttr( "aws_sagemaker_endpoint_configuration.foo", "production_variants.#", "1"), resource.TestCheckResourceAttr( "aws_sagemaker_endpoint_configuration.foo", - "production_variants.2891507008.variant_name", + "production_variants.0.variant_name", "variant-1"), resource.TestCheckResourceAttr( "aws_sagemaker_endpoint_configuration.foo", - "production_variants.2891507008.model_name", - "terraform-testacc-sagemaker-model-foo"), + "production_variants.0.model_name", + fmt.Sprintf("terraform-testacc-sagemaker-model-%s", rName)), resource.TestCheckResourceAttr( "aws_sagemaker_endpoint_configuration.foo", - "production_variants.2891507008.initial_instance_count", - "1"), + "production_variants.0.initial_instance_count", + "2"), resource.TestCheckResourceAttr( "aws_sagemaker_endpoint_configuration.foo", - "production_variants.2891507008.instance_type", + "production_variants.0.instance_type", "ml.t2.medium"), resource.TestCheckResourceAttr( "aws_sagemaker_endpoint_configuration.foo", - "production_variants.2891507008.initial_variant_weight", + "production_variants.0.initial_variant_weight", "1"), ), }, + { + ResourceName: "aws_sagemaker_endpoint_configuration.foo", + ImportState: true, + ImportStateVerify: true, + }, }, }) } -func TestAccAWSSagemakerEndpointConfig_kmsKeyId(t *testing.T) { - var endpointConfig sagemaker.DescribeEndpointConfigOutput +func TestAccAWSSagemakerEndpointConfig_productionVariants_initialVariantWeight(t *testing.T) { + rName := acctest.RandString(10) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSagemakerEndpointConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSagemakerEndpointConfigProductionVariantInitialVariantWeightConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo"), + resource.TestCheckResourceAttr( + "aws_sagemaker_endpoint_configuration.foo", + "production_variants.1.initial_variant_weight", + "0.5"), + ), + }, + { + ResourceName: "aws_sagemaker_endpoint_configuration.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSSagemakerEndpointConfig_productionVariants_acceleratorType(t *testing.T) { + rName := acctest.RandString(10) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckSagemakerEndpointConfigDestroy, Steps: []resource.TestStep{ { - Config: testAccSagemakerEndpointConfigKmsKeyIdConfig, + Config: testAccSagemakerEndpointConfigProductionVariantAcceleratorTypeConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo", - &endpointConfig), - testAccCheckSagemakerEndpointConfigKmsKeyId(&endpointConfig), + testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo"), + resource.TestCheckResourceAttr( + "aws_sagemaker_endpoint_configuration.foo", + "production_variants.0.accelerator_type", + "ml.eia1.medium"), + ), + }, + { + ResourceName: "aws_sagemaker_endpoint_configuration.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSSagemakerEndpointConfig_kmsKeyId(t *testing.T) { + rName := acctest.RandString(10) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSagemakerEndpointConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSagemakerEndpointConfigKmsKeyIdConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo"), resource.TestCheckResourceAttrSet("aws_sagemaker_endpoint_configuration.foo", "kms_key_id"), ), }, + { + ResourceName: "aws_sagemaker_endpoint_configuration.foo", + ImportState: true, + ImportStateVerify: true, + }, }, }) } func TestAccAWSSagemakerEndpointConfig_tags(t *testing.T) { - var endpointConfig sagemaker.DescribeEndpointConfigOutput + rName := acctest.RandString(10) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckSagemakerEndpointConfigDestroy, Steps: []resource.TestStep{ { - Config: testAccSagemakerEndpointConfigConfigTags, + Config: testAccSagemakerEndpointConfigConfigTags(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo", - &endpointConfig), - testAccCheckSagemakerEndpointConfigTags(&endpointConfig, "foo", "bar"), - - resource.TestCheckResourceAttr( - "aws_sagemaker_endpoint_configuration.foo", "name", - "terraform-testacc-sagemaker-endpoint-config-foo"), + testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo"), resource.TestCheckResourceAttr("aws_sagemaker_endpoint_configuration.foo", "tags.%", "1"), resource.TestCheckResourceAttr("aws_sagemaker_endpoint_configuration.foo", "tags.foo", "bar"), ), }, - { - Config: testAccSagemakerEndpointConfigConfigTagsUpdate, + Config: testAccSagemakerEndpointConfigTagsUpdateConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo", - &endpointConfig), - testAccCheckSagemakerEndpointConfigTags(&endpointConfig, "foo", ""), - testAccCheckSagemakerEndpointConfigTags(&endpointConfig, "bar", "baz"), - + testAccCheckSagemakerEndpointConfigExists("aws_sagemaker_endpoint_configuration.foo"), resource.TestCheckResourceAttr("aws_sagemaker_endpoint_configuration.foo", "tags.%", "1"), resource.TestCheckResourceAttr("aws_sagemaker_endpoint_configuration.foo", "tags.bar", "baz"), ), }, + { + ResourceName: "aws_sagemaker_endpoint_configuration.foo", + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -200,8 +253,7 @@ func testAccCheckSagemakerEndpointConfigDestroy(s *terraform.State) error { return nil } -func testAccCheckSagemakerEndpointConfigExists(n string, - endpointConfig *sagemaker.DescribeEndpointConfigOutput) resource.TestCheckFunc { +func testAccCheckSagemakerEndpointConfigExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -216,85 +268,123 @@ func testAccCheckSagemakerEndpointConfigExists(n string, opts := &sagemaker.DescribeEndpointConfigInput{ EndpointConfigName: aws.String(rs.Primary.ID), } - resp, err := conn.DescribeEndpointConfig(opts) + _, err := conn.DescribeEndpointConfig(opts) if err != nil { return err } - *endpointConfig = *resp return nil } } -func testAccCheckSagemakerEndpointConfigName(endpointConfig *sagemaker.DescribeEndpointConfigOutput, - expected string) resource.TestCheckFunc { - return func(s *terraform.State) error { - name := endpointConfig.EndpointConfigName - if *name != expected { - return fmt.Errorf("bad name: %s", *name) - } +func testAccSagemakerEndpointConfigConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_sagemaker_endpoint_configuration" "foo" { + name = "terraform-testacc-sagemaker-endpoint-config-%s" - return nil + production_variants { + variant_name = "variant-1" + model_name = "${aws_sagemaker_model.foo.name}" + initial_instance_count = 2 + instance_type = "ml.t2.medium" + initial_variant_weight = 1 } } -func testAccCheckSagemakerEndpointConfigKmsKeyId(endpointConfig *sagemaker.DescribeEndpointConfigOutput) resource.TestCheckFunc { - return func(s *terraform.State) error { - id := endpointConfig.KmsKeyId - if id == nil || len(*id) < 20 { - return fmt.Errorf("bad KMS key ID: %s", *id) - } +resource "aws_sagemaker_model" "foo" { + name = "terraform-testacc-sagemaker-model-%s" + execution_role_arn = "${aws_iam_role.foo.arn}" - return nil + + primary_container { + image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" } } -func testAccCheckSagemakerEndpointConfigTags(endpointConfig *sagemaker.DescribeEndpointConfigOutput, - key string, value string) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).sagemakerconn +resource "aws_iam_role" "foo" { + name = "terraform-testacc-sagemaker-model-%s" + path = "/" + assume_role_policy = "${data.aws_iam_policy_document.assume_role.json}" +} - ts, err := conn.ListTags(&sagemaker.ListTagsInput{ - ResourceArn: endpointConfig.EndpointConfigArn, - }) - if err != nil { - return fmt.Errorf("failed to list tags: %s", err) - } +data "aws_iam_policy_document" "assume_role" { + statement { + actions = [ "sts:AssumeRole" ] + principals { + type = "Service" + identifiers = [ "sagemaker.amazonaws.com" ] + } + } +} +`, rName, rName, rName) +} - m := tagsToMapSagemaker(ts.Tags) - v, ok := m[key] - if value != "" && !ok { - return fmt.Errorf("missing tag: %s", key) - } else if value == "" && ok { - return fmt.Errorf("extra tag: %s", key) - } - if value == "" { - return nil - } +func testAccSagemakerEndpointConfigProductionVariantInitialVariantWeightConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_sagemaker_endpoint_configuration" "foo" { + name = "terraform-testacc-sagemaker-endpoint-config-%s" - if v != value { - return fmt.Errorf("%s: bad value: %s", key, v) - } + production_variants { + variant_name = "variant-1" + model_name = "${aws_sagemaker_model.foo.name}" + initial_instance_count = 1 + instance_type = "ml.t2.medium" + } - return nil + production_variants { + variant_name = "variant-2" + model_name = "${aws_sagemaker_model.foo.name}" + initial_instance_count = 1 + instance_type = "ml.t2.medium" + initial_variant_weight = 0.5 } } -const testAccSagemakerEndpointConfigConfig = ` +resource "aws_sagemaker_model" "foo" { + name = "terraform-testacc-sagemaker-model-%s" + execution_role_arn = "${aws_iam_role.foo.arn}" + + + primary_container { + image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + } +} + +resource "aws_iam_role" "foo" { + name = "terraform-testacc-sagemaker-model-%s" + 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" ] + } + } +} +`, rName, rName, rName) +} + +func testAccSagemakerEndpointConfigProductionVariantAcceleratorTypeConfig(rName string) string { + return fmt.Sprintf(` resource "aws_sagemaker_endpoint_configuration" "foo" { - name = "terraform-testacc-sagemaker-endpoint-config-foo" + name = "terraform-testacc-sagemaker-endpoint-config-%s" production_variants { variant_name = "variant-1" model_name = "${aws_sagemaker_model.foo.name}" - initial_instance_count = 1 + initial_instance_count = 2 instance_type = "ml.t2.medium" + accelerator_type = "ml.eia1.medium" initial_variant_weight = 1 } } resource "aws_sagemaker_model" "foo" { - name = "terraform-testacc-sagemaker-model-foo" + name = "terraform-testacc-sagemaker-model-%s" execution_role_arn = "${aws_iam_role.foo.arn}" @@ -304,7 +394,7 @@ resource "aws_sagemaker_model" "foo" { } resource "aws_iam_role" "foo" { - name = "terraform-testacc-sagemaker-model-foo" + name = "terraform-testacc-sagemaker-model-%s" path = "/" assume_role_policy = "${data.aws_iam_policy_document.assume_role.json}" } @@ -318,11 +408,13 @@ data "aws_iam_policy_document" "assume_role" { } } } -` +`, rName, rName, rName) +} -const testAccSagemakerEndpointConfigKmsKeyIdConfig = ` +func testAccSagemakerEndpointConfigKmsKeyIdConfig(rName string) string { + return fmt.Sprintf(` resource "aws_sagemaker_endpoint_configuration" "foo" { - name = "terraform-testacc-sagemaker-endpoint-config-foo" + name = "terraform-testacc-sagemaker-endpoint-config-%s" kms_key_id = "${aws_kms_key.foo.arn}" production_variants { @@ -335,7 +427,7 @@ resource "aws_sagemaker_endpoint_configuration" "foo" { } resource "aws_sagemaker_model" "foo" { - name = "terraform-testacc-sagemaker-model-foo" + name = "terraform-testacc-sagemaker-model-%s" execution_role_arn = "${aws_iam_role.foo.arn}" @@ -345,7 +437,7 @@ resource "aws_sagemaker_model" "foo" { } resource "aws_iam_role" "foo" { - name = "terraform-testacc-sagemaker-model-foo" + name = "terraform-testacc-sagemaker-model-%s" path = "/" assume_role_policy = "${data.aws_iam_policy_document.assume_role.json}" } @@ -361,14 +453,16 @@ data "aws_iam_policy_document" "assume_role" { } resource "aws_kms_key" "foo" { - description = "terraform-testacc-sagemaker-model-foo" + description = "terraform-testacc-sagemaker-model-%s" deletion_window_in_days = 10 } -` +`, rName, rName, rName, rName) +} -const testAccSagemakerEndpointConfigConfigTags = ` +func testAccSagemakerEndpointConfigConfigTags(rName string) string { + return fmt.Sprintf(` resource "aws_sagemaker_endpoint_configuration" "foo" { - name = "terraform-testacc-sagemaker-endpoint-config-foo" + name = "terraform-testacc-sagemaker-endpoint-config-%s" production_variants { variant_name = "variant-1" @@ -378,13 +472,13 @@ resource "aws_sagemaker_endpoint_configuration" "foo" { initial_variant_weight = 1 } - tags { + tags = { foo = "bar" } } resource "aws_sagemaker_model" "foo" { - name = "terraform-testacc-sagemaker-model-foo" + name = "terraform-testacc-sagemaker-model-%s" execution_role_arn = "${aws_iam_role.foo.arn}" @@ -394,7 +488,7 @@ resource "aws_sagemaker_model" "foo" { } resource "aws_iam_role" "foo" { - name = "terraform-testacc-sagemaker-model-foo" + name = "terraform-testacc-sagemaker-model-%s" path = "/" assume_role_policy = "${data.aws_iam_policy_document.assume_role.json}" } @@ -408,11 +502,13 @@ data "aws_iam_policy_document" "assume_role" { } } } -` +`, rName, rName, rName) +} -const testAccSagemakerEndpointConfigConfigTagsUpdate = ` +func testAccSagemakerEndpointConfigTagsUpdateConfig(rName string) string { + return fmt.Sprintf(` resource "aws_sagemaker_endpoint_configuration" "foo" { - name = "terraform-testacc-sagemaker-endpoint-config-foo" + name = "terraform-testacc-sagemaker-endpoint-config-%s" production_variants { variant_name = "variant-1" @@ -422,13 +518,13 @@ resource "aws_sagemaker_endpoint_configuration" "foo" { initial_variant_weight = 1 } - tags { + tags = { bar = "baz" } } resource "aws_sagemaker_model" "foo" { - name = "terraform-testacc-sagemaker-model-foo" + name = "terraform-testacc-sagemaker-model-%s" execution_role_arn = "${aws_iam_role.foo.arn}" primary_container { @@ -437,7 +533,7 @@ resource "aws_sagemaker_model" "foo" { } resource "aws_iam_role" "foo" { - name = "terraform-testacc-sagemaker-model-foo" + name = "terraform-testacc-sagemaker-model-%s" path = "/" assume_role_policy = "${data.aws_iam_policy_document.assume_role.json}" } @@ -451,4 +547,5 @@ data "aws_iam_policy_document" "assume_role" { } } } -` +`, rName, rName, rName) +} diff --git a/aws/validators.go b/aws/validators.go index 47a9883f6d5..d4006e78cd3 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -40,6 +40,25 @@ func validateAny(validators ...schema.SchemaValidateFunc) schema.SchemaValidateF } } +// FloatAtLeast returns a SchemaValidateFunc which tests if the provided value +// is of type float and is at least min (inclusive) +func FloatAtLeast(min float64) schema.SchemaValidateFunc { + return func(i interface{}, k string) (s []string, es []error) { + v, ok := i.(float64) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be float", k)) + return + } + + if v < min { + es = append(es, fmt.Errorf("expected %s to be at least (%f), got %f", k, min, v)) + return + } + + return + } +} + // validateTypeStringNullableBoolean provides custom error messaging for TypeString booleans // Some arguments require three values: true, false, and "" (unspecified). // This ValidateFunc returns a custom message since the message with diff --git a/website/aws.erb b/website/aws.erb index a2e84c5ae8c..963423bed65 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -2272,6 +2272,10 @@ Sagemaker Resources