From 010396f170e0a3a99bcc7730acc24f9e1f76ae4f Mon Sep 17 00:00:00 2001 From: Jan-Christoph Kuester Date: Sun, 9 Dec 2018 22:06:56 +0100 Subject: [PATCH] More changes --- aws/resource_aws_sagemaker_model.go | 97 +++++- aws/resource_aws_sagemaker_model_test.go | 345 ++++++++++++------- aws/validators.go | 56 ++- website/docs/r/sagemaker_model.html.markdown | 5 +- 4 files changed, 340 insertions(+), 163 deletions(-) diff --git a/aws/resource_aws_sagemaker_model.go b/aws/resource_aws_sagemaker_model.go index e53f174671a..a245a8b12d1 100644 --- a/aws/resource_aws_sagemaker_model.go +++ b/aws/resource_aws_sagemaker_model.go @@ -39,7 +39,7 @@ func resourceAwsSagemakerModel() *schema.Resource { "primary_container": { Type: schema.TypeList, MaxItems: 1, - Required: true, + Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "container_hostname": { @@ -84,11 +84,13 @@ func resourceAwsSagemakerModel() *schema.Resource { Type: schema.TypeSet, Required: true, Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, }, "security_group_ids": { Type: schema.TypeSet, Required: true, Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, }, }, }, @@ -100,6 +102,47 @@ func resourceAwsSagemakerModel() *schema.Resource { ForceNew: true, }, + "enable_network_isolation": { + Type: schema.TypeBool, + Optional: true, + }, + + "container": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "container_hostname": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateSagemakerName, + }, + + "image": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateSagemakerImage, + }, + + "model_data_url": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateSagemakerModelDataUrl, + }, + + "environment": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + ValidateFunc: validateSagemakerEnvironment, + }, + }, + }, + }, + "tags": tagsSchema(), }, } @@ -119,16 +162,22 @@ func resourceAwsSagemakerModelCreate(d *schema.ResourceData, meta interface{}) e ModelName: aws.String(name), } - pContainer := d.Get("primary_container").([]interface{}) - m := pContainer[0].(map[string]interface{}) - createOpts.PrimaryContainer = expandPrimaryContainers(m) + if v, ok := d.GetOk("primary_container"); ok { + m := v.([]interface{})[0].(map[string]interface{}) + createOpts.PrimaryContainer = expandContainer(m) + } + + if v, ok := d.GetOk("container"); ok { + containers := expandContainers(v.([]interface{})) + createOpts.SetContainers(containers) + } if v, ok := d.GetOk("execution_role_arn"); ok { - createOpts.ExecutionRoleArn = aws.String(v.(string)) + createOpts.SetExecutionRoleArn(v.(string)) } if v, ok := d.GetOk("tags"); ok { - createOpts.Tags = tagsFromMapSagemaker(v.(map[string]interface{})) + createOpts.SetTags(tagsFromMapSagemaker(v.(map[string]interface{}))) } if v, ok := d.GetOk("vpc_config"); ok { @@ -136,6 +185,10 @@ func resourceAwsSagemakerModelCreate(d *schema.ResourceData, meta interface{}) e createOpts.SetVpcConfig(vpcConfig) } + if v, ok := d.GetOk("enable_network_isolation"); ok { + createOpts.SetEnableNetworkIsolation(v.(bool)) + } + log.Printf("[DEBUG] Sagemaker model create config: %#v", *createOpts) _, err := retryOnAwsCode("ValidationException", func() (interface{}, error) { return conn.CreateModel(createOpts) @@ -144,6 +197,7 @@ func resourceAwsSagemakerModelCreate(d *schema.ResourceData, meta interface{}) e if err != nil { return fmt.Errorf("error creating Sagemaker model: %s", err) } + d.SetId(name) return resourceAwsSagemakerModelRead(d, meta) } @@ -187,7 +241,10 @@ func resourceAwsSagemakerModelRead(d *schema.ResourceData, meta interface{}) err if err := d.Set("execution_role_arn", model.ExecutionRoleArn); err != nil { return err } - if err := d.Set("primary_container", flattenPrimaryContainer(model.PrimaryContainer)); err != nil { + if err := d.Set("primary_container", flattenContainer(model.PrimaryContainer)); err != nil { + return err + } + if err := d.Set("container", flattenContainers(model.Containers)); err != nil { return err } if err := d.Set("vpc_config", flattenSageMakerVpcConfigResponse(model.VpcConfig)); err != nil { @@ -259,7 +316,7 @@ func resourceAwsSagemakerModelDelete(d *schema.ResourceData, meta interface{}) e }) } -func expandPrimaryContainers(m map[string]interface{}) *sagemaker.ContainerDefinition { +func expandContainer(m map[string]interface{}) *sagemaker.ContainerDefinition { container := sagemaker.ContainerDefinition{ Image: aws.String(m["image"].(string)), } @@ -277,7 +334,21 @@ func expandPrimaryContainers(m map[string]interface{}) *sagemaker.ContainerDefin return &container } -func flattenPrimaryContainer(container *sagemaker.ContainerDefinition) []interface{} { +func expandContainers(a []interface{}) []*sagemaker.ContainerDefinition { + containers := make([]*sagemaker.ContainerDefinition, 0, len(a)) + + for _, m := range a { + containers = append(containers, expandContainer(m.(map[string]interface{}))) + } + + return containers +} + +func flattenContainer(container *sagemaker.ContainerDefinition) []interface{} { + if container == nil { + return []interface{}{} + } + cfg := make(map[string]interface{}, 0) cfg["image"] = *container.Image @@ -295,6 +366,14 @@ func flattenPrimaryContainer(container *sagemaker.ContainerDefinition) []interfa return []interface{}{cfg} } +func flattenContainers(containers []*sagemaker.ContainerDefinition) []interface{} { + fContainers := make([]interface{}, 0, len(containers)) + for _, container := range containers { + fContainers = append(fContainers, flattenContainer(container)[0].(map[string]interface{})) + } + return fContainers +} + func flattenEnvironment(env map[string]*string) map[string]string { m := map[string]string{} for k, v := range env { diff --git a/aws/resource_aws_sagemaker_model_test.go b/aws/resource_aws_sagemaker_model_test.go index 95a7ddc37b7..2b16d0e01c4 100644 --- a/aws/resource_aws_sagemaker_model_test.go +++ b/aws/resource_aws_sagemaker_model_test.go @@ -3,7 +3,6 @@ package aws import ( "fmt" "log" - "reflect" "testing" "github.com/aws/aws-sdk-go/aws" @@ -56,8 +55,8 @@ func testSweepSagemakerModels(region string) error { } func TestAccAWSSagemakerModel_importBasic(t *testing.T) { - resourceName := "aws_sagemaker_model.foo" rName := acctest.RandString(10) + image := "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -65,11 +64,11 @@ func TestAccAWSSagemakerModel_importBasic(t *testing.T) { CheckDestroy: testAccCheckSagemakerModelDestroy, Steps: []resource.TestStep{ { - Config: testAccSagemakerModelConfig(rName), + Config: testAccSagemakerModelConfig(rName, image), }, { - ResourceName: resourceName, + ResourceName: "aws_sagemaker_model.foo", ImportState: true, ImportStateVerify: true, }, @@ -78,8 +77,8 @@ func TestAccAWSSagemakerModel_importBasic(t *testing.T) { } func TestAccAWSSagemakerModel_basic(t *testing.T) { - var model sagemaker.DescribeModelOutput rName := acctest.RandString(10) + image := "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -87,16 +86,16 @@ func TestAccAWSSagemakerModel_basic(t *testing.T) { CheckDestroy: testAccCheckSagemakerModelDestroy, Steps: []resource.TestStep{ { - Config: testAccSagemakerModelConfig(rName), + Config: testAccSagemakerModelConfig(rName, image), Check: resource.ComposeTestCheckFunc( - testAccCheckSagemakerModelExists("aws_sagemaker_model.foo", &model), + testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), resource.TestCheckResourceAttr( - "aws_sagemaker_model.foo", "name", "terraform-testacc-sagemaker-model-foo"), + "aws_sagemaker_model.foo", "name", + fmt.Sprintf("terraform-testacc-sagemaker-model-%s", rName)), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "primary_container.#", "1"), resource.TestCheckResourceAttr( - "aws_sagemaker_model.foo", "primary_container.0.image", - "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1"), + "aws_sagemaker_model.foo", "primary_container.0.image", image), resource.TestCheckResourceAttrSet("aws_sagemaker_model.foo", "execution_role_arn"), resource.TestCheckResourceAttrSet("aws_sagemaker_model.foo", "arn"), ), @@ -106,7 +105,6 @@ func TestAccAWSSagemakerModel_basic(t *testing.T) { } func TestAccAWSSagemakerModel_tags(t *testing.T) { - var model sagemaker.DescribeModelOutput rName := acctest.RandString(10) resource.ParallelTest(t, resource.TestCase{ @@ -117,11 +115,7 @@ func TestAccAWSSagemakerModel_tags(t *testing.T) { { Config: testAccSagemakerModelConfigTags(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSagemakerModelExists("aws_sagemaker_model.foo", &model), - testAccCheckSagemakerTags(&model, "foo", "bar"), - - resource.TestCheckResourceAttr( - "aws_sagemaker_model.foo", "name", "terraform-testacc-sagemaker-model-foo"), + testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "tags.%", "1"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "tags.foo", "bar"), ), @@ -129,10 +123,7 @@ func TestAccAWSSagemakerModel_tags(t *testing.T) { { Config: testAccSagemakerModelConfigTagsUpdate(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSagemakerModelExists("aws_sagemaker_model.foo", &model), - testAccCheckSagemakerTags(&model, "foo", ""), - testAccCheckSagemakerTags(&model, "bar", "baz"), - + testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "tags.%", "1"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "tags.bar", "baz"), ), @@ -142,8 +133,8 @@ func TestAccAWSSagemakerModel_tags(t *testing.T) { } func TestAccAWSSagemakerModel_primaryContainerModelDataUrl(t *testing.T) { - var model sagemaker.DescribeModelOutput rName := acctest.RandString(10) + modelDataUrl := fmt.Sprintf("https://s3-us-west-2.amazonaws.com/terraform-testacc-sagemaker-model-data-bucket-%s/model.tar.gz", rName) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -151,15 +142,11 @@ func TestAccAWSSagemakerModel_primaryContainerModelDataUrl(t *testing.T) { CheckDestroy: testAccCheckSagemakerModelDestroy, Steps: []resource.TestStep{ { - Config: testAccSagemakerPrimaryContainerModelDataUrlConfig(rName), + Config: testAccSagemakerPrimaryContainerModelDataUrlConfig(rName, modelDataUrl), Check: resource.ComposeTestCheckFunc( - testAccCheckSagemakerModelExists("aws_sagemaker_model.foo", &model), - testAccCheckSagemakerModelPrimaryContainerModelDataUrl(&model, - "https://s3-us-west-2.amazonaws.com/terraform-testacc-sagemaker-model-data-bucket/model.tar.gz"), - + testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", - "primary_container.0.model_data_url", - "https://s3-us-west-2.amazonaws.com/terraform-testacc-sagemaker-model-data-bucket/model.tar.gz"), + "primary_container.0.model_data_url", modelDataUrl), ), }, }, @@ -167,7 +154,6 @@ func TestAccAWSSagemakerModel_primaryContainerModelDataUrl(t *testing.T) { } func TestAccAWSSagemakerModel_primaryContainerHostname(t *testing.T) { - var model sagemaker.DescribeModelOutput rName := acctest.RandString(10) resource.ParallelTest(t, resource.TestCase{ @@ -178,11 +164,9 @@ func TestAccAWSSagemakerModel_primaryContainerHostname(t *testing.T) { { Config: testAccSagemakerPrimaryContainerHostnameConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSagemakerModelExists("aws_sagemaker_model.foo", &model), - testAccCheckSagemakerModelPrimaryContainerHostname(&model, "primary-hostname-foo"), - + testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", - "primary_container.0.container_hostname", "primary-hostname-foo"), + "primary_container.0.container_hostname", "foo"), ), }, }, @@ -190,10 +174,6 @@ func TestAccAWSSagemakerModel_primaryContainerHostname(t *testing.T) { } func TestAccAWSSagemakerModel_primaryContainerEnvironment(t *testing.T) { - var model sagemaker.DescribeModelOutput - environment := map[string]*string{ - "foo": aws.String("bar"), - } rName := acctest.RandString(10) resource.Test(t, resource.TestCase{ @@ -204,9 +184,7 @@ func TestAccAWSSagemakerModel_primaryContainerEnvironment(t *testing.T) { { Config: testAccSagemakerPrimaryContainerEnvironmentConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckSagemakerModelExists("aws_sagemaker_model.foo", &model), - testAccCheckSagemakerModelPrimaryContainerEnvironment(&model, environment), - + testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "primary_container.0.environment.%", "1"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", @@ -217,6 +195,72 @@ func TestAccAWSSagemakerModel_primaryContainerEnvironment(t *testing.T) { }) } +func TestAccAWSSagemakerModel_containers(t *testing.T) { + rName := acctest.RandString(10) + image := "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSagemakerModelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSagemakerModelContainers(rName, image), + Check: resource.ComposeTestCheckFunc( + testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), + resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "container.#", "2"), + resource.TestCheckResourceAttr( + "aws_sagemaker_model.foo", "container.0.image", image), + resource.TestCheckResourceAttr( + "aws_sagemaker_model.foo", "container.1.image", image), + ), + }, + }, + }) +} + +func TestAccAWSSagemakerModel_vpcConfig(t *testing.T) { + rName := acctest.RandString(10) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSagemakerModelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSagemakerModelVpcConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), + resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "vpc_config.#", "1"), + resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "vpc_config.0.subnets.#", "2"), + resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "vpc_config.0.security_group_ids.#", "2"), + ), + }, + }, + }) +} + +func TestAccAWSSagemakerModel_networkIsolation(t *testing.T) { + rName := acctest.RandString(10) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSagemakerModelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSagemakerModelNetworkIsolation(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), + + resource.TestCheckResourceAttr( + "aws_sagemaker_model.foo", "enable_network_isolation", "true"), + ), + }, + }, + }) +} + func testAccCheckSagemakerModelDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).sagemakerconn @@ -230,7 +274,7 @@ func testAccCheckSagemakerModelDestroy(s *terraform.State) error { }) if err == nil { if len(resp.Models) > 0 { - return fmt.Errorf("Sagemaker models still exist.") + return fmt.Errorf("Sagemaker models still exist") } return nil @@ -248,7 +292,7 @@ func testAccCheckSagemakerModelDestroy(s *terraform.State) error { return nil } -func testAccCheckSagemakerModelExists(n string, model *sagemaker.DescribeModelOutput) resource.TestCheckFunc { +func testAccCheckSagemakerModelExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -263,88 +307,23 @@ func testAccCheckSagemakerModelExists(n string, model *sagemaker.DescribeModelOu DescribeModelOpts := &sagemaker.DescribeModelInput{ ModelName: aws.String(rs.Primary.ID), } - resp, err := conn.DescribeModel(DescribeModelOpts) + _, err := conn.DescribeModel(DescribeModelOpts) if err != nil { return err } - *model = *resp return nil } } -func testAccCheckSagemakerTags(model *sagemaker.DescribeModelOutput, key string, value string) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).sagemakerconn - - ts, err := conn.ListTags(&sagemaker.ListTagsInput{ - ResourceArn: model.ModelArn, - }) - if err != nil { - return fmt.Errorf("Error listing 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 - } -} - -func testAccCheckSagemakerModelPrimaryContainerModelDataUrl(model *sagemaker.DescribeModelOutput, expected string) resource.TestCheckFunc { - return func(s *terraform.State) error { - modelDataUrl := model.PrimaryContainer.ModelDataUrl - if *modelDataUrl != expected { - return fmt.Errorf("Bad model data url of primary container: %s", *modelDataUrl) - } - - return nil - } -} - -func testAccCheckSagemakerModelPrimaryContainerHostname(model *sagemaker.DescribeModelOutput, expected string) resource.TestCheckFunc { - return func(s *terraform.State) error { - hostname := model.PrimaryContainer.ContainerHostname - if *hostname != expected { - return fmt.Errorf("Bad container hostname of primary container: %s", *hostname) - } - - return nil - } -} - -func testAccCheckSagemakerModelPrimaryContainerEnvironment(model *sagemaker.DescribeModelOutput, expected map[string]*string) resource.TestCheckFunc { - return func(s *terraform.State) error { - environment := model.PrimaryContainer.Environment - - if !reflect.DeepEqual(environment, expected) { - return fmt.Errorf("Bad environment of primary container.") - } - - return nil - } -} - -func testAccSagemakerModelConfig(rName string) string { +func testAccSagemakerModelConfig(rName string, image string) string { return fmt.Sprintf(` -esource "aws_sagemaker_model" "foo" { +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" + image = "%s" } } @@ -363,7 +342,7 @@ data "aws_iam_policy_document" "assume_role" { } } } -`, rName, rName) +`, rName, image, rName) } func testAccSagemakerModelConfigTags(rName string) string { @@ -432,7 +411,7 @@ data "aws_iam_policy_document" "assume_role" { `, rName, rName) } -func testAccSagemakerPrimaryContainerModelDataUrlConfig(rName string) string { +func testAccSagemakerPrimaryContainerModelDataUrlConfig(rName string, modelDataUrl string) string { return fmt.Sprintf(` resource "aws_sagemaker_model" "foo" { name = "terraform-testacc-sagemaker-model-%s" @@ -440,7 +419,7 @@ resource "aws_sagemaker_model" "foo" { primary_container { image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" - model_data_url = "https://s3-us-west-2.amazonaws.com/${aws_s3_bucket.foo.bucket}/model.tar.gz" + model_data_url = "%s" } } @@ -511,7 +490,7 @@ resource "aws_s3_bucket_object" "object" { key = "model.tar.gz" content = "some-data" } -`, rName, rName, rName) +`, rName, modelDataUrl, rName, rName, rName) } func testAccSagemakerPrimaryContainerHostnameConfig(rName string) string { @@ -522,7 +501,7 @@ resource "aws_sagemaker_model" "foo" { primary_container { image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" - container_hostname = "primary-hostname-foo" + container_hostname = "foo" } } @@ -575,3 +554,137 @@ data "aws_iam_policy_document" "assume_role" { } `, rName, rName) } + +func testAccSagemakerModelContainers(rName string, image string) string { + return fmt.Sprintf(` +resource "aws_sagemaker_model" "foo" { + name = "terraform-testacc-sagemaker-model-%s" + execution_role_arn = "${aws_iam_role.foo.arn}" + + container { + image = "%s" + } + + container { + image = "%s" + } +} + +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, image, image, rName) +} + +func testAccSagemakerModelNetworkIsolation(rName string) string { + return fmt.Sprintf(` +resource "aws_sagemaker_model" "foo" { + name = "terraform-testacc-sagemaker-model-%s" + execution_role_arn = "${aws_iam_role.foo.arn}" + enable_network_isolation = true + + 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) +} + +func testAccSagemakerModelVpcConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_sagemaker_model" "foo" { + name = "terraform-testacc-sagemaker-model-%s" + execution_role_arn = "${aws_iam_role.foo.arn}" + enable_network_isolation = true + + primary_container { + image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + } + + vpc_config { + subnets = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}"] + security_group_ids = ["${aws_security_group.foo.id}", "${aws_security_group.bar.id}"] + } + +} + +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" ] + } + } +} + +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + tags { + Name = "terraform-testacc-sagemaker-model-%s" + } +} + +resource "aws_subnet" "foo" { + cidr_block = "10.1.1.0/24" + availability_zone = "us-west-2a" + vpc_id = "${aws_vpc.foo.id}" + tags { + Name = "terraform-testacc-sagemaker-model-foo-%s" + } +} + +resource "aws_subnet" "bar" { + cidr_block = "10.1.2.0/24" + availability_zone = "us-west-2b" + vpc_id = "${aws_vpc.foo.id}" + tags { + Name = "terraform-testacc-sagemaker-model-bar-%s" + } +} + +resource "aws_security_group" "foo" { + name = "terraform-testacc-sagemaker-model-foo-%s" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_security_group" "bar" { + name = "terraform-testacc-sagemaker-model-bar-%s" + vpc_id = "${aws_vpc.foo.id}" +} +`, rName, rName, rName, rName, rName, rName, rName) +} diff --git a/aws/validators.go b/aws/validators.go index 1a6834dde55..72da0e72fe5 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -401,19 +401,25 @@ func validateSagemakerName(v interface{}, k string) (ws []string, errors []error } func validateSagemakerEnvironment(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - if !regexp.MustCompile(`^[0-9A-Za-z_]+$`).MatchString(value) { - errors = append(errors, fmt.Errorf( - "only alphanumeric characters and underscore allowed in %q: %q", - k, value)) - } - if len(value) > 1024 { - errors = append(errors, fmt.Errorf( - "%q cannot be longer than 1024 characters: %q", k, value)) - } - if regexp.MustCompile(`^[0-9]`).MatchString(value) { - errors = append(errors, fmt.Errorf( - "%q cannot begin with a digit: %q", k, value)) + value := v.(map[string]interface{}) + for envK, envV := range value { + if !regexp.MustCompile(`^[0-9A-Za-z_]+$`).MatchString(envK) { + errors = append(errors, fmt.Errorf( + "only alphanumeric characters and underscore allowed in %q: %q", + k, envK)) + } + if len(envK) > 1024 { + errors = append(errors, fmt.Errorf( + "%q cannot be longer than 1024 characters: %q", k, envK)) + } + if len(envV.(string)) > 1024 { + errors = append(errors, fmt.Errorf( + "%q cannot be longer than 1024 characters: %q", k, envV.(string))) + } + if regexp.MustCompile(`^[0-9]`).MatchString(envK) { + errors = append(errors, fmt.Errorf( + "%q cannot begin with a digit: %q", k, envK)) + } } return } @@ -443,35 +449,13 @@ func validateSagemakerModelDataUrl(v interface{}, k string) (ws []string, errors errors = append(errors, fmt.Errorf( "%q cannot be longer than 1024 characters: %q", k, value)) } - if regexp.MustCompile(`^(https|s3)://`).MatchString(value) { + if !regexp.MustCompile(`^(https|s3)://`).MatchString(value) { errors = append(errors, fmt.Errorf( "%q must be a path that starts with either s3 or https: %q", k, value)) } return } -func validateEcrRepositoryName(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - if len(value) < 2 { - errors = append(errors, fmt.Errorf( - "%q must be at least 2 characters long: %q", k, value)) - } - if len(value) > 256 { - errors = append(errors, fmt.Errorf( - "%q cannot be longer than 256 characters: %q", k, value)) - } - - // http://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_CreateRepository.html - pattern := `^(?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)*[a-z0-9]+(?:[._-][a-z0-9]+)*$` - if !regexp.MustCompile(pattern).MatchString(value) { - errors = append(errors, fmt.Errorf( - "%q doesn't comply with restrictions (%q): %q", - k, pattern, value)) - } - - return -} - func validateCloudWatchDashboardName(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if len(value) > 255 { diff --git a/website/docs/r/sagemaker_model.html.markdown b/website/docs/r/sagemaker_model.html.markdown index 246ed74cf1a..084e9bf631e 100644 --- a/website/docs/r/sagemaker_model.html.markdown +++ b/website/docs/r/sagemaker_model.html.markdown @@ -31,6 +31,9 @@ The following arguments are supported: * `name` - (Optional) The name of the model (must be unique). If omitted, Terraform will assign a random, unique name. * `primary_container` - (Required) Fields are documented below. * `execution_role_arn` - (Optional) A role to with permissions that allows SageMaker to call other services on your behalf. +* `containers` (Optional) - Specifies additional containers in the inference pipeline. +* `enable_network_isolation` (Optional) - Isolates the model container. No inbound or outbound network calls can be made to or from the model container. +* `vpc_config` (Optional) - Specifies the VPC that you want your model to connect to. VpcConfig is used in hosting services and in batch transform. * `tags` - (Optional) A mapping of tags to assign to the resource. The `primary_container` block supports: @@ -41,14 +44,12 @@ The `primary_container` block supports: * `environment` - (Optional) Environment variables for the Docker container. A list of key value pairs. - ## Attributes Reference The following attributes are exported: * `name` - The name of the model. * `arn` - The Amazon Resource Name (ARN) assigned by AWS to this model. -* `creation_time` - The creation timestamp of the model. ## Import