From 48b05c025c3d04be1b1b6c3b41de8b160a8ce941 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Tue, 23 Apr 2024 13:08:32 +0300 Subject: [PATCH 1/3] add `code_editor_app_image_config` --- .../service/sagemaker/app_image_config.go | 161 +++++++++++++++++- .../sagemaker/app_image_config_test.go | 68 ++++++++ .../sagemaker_app_image_config.html.markdown | 9 +- 3 files changed, 233 insertions(+), 5 deletions(-) diff --git a/internal/service/sagemaker/app_image_config.go b/internal/service/sagemaker/app_image_config.go index e512f772999..fbc35ea671d 100644 --- a/internal/service/sagemaker/app_image_config.go +++ b/internal/service/sagemaker/app_image_config.go @@ -48,6 +48,69 @@ func ResourceAppImageConfig() *schema.Resource { validation.StringMatch(regexache.MustCompile(`^[0-9A-Za-z](-*[0-9A-Za-z])*$`), "Valid characters are a-z, A-Z, 0-9, and - (hyphen)."), ), }, + "code_editor_app_image_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "container_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "container_arguments": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "container_entrypoint": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "container_environment_variables": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + "file_system_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "default_gid": { + Type: schema.TypeInt, + Optional: true, + Default: 100, + ValidateFunc: validation.IntInSlice([]int{0, 100}), + }, + "default_uid": { + Type: schema.TypeInt, + Optional: true, + Default: 1000, + ValidateFunc: validation.IntInSlice([]int{0, 1000}), + }, + "mount_path": { + Type: schema.TypeString, + Optional: true, + Default: "/home/sagemaker-user", + ValidateFunc: validation.All( + validation.StringLenBetween(1, 1024), + validation.StringMatch(regexache.MustCompile(`^\/.*`), "Must start with `/`."), + ), + }, + }, + }, + }, + }, + }, + }, "jupyter_lab_image_config": { Type: schema.TypeList, Optional: true, @@ -78,6 +141,36 @@ func ResourceAppImageConfig() *schema.Resource { }, }, }, + "file_system_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "default_gid": { + Type: schema.TypeInt, + Optional: true, + Default: 100, + ValidateFunc: validation.IntInSlice([]int{0, 100}), + }, + "default_uid": { + Type: schema.TypeInt, + Optional: true, + Default: 1000, + ValidateFunc: validation.IntInSlice([]int{0, 1000}), + }, + "mount_path": { + Type: schema.TypeString, + Optional: true, + Default: "/home/sagemaker-user", + ValidateFunc: validation.All( + validation.StringLenBetween(1, 1024), + validation.StringMatch(regexache.MustCompile(`^\/.*`), "Must start with `/`."), + ), + }, + }, + }, + }, }, }, }, @@ -156,6 +249,10 @@ func resourceAppImageConfigCreate(ctx context.Context, d *schema.ResourceData, m Tags: getTagsIn(ctx), } + if v, ok := d.GetOk("code_editor_app_image_config"); ok && len(v.([]interface{})) > 0 { + input.CodeEditorAppImageConfig = expandCodeEditorAppImageConfig(v.([]interface{})) + } + if v, ok := d.GetOk("jupyter_lab_image_config"); ok && len(v.([]interface{})) > 0 { input.JupyterLabAppImageConfig = expandJupyterLabAppImageConfig(v.([]interface{})) } @@ -192,6 +289,10 @@ func resourceAppImageConfigRead(ctx context.Context, d *schema.ResourceData, met d.Set("app_image_config_name", image.AppImageConfigName) d.Set("arn", arn) + if err := d.Set("code_editor_app_image_config", flattenCodeEditorAppImageConfig(image.CodeEditorAppImageConfig)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting code_editor_app_image_config: %s", err) + } + if err := d.Set("kernel_gateway_image_config", flattenKernelGatewayImageConfig(image.KernelGatewayImageConfig)); err != nil { return sdkdiag.AppendErrorf(diags, "setting kernel_gateway_image_config: %s", err) } @@ -212,6 +313,12 @@ func resourceAppImageConfigUpdate(ctx context.Context, d *schema.ResourceData, m AppImageConfigName: aws.String(d.Id()), } + if d.HasChange("code_editor_app_image_config") { + if v, ok := d.GetOk("code_editor_app_image_config"); ok && len(v.([]interface{})) > 0 { + input.CodeEditorAppImageConfig = expandCodeEditorAppImageConfig(v.([]interface{})) + } + } + if d.HasChange("kernel_gateway_image_config") { if v, ok := d.GetOk("kernel_gateway_image_config"); ok && len(v.([]interface{})) > 0 { input.KernelGatewayImageConfig = expandKernelGatewayImageConfig(v.([]interface{})) @@ -266,13 +373,13 @@ func expandKernelGatewayImageConfig(l []interface{}) *sagemaker.KernelGatewayIma } if v, ok := m["file_system_config"].([]interface{}); ok && len(v) > 0 { - config.FileSystemConfig = expandKernelGatewayImageConfigFileSystemConfig(v) + config.FileSystemConfig = expandFileSystemConfig(v) } return config } -func expandKernelGatewayImageConfigFileSystemConfig(l []interface{}) *sagemaker.FileSystemConfig { +func expandFileSystemConfig(l []interface{}) *sagemaker.FileSystemConfig { if len(l) == 0 || l[0] == nil { return nil } @@ -328,13 +435,13 @@ func flattenKernelGatewayImageConfig(config *sagemaker.KernelGatewayImageConfig) } if config.FileSystemConfig != nil { - m["file_system_config"] = flattenKernelGatewayImageConfigFileSystemConfig(config.FileSystemConfig) + m["file_system_config"] = flattenFileSystemConfig(config.FileSystemConfig) } return []map[string]interface{}{m} } -func flattenKernelGatewayImageConfigFileSystemConfig(config *sagemaker.FileSystemConfig) []map[string]interface{} { +func flattenFileSystemConfig(config *sagemaker.FileSystemConfig) []map[string]interface{} { if config == nil { return []map[string]interface{}{} } @@ -366,6 +473,44 @@ func flattenKernelGatewayImageConfigKernelSpecs(kernelSpecs []*sagemaker.KernelS return res } +func expandCodeEditorAppImageConfig(l []interface{}) *sagemaker.CodeEditorAppImageConfig { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.CodeEditorAppImageConfig{} + + if v, ok := m["container_config"].([]interface{}); ok && len(v) > 0 { + config.ContainerConfig = expandContainerConfig(v) + } + + if v, ok := m["file_system_config"].([]interface{}); ok && len(v) > 0 { + config.FileSystemConfig = expandFileSystemConfig(v) + } + + return config +} + +func flattenCodeEditorAppImageConfig(config *sagemaker.CodeEditorAppImageConfig) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{} + + if config.ContainerConfig != nil { + m["container_config"] = flattenContainerConfig(config.ContainerConfig) + } + + if config.FileSystemConfig != nil { + m["file_system_config"] = flattenFileSystemConfig(config.FileSystemConfig) + } + + return []map[string]interface{}{m} +} + func expandJupyterLabAppImageConfig(l []interface{}) *sagemaker.JupyterLabAppImageConfig { if len(l) == 0 || l[0] == nil { return nil @@ -379,6 +524,10 @@ func expandJupyterLabAppImageConfig(l []interface{}) *sagemaker.JupyterLabAppIma config.ContainerConfig = expandContainerConfig(v) } + if v, ok := m["file_system_config"].([]interface{}); ok && len(v) > 0 { + config.FileSystemConfig = expandFileSystemConfig(v) + } + return config } @@ -393,6 +542,10 @@ func flattenJupyterLabAppImageConfig(config *sagemaker.JupyterLabAppImageConfig) m["container_config"] = flattenContainerConfig(config.ContainerConfig) } + if config.FileSystemConfig != nil { + m["file_system_config"] = flattenFileSystemConfig(config.FileSystemConfig) + } + return []map[string]interface{}{m} } diff --git a/internal/service/sagemaker/app_image_config_test.go b/internal/service/sagemaker/app_image_config_test.go index 582b0c2acd8..d89e6f40b14 100644 --- a/internal/service/sagemaker/app_image_config_test.go +++ b/internal/service/sagemaker/app_image_config_test.go @@ -143,6 +143,56 @@ func TestAccSageMakerAppImageConfig_KernelGatewayImage_fileSystem(t *testing.T) }) } +func TestAccSageMakerAppImageConfig_CodeEditor(t *testing.T) { + ctx := acctest.Context(t) + var config sagemaker.DescribeAppImageConfigOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rNameUpdated := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_sagemaker_app_image_config.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SageMakerServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAppImageDestroyConfig(ctx), + Steps: []resource.TestStep{ + { + Config: testAccAppImageConfigConfig_codeEditor(rName, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppImageExistsConfig(ctx, resourceName, &config), + resource.TestCheckResourceAttr(resourceName, "app_image_config_name", rName), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.0.container_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.0.container_config.0.container_arguments.#", "1"), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.0.container_config.0.container_arguments.0", rName), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.0.container_config.0.container_entrypoint.#", "1"), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.0.container_config.0.container_entrypoint.0", rName), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.0.container_config.0.container_environment_variables.%", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAppImageConfigConfig_codeEditor(rName, rNameUpdated), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppImageExistsConfig(ctx, resourceName, &config), + resource.TestCheckResourceAttr(resourceName, "app_image_config_name", rName), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.0.container_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.0.container_config.0.container_arguments.#", "1"), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.0.container_config.0.container_arguments.0", rNameUpdated), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.0.container_config.0.container_entrypoint.#", "1"), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.0.container_config.0.container_entrypoint.0", rNameUpdated), + resource.TestCheckResourceAttr(resourceName, "code_editor_app_image_config.0.container_config.0.container_environment_variables.%", "1"), + ), + }, + }, + }) +} + func TestAccSageMakerAppImageConfig_JupyterLab(t *testing.T) { ctx := acctest.Context(t) var config sagemaker.DescribeAppImageConfigOutput @@ -429,3 +479,21 @@ resource "aws_sagemaker_app_image_config" "test" { } `, rName, arg) } + +func testAccAppImageConfigConfig_codeEditor(rName, arg string) string { + return fmt.Sprintf(` +resource "aws_sagemaker_app_image_config" "test" { + app_image_config_name = %[1]q + + code_editor_app_image_config { + container_config { + container_arguments = ["%[2]s"] + container_entrypoint = ["%[2]s"] + container_environment_variables = { + %[2]q = %[2]q + } + } + } +} +`, rName, arg) +} diff --git a/website/docs/r/sagemaker_app_image_config.html.markdown b/website/docs/r/sagemaker_app_image_config.html.markdown index f34a3c95bf2..dad89e966d6 100644 --- a/website/docs/r/sagemaker_app_image_config.html.markdown +++ b/website/docs/r/sagemaker_app_image_config.html.markdown @@ -47,13 +47,20 @@ resource "aws_sagemaker_app_image_config" "test" { This resource supports the following arguments: * `app_image_config_name` - (Required) The name of the App Image Config. -* `kernel_gateway_image_config` - (Optional) The JupyterLabAppImageConfig. You can only specify one image kernel in the AppImageConfig API. This kernel is shown to users before the image starts. After the image runs, all kernels are visible in JupyterLab. See [Jupyte rLab Image Config](#jupyter-lab-image-config) details below. +* `code_editor_app_image_config` - (Optional) The CodeEditorAppImageConfig. You can only specify one image kernel in the AppImageConfig API. This kernel is shown to users before the image starts. After the image runs, all kernels are visible in Code Editor. See [Code Editor App Image Config](#code-editor-app-image-config) details below. +* `jupyter_lab_image_config` - (Optional) The JupyterLabAppImageConfig. You can only specify one image kernel in the AppImageConfig API. This kernel is shown to users before the image starts. After the image runs, all kernels are visible in JupyterLab. See [Jupyter Lab Image Config](#jupyter-lab-image-config) details below. * `kernel_gateway_image_config` - (Optional) The configuration for the file system and kernels in a SageMaker image running as a KernelGateway app. See [Kernel Gateway Image Config](#kernel-gateway-image-config) details below. * `tags` - (Optional) A map 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. +### Code Editor App Image Config + +* `container_config` - (Optional) The configuration used to run the application image container. See [Container Config](#container-config) details below. +* `file_system_config` - (Optional) The URL where the Git repository is located. See [File System Config](#file-system-config) details below. + ### Jupyter Lab Image Config * `container_config` - (Optional) The configuration used to run the application image container. See [Container Config](#container-config) details below. +* `file_system_config` - (Optional) The URL where the Git repository is located. See [File System Config](#file-system-config) details below. #### Container Config From e1d2ab9150c04b3434744f592e9a7fbc8ae27a20 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Tue, 23 Apr 2024 13:10:18 +0300 Subject: [PATCH 2/3] add `code_editor_app_image_config` --- .changelog/37059.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/37059.txt diff --git a/.changelog/37059.txt b/.changelog/37059.txt new file mode 100644 index 00000000000..be5ae9dfeb2 --- /dev/null +++ b/.changelog/37059.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_sagemaker_app_image_config: Add `code_editor_app_image_config` and `jupyter_lab_image_config.jupyter_lab_image_config` arguments +``` \ No newline at end of file From 4b59f3da86da92ca9ad0830925983144fe6ce86c Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Tue, 23 Apr 2024 13:14:30 +0300 Subject: [PATCH 3/3] add `code_editor_app_image_config` --- .changelog/37059.txt | 4 ++++ internal/service/sagemaker/app_image_config.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.changelog/37059.txt b/.changelog/37059.txt index be5ae9dfeb2..a2e96a5daba 100644 --- a/.changelog/37059.txt +++ b/.changelog/37059.txt @@ -1,3 +1,7 @@ ```release-note:enhancement resource/aws_sagemaker_app_image_config: Add `code_editor_app_image_config` and `jupyter_lab_image_config.jupyter_lab_image_config` arguments +``` + +```release-note:enhancement +resource/aws_sagemaker_app_image_config: Change `kernel_gateway_image_config.kernel_spec` MaxItems to 5 ``` \ No newline at end of file diff --git a/internal/service/sagemaker/app_image_config.go b/internal/service/sagemaker/app_image_config.go index fbc35ea671d..18efcccd554 100644 --- a/internal/service/sagemaker/app_image_config.go +++ b/internal/service/sagemaker/app_image_config.go @@ -213,7 +213,7 @@ func ResourceAppImageConfig() *schema.Resource { "kernel_spec": { Type: schema.TypeList, Required: true, - MaxItems: 1, + MaxItems: 5, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "display_name": {