diff --git a/.changelog/20065.txt b/.changelog/20065.txt new file mode 100644 index 00000000000..33b6be09f6b --- /dev/null +++ b/.changelog/20065.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_sagemaker_workforce +``` + +```release-note:enhancement +resource/aws_cognito_user_pool_client: Set `callback_urls` and `logout_urls` as computed. +``` \ No newline at end of file diff --git a/aws/internal/service/sagemaker/finder/finder.go b/aws/internal/service/sagemaker/finder/finder.go index 03d5a14587f..9b3badf6934 100644 --- a/aws/internal/service/sagemaker/finder/finder.go +++ b/aws/internal/service/sagemaker/finder/finder.go @@ -3,6 +3,8 @@ package finder import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/sagemaker" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) // CodeRepositoryByName returns the code repository corresponding to the specified name. @@ -179,3 +181,31 @@ func AppByName(conn *sagemaker.SageMaker, domainID, userProfileName, appType, ap return output, nil } + +func WorkforceByName(conn *sagemaker.SageMaker, name string) (*sagemaker.Workforce, error) { + input := &sagemaker.DescribeWorkforceInput{ + WorkforceName: aws.String(name), + } + + output, err := conn.DescribeWorkforce(input) + + if tfawserr.ErrMessageContains(err, "ValidationException", "No workforce") { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Workforce == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output.Workforce, nil +} diff --git a/aws/provider.go b/aws/provider.go index 1a20232f300..0f56371637b 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -1000,6 +1000,7 @@ func Provider() *schema.Provider { "aws_sagemaker_notebook_instance_lifecycle_configuration": resourceAwsSagemakerNotebookInstanceLifeCycleConfiguration(), "aws_sagemaker_notebook_instance": resourceAwsSagemakerNotebookInstance(), "aws_sagemaker_user_profile": resourceAwsSagemakerUserProfile(), + "aws_sagemaker_workforce": resourceAwsSagemakerWorkforce(), "aws_schemas_discoverer": resourceAwsSchemasDiscoverer(), "aws_schemas_registry": resourceAwsSchemasRegistry(), "aws_schemas_schema": resourceAwsSchemasSchema(), diff --git a/aws/resource_aws_cognito_user_pool_client.go b/aws/resource_aws_cognito_user_pool_client.go index 1a6bda9f636..e2c07a52849 100644 --- a/aws/resource_aws_cognito_user_pool_client.go +++ b/aws/resource_aws_cognito_user_pool_client.go @@ -97,6 +97,7 @@ func resourceAwsCognitoUserPoolClient() *schema.Resource { "callback_urls": { Type: schema.TypeSet, Optional: true, + Computed: true, MaxItems: 100, Elem: &schema.Schema{ Type: schema.TypeString, @@ -142,6 +143,7 @@ func resourceAwsCognitoUserPoolClient() *schema.Resource { "logout_urls": { Type: schema.TypeSet, Optional: true, + Computed: true, MaxItems: 100, Elem: &schema.Schema{ Type: schema.TypeString, diff --git a/aws/resource_aws_sagemaker_workforce.go b/aws/resource_aws_sagemaker_workforce.go new file mode 100644 index 00000000000..44297042145 --- /dev/null +++ b/aws/resource_aws_sagemaker_workforce.go @@ -0,0 +1,357 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/sagemaker" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "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/tfresource" +) + +func resourceAwsSagemakerWorkforce() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSagemakerWorkforceCreate, + Read: resourceAwsSagemakerWorkforceRead, + Update: resourceAwsSagemakerWorkforceUpdate, + Delete: resourceAwsSagemakerWorkforceDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "cognito_config": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + ExactlyOneOf: []string{"oidc_config", "cognito_config"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Required: true, + }, + "user_pool": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "oidc_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{"oidc_config", "cognito_config"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "authorization_endpoint": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 500), + validation.IsURLWithHTTPS, + ), + }, + "client_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + "client_secret": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(1, 1024), + }, + "issuer": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 500), + validation.IsURLWithHTTPS, + )}, + "jwks_uri": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 500), + validation.IsURLWithHTTPS, + )}, + "logout_endpoint": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 500), + validation.IsURLWithHTTPS, + )}, + "token_endpoint": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 500), + validation.IsURLWithHTTPS, + )}, + "user_info_endpoint": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 500), + validation.IsURLWithHTTPS, + ), + }, + }, + }, + }, + "source_ip_config": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cidrs": { + Type: schema.TypeSet, + Required: true, + MaxItems: 10, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.IsCIDR, + }, + }, + }, + }, + }, + "subdomain": { + Type: schema.TypeString, + Computed: true, + }, + "workforce_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)."), + ), + }, + }, + } +} + +func resourceAwsSagemakerWorkforceCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + name := d.Get("workforce_name").(string) + input := &sagemaker.CreateWorkforceInput{ + WorkforceName: aws.String(name), + } + + if v, ok := d.GetOk("cognito_config"); ok { + input.CognitoConfig = expandSagemakerWorkforceCognitoConfig(v.([]interface{})) + } + + if v, ok := d.GetOk("oidc_config"); ok { + input.OidcConfig = expandSagemakerWorkforceOidcConfig(v.([]interface{})) + } + + if v, ok := d.GetOk("source_ip_config"); ok { + input.SourceIpConfig = expandSagemakerWorkforceSourceIpConfig(v.([]interface{})) + } + + log.Printf("[DEBUG] Creating SageMaker Workforce: %s", input) + _, err := conn.CreateWorkforce(input) + + if err != nil { + return fmt.Errorf("error creating SageMaker Workforce (%s): %w", name, err) + } + + d.SetId(name) + + return resourceAwsSagemakerWorkforceRead(d, meta) +} + +func resourceAwsSagemakerWorkforceRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + workforce, err := finder.WorkforceByName(conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SageMaker Workforce (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading SageMaker Workforce (%s): %w", d.Id(), err) + + } + + d.Set("arn", workforce.WorkforceArn) + d.Set("subdomain", workforce.SubDomain) + d.Set("workforce_name", workforce.WorkforceName) + + if err := d.Set("cognito_config", flattenSagemakerWorkforceCognitoConfig(workforce.CognitoConfig)); err != nil { + return fmt.Errorf("error setting cognito_config : %w", err) + } + + if workforce.OidcConfig != nil { + if err := d.Set("oidc_config", flattenSagemakerWorkforceOidcConfig(workforce.OidcConfig, d.Get("oidc_config.0.client_secret").(string))); err != nil { + return fmt.Errorf("error setting oidc_config: %w", err) + } + } + + if err := d.Set("source_ip_config", flattenSagemakerWorkforceSourceIpConfig(workforce.SourceIpConfig)); err != nil { + return fmt.Errorf("error setting source_ip_config: %w", err) + } + + return nil +} + +func resourceAwsSagemakerWorkforceUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + input := &sagemaker.UpdateWorkforceInput{ + WorkforceName: aws.String(d.Id()), + } + + if d.HasChange("source_ip_config") { + input.SourceIpConfig = expandSagemakerWorkforceSourceIpConfig(d.Get("source_ip_config").([]interface{})) + } + + if d.HasChange("oidc_config") { + input.OidcConfig = expandSagemakerWorkforceOidcConfig(d.Get("oidc_config").([]interface{})) + } + + log.Printf("[DEBUG] Updating SageMaker Workforce: %s", input) + _, err := conn.UpdateWorkforce(input) + + if err != nil { + return fmt.Errorf("error updating SageMaker Workforce (%s): %w", d.Id(), err) + } + + return resourceAwsSagemakerWorkforceRead(d, meta) +} + +func resourceAwsSagemakerWorkforceDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).sagemakerconn + + log.Printf("[DEBUG] Deleting SageMaker Workforce: %s", d.Id()) + _, err := conn.DeleteWorkforce(&sagemaker.DeleteWorkforceInput{ + WorkforceName: aws.String(d.Id()), + }) + + if tfawserr.ErrMessageContains(err, "ValidationException", "No workforce") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting SageMaker Workforce (%s): %w", d.Id(), err) + } + + return nil +} + +func expandSagemakerWorkforceSourceIpConfig(l []interface{}) *sagemaker.SourceIpConfig { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.SourceIpConfig{ + Cidrs: expandStringSet(m["cidrs"].(*schema.Set)), + } + + return config +} + +func flattenSagemakerWorkforceSourceIpConfig(config *sagemaker.SourceIpConfig) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{ + "cidrs": flattenStringSet(config.Cidrs), + } + + return []map[string]interface{}{m} +} + +func expandSagemakerWorkforceCognitoConfig(l []interface{}) *sagemaker.CognitoConfig { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.CognitoConfig{ + ClientId: aws.String(m["client_id"].(string)), + UserPool: aws.String(m["user_pool"].(string)), + } + + return config +} + +func flattenSagemakerWorkforceCognitoConfig(config *sagemaker.CognitoConfig) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{ + "client_id": aws.StringValue(config.ClientId), + "user_pool": aws.StringValue(config.UserPool), + } + + return []map[string]interface{}{m} +} + +func expandSagemakerWorkforceOidcConfig(l []interface{}) *sagemaker.OidcConfig { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + config := &sagemaker.OidcConfig{ + AuthorizationEndpoint: aws.String(m["authorization_endpoint"].(string)), + ClientId: aws.String(m["client_id"].(string)), + ClientSecret: aws.String(m["client_secret"].(string)), + Issuer: aws.String(m["issuer"].(string)), + JwksUri: aws.String(m["jwks_uri"].(string)), + LogoutEndpoint: aws.String(m["logout_endpoint"].(string)), + TokenEndpoint: aws.String(m["token_endpoint"].(string)), + UserInfoEndpoint: aws.String(m["user_info_endpoint"].(string)), + } + + return config +} + +func flattenSagemakerWorkforceOidcConfig(config *sagemaker.OidcConfigForResponse, clientSecret string) []map[string]interface{} { + if config == nil { + return []map[string]interface{}{} + } + + m := map[string]interface{}{ + "authorization_endpoint": aws.StringValue(config.AuthorizationEndpoint), + "client_id": aws.StringValue(config.ClientId), + "client_secret": clientSecret, + "issuer": aws.StringValue(config.Issuer), + "jwks_uri": aws.StringValue(config.JwksUri), + "logout_endpoint": aws.StringValue(config.LogoutEndpoint), + "token_endpoint": aws.StringValue(config.TokenEndpoint), + "user_info_endpoint": aws.StringValue(config.UserInfoEndpoint), + } + + return []map[string]interface{}{m} +} diff --git a/aws/resource_aws_sagemaker_workforce_test.go b/aws/resource_aws_sagemaker_workforce_test.go new file mode 100644 index 00000000000..b280924f68c --- /dev/null +++ b/aws/resource_aws_sagemaker_workforce_test.go @@ -0,0 +1,364 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/sagemaker" + "github.com/hashicorp/go-multierror" + "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" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func init() { + resource.AddTestSweepers("aws_sagemaker_workforce", &resource.Sweeper{ + Name: "aws_sagemaker_workforce", + F: testSweepSagemakerWorkforces, + }) +} + +func testSweepSagemakerWorkforces(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.(*AWSClient).sagemakerconn + var sweeperErrs *multierror.Error + + err = conn.ListWorkforcesPages(&sagemaker.ListWorkforcesInput{}, func(page *sagemaker.ListWorkforcesOutput, lastPage bool) bool { + for _, workforce := range page.Workforces { + + r := resourceAwsSagemakerWorkforce() + d := r.Data(nil) + d.SetId(aws.StringValue(workforce.WorkforceName)) + err := r.Delete(d, client) + if err != nil { + log.Printf("[ERROR] %s", err) + sweeperErrs = multierror.Append(sweeperErrs, err) + continue + } + } + + return !lastPage + }) + + if testSweepSkipSweepError(err) { + log.Printf("[WARN] Skipping SageMaker workforce sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error retrieving Sagemaker Workforces: %w", err)) + } + + return sweeperErrs.ErrorOrNil() +} + +func TestAccAWSSagemakerWorkforce_cognitoConfig(t *testing.T) { + var workforce sagemaker.Workforce + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_workforce.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, sagemaker.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerWorkforceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerWorkforceCognitoConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerWorkforceExists(resourceName, &workforce), + resource.TestCheckResourceAttr(resourceName, "workforce_name", rName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "sagemaker", regexp.MustCompile(`workforce/.+`)), + resource.TestCheckResourceAttr(resourceName, "cognito_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "cognito_config.0.client_id", "aws_cognito_user_pool_client.test", "id"), + resource.TestCheckResourceAttrPair(resourceName, "cognito_config.0.user_pool", "aws_cognito_user_pool.test", "id"), + resource.TestCheckResourceAttr(resourceName, "oidc_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "source_ip_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "source_ip_config.0.cidrs.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "subdomain"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSSagemakerWorkforce_oidcConfig(t *testing.T) { + var workforce sagemaker.Workforce + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_workforce.test" + endpoint1 := "https://example.com" + endpoint2 := "https://test.example.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, sagemaker.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerWorkforceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerWorkforceOidcConfig(rName, endpoint1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerWorkforceExists(resourceName, &workforce), + resource.TestCheckResourceAttr(resourceName, "workforce_name", rName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "sagemaker", regexp.MustCompile(`workforce/.+`)), + resource.TestCheckResourceAttr(resourceName, "cognito_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "oidc_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.authorization_endpoint", endpoint1), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.client_id", rName), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.client_secret", rName), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.issuer", endpoint1), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.jwks_uri", endpoint1), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.logout_endpoint", endpoint1), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.token_endpoint", endpoint1), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.user_info_endpoint", endpoint1), + resource.TestCheckResourceAttr(resourceName, "source_ip_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "source_ip_config.0.cidrs.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "subdomain"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"oidc_config.0.client_secret"}, + }, + { + Config: testAccAWSSagemakerWorkforceOidcConfig(rName, endpoint2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerWorkforceExists(resourceName, &workforce), + resource.TestCheckResourceAttr(resourceName, "workforce_name", rName), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "sagemaker", regexp.MustCompile(`workforce/.+`)), + resource.TestCheckResourceAttr(resourceName, "cognito_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "oidc_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.authorization_endpoint", endpoint2), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.client_id", rName), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.client_secret", rName), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.issuer", endpoint2), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.jwks_uri", endpoint2), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.logout_endpoint", endpoint2), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.token_endpoint", endpoint2), + resource.TestCheckResourceAttr(resourceName, "oidc_config.0.user_info_endpoint", endpoint2), + resource.TestCheckResourceAttr(resourceName, "source_ip_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "source_ip_config.0.cidrs.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "subdomain"), + ), + }, + }, + }) +} +func TestAccAWSSagemakerWorkforce_sourceIpConfig(t *testing.T) { + var workforce sagemaker.Workforce + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_workforce.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, sagemaker.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerWorkforceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerWorkforceSourceIpConfig1(rName, "1.1.1.1/32"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerWorkforceExists(resourceName, &workforce), + resource.TestCheckResourceAttr(resourceName, "source_ip_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "source_ip_config.0.cidrs.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "source_ip_config.0.cidrs.*", "1.1.1.1/32"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSSagemakerWorkforceSourceIpConfig2(rName, "2.2.2.2/32", "3.3.3.3/32"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerWorkforceExists(resourceName, &workforce), + resource.TestCheckResourceAttr(resourceName, "source_ip_config.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "source_ip_config.0.cidrs.*", "2.2.2.2/32"), + resource.TestCheckTypeSetElemAttr(resourceName, "source_ip_config.0.cidrs.*", "3.3.3.3/32"), + ), + }, + { + Config: testAccAWSSagemakerWorkforceSourceIpConfig1(rName, "2.2.2.2/32"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerWorkforceExists(resourceName, &workforce), + resource.TestCheckResourceAttr(resourceName, "source_ip_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "source_ip_config.0.cidrs.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "source_ip_config.0.cidrs.*", "2.2.2.2/32"), + ), + }, + }, + }) +} + +func TestAccAWSSagemakerWorkforce_disappears(t *testing.T) { + var workforce sagemaker.Workforce + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_sagemaker_workforce.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, sagemaker.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSagemakerWorkforceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSagemakerWorkforceCognitoConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSagemakerWorkforceExists(resourceName, &workforce), + testAccCheckResourceDisappears(testAccProvider, resourceAwsSagemakerWorkforce(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAWSSagemakerWorkforceDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).sagemakerconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_sagemaker_workforce" { + continue + } + + _, err := finder.WorkforceByName(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("SageMaker Workforce %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckAWSSagemakerWorkforceExists(n string, workforce *sagemaker.Workforce) 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 SageMaker Workforce ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).sagemakerconn + + output, err := finder.WorkforceByName(conn, rs.Primary.ID) + + if err != nil { + return err + } + + *workforce = *output + + return nil + } +} + +func testAccAWSSagemakerWorkforceBaseConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_cognito_user_pool" "test" { + name = %[1]q +} + +resource "aws_cognito_user_pool_client" "test" { + name = %[1]q + generate_secret = true + user_pool_id = aws_cognito_user_pool.test.id +} + +resource "aws_cognito_user_pool_domain" "test" { + domain = %[1]q + user_pool_id = aws_cognito_user_pool.test.id +} +`, rName) +} + +func testAccAWSSagemakerWorkforceCognitoConfig(rName string) string { + return testAccAWSSagemakerWorkforceBaseConfig(rName) + fmt.Sprintf(` +resource "aws_sagemaker_workforce" "test" { + workforce_name = %[1]q + + cognito_config { + client_id = aws_cognito_user_pool_client.test.id + user_pool = aws_cognito_user_pool_domain.test.user_pool_id + } +} +`, rName) +} + +func testAccAWSSagemakerWorkforceSourceIpConfig1(rName, cidr1 string) string { + return testAccAWSSagemakerWorkforceBaseConfig(rName) + fmt.Sprintf(` +resource "aws_sagemaker_workforce" "test" { + workforce_name = %[1]q + + cognito_config { + client_id = aws_cognito_user_pool_client.test.id + user_pool = aws_cognito_user_pool_domain.test.user_pool_id + } + + source_ip_config { + cidrs = [%[2]q] + } +} +`, rName, cidr1) +} + +func testAccAWSSagemakerWorkforceSourceIpConfig2(rName, cidr1, cidr2 string) string { + return testAccAWSSagemakerWorkforceBaseConfig(rName) + fmt.Sprintf(` +resource "aws_sagemaker_workforce" "test" { + workforce_name = %[1]q + + cognito_config { + client_id = aws_cognito_user_pool_client.test.id + user_pool = aws_cognito_user_pool_domain.test.user_pool_id + } + + source_ip_config { + cidrs = [%[2]q, %[3]q] + } +} +`, rName, cidr1, cidr2) +} + +func testAccAWSSagemakerWorkforceOidcConfig(rName, endpoint string) string { + return testAccAWSSagemakerWorkforceBaseConfig(rName) + fmt.Sprintf(` +resource "aws_sagemaker_workforce" "test" { + workforce_name = %[1]q + + oidc_config { + authorization_endpoint = %[2]q + client_id = %[1]q + client_secret = %[1]q + issuer = %[2]q + jwks_uri = %[2]q + logout_endpoint = %[2]q + token_endpoint = %[2]q + user_info_endpoint = %[2]q + } +} +`, rName, endpoint) +} diff --git a/website/docs/r/sagemaker_workforce.html.markdown b/website/docs/r/sagemaker_workforce.html.markdown new file mode 100644 index 00000000000..1bb1b10c6b3 --- /dev/null +++ b/website/docs/r/sagemaker_workforce.html.markdown @@ -0,0 +1,105 @@ +--- +subcategory: "Sagemaker" +layout: "aws" +page_title: "AWS: aws_sagemaker_workforce" +description: |- + Provides a Sagemaker Workforce resource. +--- + +# Resource: aws_sagemaker_workforce + +Provides a Sagemaker Workforce resource. + +## Example Usage + +### Cognito Usage + +```terraform +resource "aws_sagemaker_workforce" "example" { + workforce_name = "example" + + cognito_config { + client_id = aws_cognito_user_pool_client.example.id + user_pool = aws_cognito_user_pool_domain.example.user_pool_id + } +} + +resource "aws_cognito_user_pool" "example" { + name = "example" +} + +resource "aws_cognito_user_pool_client" "example" { + name = "example" + generate_secret = true + user_pool_id = aws_cognito_user_pool.example.id +} + +resource "aws_cognito_user_pool_domain" "example" { + domain = "example" + user_pool_id = aws_cognito_user_pool.example.id +} +``` + +### Oidc Usage + +```terraform +resource "aws_sagemaker_workforce" "example" { + workforce_name = "example" + + oidc_config { + authorization_endpoint = "https://example.com" + client_id = "example" + client_secret = "example" + issuer = "https://example.com" + jwks_uri = "https://example.com" + logout_endpoint = "https://example.com" + token_endpoint = "https://example.com" + user_info_endpoint = "https://example.com" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `workforce_name` - (Required) The name of the Workforce (must be unique). +* `cognito_config` - (Required) Use this parameter to configure an Amazon Cognito private workforce. A single Cognito workforce is created using and corresponds to a single Amazon Cognito user pool. Conflicts with `oidc_config`. see [Cognito Config](#cognito-config) details below. +* `oidc_config` - (Required) Use this parameter to configure a private workforce using your own OIDC Identity Provider. Conflicts with `cognito_config`. see [OIDC Config](#oidc-config) details below. +* `source_ip_config` - (Required) A list of IP address ranges Used to create an allow list of IP addresses for a private workforce. By default, a workforce isn't restricted to specific IP addresses. see [Source Ip Config](#source-ip-config) details below. + +### Cognito Config + +* `client_id` - (Required) The client ID for your Amazon Cognito user pool. +* `user_pool` - (Required) The id for your Amazon Cognito user pool. + +### Oidc Config + +* `authorization_endpoint` - (Required) The OIDC IdP authorization endpoint used to configure your private workforce. +* `client_id` - (Required) The OIDC IdP client ID used to configure your private workforce. +* `client_secret` - (Required) The OIDC IdP client secret used to configure your private workforce. +* `issuer` - (Required) The OIDC IdP issuer used to configure your private workforce. +* `jwks_uri` - (Required) The OIDC IdP JSON Web Key Set (Jwks) URI used to configure your private workforce. +* `logout_endpoint` - (Required) The OIDC IdP logout endpoint used to configure your private workforce. +* `token_endpoint` - (Required) The OIDC IdP token endpoint used to configure your private workforce. +* `user_info_endpoint` - (Required) The OIDC IdP user information endpoint used to configure your private workforce. + +### Source Ip Config + +* `cidrs` - (Required) A list of up to 10 CIDR values. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The Amazon Resource Name (ARN) assigned by AWS to this Workforce. +* `id` - The name of the Workforce. +* `subdomain` - The subdomain for your OIDC Identity Provider. + +## Import + +Sagemaker Workforces can be imported using the `workforce_name`, e.g. + +``` +$ terraform import aws_sagemaker_workforce.example example +```