From cbadc98d7d131b60cfd161d0cb209d32959338af Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Fri, 18 Oct 2024 14:31:54 -0400 Subject: [PATCH 1/3] sagemaker space - support app lifecycle management Signed-off-by: drfaust92 --- internal/service/sagemaker/sagemaker_test.go | 1 + internal/service/sagemaker/space.go | 122 +++++++++++++++++++ internal/service/sagemaker/space_test.go | 75 ++++++++++++ website/docs/r/sagemaker_space.html.markdown | 15 +++ 4 files changed, 213 insertions(+) diff --git a/internal/service/sagemaker/sagemaker_test.go b/internal/service/sagemaker/sagemaker_test.go index 0b28f3aeee3..f4cf8747b6e 100644 --- a/internal/service/sagemaker/sagemaker_test.go +++ b/internal/service/sagemaker/sagemaker_test.go @@ -103,6 +103,7 @@ func TestAccSageMaker_serial(t *testing.T) { "kernelGatewayAppSettings_imageConfig": testAccSpace_kernelGatewayAppSettings_imageconfig, "jupyterServerAppSettings": testAccSpace_jupyterServerAppSettings, "jupyterLabAppSettings": testAccSpace_jupyterLabAppSettings, + "jupyterLabAppSettingsLifecycle": testAccSpace_jupyterLabAppSettingsAppLifecycle, "codeEditorAppSettings": testAccSpace_codeEditorAppSettings, "storageSettings": testAccSpace_storageSettings, "customFileSystem": testAccSpace_customFileSystem, diff --git a/internal/service/sagemaker/space.go b/internal/service/sagemaker/space.go index a386587b421..4c8c4ea9d80 100644 --- a/internal/service/sagemaker/space.go +++ b/internal/service/sagemaker/space.go @@ -100,6 +100,29 @@ func resourceSpace() *schema.Resource { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "app_lifecycle_management": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "idle_settings": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "idle_timeout_in_minutes": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(60, 525600), + }, + }, + }, + }, + }, + }, + }, "default_resource_spec": { Type: schema.TypeList, Required: true, @@ -163,6 +186,29 @@ func resourceSpace() *schema.Resource { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "app_lifecycle_management": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "idle_settings": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "idle_timeout_in_minutes": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(60, 525600), + }, + }, + }, + }, + }, + }, + }, "code_repository": { Type: schema.TypeSet, Optional: true, @@ -687,6 +733,10 @@ func expandSpaceCodeEditorAppSettings(l []interface{}) *awstypes.SpaceCodeEditor config := &awstypes.SpaceCodeEditorAppSettings{} + if v, ok := m["app_lifecycle_management"].([]interface{}); ok && len(v) > 0 { + config.AppLifecycleManagement = expandSpaceAppLifecycleManagement(v) + } + if v, ok := m["default_resource_spec"].([]interface{}); ok && len(v) > 0 { config.DefaultResourceSpec = expandResourceSpec(v) } @@ -701,6 +751,10 @@ func flattenSpaceCodeEditorAppSettings(config *awstypes.SpaceCodeEditorAppSettin m := map[string]interface{}{} + if config.AppLifecycleManagement != nil { + m["app_lifecycle_management"] = flattenSpaceAppLifecycleManagement(config.AppLifecycleManagement) + } + if config.DefaultResourceSpec != nil { m["default_resource_spec"] = flattenResourceSpec(config.DefaultResourceSpec) } @@ -717,6 +771,10 @@ func expandSpaceJupyterLabAppSettings(l []interface{}) *awstypes.SpaceJupyterLab config := &awstypes.SpaceJupyterLabAppSettings{} + if v, ok := m["app_lifecycle_management"].([]interface{}); ok && len(v) > 0 { + config.AppLifecycleManagement = expandSpaceAppLifecycleManagement(v) + } + if v, ok := m["code_repository"].(*schema.Set); ok && v.Len() > 0 { config.CodeRepositories = expandCodeRepositories(v.List()) } @@ -735,6 +793,10 @@ func flattenSpaceJupyterLabAppSettings(config *awstypes.SpaceJupyterLabAppSettin m := map[string]interface{}{} + if config.AppLifecycleManagement != nil { + m["app_lifecycle_management"] = flattenSpaceAppLifecycleManagement(config.AppLifecycleManagement) + } + if config.CodeRepositories != nil { m["code_repository"] = flattenCodeRepositories(config.CodeRepositories) } @@ -951,3 +1013,63 @@ func flattenSpaceSharingSettings(config *awstypes.SpaceSharingSettings) []map[st return []map[string]interface{}{m} } + +func expandSpaceAppLifecycleManagement(l []interface{}) *awstypes.SpaceAppLifecycleManagement { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &awstypes.SpaceAppLifecycleManagement{} + + if v, ok := m["idle_settings"].([]interface{}); ok && len(v) > 0 { + config.IdleSettings = expandSpaceIdleSettings(v) + } + + return config +} + +func expandSpaceIdleSettings(l []interface{}) *awstypes.SpaceIdleSettings { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &awstypes.SpaceIdleSettings{} + + if v, ok := m["idle_timeout_in_minutes"].(int); ok { + config.IdleTimeoutInMinutes = aws.Int32(int32(v)) + } + + return config +} + +func flattenSpaceAppLifecycleManagement(config *awstypes.SpaceAppLifecycleManagement) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{} + + if config.IdleSettings != nil { + m["idle_settings"] = flattenSpaceIdleSettings(config.IdleSettings) + } + + return []map[string]interface{}{m} +} + +func flattenSpaceIdleSettings(config *awstypes.SpaceIdleSettings) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{} + + if config.IdleTimeoutInMinutes != nil { + m["idle_timeout_in_minutes"] = aws.ToInt32(config.IdleTimeoutInMinutes) + } + + return []map[string]interface{}{m} +} diff --git a/internal/service/sagemaker/space_test.go b/internal/service/sagemaker/space_test.go index 4da7998c14c..e81cab6334d 100644 --- a/internal/service/sagemaker/space_test.go +++ b/internal/service/sagemaker/space_test.go @@ -338,6 +338,45 @@ func testAccSpace_jupyterLabAppSettings(t *testing.T) { }) } +func testAccSpace_jupyterLabAppSettingsAppLifecycle(t *testing.T) { + ctx := acctest.Context(t) + var domain sagemaker.DescribeSpaceOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sagemaker_space.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SageMakerServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckSpaceDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccSpaceConfig_jupyterLabAppSettingsLifecycle(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckSpaceExists(ctx, resourceName, &domain), + resource.TestCheckResourceAttr(resourceName, "space_settings.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "space_settings.0.app_type", "JupyterLab"), + resource.TestCheckResourceAttr(resourceName, "space_settings.0.jupyter_lab_app_settings.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "space_settings.0.jupyter_lab_app_settings.0.default_resource_spec.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "space_settings.0.jupyter_lab_app_settings.0.default_resource_spec.0.instance_type", "ml.t3.micro"), + resource.TestCheckResourceAttr(resourceName, "space_settings.0.jupyter_lab_app_settings.0.app_lifecycle_management.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "space_settings.0.jupyter_lab_app_settings.0.app_lifecycle_management.0.idle_settings.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "space_settings.0.jupyter_lab_app_settings.0.app_lifecycle_management.0.idle_settings.0.idle_timeout_in_minutes", "60"), + resource.TestCheckResourceAttr(resourceName, "space_sharing_settings.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "space_sharing_settings.0.sharing_type", "Private"), + resource.TestCheckResourceAttr(resourceName, "ownership_settings.#", acctest.Ct1), + resource.TestCheckResourceAttrPair(resourceName, "ownership_settings.0.owner_user_profile_name", "aws_sagemaker_user_profile.test", "user_profile_name"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccSpace_jupyterServerAppSettings(t *testing.T) { ctx := acctest.Context(t) var domain sagemaker.DescribeSpaceOutput @@ -618,6 +657,42 @@ resource "aws_sagemaker_space" "test" { `, rName)) } +func testAccSpaceConfig_jupyterLabAppSettingsLifecycle(rName string) string { + return acctest.ConfigCompose(testAccDomainConfig_jupyterLabAppSettingsAppLifecycle(rName), fmt.Sprintf(` +resource "aws_sagemaker_user_profile" "test" { + domain_id = aws_sagemaker_domain.test.id + user_profile_name = "%[1]s-2" +} + +resource "aws_sagemaker_space" "test" { + domain_id = aws_sagemaker_domain.test.id + space_name = %[1]q + + space_sharing_settings { + sharing_type = "Private" + } + + ownership_settings { + owner_user_profile_name = aws_sagemaker_user_profile.test.user_profile_name + } + + space_settings { + app_type = "JupyterLab" + jupyter_lab_app_settings { + app_lifecycle_management { + idle_settings { + idle_timeout_in_minutes = 60 + } + } + default_resource_spec { + instance_type = "ml.t3.micro" + } + } + } +} +`, rName)) +} + func testAccSpaceConfig_jupyterServerAppSettings(rName string) string { return acctest.ConfigCompose(testAccSpaceConfig_base(rName), fmt.Sprintf(` resource "aws_sagemaker_space" "test" { diff --git a/website/docs/r/sagemaker_space.html.markdown b/website/docs/r/sagemaker_space.html.markdown index ab51b1f8051..136759dd30b 100644 --- a/website/docs/r/sagemaker_space.html.markdown +++ b/website/docs/r/sagemaker_space.html.markdown @@ -61,6 +61,8 @@ The `space_sharing_settings` block supports the following argument: The `code_editor_app_settings` block supports the following argument: +* `app_lifecycle_management` - (Optional) Settings that are used to configure and manage the lifecycle of JupyterLab applications in a space. See [`app_lifecycle_management` Block](#app_lifecycle_management-block) below. +* `code_repository` - (Optional) A list of Git repositories that SageMaker automatically displays to users for cloning in the JupyterServer application. See [`code_repository` Block](#code_repository-block) below. * `default_resource_spec` - (Required) The default instance type and the Amazon Resource Name (ARN) of the SageMaker image created on the instance. See [`default_resource_spec` Block](#default_resource_spec-block) below. ### `custom_file_system` Block @@ -73,6 +75,7 @@ The `custom_file_system` block supports the following argument: The `jupyter_lab_app_settings` block supports the following arguments: +* `app_lifecycle_management` - (Optional) Settings that are used to configure and manage the lifecycle of JupyterLab applications in a space. See [`app_lifecycle_management` Block](#app_lifecycle_management-block) below. * `code_repository` - (Optional) A list of Git repositories that SageMaker automatically displays to users for cloning in the JupyterServer application. See [`code_repository` Block](#code_repository-block) below. * `default_resource_spec` - (Required) The default instance type and the Amazon Resource Name (ARN) of the SageMaker image created on the instance. See [`default_resource_spec` Block](#default_resource_spec-block) below. @@ -134,6 +137,18 @@ The `ebs_storage_settings` block supports the following argument: * `ebs_volume_size_in_gb` - (Required) The size of an EBS storage volume for a space. +### `app_lifecycle_management` Block + +The `app_lifecycle_management` block supports the following argument: + +* `idle_settings` - (Optional) Settings related to idle shutdown of Studio applications. See [`idle_settings` Block](#idle_settings-block) below. + +### `idle_settings` Block + +The `idle_settings` block supports the following argument: + +* `idle_timeout_in_minutes` - (Optional) The time that SageMaker waits after the application becomes idle before shutting it down. Valid values are between `60` and `525600`. + ## Attribute Reference This resource exports the following attributes in addition to the arguments above: From d8306051076bb90eab4f60994ade8996b7ba3783 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Fri, 18 Oct 2024 14:43:38 -0400 Subject: [PATCH 2/3] changelog Signed-off-by: drfaust92 --- .changelog/39800.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/39800.txt diff --git a/.changelog/39800.txt b/.changelog/39800.txt new file mode 100644 index 00000000000..ee1552f642d --- /dev/null +++ b/.changelog/39800.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_sagemaker_space: Add `space_settings.code_editor_app_settings.app_lifecycle_management`, `space_settings.jupyter_lab_app_settings.app_lifecycle_management` Arguments +``` From 8c49259f43710c2b457f33e6a32ac318cd07a7eb Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 21 Oct 2024 08:21:51 -0400 Subject: [PATCH 3/3] Update 39800.txt --- .changelog/39800.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/39800.txt b/.changelog/39800.txt index ee1552f642d..3252895472d 100644 --- a/.changelog/39800.txt +++ b/.changelog/39800.txt @@ -1,3 +1,3 @@ ```release-note:enhancement -resource/aws_sagemaker_space: Add `space_settings.code_editor_app_settings.app_lifecycle_management`, `space_settings.jupyter_lab_app_settings.app_lifecycle_management` Arguments +resource/aws_sagemaker_space: Add `space_settings.code_editor_app_settings.app_lifecycle_management` and `space_settings.jupyter_lab_app_settings.app_lifecycle_management` arguments ```