Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aws_sagemaker_endpoint_configuration - add name_prefix argument #28785

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/28785.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_sagemaker_endpoint_configuration: Add `name_prefix` argument
```
28 changes: 17 additions & 11 deletions internal/service/sagemaker/endpoint_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/create"
"github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag"
"github.com/hashicorp/terraform-provider-aws/internal/flex"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
Expand Down Expand Up @@ -207,11 +208,20 @@ func ResourceEndpointConfiguration() *schema.Resource {
ValidateFunc: verify.ValidARN,
},
"name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validName,
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
ValidateFunc: validName,
},
"name_prefix": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name"},
ValidateFunc: validPrefix,
},
"production_variants": {
Type: schema.TypeList,
Expand Down Expand Up @@ -460,12 +470,7 @@ func resourceEndpointConfigurationCreate(ctx context.Context, d *schema.Resource
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{})))

var name string
if v, ok := d.GetOk("name"); ok {
name = v.(string)
} else {
name = resource.UniqueId()
}
name := create.Name(d.Get("name").(string), d.Get("name_prefix").(string))

createOpts := &sagemaker.CreateEndpointConfigInput{
EndpointConfigName: aws.String(name),
Expand Down Expand Up @@ -522,6 +527,7 @@ func resourceEndpointConfigurationRead(ctx context.Context, d *schema.ResourceDa

d.Set("arn", endpointConfig.EndpointConfigArn)
d.Set("name", endpointConfig.EndpointConfigName)
d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(endpointConfig.EndpointConfigName)))
d.Set("kms_key_arn", endpointConfig.KmsKeyId)

if err := d.Set("production_variants", flattenProductionVariants(endpointConfig.ProductionVariants)); err != nil {
Expand Down
86 changes: 86 additions & 0 deletions internal/service/sagemaker/endpoint_configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,62 @@ func TestAccSageMakerEndpointConfiguration_basic(t *testing.T) {
})
}

func TestAccSageMakerEndpointConfiguration_nameGenerated(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_sagemaker_endpoint_configuration.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, sagemaker.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckEndpointConfigurationDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccEndpointConfigurationConfig_nameGenerated(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckEndpointConfigurationExists(ctx, resourceName),
acctest.CheckResourceAttrNameGenerated(resourceName, "name"),
resource.TestCheckResourceAttr(resourceName, "name_prefix", "terraform-"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccSageMakerEndpointConfiguration_namePrefix(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_sagemaker_endpoint_configuration.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, sagemaker.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckEndpointConfigurationDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccEndpointConfigurationConfig_namePrefix(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckEndpointConfigurationExists(ctx, resourceName),
acctest.CheckResourceAttrNameFromPrefix(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "name_prefix", rName),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccSageMakerEndpointConfiguration_shadowProductionVariants(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
Expand Down Expand Up @@ -607,6 +663,36 @@ resource "aws_sagemaker_endpoint_configuration" "test" {
`, rName))
}

func testAccEndpointConfigurationConfig_nameGenerated(rName string) string {
return acctest.ConfigCompose(testAccEndpointConfigurationConfig_base(rName), `
resource "aws_sagemaker_endpoint_configuration" "test" {
production_variants {
variant_name = "variant-1"
model_name = aws_sagemaker_model.test.name
initial_instance_count = 2
instance_type = "ml.t2.medium"
initial_variant_weight = 1
}
}
`)
}

func testAccEndpointConfigurationConfig_namePrefix(rName string) string {
return acctest.ConfigCompose(testAccEndpointConfigurationConfig_base(rName), fmt.Sprintf(`
resource "aws_sagemaker_endpoint_configuration" "test" {
name_prefix = %[1]q

production_variants {
variant_name = "variant-1"
model_name = aws_sagemaker_model.test.name
initial_instance_count = 2
instance_type = "ml.t2.medium"
initial_variant_weight = 1
}
}
`, rName))
}

func testAccEndpointConfigurationConfig_shadowProductionVariants(rName string) string {
return acctest.ConfigCompose(testAccEndpointConfigurationConfig_base(rName), fmt.Sprintf(`
resource "aws_sagemaker_endpoint_configuration" "test" {
Expand Down
21 changes: 21 additions & 0 deletions internal/service/sagemaker/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package sagemaker
import (
"fmt"
"regexp"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func validEnvironment(v interface{}, k string) (ws []string, errors []error) {
Expand Down Expand Up @@ -78,3 +80,22 @@ func validName(v interface{}, k string) (ws []string, errors []error) {
}
return
}

func validPrefix(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))
}
maxLength := 63 - resource.UniqueIDSuffixLength
if len(value) > maxLength {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than %d characters: %q", k, maxLength, value))
}
if regexp.MustCompile(`^-`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot begin with a hyphen: %q", k, value))
}
return
}
32 changes: 32 additions & 0 deletions internal/service/sagemaker/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,35 @@ func TestValidName(t *testing.T) {
}
}
}

func TestValidPrefix(t *testing.T) {
t.Parallel()

maxLength := 37
validPrefixes := []string{
"ValidSageMakerName",
"Valid-5a63Mak3r-Name",
"123-456-789",
"1234",
strings.Repeat("W", maxLength),
}
for _, v := range validPrefixes {
_, errors := validPrefix(v, "name_prefix")
if len(errors) != 0 {
t.Fatalf("%q should be a valid SageMaker prefix with maximum length %d chars: %q", v, maxLength, errors)
}
}

invalidPrefixes := []string{
"Invalid prefix", // blanks are not allowed
"1#{}nook", // other non-alphanumeric chars
"-nook", // cannot start with hyphen
strings.Repeat("W", maxLength+1), // length > maxLength
}
for _, v := range invalidPrefixes {
_, errors := validPrefix(v, "name_prefix")
if len(errors) == 0 {
t.Fatalf("%q should be an invalid SageMaker prefix", v)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ The following arguments are supported:

* `production_variants` - (Required) An list of ProductionVariant objects, one for each model that you want to host at this endpoint. Fields are documented below.
* `kms_key_arn` - (Optional) Amazon Resource Name (ARN) of a AWS Key Management Service key that Amazon SageMaker uses to encrypt data on the storage volume attached to the ML compute instance that hosts the endpoint.
* `name` - (Optional) The name of the endpoint configuration. If omitted, Terraform will assign a random, unique name.
* `name` - (Optional) The name of the endpoint configuration. If omitted, Terraform will assign a random, unique name. Conflicts with `name_prefix`.
* `name_prefix` - (Optional) Creates a unique endpoint configuration name beginning with the specified prefix. Conflicts with `name`.
* `tags` - (Optional) A mapping of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.
* `data_capture_config` - (Optional) Specifies the parameters to capture input/output of SageMaker models endpoints. Fields are documented below.
* `async_inference_config` - (Optional) Specifies configuration for how an endpoint performs asynchronous inference.
Expand Down