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

r/sagemaker_app_image_config - add code_editor_app_image_config #37059

Merged
merged 3 commits into from
Apr 24, 2024
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
7 changes: 7 additions & 0 deletions .changelog/37059.txt
Original file line number Diff line number Diff line change
@@ -0,0 +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
```
163 changes: 158 additions & 5 deletions internal/service/sagemaker/app_image_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 `/`."),
),
},
},
},
},
},
},
},
Expand Down Expand Up @@ -120,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": {
Expand Down Expand Up @@ -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{}))
}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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{}))
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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{}{}
}
Expand Down Expand Up @@ -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
Expand All @@ -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
}

Expand All @@ -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}
}

Expand Down
68 changes: 68 additions & 0 deletions internal/service/sagemaker/app_image_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
9 changes: 8 additions & 1 deletion website/docs/r/sagemaker_app_image_config.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading