diff --git a/.changelog/42610.txt b/.changelog/42610.txt new file mode 100644 index 000000000000..9f0c7adf791f --- /dev/null +++ b/.changelog/42610.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_sagemaker_image_version: Add `aliases` argument +``` \ No newline at end of file diff --git a/internal/service/sagemaker/image_version.go b/internal/service/sagemaker/image_version.go index c73b65123952..5e8ae2855082 100644 --- a/internal/service/sagemaker/image_version.go +++ b/internal/service/sagemaker/image_version.go @@ -54,6 +54,13 @@ func resourceImageVersion() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "aliases": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, "base_image": { Type: schema.TypeString, Required: true, @@ -153,6 +160,10 @@ func resourceImageVersionCreate(ctx context.Context, d *schema.ResourceData, met input.ProgrammingLang = aws.String(v.(string)) } + if v, ok := d.GetOk("aliases"); ok && v.(*schema.Set).Len() > 0 { + input.Aliases = flex.ExpandStringValueSet(v.(*schema.Set)) + } + if _, err := conn.CreateImageVersion(ctx, &input); err != nil { return sdkdiag.AppendErrorf(diags, "creating SageMaker AI Image Version %s: %s", name, err) } @@ -208,6 +219,17 @@ func resourceImageVersionRead(ctx context.Context, d *schema.ResourceData, meta d.Set("ml_framework", image.MLFramework) d.Set("programming_lang", image.ProgrammingLang) + // The DescribeImageVersion API response does not include aliases, so these must + // be fetched separately using the ListAliases API + aliases, err := findImageVersionAliasesByTwoPartKey(ctx, conn, name, version) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading aliases for SageMaker AI Image Version (%s): %s", d.Id(), err) + } + + if err := d.Set("aliases", aliases); err != nil { + return sdkdiag.AppendErrorf(diags, "setting aliases: %s", err) + } + return diags } @@ -251,6 +273,21 @@ func resourceImageVersionUpdate(ctx context.Context, d *schema.ResourceData, met input.ProgrammingLang = aws.String(d.Get("programming_lang").(string)) } + if d.HasChange("aliases") { + // For UpdateImageVersion, we need to use AliasesToAdd and AliasesToDelete + // instead of Aliases directly + o, n := d.GetChange("aliases") + os, ns := o.(*schema.Set), n.(*schema.Set) + add, del := flex.ExpandStringValueSet(ns.Difference(os)), flex.ExpandStringValueSet(os.Difference(ns)) + + if len(add) > 0 { + input.AliasesToAdd = add + } + if len(del) > 0 { + input.AliasesToDelete = del + } + } + if _, err := conn.UpdateImageVersion(ctx, &input); err != nil { return sdkdiag.AppendErrorf(diags, "updating SageMaker AI Image Version (%s): %s", d.Id(), err) } @@ -265,12 +302,12 @@ func resourceImageVersionDelete(ctx context.Context, d *schema.ResourceData, met name := d.Get("image_name").(string) version := d.Get(names.AttrVersion).(int) - input := &sagemaker.DeleteImageVersionInput{ + input := sagemaker.DeleteImageVersionInput{ ImageName: aws.String(name), Version: aws.Int32(int32(version)), } - if _, err := conn.DeleteImageVersion(ctx, input); err != nil { + if _, err := conn.DeleteImageVersion(ctx, &input); err != nil { if errs.IsAErrorMessageContains[*awstypes.ResourceNotFound](err, "does not exist") { return diags } @@ -342,6 +379,32 @@ func findImageVersionByTwoPartKey(ctx context.Context, conn *sagemaker.Client, n return output, nil } +func findImageVersionAliasesByTwoPartKey(ctx context.Context, conn *sagemaker.Client, name string, version int) ([]string, error) { + input := sagemaker.ListAliasesInput{ + ImageName: aws.String(name), + Version: aws.Int32(int32(version)), + } + + output, err := conn.ListAliases(ctx, &input) + + if errs.IsAErrorMessageContains[*awstypes.ResourceNotFound](err, "does not exist") { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.SageMakerImageVersionAliases, nil +} + // expandImageVersionResourceID wraps flex.ExpandResourceId and handles conversion // of the version portion to an integer func expandImageVersionResourceID(id string) (string, int, error) { @@ -353,7 +416,7 @@ func expandImageVersionResourceID(id string) (string, int, error) { name := parts[0] version, err := strconv.Atoi(parts[1]) if err != nil { - return name, version, err + return name, 0, err } return name, version, nil diff --git a/internal/service/sagemaker/image_version_test.go b/internal/service/sagemaker/image_version_test.go index 8d70b2dec887..adaf4e2f16bc 100644 --- a/internal/service/sagemaker/image_version_test.go +++ b/internal/service/sagemaker/image_version_test.go @@ -237,6 +237,60 @@ func TestAccSageMakerImageVersion_multiple(t *testing.T) { }) } +func TestAccSageMakerImageVersion_aliases(t *testing.T) { + ctx := acctest.Context(t) + + var image sagemaker.DescribeImageVersionOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sagemaker_image_version.test" + baseImage := acctest.SkipIfEnvVarNotSet(t, imageVersionBaseImageEnvVar) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SageMakerServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckImageVersionDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccImageVersionConfig_aliases(rName, baseImage), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckImageVersionExists(ctx, resourceName, &image), + resource.TestCheckResourceAttr(resourceName, "image_name", rName), + resource.TestCheckResourceAttr(resourceName, "base_image", baseImage), + resource.TestCheckResourceAttr(resourceName, "aliases.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "aliases.*", "latest"), + resource.TestCheckTypeSetElemAttr(resourceName, "aliases.*", "stable"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccImageVersionConfig_basic(rName, baseImage), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckImageVersionExists(ctx, resourceName, &image), + resource.TestCheckResourceAttr(resourceName, "image_name", rName), + resource.TestCheckResourceAttr(resourceName, "base_image", baseImage), + resource.TestCheckResourceAttr(resourceName, "aliases.#", "0"), + ), + }, + { + Config: testAccImageVersionConfig_aliases(rName, baseImage), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckImageVersionExists(ctx, resourceName, &image), + resource.TestCheckResourceAttr(resourceName, "image_name", rName), + resource.TestCheckResourceAttr(resourceName, "base_image", baseImage), + resource.TestCheckResourceAttr(resourceName, "aliases.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "aliases.*", "latest"), + resource.TestCheckTypeSetElemAttr(resourceName, "aliases.*", "stable"), + ), + }, + }, + }) +} + func TestAccSageMakerImageVersion_upgrade_V5_98_0(t *testing.T) { ctx := acctest.Context(t) @@ -423,3 +477,15 @@ resource "aws_sagemaker_image_version" "test_v2" { } `, baseImage)) } + +func testAccImageVersionConfig_aliases(rName, baseImage string) string { + return acctest.ConfigCompose( + testAccImageVersionConfigBase(rName), + fmt.Sprintf(` +resource "aws_sagemaker_image_version" "test" { + image_name = aws_sagemaker_image.test.id + base_image = %[1]q + aliases = ["latest", "stable"] +} +`, baseImage)) +} diff --git a/website/docs/r/sagemaker_image_version.html.markdown b/website/docs/r/sagemaker_image_version.html.markdown index 2c9375f73928..7858bf2f6742 100644 --- a/website/docs/r/sagemaker_image_version.html.markdown +++ b/website/docs/r/sagemaker_image_version.html.markdown @@ -21,12 +21,23 @@ resource "aws_sagemaker_image_version" "example" { } ``` +### With Aliases + +```terraform +resource "aws_sagemaker_image_version" "test" { + image_name = aws_sagemaker_image.test.id + base_image = "012345678912.dkr.ecr.us-west-2.amazonaws.com/image:latest" + aliases = ["latest", "stable"] +} +``` + ## Argument Reference This resource supports the following arguments: * `image_name` - (Required) The name of the image. Must be unique to your account. * `base_image` - (Required) The registry path of the container image on which this image version is based. +* `aliases` - (Optional) A list of aliases for the image version. * `horovod` - (Optional) Indicates Horovod compatibility. * `job_type` - (Optional) Indicates SageMaker AI job type compatibility. Valid values are: `TRAINING`, `INFERENCE`, and `NOTEBOOK_KERNEL`. * `ml_framework` - (Optional) The machine learning framework vended in the image version.