From 05bc4d3d34d0a06a05d6e02b93b7464eeb54275a Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sun, 1 Nov 2020 09:47:11 +0200 Subject: [PATCH 01/33] add resource --- .../service/sagemaker/finder/finder.go | 19 + aws/resource_aws_sagemaker_domain.go | 568 ++++++++++++++++++ 2 files changed, 587 insertions(+) create mode 100644 aws/resource_aws_sagemaker_domain.go diff --git a/aws/internal/service/sagemaker/finder/finder.go b/aws/internal/service/sagemaker/finder/finder.go index 0abb4a7a7e9..06d23f1f24f 100644 --- a/aws/internal/service/sagemaker/finder/finder.go +++ b/aws/internal/service/sagemaker/finder/finder.go @@ -42,3 +42,22 @@ func ImageByName(conn *sagemaker.SageMaker, name string) (*sagemaker.DescribeIma return output, nil } + +// DomainByName returns the domain corresponding to the specified domain id. +// Returns nil if no domain is found. +func DomainByName(conn *sagemaker.SageMaker, domainID string) (*sagemaker.DescribeDomainOutput, error) { + input := &sagemaker.DescribeDomainInput{ + DomainId: aws.String(domainID), + } + + output, err := conn.DescribeDomain(input) + if err != nil { + return nil, err + } + + if output == nil { + return nil, nil + } + + return output, nil +} diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go new file mode 100644 index 00000000000..e04f9b29bb8 --- /dev/null +++ b/aws/resource_aws_sagemaker_domain.go @@ -0,0 +1,568 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/sagemaker" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sagemaker/finder" +) + +func resourceAwsSagemakerDomain() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSagemakerDomainCreate, + Read: resourceAwsSagemakerDomainRead, + Update: resourceAwsSagemakerDomainUpdate, + Delete: resourceAwsSagemakerDomainDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "domain_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 63), + validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9](-*[a-zA-Z0-9])*$`), "Valid characters are a-z, A-Z, 0-9, and - (hyphen)."), + ), + }, + "auth_mode": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringInSlice(sagemaker.AuthMode_Values(), false), + }, + "vpc_id": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + "subnet_ids": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + MaxItems: 16, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "app_network_access_type": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + Default: sagemaker.AppNetworkAccessTypePublicInternetOnly, + ValidateFunc: validation.StringInSlice(sagemaker.AppNetworkAccessType_Values(), false), + }, + "home_efs_file_system_kms_key_id": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + ValidateFunc: validateArn, + }, + + "default_user_settings": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "security_groups": { + Type: schema.TypeSet, + Optional: true, + MaxItems: 5, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "execution_role": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + }, + "sharing_settings": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "notebook_output_option": { + Type: schema.TypeString, + Optional: true, + Default: sagemaker.NotebookOutputOptionDisabled, + ValidateFunc: validation.StringInSlice(sagemaker.NotebookOutputOption_Values(), false), + }, + "s3_kms_Key_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + }, + "s3_output_path": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "tensor_board_app_settings": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "default_resource_spec": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "instance_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(sagemaker.AppInstanceType_Values(), false), + }, + "sagemaker_image_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + }, + }, + }, + }, + }, + }, + }, + "jupyter_server_app_settings": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "default_resource_spec": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "instance_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(sagemaker.AppInstanceType_Values(), false), + }, + "sagemaker_image_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + }, + }, + }, + }, + }, + }, + }, + "kernel_gateway_app_settings": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "default_resource_spec": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "instance_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(sagemaker.AppInstanceType_Values(), false), + }, + "sagemaker_image_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "tags": tagsSchema(), + "url": { + Type: schema.TypeString, + Computed: true, + }, + "single_sign_on_managed_application_instance_id": { + Type: schema.TypeString, + Computed: true, + }, + "home_efs_file_system_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAwsSagemakerDomainCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + input := &sagemaker.CreateDomainInput{ + DomainName: aws.String(d.Get("domain_name").(string)), + AuthMode: aws.String(d.Get("auth_mode").(string)), + VpcId: aws.String(d.Get("vpc_id").(string)), + AppNetworkAccessType: aws.String(d.Get("app_network_access_type").(string)), + SubnetIds: expandStringSet(d.Get("subnet_ids").(*schema.Set)), + DefaultUserSettings: expandSagemakerDomainDefaultUserSettings(d.Get("default_user_settings").([]interface{})), + } + + if v, ok := d.GetOk("home_efs_file_system_kms_key_id"); ok { + input.HomeEfsFileSystemKmsKeyId = aws.String(v.(string)) + } + + log.Printf("[DEBUG] sagemaker domain create config: %#v", *input) + output, err := conn.CreateDomain(input) + if err != nil { + return fmt.Errorf("error creating SageMaker domain: %w", err) + } + + domainArn := aws.StringValue(output.DomainArn) + domainID, err := decodeSagemakerDomainID(domainArn) + if err != nil { + return err + } + + d.SetId(domainID) + + return resourceAwsSagemakerDomainRead(d, meta) +} + +func resourceAwsSagemakerDomainRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + domain, err := finder.DomainByName(conn, d.Id()) + if err != nil { + if isAWSErr(err, "ValidationException", "Cannot find Domain") { + d.SetId("") + log.Printf("[WARN] Unable to find SageMaker domain (%s), removing from state", d.Id()) + return nil + } + return fmt.Errorf("error reading SageMaker domain (%s): %w", d.Id(), err) + } + + d.Set("domain_name", domain.DomainName) + d.Set("auth_mode", domain.AuthMode) + d.Set("app_network_access_type", domain.AppNetworkAccessType) + d.Set("arn", domain.DomainArn) + d.Set("home_efs_file_system_id", domain.HomeEfsFileSystemId) + d.Set("home_efs_file_system_kms_key_id", domain.HomeEfsFileSystemKmsKeyId) + d.Set("single_sign_on_managed_application_instance_id", domain.SingleSignOnManagedApplicationInstanceId) + d.Set("url", domain.Url) + d.Set("vpc_id", domain.VpcId) + + if err := d.Set("subnet_ids", flattenStringSet(domain.SubnetIds)); err != nil { + return fmt.Errorf("error setting subnet_ids for sagemaker domain (%s): %w", d.Id(), err) + } + + if err := d.Set("default_user_settings", flattenSagemakerDomainDefaultUserSettings(domain.DefaultUserSettings)); err != nil { + return fmt.Errorf("error setting default_user_settings for sagemaker domain (%s): %w", d.Id(), err) + } + + return nil +} + +func resourceAwsSagemakerDomainUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + input := &sagemaker.UpdateDomainInput{ + DomainId: aws.String(d.Id()), + DefaultUserSettings: expandSagemakerDomainDefaultUserSettings(d.Get("default_user_settings").([]interface{})), + } + + log.Printf("[DEBUG] sagemaker domain update config: %#v", *input) + _, err := conn.UpdateDomain(input) + if err != nil { + return fmt.Errorf("error updating SageMaker domain: %w", err) + } + + return resourceAwsSagemakerDomainRead(d, meta) +} + +func resourceAwsSagemakerDomainDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + input := &sagemaker.DeleteDomainInput{ + DomainId: aws.String(d.Id()), + } + + if _, err := conn.DeleteDomain(input); err != nil { + if isAWSErr(err, "ValidationException", "Cannot find Domain") { + return nil + } + return fmt.Errorf("error deleting SageMaker domain (%s): %w", d.Id(), err) + } + + return nil +} + +func expandSagemakerDomainDefaultUserSettings(l []interface{}) *sagemaker.UserSettings { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.UserSettings{} + + if v, ok := m["execution_role"].(string); ok && v != "" { + config.ExecutionRole = aws.String(v) + } + + if v, ok := m["security_groups"].(*schema.Set); ok && v.Len() > 0 { + config.SecurityGroups = expandStringSet(v) + } + + if v, ok := m["tensor_board_app_settings"].([]interface{}); ok && len(v) > 0 { + config.TensorBoardAppSettings = expandSagemakerDomainTensorBoardAppSettings(v) + } + + if v, ok := m["kernel_gateway_app_settings"].([]interface{}); ok && len(v) > 0 { + config.KernelGatewayAppSettings = expandSagemakerDomainKernelGatewayAppSettings(v) + } + + if v, ok := m["jupyter_server_app_settings"].([]interface{}); ok && len(v) > 0 { + config.JupyterServerAppSettings = expandSagemakerDomainJupyterServerAppSettings(v) + } + + if v, ok := m["share_settings"].([]interface{}); ok && len(v) > 0 { + config.SharingSettings = expandSagemakerDomainShareSettings(v) + } + + return config +} + +func expandSagemakerDomainJupyterServerAppSettings(l []interface{}) *sagemaker.JupyterServerAppSettings { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.JupyterServerAppSettings{} + + if v, ok := m["default_resurce_spec"].([]interface{}); ok && len(v) > 0 { + config.DefaultResourceSpec = expandSagemakerDomainDefaultResourceSpec(v) + } + + return config +} + +func expandSagemakerDomainKernelGatewayAppSettings(l []interface{}) *sagemaker.KernelGatewayAppSettings { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.KernelGatewayAppSettings{} + + if v, ok := m["default_resurce_spec"].([]interface{}); ok && len(v) > 0 { + config.DefaultResourceSpec = expandSagemakerDomainDefaultResourceSpec(v) + } + + return config +} + +func expandSagemakerDomainTensorBoardAppSettings(l []interface{}) *sagemaker.TensorBoardAppSettings { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.TensorBoardAppSettings{} + + if v, ok := m["default_resurce_spec"].([]interface{}); ok && len(v) > 0 { + config.DefaultResourceSpec = expandSagemakerDomainDefaultResourceSpec(v) + } + + return config +} + +func expandSagemakerDomainDefaultResourceSpec(l []interface{}) *sagemaker.ResourceSpec { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.ResourceSpec{} + + if v, ok := m["instance_type"].(string); ok && v != "" { + config.InstanceType = aws.String(v) + } + + if v, ok := m["sagemaker_image_arn"].(string); ok && v != "" { + config.SageMakerImageArn = aws.String(v) + } + + return config +} + +func expandSagemakerDomainShareSettings(l []interface{}) *sagemaker.SharingSettings { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.SharingSettings{ + NotebookOutputOption: aws.String(m["notebook_output_option"].(string)), + } + + if v, ok := m["s3_kms_key_id"].(string); ok && v != "" { + config.S3KmsKeyId = aws.String(v) + } + + if v, ok := m["s3_output_path"].(string); ok && v != "" { + config.S3OutputPath = aws.String(v) + } + + return config +} + +func flattenSagemakerDomainDefaultUserSettings(config *sagemaker.UserSettings) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{} + + if config.ExecutionRole != nil { + m["execution_role"] = aws.StringValue(config.ExecutionRole) + } + + if config.SecurityGroups != nil { + m["security_groups"] = flattenStringSet(config.SecurityGroups) + } + + if config.JupyterServerAppSettings != nil { + m["jupyter_server_app_settings"] = flattenSagemakerDomainJupyterServerAppSettings(config.JupyterServerAppSettings) + } + + if config.KernelGatewayAppSettings != nil { + m["kernel_gateway_app_settings"] = flattenSagemakerDomainKernelGatewayAppSettings(config.KernelGatewayAppSettings) + } + + if config.TensorBoardAppSettings != nil { + m["tensor_board_app_settings"] = flattenSagemakerDomainTensorBoardAppSettings(config.TensorBoardAppSettings) + } + + if config.SharingSettings != nil { + m["share_settings"] = flattenSagemakerDomainShareSettings(config.SharingSettings) + } + + return []map[string]interface{}{m} +} + +func flattenSagemakerDomainDefaultResourceSpec(config *sagemaker.ResourceSpec) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{} + + if config.InstanceType != nil { + m["instance_type"] = aws.StringValue(config.InstanceType) + } + + if config.SageMakerImageArn != nil { + m["sagemaker_image_arn"] = aws.StringValue(config.SageMakerImageArn) + } + + return []map[string]interface{}{m} +} + +func flattenSagemakerDomainTensorBoardAppSettings(config *sagemaker.TensorBoardAppSettings) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{} + + if config.DefaultResourceSpec != nil { + m["default_resurce_spec"] = flattenSagemakerDomainDefaultResourceSpec(config.DefaultResourceSpec) + } + + return []map[string]interface{}{m} +} + +func flattenSagemakerDomainJupyterServerAppSettings(config *sagemaker.JupyterServerAppSettings) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{} + + if config.DefaultResourceSpec != nil { + m["default_resurce_spec"] = flattenSagemakerDomainDefaultResourceSpec(config.DefaultResourceSpec) + } + + return []map[string]interface{}{m} +} + +func flattenSagemakerDomainKernelGatewayAppSettings(config *sagemaker.KernelGatewayAppSettings) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{} + + if config.DefaultResourceSpec != nil { + m["default_resurce_spec"] = flattenSagemakerDomainDefaultResourceSpec(config.DefaultResourceSpec) + } + + return []map[string]interface{}{m} +} + +func flattenSagemakerDomainShareSettings(config *sagemaker.SharingSettings) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{ + "notebook_output_option": aws.StringValue(config.NotebookOutputOption), + } + + if config.S3KmsKeyId != nil { + m["s3_kms_key_id"] = aws.StringValue(config.S3KmsKeyId) + } + + if config.S3OutputPath != nil { + m["s3_output_path"] = aws.StringValue(config.S3OutputPath) + } + + return []map[string]interface{}{m} +} + +func decodeSagemakerDomainID(id string) (string, error) { + domainArn, err := arn.Parse(id) + if err != nil { + return "", err + } + + domainName := strings.TrimPrefix(domainArn.Resource, "domain/") + return domainName, nil +} From 4c9dc3ddcea649dd784c8d545746058a745e6fff Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sun, 1 Nov 2020 09:48:02 +0200 Subject: [PATCH 02/33] add to provider --- aws/provider.go | 1 + 1 file changed, 1 insertion(+) diff --git a/aws/provider.go b/aws/provider.go index 0543109d0f1..98041dee66f 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -870,6 +870,7 @@ func Provider() *schema.Provider { "aws_default_route_table": resourceAwsDefaultRouteTable(), "aws_route_table_association": resourceAwsRouteTableAssociation(), "aws_sagemaker_code_repository": resourceAwsSagemakerCodeRepository(), + "aws_sagemaker_domain": resourceAwsSagemakerDomain(), "aws_sagemaker_endpoint_configuration": resourceAwsSagemakerEndpointConfiguration(), "aws_sagemaker_image": resourceAwsSagemakerImage(), "aws_sagemaker_endpoint": resourceAwsSagemakerEndpoint(), From bdc8be99d7aa11a69340b045ec281a3917c10d78 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sun, 1 Nov 2020 09:55:12 +0200 Subject: [PATCH 03/33] add waiters --- .../service/sagemaker/waiter/status.go | 26 +++++++++++ .../service/sagemaker/waiter/waiter.go | 43 +++++++++++++++++++ aws/resource_aws_sagemaker_domain.go | 12 ++++++ 3 files changed, 81 insertions(+) diff --git a/aws/internal/service/sagemaker/waiter/status.go b/aws/internal/service/sagemaker/waiter/status.go index 1f8f5d00dbe..cb55b5ee3b3 100644 --- a/aws/internal/service/sagemaker/waiter/status.go +++ b/aws/internal/service/sagemaker/waiter/status.go @@ -13,6 +13,7 @@ const ( SagemakerNotebookInstanceStatusNotFound = "NotFound" SagemakerImageStatusNotFound = "NotFound" SagemakerImageStatusFailed = "Failed" + SagemakerDomainStatusNotFound = "NotFound" ) // NotebookInstanceStatus fetches the NotebookInstance and its Status @@ -68,3 +69,28 @@ func ImageStatus(conn *sagemaker.SageMaker, name string) resource.StateRefreshFu return output, aws.StringValue(output.ImageStatus), nil } } + +// DomainStatus fetches the Domain and its Status +func DomainStatus(conn *sagemaker.SageMaker, domainID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + input := &sagemaker.DescribeDomainInput{ + DomainId: aws.String(domainID), + } + + output, err := conn.DescribeDomain(input) + + if tfawserr.ErrMessageContains(err, "ValidationException", "RecordNotFound") { + return nil, SagemakerDomainStatusNotFound, nil + } + + if err != nil { + return nil, sagemaker.DomainStatusFailed, err + } + + if output == nil { + return nil, SagemakerDomainStatusNotFound, nil + } + + return output, aws.StringValue(output.Status), nil + } +} diff --git a/aws/internal/service/sagemaker/waiter/waiter.go b/aws/internal/service/sagemaker/waiter/waiter.go index e6ff40fb82a..decbbab95bb 100644 --- a/aws/internal/service/sagemaker/waiter/waiter.go +++ b/aws/internal/service/sagemaker/waiter/waiter.go @@ -13,6 +13,8 @@ const ( NotebookInstanceDeletedTimeout = 10 * time.Minute ImageCreatedTimeout = 10 * time.Minute ImageDeletedTimeout = 10 * time.Minute + DomainInServiceTimeout = 10 * time.Minute + DomainDeletedTimeout = 10 * time.Minute ) // NotebookInstanceInService waits for a NotebookInstance to return InService @@ -117,3 +119,44 @@ func ImageDeleted(conn *sagemaker.SageMaker, name string) (*sagemaker.DescribeIm return nil, err } + +// DomainInService waits for a Domain to return InService +func DomainInService(conn *sagemaker.SageMaker, domainID string) (*sagemaker.DescribeDomainOutput, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + SagemakerDomainStatusNotFound, + sagemaker.DomainStatusPending, + }, + Target: []string{sagemaker.DomainStatusInService}, + Refresh: DomainStatus(conn, domainID), + Timeout: DomainInServiceTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*sagemaker.DescribeDomainOutput); ok { + return output, err + } + + return nil, err +} + +// DomainDeleted waits for a Domain to return Deleted +func DomainDeleted(conn *sagemaker.SageMaker, domainID string) (*sagemaker.DescribeDomainOutput, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + sagemaker.DomainStatusDeleting, + }, + Target: []string{}, + Refresh: DomainStatus(conn, domainID), + Timeout: DomainDeletedTimeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*sagemaker.DescribeDomainOutput); ok { + return output, err + } + + return nil, err +} diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index e04f9b29bb8..a4719911fcc 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sagemaker/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sagemaker/waiter" ) func resourceAwsSagemakerDomain() *schema.Resource { @@ -246,6 +247,10 @@ func resourceAwsSagemakerDomainCreate(d *schema.ResourceData, meta interface{}) d.SetId(domainID) + if _, err := waiter.DomainInService(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for sagemaker domain (%s) to create: %w", d.Id(), err) + } + return resourceAwsSagemakerDomainRead(d, meta) } @@ -314,6 +319,13 @@ func resourceAwsSagemakerDomainDelete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("error deleting SageMaker domain (%s): %w", d.Id(), err) } + if _, err := waiter.DomainDeleted(conn, d.Id()); err != nil { + if isAWSErr(err, "ValidationException", "RecordNotFound") { + return nil + } + return fmt.Errorf("error waiting for sagemaker domain (%s) to delete: %w", d.Id(), err) + } + return nil } From 97b11e228431232575f2de5c632a002a53730813 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sun, 1 Nov 2020 11:11:13 +0200 Subject: [PATCH 04/33] fix argument name --- aws/resource_aws_sagemaker_domain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index a4719911fcc..97f96063b7e 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -101,7 +101,7 @@ func resourceAwsSagemakerDomain() *schema.Resource { Default: sagemaker.NotebookOutputOptionDisabled, ValidateFunc: validation.StringInSlice(sagemaker.NotebookOutputOption_Values(), false), }, - "s3_kms_Key_id": { + "s3_kms_key_id": { Type: schema.TypeString, Optional: true, ValidateFunc: validateArn, From 419959435192e06b42d0673fe2ed3835ce206220 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sun, 1 Nov 2020 11:33:13 +0200 Subject: [PATCH 05/33] add tests --- aws/resource_aws_sagemaker_domain.go | 4 +- aws/resource_aws_sagemaker_domain_test.go | 345 ++++++++++++++++++++++ 2 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 aws/resource_aws_sagemaker_domain_test.go diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index 97f96063b7e..fcedafdc8a0 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -30,7 +30,6 @@ func resourceAwsSagemakerDomain() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "domain_name": { Type: schema.TypeString, Required: true, @@ -71,7 +70,6 @@ func resourceAwsSagemakerDomain() *schema.Resource { Optional: true, ValidateFunc: validateArn, }, - "default_user_settings": { Type: schema.TypeList, Required: true, @@ -86,7 +84,7 @@ func resourceAwsSagemakerDomain() *schema.Resource { }, "execution_role": { Type: schema.TypeString, - Optional: true, + Required: true, ValidateFunc: validateArn, }, "sharing_settings": { diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go new file mode 100644 index 00000000000..ca31ef452ef --- /dev/null +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -0,0 +1,345 @@ +package aws + +import ( + "fmt" + "log" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/sagemaker" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sagemaker/finder" +) + +func init() { + resource.AddTestSweepers("aws_sagemaker_domain", &resource.Sweeper{ + Name: "aws_sagemaker_domain", + F: testSweepSagemakerDomains, + }) +} + +func testSweepSagemakerDomains(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).sagemakerconn + + err = conn.ListDomainsPages(&sagemaker.ListDomainsInput{}, func(page *sagemaker.ListDomainsOutput, lastPage bool) bool { + for _, instance := range page.Domains { + domainArn := aws.StringValue(instance.DomainArn) + domainID, err := decodeSagemakerDomainID(domainArn) + if err != nil { + log.Printf("[ERROR] Error parsing sagemaker domain arn (%s): %s", domainArn, err) + } + input := &sagemaker.DeleteDomainInput{ + DomainId: aws.String(domainID), + } + + log.Printf("[INFO] Deleting SageMaker domain: %s", domainArn) + if _, err := conn.DeleteDomain(input); err != nil { + log.Printf("[ERROR] Error deleting SageMaker domain (%s): %s", domainArn, err) + continue + } + } + + return !lastPage + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping SageMaker domain sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("Error retrieving SageMaker domains: %w", err) + } + + return nil +} + +func TestAccAWSSagemakerDomain_basic(t *testing.T) { + var notebook sagemaker.DescribeDomainOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerDomainBasicConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + resource.TestCheckResourceAttr(resourceName, "domain_name", rName), + testAccCheckResourceAttrRegionalARN(resourceName, "arn", "sagemaker", fmt.Sprintf("domain/%s", rName)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +// func TestAccAWSSagemakerDomain_gitConfig_branch(t *testing.T) { +// var notebook sagemaker.DescribeDomainOutput +// rName := acctest.RandomWithPrefix("tf-acc-test") +// resourceName := "aws_sagemaker_domain.test" + +// resource.ParallelTest(t, resource.TestCase{ +// PreCheck: func() { testAccPreCheck(t) }, +// Providers: testAccProviders, +// CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, +// Steps: []resource.TestStep{ +// { +// Config: testAccAWSSagemakerDomainGitConfigBranchConfig(rName), +// Check: resource.ComposeTestCheckFunc( +// testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), +// resource.TestCheckResourceAttr(resourceName, "domain_name", rName), +// testAccCheckResourceAttrRegionalARN(resourceName0......, "arn", "sagemaker", fmt.Sprintf("code-repository/%s", rName)), +// resource.TestCheckResourceAttr(resourceName, "git_config.#", "1"), +// resource.TestCheckResourceAttr(resourceName, "git_config.0.repository_url", "https://github.com/terraform-providers/terraform-provider-aws.git"), +// resource.TestCheckResourceAttr(resourceName, "git_config.0.branch", "master"), +// ), +// }, +// { +// ResourceName: resourceName, +// ImportState: true, +// ImportStateVerify: true, +// }, +// }, +// }) +// } + +// func TestAccAWSSagemakerDomain_gitConfig_secret(t *testing.T) { +// var notebook sagemaker.DescribeDomainOutput +// rName := acctest.RandomWithPrefix("tf-acc-test") +// resourceName := "aws_sagemaker_domain.test" + +// resource.ParallelTest(t, resource.TestCase{ +// PreCheck: func() { testAccPreCheck(t) }, +// Providers: testAccProviders, +// CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, +// Steps: []resource.TestStep{ +// { +// Config: testAccAWSSagemakerDomainGitConfigSecretConfig(rName), +// Check: resource.ComposeTestCheckFunc( +// testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), +// resource.TestCheckResourceAttr(resourceName, "domain_name", rName), +// testAccCheckResourceAttrRegionalARN(resourceName, "arn", "sagemaker", fmt.Sprintf("code-repository/%s", rName)), +// resource.TestCheckResourceAttr(resourceName, "git_config.#", "1"), +// resource.TestCheckResourceAttr(resourceName, "git_config.0.repository_url", "https://github.com/terraform-providers/terraform-provider-aws.git"), +// resource.TestCheckResourceAttrPair(resourceName, "git_config.0.secret_arn", "aws_secretsmanager_secret.test", "arn"), +// ), +// }, +// { +// ResourceName: resourceName, +// ImportState: true, +// ImportStateVerify: true, +// }, +// { +// Config: testAccAWSSagemakerDomainGitConfigSecretUpdatedConfig(rName), +// Check: resource.ComposeTestCheckFunc( +// testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), +// resource.TestCheckResourceAttr(resourceName, "domain_name", rName), +// testAccCheckResourceAttrRegionalARN(resourceName, "arn", "sagemaker", fmt.Sprintf("code-repository/%s", rName)), +// resource.TestCheckResourceAttr(resourceName, "git_config.#", "1"), +// resource.TestCheckResourceAttr(resourceName, "git_config.0.repository_url", "https://github.com/terraform-providers/terraform-provider-aws.git"), +// resource.TestCheckResourceAttrPair(resourceName, "git_config.0.secret_arn", "aws_secretsmanager_secret.test2", "arn"), +// ), +// }, +// }, +// }) +// } + +func TestAccAWSSagemakerDomain_disappears(t *testing.T) { + var notebook sagemaker.DescribeDomainOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerDomainBasicConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckResourceDisappears(testAccProvider, resourceAwsSagemakerDomain(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAWSSagemakerDomainDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).sagemakerconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_sagemaker_domain" { + continue + } + + domain, err := finder.DomainByName(conn, rs.Primary.ID) + if err != nil { + return nil + } + + domainArn := aws.StringValue(domain.DomainArn) + domainID, err := decodeSagemakerDomainID(domainArn) + if err != nil { + return err + } + + if domainID == rs.Primary.ID { + return fmt.Errorf("sagemaker domain %q still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckAWSSagemakerDomainExists(n string, codeRepo *sagemaker.DescribeDomainOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No sagmaker domain ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).sagemakerconn + resp, err := finder.DomainByName(conn, rs.Primary.ID) + if err != nil { + return err + } + + *codeRepo = *resp + + return nil + } +} + +func testAccAWSSagemakerDomainConfigBase(rName string) string { + return fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + vpc_id = aws_vpc.test.id + cidr_block = "10.0.1.0/24" + + tags = { + Name = %[1]q + } +} + +resource "aws_iam_role" "test" { + name = %[1]q + path = "/" + assume_role_policy = data.aws_iam_policy_document.test.json +} + +data "aws_iam_policy_document" "test" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["sagemaker.amazonaws.com"] + } + } +} +`, rName) +} + +func testAccAWSSagemakerDomainBasicConfig(rName string) string { + return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` +resource "aws_sagemaker_domain" "test" { + domain_name = %[1]q + auth_mode = "IAM" + vpc_id = aws_vpc.test.id + subnet_ids = [aws_subnet.test.id] + + default_user_settings { + execution_role = aws_iam_role.test.arn + } +} +`, rName) +} + +// func testAccAWSSagemakerDomainGitConfigBranchConfig(rName string) string { +// return fmt.Sprintf(` +// resource "aws_sagemaker_domain" "test" { +// domain_name = %[1]q + +// git_config { +// repository_url = "https://github.com/terraform-providers/terraform-provider-aws.git" +// branch = "master" +// } +// } +// `, rName) +// } + +// func testAccAWSSagemakerDomainGitConfigSecretConfig(rName string) string { +// return fmt.Sprintf(` +// resource "aws_secretsmanager_secret" "test" { +// name = %[1]q +// } + +// resource "aws_secretsmanager_secret_version" "test" { +// secret_id = aws_secretsmanager_secret.test.id +// secret_string = jsonencode({ username = "example", passowrd = "example" }) +// } + +// resource "aws_sagemaker_domain" "test" { +// domain_name = %[1]q + +// git_config { +// repository_url = "https://github.com/terraform-providers/terraform-provider-aws.git" +// secret_arn = aws_secretsmanager_secret.test.arn +// } + +// depends_on = [aws_secretsmanager_secret_version.test] +// } +// `, rName) +// } + +// func testAccAWSSagemakerDomainGitConfigSecretUpdatedConfig(rName string) string { +// return fmt.Sprintf(` +// resource "aws_secretsmanager_secret" "test2" { +// name = "%[1]s-2" +// } + +// resource "aws_secretsmanager_secret_version" "test2" { +// secret_id = aws_secretsmanager_secret.test2.id +// secret_string = jsonencode({ username = "example", passowrd = "example" }) +// } + +// resource "aws_sagemaker_domain" "test" { +// domain_name = %[1]q + +// git_config { +// repository_url = "https://github.com/terraform-providers/terraform-provider-aws.git" +// secret_arn = aws_secretsmanager_secret.test2.arn +// } + +// depends_on = [aws_secretsmanager_secret_version.test2] +// } +// `, rName) +// } From bedbee64310a158e2c62bcec7038d7763f0f4b2c Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Tue, 3 Nov 2020 17:05:00 +0200 Subject: [PATCH 06/33] domain tests --- aws/resource_aws_sagemaker_domain.go | 32 ++++++++++++++++------- aws/resource_aws_sagemaker_domain_test.go | 15 ++++++++++- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index fcedafdc8a0..8225a24b26e 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/efs" "github.com/aws/aws-sdk-go/service/sagemaker" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -89,7 +90,7 @@ func resourceAwsSagemakerDomain() *schema.Resource { }, "sharing_settings": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -113,7 +114,7 @@ func resourceAwsSagemakerDomain() *schema.Resource { }, "tensor_board_app_settings": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -141,7 +142,7 @@ func resourceAwsSagemakerDomain() *schema.Resource { }, "jupyter_server_app_settings": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -169,7 +170,7 @@ func resourceAwsSagemakerDomain() *schema.Resource { }, "kernel_gateway_app_settings": { Type: schema.TypeList, - Required: true, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -257,7 +258,7 @@ func resourceAwsSagemakerDomainRead(d *schema.ResourceData, meta interface{}) er domain, err := finder.DomainByName(conn, d.Id()) if err != nil { - if isAWSErr(err, "ValidationException", "Cannot find Domain") { + if isAWSErr(err, sagemaker.ErrCodeResourceNotFound, "") { d.SetId("") log.Printf("[WARN] Unable to find SageMaker domain (%s), removing from state", d.Id()) return nil @@ -311,17 +312,28 @@ func resourceAwsSagemakerDomainDelete(d *schema.ResourceData, meta interface{}) } if _, err := conn.DeleteDomain(input); err != nil { - if isAWSErr(err, "ValidationException", "Cannot find Domain") { - return nil + if !isAWSErr(err, sagemaker.ErrCodeResourceNotFound, "") { + return fmt.Errorf("error deleting SageMaker domain (%s): %w", d.Id(), err) } - return fmt.Errorf("error deleting SageMaker domain (%s): %w", d.Id(), err) } if _, err := waiter.DomainDeleted(conn, d.Id()); err != nil { - if isAWSErr(err, "ValidationException", "RecordNotFound") { + if !isAWSErr(err, sagemaker.ErrCodeResourceNotFound, "") { + return fmt.Errorf("error waiting for sagemaker domain (%s) to delete: %w", d.Id(), err) + } + } + + efsConn := meta.(*AWSClient).efsconn + efsFsID := d.Get("home_efs_file_system_id").(string) + + _, err := efsConn.DeleteFileSystem(&efs.DeleteFileSystemInput{ + FileSystemId: aws.String(efsFsID), + }) + if err != nil { + if isAWSErr(err, efs.ErrCodeFileSystemNotFound, "") { return nil } - return fmt.Errorf("error waiting for sagemaker domain (%s) to delete: %w", d.Id(), err) + return fmt.Errorf("Error delete EFS file system (%s): %w", efsFsID, err) } return nil diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index ca31ef452ef..903b390c29a 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "log" + "regexp" "testing" "github.com/aws/aws-sdk-go/aws" @@ -17,6 +18,9 @@ func init() { resource.AddTestSweepers("aws_sagemaker_domain", &resource.Sweeper{ Name: "aws_sagemaker_domain", F: testSweepSagemakerDomains, + Dependencies: []string{ + "aws_efs_file_system", + }, }) } @@ -75,7 +79,15 @@ func TestAccAWSSagemakerDomain_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), resource.TestCheckResourceAttr(resourceName, "domain_name", rName), - testAccCheckResourceAttrRegionalARN(resourceName, "arn", "sagemaker", fmt.Sprintf("domain/%s", rName)), + resource.TestCheckResourceAttr(resourceName, "auth_mode", "IAM"), + resource.TestCheckResourceAttr(resourceName, "app_network_access_type", "PublicInternetOnly"), + resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "default_user_settings.0.execution_role", "aws_iam_role.test", "arn"), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "sagemaker", regexp.MustCompile(`domain/.+`)), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttrPair(resourceName, "vpc_id", "aws_vpc.test", "id"), + resource.TestCheckResourceAttrSet(resourceName, "url"), ), }, { @@ -233,6 +245,7 @@ func testAccCheckAWSSagemakerDomainExists(n string, codeRepo *sagemaker.Describe func testAccAWSSagemakerDomainConfigBase(rName string) string { return fmt.Sprintf(` resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" tags = { From 0a8f26618bedbc2bea0f3c75406823bbff5fbb12 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Fri, 6 Nov 2020 23:29:34 +0200 Subject: [PATCH 07/33] move efs delete to tests --- aws/resource_aws_sagemaker_domain.go | 14 ------- aws/resource_aws_sagemaker_domain_test.go | 51 +++++++++++++++++++++++ 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index 8225a24b26e..c822aa84d79 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -8,7 +8,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/service/efs" "github.com/aws/aws-sdk-go/service/sagemaker" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -323,19 +322,6 @@ func resourceAwsSagemakerDomainDelete(d *schema.ResourceData, meta interface{}) } } - efsConn := meta.(*AWSClient).efsconn - efsFsID := d.Get("home_efs_file_system_id").(string) - - _, err := efsConn.DeleteFileSystem(&efs.DeleteFileSystemInput{ - FileSystemId: aws.String(efsFsID), - }) - if err != nil { - if isAWSErr(err, efs.ErrCodeFileSystemNotFound, "") { - return nil - } - return fmt.Errorf("Error delete EFS file system (%s): %w", efsFsID, err) - } - return nil } diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 903b390c29a..0c821a01c15 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/efs" "github.com/aws/aws-sdk-go/service/sagemaker" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -19,6 +20,7 @@ func init() { Name: "aws_sagemaker_domain", F: testSweepSagemakerDomains, Dependencies: []string{ + "aws_efs_mount_target", "aws_efs_file_system", }, }) @@ -88,6 +90,7 @@ func TestAccAWSSagemakerDomain_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttrPair(resourceName, "vpc_id", "aws_vpc.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "url"), + testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), ), }, { @@ -242,6 +245,54 @@ func testAccCheckAWSSagemakerDomainExists(n string, codeRepo *sagemaker.Describe } } +func testAccCheckAWSSagemakerDomainDeleteImplicitResources(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Sagemaker domain not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Sagemaker domain name not set") + } + + conn := testAccProvider.Meta().(*AWSClient).efsconn + efsFsID := rs.Primary.Attributes["home_efs_file_system_id"] + vpcID := rs.Primary.Attributes["vpc_id"] + + resp, err := conn.DescribeMountTargets(&efs.DescribeMountTargetsInput{ + FileSystemId: aws.String(efsFsID), + }) + + if err != nil { + return fmt.Errorf("Sagemaker domain EFS mount targets not found: %w", err) + } + + //reusing EFS mount target delete for wait logic + mountTargets := resp.MountTargets + for _, mt := range mountTargets { + r := resourceAwsEfsMountTarget() + d := r.Data(nil) + mtId := aws.StringValue(mt.MountTargetId) + d.SetId(mtId) + err := r.Delete(d, testAccProvider.Meta()) + if err != nil { + return fmt.Errorf("Sagemaker domain EFS mount target (%s) failed to delete: %w", mtId, err) + } + } + + r := resourceAwsEfsFileSystem() + d := r.Data(nil) + d.SetId(efsFsID) + err = r.Delete(d, testAccProvider.Meta()) + if err != nil { + return fmt.Errorf("Sagemaker domain EFS file system (%s) failed to delete: %w", efsFsID, err) + } + + return nil + } +} + func testAccAWSSagemakerDomainConfigBase(rName string) string { return fmt.Sprintf(` resource "aws_vpc" "test" { From 491c6765f576d1e78a6149c8318f984900b7bc2d Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 00:21:07 +0200 Subject: [PATCH 08/33] add tags test --- aws/resource_aws_sagemaker_domain_test.go | 220 +++++++++------------- 1 file changed, 86 insertions(+), 134 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 0c821a01c15..0ee0f0da04a 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -90,6 +90,7 @@ func TestAccAWSSagemakerDomain_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttrPair(resourceName, "vpc_id", "aws_vpc.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "url"), + resource.TestCheckResourceAttrSet(resourceName, "home_efs_file_system_id"), testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), ), }, @@ -102,76 +103,49 @@ func TestAccAWSSagemakerDomain_basic(t *testing.T) { }) } -// func TestAccAWSSagemakerDomain_gitConfig_branch(t *testing.T) { -// var notebook sagemaker.DescribeDomainOutput -// rName := acctest.RandomWithPrefix("tf-acc-test") -// resourceName := "aws_sagemaker_domain.test" - -// resource.ParallelTest(t, resource.TestCase{ -// PreCheck: func() { testAccPreCheck(t) }, -// Providers: testAccProviders, -// CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, -// Steps: []resource.TestStep{ -// { -// Config: testAccAWSSagemakerDomainGitConfigBranchConfig(rName), -// Check: resource.ComposeTestCheckFunc( -// testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), -// resource.TestCheckResourceAttr(resourceName, "domain_name", rName), -// testAccCheckResourceAttrRegionalARN(resourceName0......, "arn", "sagemaker", fmt.Sprintf("code-repository/%s", rName)), -// resource.TestCheckResourceAttr(resourceName, "git_config.#", "1"), -// resource.TestCheckResourceAttr(resourceName, "git_config.0.repository_url", "https://github.com/terraform-providers/terraform-provider-aws.git"), -// resource.TestCheckResourceAttr(resourceName, "git_config.0.branch", "master"), -// ), -// }, -// { -// ResourceName: resourceName, -// ImportState: true, -// ImportStateVerify: true, -// }, -// }, -// }) -// } - -// func TestAccAWSSagemakerDomain_gitConfig_secret(t *testing.T) { -// var notebook sagemaker.DescribeDomainOutput -// rName := acctest.RandomWithPrefix("tf-acc-test") -// resourceName := "aws_sagemaker_domain.test" - -// resource.ParallelTest(t, resource.TestCase{ -// PreCheck: func() { testAccPreCheck(t) }, -// Providers: testAccProviders, -// CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, -// Steps: []resource.TestStep{ -// { -// Config: testAccAWSSagemakerDomainGitConfigSecretConfig(rName), -// Check: resource.ComposeTestCheckFunc( -// testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), -// resource.TestCheckResourceAttr(resourceName, "domain_name", rName), -// testAccCheckResourceAttrRegionalARN(resourceName, "arn", "sagemaker", fmt.Sprintf("code-repository/%s", rName)), -// resource.TestCheckResourceAttr(resourceName, "git_config.#", "1"), -// resource.TestCheckResourceAttr(resourceName, "git_config.0.repository_url", "https://github.com/terraform-providers/terraform-provider-aws.git"), -// resource.TestCheckResourceAttrPair(resourceName, "git_config.0.secret_arn", "aws_secretsmanager_secret.test", "arn"), -// ), -// }, -// { -// ResourceName: resourceName, -// ImportState: true, -// ImportStateVerify: true, -// }, -// { -// Config: testAccAWSSagemakerDomainGitConfigSecretUpdatedConfig(rName), -// Check: resource.ComposeTestCheckFunc( -// testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), -// resource.TestCheckResourceAttr(resourceName, "domain_name", rName), -// testAccCheckResourceAttrRegionalARN(resourceName, "arn", "sagemaker", fmt.Sprintf("code-repository/%s", rName)), -// resource.TestCheckResourceAttr(resourceName, "git_config.#", "1"), -// resource.TestCheckResourceAttr(resourceName, "git_config.0.repository_url", "https://github.com/terraform-providers/terraform-provider-aws.git"), -// resource.TestCheckResourceAttrPair(resourceName, "git_config.0.secret_arn", "aws_secretsmanager_secret.test2", "arn"), -// ), -// }, -// }, -// }) -// } +func TestAccAWSSagemakerDomain_tags(t *testing.T) { + var notebook sagemaker.DescribeDomainOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerDomainBasicConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSSagemakerDomainBasicConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAWSSagemakerDomainBasicConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} func TestAccAWSSagemakerDomain_disappears(t *testing.T) { var notebook sagemaker.DescribeDomainOutput @@ -187,6 +161,7 @@ func TestAccAWSSagemakerDomain_disappears(t *testing.T) { Config: testAccAWSSagemakerDomainBasicConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), testAccCheckResourceDisappears(testAccProvider, resourceAwsSagemakerDomain(), resourceName), ), ExpectNonEmptyPlan: true, @@ -258,7 +233,6 @@ func testAccCheckAWSSagemakerDomainDeleteImplicitResources(n string) resource.Te conn := testAccProvider.Meta().(*AWSClient).efsconn efsFsID := rs.Primary.Attributes["home_efs_file_system_id"] - vpcID := rs.Primary.Attributes["vpc_id"] resp, err := conn.DescribeMountTargets(&efs.DescribeMountTargetsInput{ FileSystemId: aws.String(efsFsID), @@ -273,11 +247,11 @@ func testAccCheckAWSSagemakerDomainDeleteImplicitResources(n string) resource.Te for _, mt := range mountTargets { r := resourceAwsEfsMountTarget() d := r.Data(nil) - mtId := aws.StringValue(mt.MountTargetId) - d.SetId(mtId) + mtID := aws.StringValue(mt.MountTargetId) + d.SetId(mtID) err := r.Delete(d, testAccProvider.Meta()) if err != nil { - return fmt.Errorf("Sagemaker domain EFS mount target (%s) failed to delete: %w", mtId, err) + return fmt.Errorf("Sagemaker domain EFS mount target (%s) failed to delete: %w", mtID, err) } } @@ -347,63 +321,41 @@ resource "aws_sagemaker_domain" "test" { `, rName) } -// func testAccAWSSagemakerDomainGitConfigBranchConfig(rName string) string { -// return fmt.Sprintf(` -// resource "aws_sagemaker_domain" "test" { -// domain_name = %[1]q - -// git_config { -// repository_url = "https://github.com/terraform-providers/terraform-provider-aws.git" -// branch = "master" -// } -// } -// `, rName) -// } - -// func testAccAWSSagemakerDomainGitConfigSecretConfig(rName string) string { -// return fmt.Sprintf(` -// resource "aws_secretsmanager_secret" "test" { -// name = %[1]q -// } - -// resource "aws_secretsmanager_secret_version" "test" { -// secret_id = aws_secretsmanager_secret.test.id -// secret_string = jsonencode({ username = "example", passowrd = "example" }) -// } - -// resource "aws_sagemaker_domain" "test" { -// domain_name = %[1]q - -// git_config { -// repository_url = "https://github.com/terraform-providers/terraform-provider-aws.git" -// secret_arn = aws_secretsmanager_secret.test.arn -// } - -// depends_on = [aws_secretsmanager_secret_version.test] -// } -// `, rName) -// } - -// func testAccAWSSagemakerDomainGitConfigSecretUpdatedConfig(rName string) string { -// return fmt.Sprintf(` -// resource "aws_secretsmanager_secret" "test2" { -// name = "%[1]s-2" -// } - -// resource "aws_secretsmanager_secret_version" "test2" { -// secret_id = aws_secretsmanager_secret.test2.id -// secret_string = jsonencode({ username = "example", passowrd = "example" }) -// } - -// resource "aws_sagemaker_domain" "test" { -// domain_name = %[1]q - -// git_config { -// repository_url = "https://github.com/terraform-providers/terraform-provider-aws.git" -// secret_arn = aws_secretsmanager_secret.test2.arn -// } - -// depends_on = [aws_secretsmanager_secret_version.test2] -// } -// `, rName) -// } +func testAccAWSSagemakerDomainBasicConfigTags1(rName, tagKey1, tagValue1 string) string { + return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` +resource "aws_sagemaker_domain" "test" { + domain_name = %[1]q + auth_mode = "IAM" + vpc_id = aws_vpc.test.id + subnet_ids = [aws_subnet.test.id] + + default_user_settings { + execution_role = aws_iam_role.test.arn + } + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccAWSSagemakerDomainBasicConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` +resource "aws_sagemaker_domain" "test" { + domain_name = %[1]q + auth_mode = "IAM" + vpc_id = aws_vpc.test.id + subnet_ids = [aws_subnet.test.id] + + default_user_settings { + execution_role = aws_iam_role.test.arn + } + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} From 2706ac02b6a8a591671024479fe64a3da1817a86 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 00:28:34 +0200 Subject: [PATCH 09/33] add sg test --- aws/resource_aws_sagemaker_domain_test.go | 91 +++++++++++++++++++++-- 1 file changed, 86 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 0ee0f0da04a..fc2f1ab1664 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -114,7 +114,7 @@ func TestAccAWSSagemakerDomain_tags(t *testing.T) { CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSSagemakerDomainBasicConfigTags1(rName, "key1", "value1"), + Config: testAccAWSSagemakerDomainConfigTags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -127,7 +127,7 @@ func TestAccAWSSagemakerDomain_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSSagemakerDomainBasicConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Config: testAccAWSSagemakerDomainConfigTags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), @@ -136,11 +136,48 @@ func TestAccAWSSagemakerDomain_tags(t *testing.T) { ), }, { - Config: testAccAWSSagemakerDomainBasicConfigTags1(rName, "key2", "value2"), + Config: testAccAWSSagemakerDomainConfigTags1(rName, "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), + ), + }, + }, + }) +} + +func TestAccAWSSagemakerDomain_securityGroup(t *testing.T) { + var notebook sagemaker.DescribeDomainOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerDomainConfigSecurityGroup1(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.security_groups.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSSagemakerDomainConfigSecurityGroup2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.security_groups.#", "2"), + testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), ), }, }, @@ -321,7 +358,51 @@ resource "aws_sagemaker_domain" "test" { `, rName) } -func testAccAWSSagemakerDomainBasicConfigTags1(rName, tagKey1, tagValue1 string) string { +func testAccAWSSagemakerDomainConfigSecurityGroup1(rName string) string { + return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` +resource "aws_security_group" "test" { + name = "%[1]s" +} + +resource "aws_sagemaker_domain" "test" { + domain_name = %[1]q + auth_mode = "IAM" + vpc_id = aws_vpc.test.id + subnet_ids = [aws_subnet.test.id] + + default_user_settings { + execution_role = aws_iam_role.test.arn + security_groups = [aws_security_group.test.id] + } +} +`, rName) +} + +func testAccAWSSagemakerDomainConfigSecurityGroup2(rName string) string { + return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` +resource "aws_security_group" "test" { + name = %[1]q +} + +resource "aws_security_group" "test2" { + name = "%[2]s-2" +} + +resource "aws_sagemaker_domain" "test" { + domain_name = %[1]q + auth_mode = "IAM" + vpc_id = aws_vpc.test.id + subnet_ids = [aws_subnet.test.id] + + default_user_settings { + execution_role = aws_iam_role.test.arn + security_groups = [aws_security_group.test.id, aws_security_group.test2.id] + } +} +`, rName) +} + +func testAccAWSSagemakerDomainConfigTags1(rName, tagKey1, tagValue1 string) string { return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` resource "aws_sagemaker_domain" "test" { domain_name = %[1]q @@ -340,7 +421,7 @@ resource "aws_sagemaker_domain" "test" { `, rName, tagKey1, tagValue1) } -func testAccAWSSagemakerDomainBasicConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { +func testAccAWSSagemakerDomainConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` resource "aws_sagemaker_domain" "test" { domain_name = %[1]q From f75acfffea32fe57dbf401e98f6d6350eb8ee1c0 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 00:32:03 +0200 Subject: [PATCH 10/33] add kms test --- aws/resource_aws_sagemaker_domain_test.go | 66 +++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index fc2f1ab1664..d45833042b1 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -103,6 +103,33 @@ func TestAccAWSSagemakerDomain_basic(t *testing.T) { }) } +func TestAccAWSSagemakerDomain_efsKms(t *testing.T) { + var notebook sagemaker.DescribeDomainOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerDomainConfigEFSKMS(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + resource.TestCheckResourceAttrPair(resourceName, "home_efs_file_system_kms_key_id", "aws_kms_key.test", "arn"), + testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAWSSagemakerDomain_tags(t *testing.T) { var notebook sagemaker.DescribeDomainOutput rName := acctest.RandomWithPrefix("tf-acc-test") @@ -402,6 +429,45 @@ resource "aws_sagemaker_domain" "test" { `, rName) } +func testAccAWSSagemakerDomainConfigEFSKMS(rName string) string { + return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` +resource "aws_kms_key" "test" { + description = "Terraform acc test %s" + deletion_window_in_days = 7 + + policy = < Date: Sat, 7 Nov 2020 00:32:49 +0200 Subject: [PATCH 11/33] fix sg name --- aws/resource_aws_sagemaker_domain_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index d45833042b1..1ef239bce51 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -412,7 +412,7 @@ resource "aws_security_group" "test" { } resource "aws_security_group" "test2" { - name = "%[2]s-2" + name = "%[1]s-2" } resource "aws_sagemaker_domain" "test" { From f390f8c5974e1285d8ba3aeb9b2d75ee7db2cf9c Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 00:56:25 +0200 Subject: [PATCH 12/33] docs --- website/docs/r/sagemaker_domain.html.markdown | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 website/docs/r/sagemaker_domain.html.markdown diff --git a/website/docs/r/sagemaker_domain.html.markdown b/website/docs/r/sagemaker_domain.html.markdown new file mode 100644 index 00000000000..0aea6514664 --- /dev/null +++ b/website/docs/r/sagemaker_domain.html.markdown @@ -0,0 +1,109 @@ +--- +subcategory: "Sagemaker" +layout: "aws" +page_title: "AWS: aws_sagemaker_domain" +description: |- + Provides a Sagemaker Domain resource. +--- + +# Resource: aws_sagemaker_domain + +Provides a Sagemaker Domain resource. + +## Example Usage + +### Basic usage + +```hcl +resource "aws_sagemaker_domain" "example" { + domain_name = "example" + auth_mode = "IAM" + vpc_id = aws_vpc.test.id + subnet_ids = [aws_subnet.test.id] + + default_user_settings { + execution_role = aws_iam_role.test.arn + } +} + +resource "aws_iam_role" "example" { + name = "example" + path = "/" + assume_role_policy = data.aws_iam_policy_document.example.json +} + +data "aws_iam_policy_document" "example" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["sagemaker.amazonaws.com"] + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `domain_name` - (Required) The domain name. +* `auth_mode` - (Required) The mode of authentication that members use to access the domain. Valid values are `IAM` and `SSO`. +* `vpc_id` - (Required) The ID of the Amazon Virtual Private Cloud (VPC) that Studio uses for communication. +* `subnet_ids` - (Required) The VPC subnets that Studio uses for communication. +* `default_user_settings` - (Required) The default user settings. see [Default User Settings](#default-user-settings) below. +* `app_network_access_type` - (Optional) Specifies the VPC used for non-EFS traffic. The default value is `PublicInternetOnly`. Valid values are `PublicInternetOnly` and `VpcOnly`. +* `home_efs_file_system_kms_key_id` - (Optional) The AWS Key Management Service (KMS) encryption key ARN. +* `tags` - (Optional) A map of tags to assign to the resource. + +### Default User Settings + +* `execution_role` - (Required) The execution role ARN for the user. +* `security_groups` - (Optional) The security groups. +* `sharing_settings` - (Optional) The sharing settings. see [Sharing Settings](#sharing-settings) below. +* `tensor_board_app_settings` - (Optional) The TensorBoard app settings. see [TensorBoard App Settings](#tensorboard-app-settings) below. +* `jupyter_server_app_settings` - (Optional) The kernel gateway app settings. see [Jupyter Server App Settings](#jupyter-server-app-settings) below. +* `kernel_gateway_app_settings` - (Optional) The Jupyter server's app settings. see [Kernel Gateway App Settings](#kernal-gateway-app-settings) below. + +#### Sharing Settings + +* `notebook_output_option` - (Optional) Whether to include the notebook cell output when sharing the notebook. The default is `Disabled`. Valid values are `Allowed` and `Disabled`. +* `s3_kms_key_id` - (Optional) When `notebook_output_option` is Allowed, the AWS Key Management Service (KMS) encryption key ID used to encrypt the notebook cell output in the Amazon S3 bucket. +* `s3_output_path` - (Optional) When `notebook_output_option` is Allowed, the Amazon S3 bucket used to save the notebook cell output. + +#### TensorBoard App Settings + +* `default_resource_spec` - (Optional) The default instance type and the Amazon Resource Name (ARN) of the SageMaker image created on the instance. see [Default Resource Spec](#default-resource-spec) below. + +#### Kernel Gateway App Settings + +* `default_resource_spec` - (Optional) The default instance type and the Amazon Resource Name (ARN) of the SageMaker image created on the instance. see [Default Resource Spec](#default-resource-spec) below. + +#### Jupyter Server App Settings + +* `default_resource_spec` - (Optional) The default instance type and the Amazon Resource Name (ARN) of the SageMaker image created on the instance. see [Default Resource Spec](#default-resource-spec) below. + +##### Default Resource Spec + +* `instance_type` - (Optional) The instance type. +* `sagemaker_image_arn` - (Optional) The Amazon Resource Name (ARN) of the SageMaker image created on the instance. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Domain. +* `arn` - The Amazon Resource Name (ARN) assigned by AWS to this Domain. +* `url` - The domain's URL. +* `single_sign_on_managed_application_instance_id` - The SSO managed application instance ID. +* `home_efs_file_system_id` - The ID of the Amazon Elastic File System (EFS) managed by this Domain. + + +## Import + +Sagemaker Code Domains can be imported using the `id`, e.g. + +``` +$ terraform import aws_sagemaker_domain.test_domain d-8jgsjtilstu8 +``` From 979339c4c453d941e63d2c497dfb35b370fa3451 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 01:00:17 +0200 Subject: [PATCH 13/33] fmt --- aws/resource_aws_sagemaker_domain_test.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 1ef239bce51..06ca2655c55 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -334,7 +334,6 @@ func testAccCheckAWSSagemakerDomainDeleteImplicitResources(n string) resource.Te func testAccAWSSagemakerDomainConfigBase(rName string) string { return fmt.Sprintf(` resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" tags = { @@ -398,8 +397,8 @@ resource "aws_sagemaker_domain" "test" { subnet_ids = [aws_subnet.test.id] default_user_settings { - execution_role = aws_iam_role.test.arn - security_groups = [aws_security_group.test.id] + execution_role = aws_iam_role.test.arn + security_groups = [aws_security_group.test.id] } } `, rName) @@ -422,8 +421,8 @@ resource "aws_sagemaker_domain" "test" { subnet_ids = [aws_subnet.test.id] default_user_settings { - execution_role = aws_iam_role.test.arn - security_groups = [aws_security_group.test.id, aws_security_group.test2.id] + execution_role = aws_iam_role.test.arn + security_groups = [aws_security_group.test.id, aws_security_group.test2.id] } } `, rName) @@ -500,8 +499,8 @@ resource "aws_sagemaker_domain" "test" { } tags = { - %[2]q = %[3]q - %[4]q = %[5]q + %[2]q = %[3]q + %[4]q = %[5]q } } `, rName, tagKey1, tagValue1, tagKey2, tagValue2) From cbd61bd5481792a3fff01549a09f40fdeb216ad4 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 01:07:31 +0200 Subject: [PATCH 14/33] sg test fmt --- aws/resource_aws_sagemaker_domain_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 06ca2655c55..1fe27f220e8 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -398,7 +398,7 @@ resource "aws_sagemaker_domain" "test" { default_user_settings { execution_role = aws_iam_role.test.arn - security_groups = [aws_security_group.test.id] + security_groups = [aws_security_group.test.id] } } `, rName) @@ -422,7 +422,7 @@ resource "aws_sagemaker_domain" "test" { default_user_settings { execution_role = aws_iam_role.test.arn - security_groups = [aws_security_group.test.id, aws_security_group.test2.id] + security_groups = [aws_security_group.test.id, aws_security_group.test2.id] } } `, rName) From 00582269e931108fb62ed07d9404c85cc60d2b00 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 01:23:26 +0200 Subject: [PATCH 15/33] delete implict sgs --- aws/resource_aws_sagemaker_domain_test.go | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 1fe27f220e8..3078d1783b5 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/efs" "github.com/aws/aws-sdk-go/service/sagemaker" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" @@ -297,6 +298,7 @@ func testAccCheckAWSSagemakerDomainDeleteImplicitResources(n string) resource.Te conn := testAccProvider.Meta().(*AWSClient).efsconn efsFsID := rs.Primary.Attributes["home_efs_file_system_id"] + vpcID := rs.Primary.Attributes["vpc_id"] resp, err := conn.DescribeMountTargets(&efs.DescribeMountTargetsInput{ FileSystemId: aws.String(efsFsID), @@ -327,6 +329,34 @@ func testAccCheckAWSSagemakerDomainDeleteImplicitResources(n string) resource.Te return fmt.Errorf("Sagemaker domain EFS file system (%s) failed to delete: %w", efsFsID, err) } + var filters []*ec2.Filter + filters = append(filters, &ec2.Filter{ + Name: aws.String("vpc-id"), + Values: aws.StringSlice([]string{vpcID}), + }) + + req := &ec2.DescribeSecurityGroupsInput{ + Filters: filters, + } + + ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn + + sgResp, err := ec2conn.DescribeSecurityGroups(req) + if err != nil { + return fmt.Errorf("error reading security groups: %w", err) + } + + for _, sg := range sgResp.SecurityGroups { + sgID := aws.StringValue(sg.GroupId) + r := resourceAwsSecurityGroup() + d := r.Data(nil) + d.SetId(sgID) + err = r.Delete(d, testAccProvider.Meta()) + if err != nil { + return fmt.Errorf("Sagemaker domain EFS file system sg (%s) failed to delete: %w", sgID, err) + } + } + return nil } } From 2f4fedf3a74bff0f0e0906b77178041179c5b10d Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 13:10:01 +0200 Subject: [PATCH 16/33] tests are passing --- aws/resource_aws_sagemaker_domain_test.go | 47 +++++++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 3078d1783b5..ae65771b2ef 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -346,14 +346,45 @@ func testAccCheckAWSSagemakerDomainDeleteImplicitResources(n string) resource.Te return fmt.Errorf("error reading security groups: %w", err) } + //revoke permissions for _, sg := range sgResp.SecurityGroups { sgID := aws.StringValue(sg.GroupId) - r := resourceAwsSecurityGroup() - d := r.Data(nil) - d.SetId(sgID) - err = r.Delete(d, testAccProvider.Meta()) - if err != nil { - return fmt.Errorf("Sagemaker domain EFS file system sg (%s) failed to delete: %w", sgID, err) + + if len(sg.IpPermissions) > 0 { + req := &ec2.RevokeSecurityGroupIngressInput{ + GroupId: sg.GroupId, + IpPermissions: sg.IpPermissions, + } + _, err = ec2conn.RevokeSecurityGroupIngress(req) + + if err != nil { + return fmt.Errorf("Error revoking security group %s rules: %w", sgID, err) + } + } + + if len(sg.IpPermissionsEgress) > 0 { + req := &ec2.RevokeSecurityGroupEgressInput{ + GroupId: sg.GroupId, + IpPermissions: sg.IpPermissionsEgress, + } + _, err = ec2conn.RevokeSecurityGroupEgress(req) + + if err != nil { + return fmt.Errorf("Error revoking security group %s rules: %w", sgID, err) + } + } + } + + for _, sg := range sgResp.SecurityGroups { + sgID := aws.StringValue(sg.GroupId) + if aws.StringValue(sg.GroupName) != "default" { + r := resourceAwsSecurityGroup() + d := r.Data(nil) + d.SetId(sgID) + err = r.Delete(d, testAccProvider.Meta()) + if err != nil { + return fmt.Errorf("Sagemaker domain EFS file system sg (%s) failed to delete: %w", sgID, err) + } } } @@ -428,7 +459,7 @@ resource "aws_sagemaker_domain" "test" { default_user_settings { execution_role = aws_iam_role.test.arn - security_groups = [aws_security_group.test.id] + security_groups = [aws_security_sg.test.id] } } `, rName) @@ -452,7 +483,7 @@ resource "aws_sagemaker_domain" "test" { default_user_settings { execution_role = aws_iam_role.test.arn - security_groups = [aws_security_group.test.id, aws_security_group.test2.id] + security_groups = [aws_security_sg.test.id, aws_security_sg.test2.id] } } `, rName) From 8d67e972a7e3d8ae3c4e307318160b10f39e2312 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 14:13:19 +0200 Subject: [PATCH 17/33] fix tags --- aws/resource_aws_sagemaker_domain.go | 43 ++++++++++++++++++----- aws/resource_aws_sagemaker_domain_test.go | 10 +++--- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index c822aa84d79..87291d0e4a5 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -11,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go/service/sagemaker" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sagemaker/finder" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/sagemaker/waiter" ) @@ -231,6 +232,10 @@ func resourceAwsSagemakerDomainCreate(d *schema.ResourceData, meta interface{}) input.HomeEfsFileSystemKmsKeyId = aws.String(v.(string)) } + if v, ok := d.GetOk("tags"); ok { + input.Tags = keyvaluetags.New(v.(map[string]interface{})).IgnoreAws().SagemakerTags() + } + log.Printf("[DEBUG] sagemaker domain create config: %#v", *input) output, err := conn.CreateDomain(input) if err != nil { @@ -254,6 +259,7 @@ func resourceAwsSagemakerDomainCreate(d *schema.ResourceData, meta interface{}) func resourceAwsSagemakerDomainRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).sagemakerconn + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig domain, err := finder.DomainByName(conn, d.Id()) if err != nil { @@ -265,10 +271,11 @@ func resourceAwsSagemakerDomainRead(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("error reading SageMaker domain (%s): %w", d.Id(), err) } + arn := aws.StringValue(domain.DomainArn) d.Set("domain_name", domain.DomainName) d.Set("auth_mode", domain.AuthMode) d.Set("app_network_access_type", domain.AppNetworkAccessType) - d.Set("arn", domain.DomainArn) + d.Set("arn", arn) d.Set("home_efs_file_system_id", domain.HomeEfsFileSystemId) d.Set("home_efs_file_system_kms_key_id", domain.HomeEfsFileSystemKmsKeyId) d.Set("single_sign_on_managed_application_instance_id", domain.SingleSignOnManagedApplicationInstanceId) @@ -283,21 +290,41 @@ func resourceAwsSagemakerDomainRead(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("error setting default_user_settings for sagemaker domain (%s): %w", d.Id(), err) } + tags, err := keyvaluetags.SagemakerListTags(conn, arn) + + if err != nil { + return fmt.Errorf("error listing tags for Sagemaker Domain (%s): %w", d.Id(), err) + } + + if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + return nil } func resourceAwsSagemakerDomainUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).sagemakerconn - input := &sagemaker.UpdateDomainInput{ - DomainId: aws.String(d.Id()), - DefaultUserSettings: expandSagemakerDomainDefaultUserSettings(d.Get("default_user_settings").([]interface{})), + if d.HasChange("default_user_settings") { + input := &sagemaker.UpdateDomainInput{ + DomainId: aws.String(d.Id()), + DefaultUserSettings: expandSagemakerDomainDefaultUserSettings(d.Get("default_user_settings").([]interface{})), + } + + log.Printf("[DEBUG] sagemaker domain update config: %#v", *input) + _, err := conn.UpdateDomain(input) + if err != nil { + return fmt.Errorf("error updating SageMaker domain: %w", err) + } } - log.Printf("[DEBUG] sagemaker domain update config: %#v", *input) - _, err := conn.UpdateDomain(input) - if err != nil { - return fmt.Errorf("error updating SageMaker domain: %w", err) + if d.HasChange("tags") { + o, n := d.GetChange("tags") + + if err := keyvaluetags.SagemakerUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating Sagemaker Notebook Instance (%s) tags: %s", d.Id(), err) + } } return resourceAwsSagemakerDomainRead(d, meta) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index ae65771b2ef..62ee3a685c2 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "regexp" + "strings" "testing" "github.com/aws/aws-sdk-go/aws" @@ -305,7 +306,7 @@ func testAccCheckAWSSagemakerDomainDeleteImplicitResources(n string) resource.Te }) if err != nil { - return fmt.Errorf("Sagemaker domain EFS mount targets not found: %w", err) + return fmt.Errorf("Sagemaker domain EFS mount targets for EFS FS (%s) not found: %w", efsFsID, err) } //reusing EFS mount target delete for wait logic @@ -377,7 +378,8 @@ func testAccCheckAWSSagemakerDomainDeleteImplicitResources(n string) resource.Te for _, sg := range sgResp.SecurityGroups { sgID := aws.StringValue(sg.GroupId) - if aws.StringValue(sg.GroupName) != "default" { + sgName := aws.StringValue(sg.GroupName) + if sgName != "default" && strings.HasPrefix(sgName, "tf-acc-test") { r := resourceAwsSecurityGroup() d := r.Data(nil) d.SetId(sgID) @@ -459,7 +461,7 @@ resource "aws_sagemaker_domain" "test" { default_user_settings { execution_role = aws_iam_role.test.arn - security_groups = [aws_security_sg.test.id] + security_groups = [aws_security_group.test.id] } } `, rName) @@ -483,7 +485,7 @@ resource "aws_sagemaker_domain" "test" { default_user_settings { execution_role = aws_iam_role.test.arn - security_groups = [aws_security_sg.test.id, aws_security_sg.test2.id] + security_groups = [aws_security_group.test.id, aws_security_group.test2.id] } } `, rName) From 71b63853419014b0def95cc01b3cf71d4d684d74 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 14:39:50 +0200 Subject: [PATCH 18/33] fix filter for implict sgs --- aws/resource_aws_sagemaker_domain_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 62ee3a685c2..54e666ba7fe 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -379,7 +379,7 @@ func testAccCheckAWSSagemakerDomainDeleteImplicitResources(n string) resource.Te for _, sg := range sgResp.SecurityGroups { sgID := aws.StringValue(sg.GroupId) sgName := aws.StringValue(sg.GroupName) - if sgName != "default" && strings.HasPrefix(sgName, "tf-acc-test") { + if sgName != "default" && !strings.HasPrefix(sgName, "tf-acc-test") { r := resourceAwsSecurityGroup() d := r.Data(nil) d.SetId(sgID) From 6d1b9ba87b05172f09cca7cb5fca31e871b74f6c Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 17:02:21 +0200 Subject: [PATCH 19/33] remove kms key attributes and tests --- aws/resource_aws_sagemaker_domain.go | 11 ---- aws/resource_aws_sagemaker_domain_test.go | 66 ------------------- website/docs/r/sagemaker_domain.html.markdown | 1 - 3 files changed, 78 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index 87291d0e4a5..2aa1c2caa76 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -65,12 +65,6 @@ func resourceAwsSagemakerDomain() *schema.Resource { Default: sagemaker.AppNetworkAccessTypePublicInternetOnly, ValidateFunc: validation.StringInSlice(sagemaker.AppNetworkAccessType_Values(), false), }, - "home_efs_file_system_kms_key_id": { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - ValidateFunc: validateArn, - }, "default_user_settings": { Type: schema.TypeList, Required: true, @@ -228,10 +222,6 @@ func resourceAwsSagemakerDomainCreate(d *schema.ResourceData, meta interface{}) DefaultUserSettings: expandSagemakerDomainDefaultUserSettings(d.Get("default_user_settings").([]interface{})), } - if v, ok := d.GetOk("home_efs_file_system_kms_key_id"); ok { - input.HomeEfsFileSystemKmsKeyId = aws.String(v.(string)) - } - if v, ok := d.GetOk("tags"); ok { input.Tags = keyvaluetags.New(v.(map[string]interface{})).IgnoreAws().SagemakerTags() } @@ -277,7 +267,6 @@ func resourceAwsSagemakerDomainRead(d *schema.ResourceData, meta interface{}) er d.Set("app_network_access_type", domain.AppNetworkAccessType) d.Set("arn", arn) d.Set("home_efs_file_system_id", domain.HomeEfsFileSystemId) - d.Set("home_efs_file_system_kms_key_id", domain.HomeEfsFileSystemKmsKeyId) d.Set("single_sign_on_managed_application_instance_id", domain.SingleSignOnManagedApplicationInstanceId) d.Set("url", domain.Url) d.Set("vpc_id", domain.VpcId) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 54e666ba7fe..b443380e706 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -105,33 +105,6 @@ func TestAccAWSSagemakerDomain_basic(t *testing.T) { }) } -func TestAccAWSSagemakerDomain_efsKms(t *testing.T) { - var notebook sagemaker.DescribeDomainOutput - rName := acctest.RandomWithPrefix("tf-acc-test") - resourceName := "aws_sagemaker_domain.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSSagemakerDomainConfigEFSKMS(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), - resource.TestCheckResourceAttrPair(resourceName, "home_efs_file_system_kms_key_id", "aws_kms_key.test", "arn"), - testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - func TestAccAWSSagemakerDomain_tags(t *testing.T) { var notebook sagemaker.DescribeDomainOutput rName := acctest.RandomWithPrefix("tf-acc-test") @@ -491,45 +464,6 @@ resource "aws_sagemaker_domain" "test" { `, rName) } -func testAccAWSSagemakerDomainConfigEFSKMS(rName string) string { - return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` -resource "aws_kms_key" "test" { - description = "Terraform acc test %s" - deletion_window_in_days = 7 - - policy = < Date: Sat, 7 Nov 2020 18:45:03 +0200 Subject: [PATCH 20/33] share settings --- aws/resource_aws_sagemaker_domain.go | 5 +- aws/resource_aws_sagemaker_domain_test.go | 65 +++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index 2aa1c2caa76..1611431b849 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -85,6 +85,7 @@ func resourceAwsSagemakerDomain() *schema.Resource { "sharing_settings": { Type: schema.TypeList, Optional: true, + ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -370,7 +371,7 @@ func expandSagemakerDomainDefaultUserSettings(l []interface{}) *sagemaker.UserSe config.JupyterServerAppSettings = expandSagemakerDomainJupyterServerAppSettings(v) } - if v, ok := m["share_settings"].([]interface{}); ok && len(v) > 0 { + if v, ok := m["sharing_settings"].([]interface{}); ok && len(v) > 0 { config.SharingSettings = expandSagemakerDomainShareSettings(v) } @@ -495,7 +496,7 @@ func flattenSagemakerDomainDefaultUserSettings(config *sagemaker.UserSettings) [ } if config.SharingSettings != nil { - m["share_settings"] = flattenSagemakerDomainShareSettings(config.SharingSettings) + m["sharing_settings"] = flattenSagemakerDomainShareSettings(config.SharingSettings) } return []map[string]interface{}{m} diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index b443380e706..576661911b9 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -88,6 +88,8 @@ func TestAccAWSSagemakerDomain_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), resource.TestCheckResourceAttrPair(resourceName, "default_user_settings.0.execution_role", "aws_iam_role.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.sharing_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.sharing_settings.0.notebook_output_option", "Disabled"), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "sagemaker", regexp.MustCompile(`domain/.+`)), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttrPair(resourceName, "vpc_id", "aws_vpc.test", "id"), @@ -186,6 +188,37 @@ func TestAccAWSSagemakerDomain_securityGroup(t *testing.T) { }) } +func TestAccAWSSagemakerDomain_sharingSettings(t *testing.T) { + var notebook sagemaker.DescribeDomainOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerDomainConfigSharingSettings(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.sharing_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.sharing_settings.0.notebook_output_option", "Allowed"), + resource.TestCheckResourceAttrPair(resourceName, "default_user_settings.0.sharing_settings.0.s3_kms_key_id", "aws_kms_key.test", "arn"), + resource.TestCheckResourceAttrSet(resourceName, "default_user_settings.0.sharing_settings.0.s3_output_path"), + testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAWSSagemakerDomain_disappears(t *testing.T) { var notebook sagemaker.DescribeDomainOutput rName := acctest.RandomWithPrefix("tf-acc-test") @@ -502,3 +535,35 @@ resource "aws_sagemaker_domain" "test" { } `, rName, tagKey1, tagValue1, tagKey2, tagValue2) } + +func testAccAWSSagemakerDomainConfigSharingSettings(rName string) string { + return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` +resource "aws_kms_key" "test" { + description = %[1]q + deletion_window_in_days = 7 +} + + +resource "aws_s3_bucket" "test" { + bucket = %[1]q + force_destroy = true +} + +resource "aws_sagemaker_domain" "test" { + domain_name = %[1]q + auth_mode = "IAM" + vpc_id = aws_vpc.test.id + subnet_ids = [aws_subnet.test.id] + + default_user_settings { + execution_role = aws_iam_role.test.arn + + sharing_settings { + notebook_output_option = "Allowed" + s3_kms_key_id = aws_kms_key.test.arn + s3_output_path = "s3://${aws_s3_bucket.test.bucket}/sharing" + } + } +} +`, rName) +} From 7f98573ede8338045926bb3fd2605e6c9b8cfdea Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 23:10:56 +0200 Subject: [PATCH 21/33] fix basic test --- aws/resource_aws_sagemaker_domain_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 576661911b9..6d80a6b2704 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -88,8 +88,6 @@ func TestAccAWSSagemakerDomain_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), resource.TestCheckResourceAttrPair(resourceName, "default_user_settings.0.execution_role", "aws_iam_role.test", "arn"), - resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.sharing_settings.#", "1"), - resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.sharing_settings.0.notebook_output_option", "Disabled"), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "sagemaker", regexp.MustCompile(`domain/.+`)), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttrPair(resourceName, "vpc_id", "aws_vpc.test", "id"), From d810be1e2a7c66df5e6fd7cc6ac1420e64afdc40 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 23:32:36 +0200 Subject: [PATCH 22/33] fix default_resource_spec --- aws/resource_aws_sagemaker_domain.go | 12 +++--- aws/resource_aws_sagemaker_domain_test.go | 51 +++++++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index 1611431b849..1bf7068cefb 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -387,7 +387,7 @@ func expandSagemakerDomainJupyterServerAppSettings(l []interface{}) *sagemaker.J config := &sagemaker.JupyterServerAppSettings{} - if v, ok := m["default_resurce_spec"].([]interface{}); ok && len(v) > 0 { + if v, ok := m["default_resource_spec"].([]interface{}); ok && len(v) > 0 { config.DefaultResourceSpec = expandSagemakerDomainDefaultResourceSpec(v) } @@ -403,7 +403,7 @@ func expandSagemakerDomainKernelGatewayAppSettings(l []interface{}) *sagemaker.K config := &sagemaker.KernelGatewayAppSettings{} - if v, ok := m["default_resurce_spec"].([]interface{}); ok && len(v) > 0 { + if v, ok := m["default_resource_spec"].([]interface{}); ok && len(v) > 0 { config.DefaultResourceSpec = expandSagemakerDomainDefaultResourceSpec(v) } @@ -419,7 +419,7 @@ func expandSagemakerDomainTensorBoardAppSettings(l []interface{}) *sagemaker.Ten config := &sagemaker.TensorBoardAppSettings{} - if v, ok := m["default_resurce_spec"].([]interface{}); ok && len(v) > 0 { + if v, ok := m["default_resource_spec"].([]interface{}); ok && len(v) > 0 { config.DefaultResourceSpec = expandSagemakerDomainDefaultResourceSpec(v) } @@ -528,7 +528,7 @@ func flattenSagemakerDomainTensorBoardAppSettings(config *sagemaker.TensorBoardA m := map[string]interface{}{} if config.DefaultResourceSpec != nil { - m["default_resurce_spec"] = flattenSagemakerDomainDefaultResourceSpec(config.DefaultResourceSpec) + m["default_resource_spec"] = flattenSagemakerDomainDefaultResourceSpec(config.DefaultResourceSpec) } return []map[string]interface{}{m} @@ -542,7 +542,7 @@ func flattenSagemakerDomainJupyterServerAppSettings(config *sagemaker.JupyterSer m := map[string]interface{}{} if config.DefaultResourceSpec != nil { - m["default_resurce_spec"] = flattenSagemakerDomainDefaultResourceSpec(config.DefaultResourceSpec) + m["default_resource_spec"] = flattenSagemakerDomainDefaultResourceSpec(config.DefaultResourceSpec) } return []map[string]interface{}{m} @@ -556,7 +556,7 @@ func flattenSagemakerDomainKernelGatewayAppSettings(config *sagemaker.KernelGate m := map[string]interface{}{} if config.DefaultResourceSpec != nil { - m["default_resurce_spec"] = flattenSagemakerDomainDefaultResourceSpec(config.DefaultResourceSpec) + m["default_resource_spec"] = flattenSagemakerDomainDefaultResourceSpec(config.DefaultResourceSpec) } return []map[string]interface{}{m} diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 6d80a6b2704..bcd33a82b28 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -217,6 +217,36 @@ func TestAccAWSSagemakerDomain_sharingSettings(t *testing.T) { }) } +func TestAccAWSSagemakerDomain_tensorboardAppSettings(t *testing.T) { + var notebook sagemaker.DescribeDomainOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerDomainConfigTensorBoardAppSettings(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.default_resource_spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.default_resource_spec.instance_type", "ml.t3.micro"), + testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAWSSagemakerDomain_disappears(t *testing.T) { var notebook sagemaker.DescribeDomainOutput rName := acctest.RandomWithPrefix("tf-acc-test") @@ -565,3 +595,24 @@ resource "aws_sagemaker_domain" "test" { } `, rName) } + +func testAccAWSSagemakerDomainConfigTensorBoardAppSettings(rName string) string { + return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` +resource "aws_sagemaker_domain" "test" { + domain_name = %[1]q + auth_mode = "IAM" + vpc_id = aws_vpc.test.id + subnet_ids = [aws_subnet.test.id] + + default_user_settings { + execution_role = aws_iam_role.test.arn + + tensor_board_app_settings { + default_resource_spec { + instance_type = "ml.t3.micro" + } + } + } +} +`, rName) +} From dd67c32b62e21ea9dd60374343d68b4b22a4643d Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 7 Nov 2020 23:55:49 +0200 Subject: [PATCH 23/33] force new --- aws/resource_aws_sagemaker_domain.go | 3 +++ aws/resource_aws_sagemaker_domain_test.go | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index 1bf7068cefb..e2b4da82367 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -110,6 +110,7 @@ func resourceAwsSagemakerDomain() *schema.Resource { "tensor_board_app_settings": { Type: schema.TypeList, Optional: true, + ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -138,6 +139,7 @@ func resourceAwsSagemakerDomain() *schema.Resource { "jupyter_server_app_settings": { Type: schema.TypeList, Optional: true, + ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -166,6 +168,7 @@ func resourceAwsSagemakerDomain() *schema.Resource { "kernel_gateway_app_settings": { Type: schema.TypeList, Optional: true, + ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index bcd33a82b28..d2a38907c0f 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -233,9 +233,9 @@ func TestAccAWSSagemakerDomain_tensorboardAppSettings(t *testing.T) { testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.#", "1"), - resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.default_resource_spec.#", "1"), - resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.default_resource_spec.instance_type", "ml.t3.micro"), - testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.0.default_resource_spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.0.default_resource_spec.0.instance_type", "ml.t3.micro"), + // testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), ), }, { @@ -609,7 +609,7 @@ resource "aws_sagemaker_domain" "test" { tensor_board_app_settings { default_resource_spec { - instance_type = "ml.t3.micro" + instance_type = "ml.t3.micro" } } } From 07a393d1b02c5bd87b50ab46d968ed122bf6015e Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sun, 8 Nov 2020 00:01:02 +0200 Subject: [PATCH 24/33] add last tests --- aws/resource_aws_sagemaker_domain_test.go | 104 +++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index d2a38907c0f..970a0e3d467 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -235,7 +235,67 @@ func TestAccAWSSagemakerDomain_tensorboardAppSettings(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.0.default_resource_spec.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.0.default_resource_spec.0.instance_type", "ml.t3.micro"), - // testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), + testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSSagemakerDomain_kernelGatewayAppSettings(t *testing.T) { + var notebook sagemaker.DescribeDomainOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerDomainConfigKernelGatewayAppSettings(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.kernel_gateway_app_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.kernel_gateway_app_settings.0.default_resource_spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.kernel_gateway_app_settings.0.default_resource_spec.0.instance_type", "ml.t3.micro"), + testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSSagemakerDomain_jupyterServerAppSettings(t *testing.T) { + var notebook sagemaker.DescribeDomainOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerDomainConfigJupyterServerAppSettings(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.jupyter_server_app_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.jupyter_server_app_settings.0.default_resource_spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.jupyter_server_app_settings.0.default_resource_spec.0.instance_type", "ml.t3.micro"), + testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), ), }, { @@ -616,3 +676,45 @@ resource "aws_sagemaker_domain" "test" { } `, rName) } + +func testAccAWSSagemakerDomainConfigJupyterServerAppSettings(rName string) string { + return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` +resource "aws_sagemaker_domain" "test" { + domain_name = %[1]q + auth_mode = "IAM" + vpc_id = aws_vpc.test.id + subnet_ids = [aws_subnet.test.id] + + default_user_settings { + execution_role = aws_iam_role.test.arn + + jupyter_server_app_settings { + default_resource_spec { + instance_type = "ml.t3.micro" + } + } + } +} +`, rName) +} + +func testAccAWSSagemakerDomainConfigKernelGatewayAppSettings(rName string) string { + return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` +resource "aws_sagemaker_domain" "test" { + domain_name = %[1]q + auth_mode = "IAM" + vpc_id = aws_vpc.test.id + subnet_ids = [aws_subnet.test.id] + + default_user_settings { + execution_role = aws_iam_role.test.arn + + kernel_gateway_app_settings { + default_resource_spec { + instance_type = "ml.t3.micro" + } + } + } +} +`, rName) +} From 8806d20ddbb5a32aaf3a0c537f81c860988b4f83 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sun, 8 Nov 2020 11:32:26 +0200 Subject: [PATCH 25/33] fmt --- aws/resource_aws_sagemaker_domain_test.go | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 970a0e3d467..eb3e4e612ee 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -644,13 +644,13 @@ resource "aws_sagemaker_domain" "test" { subnet_ids = [aws_subnet.test.id] default_user_settings { - execution_role = aws_iam_role.test.arn - + execution_role = aws_iam_role.test.arn + sharing_settings { notebook_output_option = "Allowed" s3_kms_key_id = aws_kms_key.test.arn s3_output_path = "s3://${aws_s3_bucket.test.bucket}/sharing" - } + } } } `, rName) @@ -665,10 +665,10 @@ resource "aws_sagemaker_domain" "test" { subnet_ids = [aws_subnet.test.id] default_user_settings { - execution_role = aws_iam_role.test.arn - + execution_role = aws_iam_role.test.arn + tensor_board_app_settings { - default_resource_spec { + default_resource_spec { instance_type = "ml.t3.micro" } } @@ -686,10 +686,10 @@ resource "aws_sagemaker_domain" "test" { subnet_ids = [aws_subnet.test.id] default_user_settings { - execution_role = aws_iam_role.test.arn - + execution_role = aws_iam_role.test.arn + jupyter_server_app_settings { - default_resource_spec { + default_resource_spec { instance_type = "ml.t3.micro" } } @@ -707,10 +707,10 @@ resource "aws_sagemaker_domain" "test" { subnet_ids = [aws_subnet.test.id] default_user_settings { - execution_role = aws_iam_role.test.arn - + execution_role = aws_iam_role.test.arn + kernel_gateway_app_settings { - default_resource_spec { + default_resource_spec { instance_type = "ml.t3.micro" } } From 4cf57ad6584df6ee642e5b70ffe88be3a8e31b13 Mon Sep 17 00:00:00 2001 From: Ilia Lazebnik Date: Mon, 9 Nov 2020 17:05:06 +0200 Subject: [PATCH 26/33] Apply suggestions from code review Co-authored-by: Kit Ewbank --- aws/resource_aws_sagemaker_domain.go | 12 ++++++------ website/docs/r/sagemaker_domain.html.markdown | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index e2b4da82367..ce08e69e66c 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -245,7 +245,7 @@ func resourceAwsSagemakerDomainCreate(d *schema.ResourceData, meta interface{}) d.SetId(domainID) if _, err := waiter.DomainInService(conn, d.Id()); err != nil { - return fmt.Errorf("error waiting for sagemaker domain (%s) to create: %w", d.Id(), err) + return fmt.Errorf("error waiting for SageMaker domain (%s) to create: %w", d.Id(), err) } return resourceAwsSagemakerDomainRead(d, meta) @@ -276,17 +276,17 @@ func resourceAwsSagemakerDomainRead(d *schema.ResourceData, meta interface{}) er d.Set("vpc_id", domain.VpcId) if err := d.Set("subnet_ids", flattenStringSet(domain.SubnetIds)); err != nil { - return fmt.Errorf("error setting subnet_ids for sagemaker domain (%s): %w", d.Id(), err) + return fmt.Errorf("error setting subnet_ids for SageMaker domain (%s): %w", d.Id(), err) } if err := d.Set("default_user_settings", flattenSagemakerDomainDefaultUserSettings(domain.DefaultUserSettings)); err != nil { - return fmt.Errorf("error setting default_user_settings for sagemaker domain (%s): %w", d.Id(), err) + return fmt.Errorf("error setting default_user_settings for SageMaker domain (%s): %w", d.Id(), err) } tags, err := keyvaluetags.SagemakerListTags(conn, arn) if err != nil { - return fmt.Errorf("error listing tags for Sagemaker Domain (%s): %w", d.Id(), err) + return fmt.Errorf("error listing tags for SageMaker Domain (%s): %w", d.Id(), err) } if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { @@ -316,7 +316,7 @@ func resourceAwsSagemakerDomainUpdate(d *schema.ResourceData, meta interface{}) o, n := d.GetChange("tags") if err := keyvaluetags.SagemakerUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return fmt.Errorf("error updating Sagemaker Notebook Instance (%s) tags: %s", d.Id(), err) + return fmt.Errorf("error updating SageMaker domain (%s) tags: %w", d.Id(), err) } } @@ -338,7 +338,7 @@ func resourceAwsSagemakerDomainDelete(d *schema.ResourceData, meta interface{}) if _, err := waiter.DomainDeleted(conn, d.Id()); err != nil { if !isAWSErr(err, sagemaker.ErrCodeResourceNotFound, "") { - return fmt.Errorf("error waiting for sagemaker domain (%s) to delete: %w", d.Id(), err) + return fmt.Errorf("error waiting for SageMaker domain (%s) to delete: %w", d.Id(), err) } } diff --git a/website/docs/r/sagemaker_domain.html.markdown b/website/docs/r/sagemaker_domain.html.markdown index 3235315d880..08830fdf22e 100644 --- a/website/docs/r/sagemaker_domain.html.markdown +++ b/website/docs/r/sagemaker_domain.html.markdown @@ -52,7 +52,7 @@ The following arguments are supported: * `auth_mode` - (Required) The mode of authentication that members use to access the domain. Valid values are `IAM` and `SSO`. * `vpc_id` - (Required) The ID of the Amazon Virtual Private Cloud (VPC) that Studio uses for communication. * `subnet_ids` - (Required) The VPC subnets that Studio uses for communication. -* `default_user_settings` - (Required) The default user settings. see [Default User Settings](#default-user-settings) below. +* `default_user_settings` - (Required) The default user settings. See [Default User Settings](#default-user-settings) below. * `app_network_access_type` - (Optional) Specifies the VPC used for non-EFS traffic. The default value is `PublicInternetOnly`. Valid values are `PublicInternetOnly` and `VpcOnly`. * `tags` - (Optional) A map of tags to assign to the resource. @@ -60,10 +60,10 @@ The following arguments are supported: * `execution_role` - (Required) The execution role ARN for the user. * `security_groups` - (Optional) The security groups. -* `sharing_settings` - (Optional) The sharing settings. see [Sharing Settings](#sharing-settings) below. -* `tensor_board_app_settings` - (Optional) The TensorBoard app settings. see [TensorBoard App Settings](#tensorboard-app-settings) below. -* `jupyter_server_app_settings` - (Optional) The kernel gateway app settings. see [Jupyter Server App Settings](#jupyter-server-app-settings) below. -* `kernel_gateway_app_settings` - (Optional) The Jupyter server's app settings. see [Kernel Gateway App Settings](#kernal-gateway-app-settings) below. +* `sharing_settings` - (Optional) The sharing settings. See [Sharing Settings](#sharing-settings) below. +* `tensor_board_app_settings` - (Optional) The TensorBoard app settings. See [TensorBoard App Settings](#tensorboard-app-settings) below. +* `jupyter_server_app_settings` - (Optional) The Jupyter server's app settings. See [Jupyter Server App Settings](#jupyter-server-app-settings) below. +* `kernel_gateway_app_settings` - (Optional) The kernel gateway app settings. See [Kernel Gateway App Settings](#kernal-gateway-app-settings) below. #### Sharing Settings From 201716234b35bcf8bd92dc7b3d6cf04ae7b98f54 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Mon, 9 Nov 2020 21:13:32 +0200 Subject: [PATCH 27/33] add custom image --- aws/resource_aws_sagemaker_domain.go | 69 +++++++++++++++++++ website/docs/r/sagemaker_domain.html.markdown | 7 ++ 2 files changed, 76 insertions(+) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index ce08e69e66c..d48cdd622f2 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -191,6 +191,27 @@ func resourceAwsSagemakerDomain() *schema.Resource { }, }, }, + "custom_image": { + Type: schema.TypeList, + Optional: true, + MaxItems: 30, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "app_image_config_name": { + Type: schema.TypeString, + Required: true, + }, + "image_name": { + Type: schema.TypeString, + Required: true, + }, + "image_version_number": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, }, }, }, @@ -410,6 +431,10 @@ func expandSagemakerDomainKernelGatewayAppSettings(l []interface{}) *sagemaker.K config.DefaultResourceSpec = expandSagemakerDomainDefaultResourceSpec(v) } + if v, ok := m["custom_image"].([]interface{}); ok && len(v) > 0 { + config.CustomImages = expandSagemakerDomainCustomImages(v) + } + return config } @@ -471,6 +496,27 @@ func expandSagemakerDomainShareSettings(l []interface{}) *sagemaker.SharingSetti return config } +func expandSagemakerDomainCustomImages(l []interface{}) []*sagemaker.CustomImage { + images := make([]*sagemaker.CustomImage, 0, len(l)) + + for _, eRaw := range l { + data := eRaw.(map[string]interface{}) + + image := &sagemaker.CustomImage{ + AppImageConfigName: aws.String(data["app_image_config_name"].(string)), + ImageName: aws.String(data["image_name"].(string)), + } + + if v, ok := data["image_version_number"].(int); ok { + image.ImageVersionNumber = aws.Int64(int64(v)) + } + + images = append(images, image) + } + + return images +} + func flattenSagemakerDomainDefaultUserSettings(config *sagemaker.UserSettings) []map[string]interface{} { if config == nil { return []map[string]interface{}{} @@ -562,6 +608,10 @@ func flattenSagemakerDomainKernelGatewayAppSettings(config *sagemaker.KernelGate m["default_resource_spec"] = flattenSagemakerDomainDefaultResourceSpec(config.DefaultResourceSpec) } + if config.CustomImages != nil { + m["custom_image"] = flattenSagemakerDomainCustomImages(config.CustomImages) + } + return []map[string]interface{}{m} } @@ -585,6 +635,25 @@ func flattenSagemakerDomainShareSettings(config *sagemaker.SharingSettings) []ma return []map[string]interface{}{m} } +func flattenSagemakerDomainCustomImages(config []*sagemaker.CustomImage) []map[string]interface{} { + images := make([]map[string]interface{}, 0, len(config)) + + for _, raw := range config { + image := make(map[string]interface{}) + + image["app_image_config_name"] = aws.StringValue(raw.AppImageConfigName) + image["image_name"] = aws.StringValue(raw.ImageName) + + if raw.ImageVersionNumber != nil { + image["image_version_number"] = aws.Int64Value(raw.ImageVersionNumber) + } + + images = append(images, image) + } + + return images +} + func decodeSagemakerDomainID(id string) (string, error) { domainArn, err := arn.Parse(id) if err != nil { diff --git a/website/docs/r/sagemaker_domain.html.markdown b/website/docs/r/sagemaker_domain.html.markdown index 08830fdf22e..03d07d2fe69 100644 --- a/website/docs/r/sagemaker_domain.html.markdown +++ b/website/docs/r/sagemaker_domain.html.markdown @@ -78,6 +78,7 @@ The following arguments are supported: #### Kernel Gateway App Settings * `default_resource_spec` - (Optional) The default instance type and the Amazon Resource Name (ARN) of the SageMaker image created on the instance. see [Default Resource Spec](#default-resource-spec) below. +* `custom_image` - (Optional) A list of custom SageMaker images that are configured to run as a KernelGateway app. see [Custom Image](#custom-image) below. #### Jupyter Server App Settings @@ -88,6 +89,12 @@ The following arguments are supported: * `instance_type` - (Optional) The instance type. * `sagemaker_image_arn` - (Optional) The Amazon Resource Name (ARN) of the SageMaker image created on the instance. +##### Custom Image + +* `app_image_config_name` - (Required) The name of the App Image Config. +* `image_name` - (Required) The name of the Custom Image. +* `image_version_number` - (Optional) The version number of the Custom Image. + ## Attributes Reference The following attributes are exported: From ff805a66216c0af214e64374670b1aa8e4ce3c96 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 9 Jan 2021 10:24:29 +0200 Subject: [PATCH 28/33] add with image test --- aws/resource_aws_sagemaker_domain_test.go | 100 +++++++++++++++++----- aws/resource_aws_sagemaker_image.go | 3 +- 2 files changed, 80 insertions(+), 23 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index eb3e4e612ee..e544c5f2217 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -69,7 +69,7 @@ func testSweepSagemakerDomains(region string) error { } func TestAccAWSSagemakerDomain_basic(t *testing.T) { - var notebook sagemaker.DescribeDomainOutput + var domain sagemaker.DescribeDomainOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_domain.test" @@ -81,7 +81,7 @@ func TestAccAWSSagemakerDomain_basic(t *testing.T) { { Config: testAccAWSSagemakerDomainBasicConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), resource.TestCheckResourceAttr(resourceName, "domain_name", rName), resource.TestCheckResourceAttr(resourceName, "auth_mode", "IAM"), resource.TestCheckResourceAttr(resourceName, "app_network_access_type", "PublicInternetOnly"), @@ -106,7 +106,7 @@ func TestAccAWSSagemakerDomain_basic(t *testing.T) { } func TestAccAWSSagemakerDomain_tags(t *testing.T) { - var notebook sagemaker.DescribeDomainOutput + var domain sagemaker.DescribeDomainOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_domain.test" @@ -118,7 +118,7 @@ func TestAccAWSSagemakerDomain_tags(t *testing.T) { { Config: testAccAWSSagemakerDomainConfigTags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), @@ -131,7 +131,7 @@ func TestAccAWSSagemakerDomain_tags(t *testing.T) { { Config: testAccAWSSagemakerDomainConfigTags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), @@ -140,7 +140,7 @@ func TestAccAWSSagemakerDomain_tags(t *testing.T) { { Config: testAccAWSSagemakerDomainConfigTags1(rName, "key2", "value2"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), @@ -151,7 +151,7 @@ func TestAccAWSSagemakerDomain_tags(t *testing.T) { } func TestAccAWSSagemakerDomain_securityGroup(t *testing.T) { - var notebook sagemaker.DescribeDomainOutput + var domain sagemaker.DescribeDomainOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_domain.test" @@ -163,7 +163,7 @@ func TestAccAWSSagemakerDomain_securityGroup(t *testing.T) { { Config: testAccAWSSagemakerDomainConfigSecurityGroup1(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.security_groups.#", "1"), ), @@ -176,7 +176,7 @@ func TestAccAWSSagemakerDomain_securityGroup(t *testing.T) { { Config: testAccAWSSagemakerDomainConfigSecurityGroup2(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.security_groups.#", "2"), testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), @@ -187,7 +187,7 @@ func TestAccAWSSagemakerDomain_securityGroup(t *testing.T) { } func TestAccAWSSagemakerDomain_sharingSettings(t *testing.T) { - var notebook sagemaker.DescribeDomainOutput + var domain sagemaker.DescribeDomainOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_domain.test" @@ -199,10 +199,10 @@ func TestAccAWSSagemakerDomain_sharingSettings(t *testing.T) { { Config: testAccAWSSagemakerDomainConfigSharingSettings(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.sharing_settings.#", "1"), - resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.sharing_settings.0.notebook_output_option", "Allowed"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.sharing_settings.0.domain_output_option", "Allowed"), resource.TestCheckResourceAttrPair(resourceName, "default_user_settings.0.sharing_settings.0.s3_kms_key_id", "aws_kms_key.test", "arn"), resource.TestCheckResourceAttrSet(resourceName, "default_user_settings.0.sharing_settings.0.s3_output_path"), testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), @@ -218,7 +218,7 @@ func TestAccAWSSagemakerDomain_sharingSettings(t *testing.T) { } func TestAccAWSSagemakerDomain_tensorboardAppSettings(t *testing.T) { - var notebook sagemaker.DescribeDomainOutput + var domain sagemaker.DescribeDomainOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_domain.test" @@ -230,7 +230,7 @@ func TestAccAWSSagemakerDomain_tensorboardAppSettings(t *testing.T) { { Config: testAccAWSSagemakerDomainConfigTensorBoardAppSettings(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.0.default_resource_spec.#", "1"), @@ -247,8 +247,39 @@ func TestAccAWSSagemakerDomain_tensorboardAppSettings(t *testing.T) { }) } +func TestAccAWSSagemakerDomain_tensorboardAppSettingsWithImage(t *testing.T) { + var domain sagemaker.DescribeDomainOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerDomainConfigTensorBoardAppSettingsWithImage(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.0.default_resource_spec.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.tensor_board_app_settings.0.default_resource_spec.0.instance_type", "ml.t3.micro"), + resource.TestCheckResourceAttrPair(resourceName, "default_user_settings.0.tensor_board_app_settings.0.default_resource_spec.0.sagemaker_image_arn", "aws_sagemaker_image.test", "arn"), + testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAWSSagemakerDomain_kernelGatewayAppSettings(t *testing.T) { - var notebook sagemaker.DescribeDomainOutput + var domain sagemaker.DescribeDomainOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_domain.test" @@ -260,7 +291,7 @@ func TestAccAWSSagemakerDomain_kernelGatewayAppSettings(t *testing.T) { { Config: testAccAWSSagemakerDomainConfigKernelGatewayAppSettings(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.kernel_gateway_app_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.kernel_gateway_app_settings.0.default_resource_spec.#", "1"), @@ -278,7 +309,7 @@ func TestAccAWSSagemakerDomain_kernelGatewayAppSettings(t *testing.T) { } func TestAccAWSSagemakerDomain_jupyterServerAppSettings(t *testing.T) { - var notebook sagemaker.DescribeDomainOutput + var domain sagemaker.DescribeDomainOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_domain.test" @@ -290,7 +321,7 @@ func TestAccAWSSagemakerDomain_jupyterServerAppSettings(t *testing.T) { { Config: testAccAWSSagemakerDomainConfigJupyterServerAppSettings(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.jupyter_server_app_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.jupyter_server_app_settings.0.default_resource_spec.#", "1"), @@ -308,7 +339,7 @@ func TestAccAWSSagemakerDomain_jupyterServerAppSettings(t *testing.T) { } func TestAccAWSSagemakerDomain_disappears(t *testing.T) { - var notebook sagemaker.DescribeDomainOutput + var domain sagemaker.DescribeDomainOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_sagemaker_domain.test" @@ -320,7 +351,7 @@ func TestAccAWSSagemakerDomain_disappears(t *testing.T) { { Config: testAccAWSSagemakerDomainBasicConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSagemakerDomainExists(resourceName, ¬ebook), + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), testAccCheckResourceDisappears(testAccProvider, resourceAwsSagemakerDomain(), resourceName), ), @@ -647,7 +678,7 @@ resource "aws_sagemaker_domain" "test" { execution_role = aws_iam_role.test.arn sharing_settings { - notebook_output_option = "Allowed" + domain_output_option = "Allowed" s3_kms_key_id = aws_kms_key.test.arn s3_output_path = "s3://${aws_s3_bucket.test.bucket}/sharing" } @@ -677,6 +708,33 @@ resource "aws_sagemaker_domain" "test" { `, rName) } +func testAccAWSSagemakerDomainConfigTensorBoardAppSettingsWithImage(rName string) string { + return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` +resource "aws_sagemaker_image" "test" { + image_name = %[1]q + role_arn = aws_iam_role.test.arn +} + +resource "aws_sagemaker_domain" "test" { + domain_name = %[1]q + auth_mode = "IAM" + vpc_id = aws_vpc.test.id + subnet_ids = [aws_subnet.test.id] + + default_user_settings { + execution_role = aws_iam_role.test.arn + + tensor_board_app_settings { + default_resource_spec { + instance_type = "ml.t3.micro" + sagemaker_image_arn = aws_sagemaker_image.test.arn + } + } + } +} +`, rName) +} + func testAccAWSSagemakerDomainConfigJupyterServerAppSettings(rName string) string { return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` resource "aws_sagemaker_domain" "test" { diff --git a/aws/resource_aws_sagemaker_image.go b/aws/resource_aws_sagemaker_image.go index 3da9bdb809c..104da90c363 100644 --- a/aws/resource_aws_sagemaker_image.go +++ b/aws/resource_aws_sagemaker_image.go @@ -201,11 +201,10 @@ func resourceAwsSagemakerImageDelete(d *schema.ResourceData, meta interface{}) e } if _, err := waiter.ImageDeleted(conn, d.Id()); err != nil { - if isAWSErr(err, sagemaker.ErrCodeResourceNotFound, "No Image with the name") { + if isAWSErr(err, sagemaker.ErrCodeResourceNotFound, "does not exist") { return nil } return fmt.Errorf("error waiting for SageMaker Image (%s) to delete: %w", d.Id(), err) - } return nil From d0f80f91f1abb43e0b1fb9db1af07e4af36465e9 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sat, 9 Jan 2021 10:27:11 +0200 Subject: [PATCH 29/33] fmt --- aws/resource_aws_sagemaker_domain_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index e544c5f2217..95f7b3d3ab7 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -679,8 +679,8 @@ resource "aws_sagemaker_domain" "test" { sharing_settings { domain_output_option = "Allowed" - s3_kms_key_id = aws_kms_key.test.arn - s3_output_path = "s3://${aws_s3_bucket.test.bucket}/sharing" + s3_kms_key_id = aws_kms_key.test.arn + s3_output_path = "s3://${aws_s3_bucket.test.bucket}/sharing" } } } @@ -726,7 +726,7 @@ resource "aws_sagemaker_domain" "test" { tensor_board_app_settings { default_resource_spec { - instance_type = "ml.t3.micro" + instance_type = "ml.t3.micro" sagemaker_image_arn = aws_sagemaker_image.test.arn } } From 6ae4fb45d448c0a0ebe7e58f046bc08fc1ce1dea Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sun, 10 Jan 2021 00:14:57 +0200 Subject: [PATCH 30/33] fix non existent argument + add kms key support --- aws/resource_aws_sagemaker_domain.go | 13 ++++- aws/resource_aws_sagemaker_domain_test.go | 55 ++++++++++++++++++- website/docs/r/sagemaker_domain.html.markdown | 1 + 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index d48cdd622f2..b3f55cb7d37 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -58,6 +58,13 @@ func resourceAwsSagemakerDomain() *schema.Resource { MaxItems: 16, Elem: &schema.Schema{Type: schema.TypeString}, }, + "kms_key_id": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + Computed: true, + ValidateFunc: validateArn, + }, "app_network_access_type": { Type: schema.TypeString, ForceNew: true, @@ -251,6 +258,10 @@ func resourceAwsSagemakerDomainCreate(d *schema.ResourceData, meta interface{}) input.Tags = keyvaluetags.New(v.(map[string]interface{})).IgnoreAws().SagemakerTags() } + if v, ok := d.GetOk("kms_key_id"); ok { + input.KmsKeyId = aws.String(v.(string)) + } + log.Printf("[DEBUG] sagemaker domain create config: %#v", *input) output, err := conn.CreateDomain(input) if err != nil { @@ -294,7 +305,7 @@ func resourceAwsSagemakerDomainRead(d *schema.ResourceData, meta interface{}) er d.Set("home_efs_file_system_id", domain.HomeEfsFileSystemId) d.Set("single_sign_on_managed_application_instance_id", domain.SingleSignOnManagedApplicationInstanceId) d.Set("url", domain.Url) - d.Set("vpc_id", domain.VpcId) + d.Set("kms_key_id", domain.KmsKeyId) if err := d.Set("subnet_ids", flattenStringSet(domain.SubnetIds)); err != nil { return fmt.Errorf("error setting subnet_ids for SageMaker domain (%s): %w", d.Id(), err) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 95f7b3d3ab7..a3395c8e717 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -93,6 +93,34 @@ func TestAccAWSSagemakerDomain_basic(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "vpc_id", "aws_vpc.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "url"), resource.TestCheckResourceAttrSet(resourceName, "home_efs_file_system_id"), + resource.TestCheckResourceAttrSet(resourceName, "kms_key_id"), + testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSSagemakerDomain_kms(t *testing.T) { + var domain sagemaker.DescribeDomainOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerDomainKMSConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerDomainExists(resourceName, &domain), + resource.TestCheckResourceAttrPair(resourceName, "kms_key_id", "aws_kms_key.test", "arn"), testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), ), }, @@ -572,6 +600,27 @@ resource "aws_sagemaker_domain" "test" { `, rName) } +func testAccAWSSagemakerDomainKMSConfig(rName string) string { + return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` +resource "aws_kms_key" "test" { + description = "Terraform acc test" + deletion_window_in_days = 7 +} + +resource "aws_sagemaker_domain" "test" { + domain_name = %[1]q + auth_mode = "IAM" + vpc_id = aws_vpc.test.id + subnet_ids = [aws_subnet.test.id] + kms_key_id = aws_kms_key.test.arn + + default_user_settings { + execution_role = aws_iam_role.test.arn + } +} +`, rName) +} + func testAccAWSSagemakerDomainConfigSecurityGroup1(rName string) string { return testAccAWSSagemakerDomainConfigBase(rName) + fmt.Sprintf(` resource "aws_security_group" "test" { @@ -678,9 +727,9 @@ resource "aws_sagemaker_domain" "test" { execution_role = aws_iam_role.test.arn sharing_settings { - domain_output_option = "Allowed" - s3_kms_key_id = aws_kms_key.test.arn - s3_output_path = "s3://${aws_s3_bucket.test.bucket}/sharing" + notebook_output_option = "Allowed" + s3_kms_key_id = aws_kms_key.test.arn + s3_output_path = "s3://${aws_s3_bucket.test.bucket}/sharing" } } } diff --git a/website/docs/r/sagemaker_domain.html.markdown b/website/docs/r/sagemaker_domain.html.markdown index 03d07d2fe69..4573e9bb62d 100644 --- a/website/docs/r/sagemaker_domain.html.markdown +++ b/website/docs/r/sagemaker_domain.html.markdown @@ -53,6 +53,7 @@ The following arguments are supported: * `vpc_id` - (Required) The ID of the Amazon Virtual Private Cloud (VPC) that Studio uses for communication. * `subnet_ids` - (Required) The VPC subnets that Studio uses for communication. * `default_user_settings` - (Required) The default user settings. See [Default User Settings](#default-user-settings) below. +* `kms_key_id` - (Optional) The AWS KMS customer managed CMK used to encrypt the EFS volume attached to the domain. * `app_network_access_type` - (Optional) Specifies the VPC used for non-EFS traffic. The default value is `PublicInternetOnly`. Valid values are `PublicInternetOnly` and `VpcOnly`. * `tags` - (Optional) A map of tags to assign to the resource. From abdcf868430492d3b4cbeb22296503cf2c83e696 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sun, 10 Jan 2021 00:19:47 +0200 Subject: [PATCH 31/33] restore vpc id --- aws/resource_aws_sagemaker_domain.go | 1 + 1 file changed, 1 insertion(+) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index b3f55cb7d37..d35b27f74d1 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -305,6 +305,7 @@ func resourceAwsSagemakerDomainRead(d *schema.ResourceData, meta interface{}) er d.Set("home_efs_file_system_id", domain.HomeEfsFileSystemId) d.Set("single_sign_on_managed_application_instance_id", domain.SingleSignOnManagedApplicationInstanceId) d.Set("url", domain.Url) + d.Set("vpc_id", domain.VpcId) d.Set("kms_key_id", domain.KmsKeyId) if err := d.Set("subnet_ids", flattenStringSet(domain.SubnetIds)); err != nil { From 54a577afacd90b58b83f0648c9e385975b27a014 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sun, 10 Jan 2021 00:44:19 +0200 Subject: [PATCH 32/33] missed a spot --- aws/resource_aws_sagemaker_domain_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index a3395c8e717..60d02056fae 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -230,7 +230,7 @@ func TestAccAWSSagemakerDomain_sharingSettings(t *testing.T) { testAccCheckAWSSagemakerDomainExists(resourceName, &domain), resource.TestCheckResourceAttr(resourceName, "default_user_settings.#", "1"), resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.sharing_settings.#", "1"), - resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.sharing_settings.0.domain_output_option", "Allowed"), + resource.TestCheckResourceAttr(resourceName, "default_user_settings.0.sharing_settings.0.notebook_output_option", "Allowed"), resource.TestCheckResourceAttrPair(resourceName, "default_user_settings.0.sharing_settings.0.s3_kms_key_id", "aws_kms_key.test", "arn"), resource.TestCheckResourceAttrSet(resourceName, "default_user_settings.0.sharing_settings.0.s3_output_path"), testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), From f2aabe646f4995efd9e607063293bf699d2c9b20 Mon Sep 17 00:00:00 2001 From: drfaust92 Date: Sun, 10 Jan 2021 01:03:17 +0200 Subject: [PATCH 33/33] remove computed from kms --- aws/resource_aws_sagemaker_domain.go | 1 - aws/resource_aws_sagemaker_domain_test.go | 1 - 2 files changed, 2 deletions(-) diff --git a/aws/resource_aws_sagemaker_domain.go b/aws/resource_aws_sagemaker_domain.go index d35b27f74d1..baea64f0eed 100644 --- a/aws/resource_aws_sagemaker_domain.go +++ b/aws/resource_aws_sagemaker_domain.go @@ -62,7 +62,6 @@ func resourceAwsSagemakerDomain() *schema.Resource { Type: schema.TypeString, ForceNew: true, Optional: true, - Computed: true, ValidateFunc: validateArn, }, "app_network_access_type": { diff --git a/aws/resource_aws_sagemaker_domain_test.go b/aws/resource_aws_sagemaker_domain_test.go index 60d02056fae..a1b7c62f819 100644 --- a/aws/resource_aws_sagemaker_domain_test.go +++ b/aws/resource_aws_sagemaker_domain_test.go @@ -93,7 +93,6 @@ func TestAccAWSSagemakerDomain_basic(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "vpc_id", "aws_vpc.test", "id"), resource.TestCheckResourceAttrSet(resourceName, "url"), resource.TestCheckResourceAttrSet(resourceName, "home_efs_file_system_id"), - resource.TestCheckResourceAttrSet(resourceName, "kms_key_id"), testAccCheckAWSSagemakerDomainDeleteImplicitResources(resourceName), ), },