diff --git a/aws/resource_aws_sagemaker_model.go b/aws/resource_aws_sagemaker_model.go index a245a8b12d1..9cdb7526c69 100644 --- a/aws/resource_aws_sagemaker_model.go +++ b/aws/resource_aws_sagemaker_model.go @@ -105,6 +105,7 @@ func resourceAwsSagemakerModel() *schema.Resource { "enable_network_isolation": { Type: schema.TypeBool, Optional: true, + ForceNew: true, }, "container": { @@ -224,8 +225,8 @@ func resourceAwsSagemakerModelRead(d *schema.ResourceData, meta interface{}) err model, err := conn.DescribeModel(request) if err != nil { - if sagemakerErr, ok := err.(awserr.Error); ok && sagemakerErr.Code() == "ResourceNotFound" { - log.Printf("[INFO] unable to find the sagemaker resource and therfore it is removed from the state: %s", d.Id()) + if sagemakerErr, ok := err.(awserr.Error); ok && sagemakerErr.Code() == "ValidationException" { + log.Printf("[INFO] unable to find the sagemaker model resource and therefore it is removed from the state: %s", d.Id()) d.SetId("") return nil } @@ -241,6 +242,9 @@ 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("enable_network_isolation", model.EnableNetworkIsolation); err != nil { + return err + } if err := d.Set("primary_container", flattenContainer(model.PrimaryContainer)); err != nil { return err } @@ -254,6 +258,10 @@ func resourceAwsSagemakerModelRead(d *schema.ResourceData, meta interface{}) err tagsOutput, err := conn.ListTags(&sagemaker.ListTagsInput{ ResourceArn: model.ModelArn, }) + if err != nil { + return fmt.Errorf("error listing tags of Sagemaker model %s: %s", d.Id(), err) + } + if err := d.Set("tags", tagsToMapSagemaker(tagsOutput.Tags)); err != nil { return err } diff --git a/aws/resource_aws_sagemaker_model_test.go b/aws/resource_aws_sagemaker_model_test.go index 2b16d0e01c4..527a7f98c30 100644 --- a/aws/resource_aws_sagemaker_model_test.go +++ b/aws/resource_aws_sagemaker_model_test.go @@ -13,6 +13,10 @@ import ( "github.com/hashicorp/terraform/terraform" ) +const ( + image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" +) + func init() { resource.AddTestSweepers("aws_sagemaker_model", &resource.Sweeper{ Name: "aws_sagemaker_model", @@ -32,7 +36,7 @@ func testSweepSagemakerModels(region string) error { } resp, err := conn.ListModels(req) if err != nil { - return fmt.Errorf("Error listing models: %s", err) + return fmt.Errorf("error listing models: %s", err) } if len(resp.Models) == 0 { @@ -46,7 +50,7 @@ func testSweepSagemakerModels(region string) error { }) if err != nil { return fmt.Errorf( - "Error deleting sagemaker model (%s): %s", + "error deleting sagemaker model (%s): %s", *model.ModelName, err) } } @@ -54,31 +58,8 @@ func testSweepSagemakerModels(region string) error { return nil } -func TestAccAWSSagemakerModel_importBasic(t *testing.T) { - rName := acctest.RandString(10) - image := "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckSagemakerModelDestroy, - Steps: []resource.TestStep{ - { - Config: testAccSagemakerModelConfig(rName, image), - }, - - { - ResourceName: "aws_sagemaker_model.foo", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - func TestAccAWSSagemakerModel_basic(t *testing.T) { rName := acctest.RandString(10) - image := "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -89,7 +70,6 @@ func TestAccAWSSagemakerModel_basic(t *testing.T) { Config: testAccSagemakerModelConfig(rName, image), Check: resource.ComposeTestCheckFunc( testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), - resource.TestCheckResourceAttr( "aws_sagemaker_model.foo", "name", fmt.Sprintf("terraform-testacc-sagemaker-model-%s", rName)), @@ -100,6 +80,11 @@ func TestAccAWSSagemakerModel_basic(t *testing.T) { resource.TestCheckResourceAttrSet("aws_sagemaker_model.foo", "arn"), ), }, + { + ResourceName: "aws_sagemaker_model.foo", + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -113,7 +98,7 @@ func TestAccAWSSagemakerModel_tags(t *testing.T) { CheckDestroy: testAccCheckSagemakerModelDestroy, Steps: []resource.TestStep{ { - Config: testAccSagemakerModelConfigTags(rName), + Config: testAccSagemakerModelConfigTags(rName, image), Check: resource.ComposeTestCheckFunc( testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "tags.%", "1"), @@ -121,13 +106,18 @@ func TestAccAWSSagemakerModel_tags(t *testing.T) { ), }, { - Config: testAccSagemakerModelConfigTagsUpdate(rName), + Config: testAccSagemakerModelConfigTagsUpdate(rName, image), Check: resource.ComposeTestCheckFunc( testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "tags.%", "1"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "tags.bar", "baz"), ), }, + { + ResourceName: "aws_sagemaker_model.foo", + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -142,13 +132,18 @@ func TestAccAWSSagemakerModel_primaryContainerModelDataUrl(t *testing.T) { CheckDestroy: testAccCheckSagemakerModelDestroy, Steps: []resource.TestStep{ { - Config: testAccSagemakerPrimaryContainerModelDataUrlConfig(rName, modelDataUrl), + Config: testAccSagemakerPrimaryContainerModelDataUrlConfig(rName, image, modelDataUrl), Check: resource.ComposeTestCheckFunc( testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "primary_container.0.model_data_url", modelDataUrl), ), }, + { + ResourceName: "aws_sagemaker_model.foo", + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -162,13 +157,18 @@ func TestAccAWSSagemakerModel_primaryContainerHostname(t *testing.T) { CheckDestroy: testAccCheckSagemakerModelDestroy, Steps: []resource.TestStep{ { - Config: testAccSagemakerPrimaryContainerHostnameConfig(rName), + Config: testAccSagemakerPrimaryContainerHostnameConfig(rName, image), Check: resource.ComposeTestCheckFunc( testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "primary_container.0.container_hostname", "foo"), ), }, + { + ResourceName: "aws_sagemaker_model.foo", + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -182,7 +182,7 @@ func TestAccAWSSagemakerModel_primaryContainerEnvironment(t *testing.T) { CheckDestroy: testAccCheckSagemakerModelDestroy, Steps: []resource.TestStep{ { - Config: testAccSagemakerPrimaryContainerEnvironmentConfig(rName), + Config: testAccSagemakerPrimaryContainerEnvironmentConfig(rName, image), Check: resource.ComposeTestCheckFunc( testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", @@ -191,13 +191,17 @@ func TestAccAWSSagemakerModel_primaryContainerEnvironment(t *testing.T) { "primary_container.0.environment.foo", "bar"), ), }, + { + ResourceName: "aws_sagemaker_model.foo", + ImportState: true, + ImportStateVerify: true, + }, }, }) } 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) }, @@ -215,6 +219,11 @@ func TestAccAWSSagemakerModel_containers(t *testing.T) { "aws_sagemaker_model.foo", "container.1.image", image), ), }, + { + ResourceName: "aws_sagemaker_model.foo", + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -228,7 +237,7 @@ func TestAccAWSSagemakerModel_vpcConfig(t *testing.T) { CheckDestroy: testAccCheckSagemakerModelDestroy, Steps: []resource.TestStep{ { - Config: testAccSagemakerModelVpcConfig(rName), + Config: testAccSagemakerModelVpcConfig(rName, image), Check: resource.ComposeTestCheckFunc( testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "vpc_config.#", "1"), @@ -236,6 +245,11 @@ func TestAccAWSSagemakerModel_vpcConfig(t *testing.T) { resource.TestCheckResourceAttr("aws_sagemaker_model.foo", "vpc_config.0.security_group_ids.#", "2"), ), }, + { + ResourceName: "aws_sagemaker_model.foo", + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -249,14 +263,18 @@ func TestAccAWSSagemakerModel_networkIsolation(t *testing.T) { CheckDestroy: testAccCheckSagemakerModelDestroy, Steps: []resource.TestStep{ { - Config: testAccSagemakerModelNetworkIsolation(rName), + Config: testAccSagemakerModelNetworkIsolation(rName, image), Check: resource.ComposeTestCheckFunc( testAccCheckSagemakerModelExists("aws_sagemaker_model.foo"), - resource.TestCheckResourceAttr( "aws_sagemaker_model.foo", "enable_network_isolation", "true"), ), }, + { + ResourceName: "aws_sagemaker_model.foo", + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -345,14 +363,14 @@ data "aws_iam_policy_document" "assume_role" { `, rName, image, rName) } -func testAccSagemakerModelConfigTags(rName string) string { +func testAccSagemakerModelConfigTags(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}" primary_container { - image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + image = "%s" } tags { @@ -375,17 +393,17 @@ data "aws_iam_policy_document" "assume_role" { } } } -`, rName, rName) +`, rName, image, rName) } -func testAccSagemakerModelConfigTagsUpdate(rName string) string { +func testAccSagemakerModelConfigTagsUpdate(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}" primary_container { - image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + image = "%s" } tags { @@ -408,17 +426,17 @@ data "aws_iam_policy_document" "assume_role" { } } } -`, rName, rName) +`, rName, image, rName) } -func testAccSagemakerPrimaryContainerModelDataUrlConfig(rName string, modelDataUrl string) string { +func testAccSagemakerPrimaryContainerModelDataUrlConfig(rName string, image string, modelDataUrl string) string { return fmt.Sprintf(` 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" model_data_url = "%s" } } @@ -490,17 +508,17 @@ resource "aws_s3_bucket_object" "object" { key = "model.tar.gz" content = "some-data" } -`, rName, modelDataUrl, rName, rName, rName) +`, rName, image, modelDataUrl, rName, rName, rName) } -func testAccSagemakerPrimaryContainerHostnameConfig(rName string) string { +func testAccSagemakerPrimaryContainerHostnameConfig(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}" primary_container { - image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + image = "%s" container_hostname = "foo" } } @@ -520,17 +538,18 @@ data "aws_iam_policy_document" "assume_role" { } } } -`, rName, rName) +`, rName, image, rName) } -func testAccSagemakerPrimaryContainerEnvironmentConfig(rName string) string { +func testAccSagemakerPrimaryContainerEnvironmentConfig(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}" primary_container { - image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + image = "%s" + environment { foo = "bar" } @@ -552,7 +571,7 @@ data "aws_iam_policy_document" "assume_role" { } } } -`, rName, rName) +`, rName, image, rName) } func testAccSagemakerModelContainers(rName string, image string) string { @@ -588,7 +607,7 @@ data "aws_iam_policy_document" "assume_role" { `, rName, image, image, rName) } -func testAccSagemakerModelNetworkIsolation(rName string) string { +func testAccSagemakerModelNetworkIsolation(rName string, image string) string { return fmt.Sprintf(` resource "aws_sagemaker_model" "foo" { name = "terraform-testacc-sagemaker-model-%s" @@ -596,7 +615,7 @@ resource "aws_sagemaker_model" "foo" { enable_network_isolation = true primary_container { - image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + image = "%s" } } @@ -615,10 +634,10 @@ data "aws_iam_policy_document" "assume_role" { } } } -`, rName, rName) +`, rName, image, rName) } -func testAccSagemakerModelVpcConfig(rName string) string { +func testAccSagemakerModelVpcConfig(rName string, image string) string { return fmt.Sprintf(` resource "aws_sagemaker_model" "foo" { name = "terraform-testacc-sagemaker-model-%s" @@ -626,7 +645,7 @@ resource "aws_sagemaker_model" "foo" { enable_network_isolation = true primary_container { - image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" + image = "%s" } vpc_config { @@ -686,5 +705,5 @@ 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) +`, rName, image, rName, rName, rName, rName, rName, rName) } diff --git a/aws/validators.go b/aws/validators.go index 72da0e72fe5..47a9883f6d5 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -801,24 +801,6 @@ func validateS3BucketLifecycleTransitionStorageClass() schema.SchemaValidateFunc }, false) } -func validateSagemakerName(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 hyphens allowed in %q: %q", - k, value)) - } - if len(value) > 63 { - errors = append(errors, fmt.Errorf( - "%q cannot be longer than 63 characters: %q", k, value)) - } - if regexp.MustCompile(`^-`).MatchString(value) { - errors = append(errors, fmt.Errorf( - "%q cannot begin with a hyphen: %q", k, value)) - } - return -} - func validateDbEventSubscriptionName(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { diff --git a/website/docs/r/sagemaker_model.html.markdown b/website/docs/r/sagemaker_model.html.markdown index 084e9bf631e..0a04681fd0e 100644 --- a/website/docs/r/sagemaker_model.html.markdown +++ b/website/docs/r/sagemaker_model.html.markdown @@ -3,12 +3,12 @@ layout: "aws" page_title: "AWS: sagemaker_model" sidebar_current: "docs-aws-resource-sagemaker-model" description: |- - Provides a Sagemaker model resource. + Provides a SageMaker model resource. --- # aws\_sagemaker\_model -Provides a Sagemaker model resource. +Provides a SageMaker model resource. ## Example Usage @@ -17,11 +17,26 @@ Basic usage: ```hcl resource "aws_sagemaker_model" "m" { name = "my-model" + execution_role_arn = "${aws_iam_role.foo.arn}" primary_container { - image = "111111111111.ecr.us-west-2.amazonaws.com/my-docker-image:latest" + image = "174872318107.dkr.ecr.us-west-2.amazonaws.com/kmeans:1" } } + +resource "aws_iam_role" "r" { + 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" ] + } + } +} ``` ## Argument Reference @@ -29,14 +44,14 @@ resource "aws_sagemaker_model" "m" { 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. +* `primary_container` - (Optional) The primary docker image containing inference code that is used when the model is deployed for predictions. If not specified, the `container` argument is required. Fields are documented below. +* `execution_role_arn` - (Required) A role that SageMaker can assume to access model artifacts and docker images for deployment. +* `container` (Optional) - Specifies containers in the inference pipeline. If not specified, the `primary_container` argument is required. Fields are documented below. * `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: +The `primary_container` and `container` block both support: * `image` - (Required) The registry path where the inference code image is stored in Amazon ECR. * `model_data_url` - (Optional) The URL for the S3 location where model artifacts are stored.