diff --git a/okta/provider.go b/okta/provider.go index c3b7e9487..a7867a276 100644 --- a/okta/provider.go +++ b/okta/provider.go @@ -505,3 +505,7 @@ func resourceOIEOnlyFeatureError(name string) diag.Diagnostics { func datasourceOIEOnlyFeatureError(name string) diag.Diagnostics { return oieOnlyFeatureError("data-sources", name) } + +func resourceFuncNoOp(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics { + return nil +} diff --git a/okta/resource_okta_admin_role_targets.go b/okta/resource_okta_admin_role_targets.go index 68da7f520..9067c57df 100644 --- a/okta/resource_okta_admin_role_targets.go +++ b/okta/resource_okta_admin_role_targets.go @@ -216,6 +216,9 @@ func removeAllTargets(ctx context.Context, d *schema.ResourceData, m interface{} d.SetId("") return "", fmt.Errorf("failed to assign '%s' role back to user: %v", d.Get("role_type").(string), err) } + if role == nil { + return "", errors.New("role was nil") + } return role.Id, nil } diff --git a/okta/resource_okta_app_oauth_post_logout_redirect_uri.go b/okta/resource_okta_app_oauth_post_logout_redirect_uri.go index d96c77ef8..79af905d3 100644 --- a/okta/resource_okta_app_oauth_post_logout_redirect_uri.go +++ b/okta/resource_okta_app_oauth_post_logout_redirect_uri.go @@ -12,7 +12,7 @@ import ( func resourceAppOAuthPostLogoutRedirectURI() *schema.Resource { return &schema.Resource{ CreateContext: resourceAppOAuthPostLogoutRedirectURICreate, - ReadContext: resourceAppOAuthPostLogoutRedirectURIRead, + ReadContext: resourceFuncNoOp, UpdateContext: resourceAppOAuthPostLogoutRedirectURIUpdate, DeleteContext: resourceAppOAuthPostLogoutRedirectURIDelete, // The id for this is the uri @@ -39,12 +39,7 @@ func resourceAppOAuthPostLogoutRedirectURICreate(ctx context.Context, d *schema. return diag.Errorf("failed to create post logout redirect URI: %v", err) } d.SetId(d.Get("uri").(string)) - return resourceAppOAuthPostLogoutRedirectURIRead(ctx, d, m) -} - -// read does nothing due to the nature of this resource -func resourceAppOAuthPostLogoutRedirectURIRead(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics { - return nil + return resourceFuncNoOp(ctx, d, m) } func resourceAppOAuthPostLogoutRedirectURIUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { @@ -53,7 +48,7 @@ func resourceAppOAuthPostLogoutRedirectURIUpdate(ctx context.Context, d *schema. } // Normally not advisable, but ForceNew generated unnecessary calls d.SetId(d.Get("uri").(string)) - return resourceAppOAuthPostLogoutRedirectURIRead(ctx, d, m) + return resourceFuncNoOp(ctx, d, m) } func resourceAppOAuthPostLogoutRedirectURIDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { diff --git a/okta/resource_okta_app_oauth_redirect_uri.go b/okta/resource_okta_app_oauth_redirect_uri.go index d6e3f0d8b..2a7baf985 100644 --- a/okta/resource_okta_app_oauth_redirect_uri.go +++ b/okta/resource_okta_app_oauth_redirect_uri.go @@ -12,7 +12,7 @@ import ( func resourceAppOAuthRedirectURI() *schema.Resource { return &schema.Resource{ CreateContext: resourceAppOAuthRedirectURICreate, - ReadContext: resourceAppOAuthRedirectURIRead, + ReadContext: resourceFuncNoOp, UpdateContext: resourceAppOAuthRedirectURIUpdate, DeleteContext: resourceAppOAuthRedirectURIDelete, // The id for this is the uri @@ -39,12 +39,7 @@ func resourceAppOAuthRedirectURICreate(ctx context.Context, d *schema.ResourceDa return diag.Errorf("failed to create redirect URI: %v", err) } d.SetId(d.Get("uri").(string)) - return resourceAppOAuthRedirectURIRead(ctx, d, m) -} - -// read does nothing due to the nature of this resource -func resourceAppOAuthRedirectURIRead(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics { - return nil + return resourceFuncNoOp(ctx, d, m) } func resourceAppOAuthRedirectURIUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { @@ -53,7 +48,7 @@ func resourceAppOAuthRedirectURIUpdate(ctx context.Context, d *schema.ResourceDa } // Normally not advisable, but ForceNew generated unnecessary calls d.SetId(d.Get("uri").(string)) - return resourceAppOAuthRedirectURIRead(ctx, d, m) + return resourceFuncNoOp(ctx, d, m) } func resourceAppOAuthRedirectURIDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { diff --git a/okta/resource_okta_app_saml_app_settings.go b/okta/resource_okta_app_saml_app_settings.go index 36c1647b9..91b774ab2 100644 --- a/okta/resource_okta_app_saml_app_settings.go +++ b/okta/resource_okta_app_saml_app_settings.go @@ -15,7 +15,7 @@ func resourceAppSamlAppSettings() *schema.Resource { CreateContext: resourceAppSamlSettingsCreate, ReadContext: resourceAppSamlSettingsRead, UpdateContext: resourceAppSamlSettingsUpdate, - DeleteContext: resourceAppSamlSettingsDelete, + DeleteContext: resourceFuncNoOp, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -99,7 +99,3 @@ func updateOrCreateAppSettings(ctx context.Context, d *schema.ResourceData, m in } return app.Id, nil } - -func resourceAppSamlSettingsDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return nil -} diff --git a/okta/resource_okta_app_user_base_schema_property.go b/okta/resource_okta_app_user_base_schema_property.go index f18bfb501..6bc4d4f08 100644 --- a/okta/resource_okta_app_user_base_schema_property.go +++ b/okta/resource_okta_app_user_base_schema_property.go @@ -13,7 +13,7 @@ func resourceAppUserBaseSchemaProperty() *schema.Resource { CreateContext: resourceAppUserBaseSchemaCreate, ReadContext: resourceAppUserBaseSchemaRead, UpdateContext: resourceAppUserBaseSchemaUpdate, - DeleteContext: resourceAppUserBaseSchemaDelete, + DeleteContext: resourceFuncNoOp, Importer: createNestedResourceImporter([]string{"app_id", "index"}), Schema: buildSchema( userBaseSchemaSchema, @@ -85,11 +85,6 @@ func resourceAppUserBaseSchemaUpdate(ctx context.Context, d *schema.ResourceData return resourceAppUserBaseSchemaRead(ctx, d, m) } -// can't delete Base -func resourceAppUserBaseSchemaDelete(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics { - return nil -} - // create or modify a subschema func updateAppUserBaseSubschema(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { err := validateAppUserBaseSchema(d) diff --git a/okta/resource_okta_app_user_custom_schema_property_test.go b/okta/resource_okta_app_user_custom_schema_property_test.go index 5a747a663..76d369777 100644 --- a/okta/resource_okta_app_user_custom_schema_property_test.go +++ b/okta/resource_okta_app_user_custom_schema_property_test.go @@ -601,3 +601,95 @@ func testAppUserSchemaExists(index string) (bool, error) { } return false, nil } + +// TestAccOktaAppUserSchemas_parallel_api_calls test coverage to ensure backoff +// in create, update, delete for okta_app_user_schema_property resource is +// operating correctly. +func TestAccOktaAppUserSchemas_parallel_api_calls(t *testing.T) { + config := ` +resource "okta_app_oauth" "test" { + label = "testAcc_replace_with_uuid" + type = "native" + grant_types = ["authorization_code"] + redirect_uris = ["http://d.com/"] + response_types = ["code"] +} +resource "okta_app_user_schema_property" "one" { + app_id = okta_app_oauth.test.id + index = "testAcc_replace_with_uuid_one" + title = "one" + type = "string" + permissions = "%s" +} +resource "okta_app_user_schema_property" "two" { + app_id = okta_app_oauth.test.id + index = "testAcc_replace_with_uuid_two" + title = "two" + type = "string" + permissions = "%s" +} +resource "okta_app_user_schema_property" "three" { + app_id = okta_app_oauth.test.id + index = "testAcc_replace_with_uuid_three" + title = "three" + type = "string" + permissions = "%s" +} +resource "okta_app_user_schema_property" "four" { + app_id = okta_app_oauth.test.id + index = "testAcc_replace_with_uuid_four" + title = "four" + type = "string" + permissions = "%s" +} +resource "okta_app_user_schema_property" "five" { + app_id = okta_app_oauth.test.id + index = "testAcc_replace_with_uuid_five" + title = "five" + type = "string" + permissions = "%s" +} +` + ri := acctest.RandInt() + mgr := newFixtureManager(appUserSchemaProperty) + ro := make([]interface{}, 5) + for i := 0; i < 5; i++ { + ro[i] = "READ_ONLY" + } + rw := make([]interface{}, 5) + for i := 0; i < 5; i++ { + rw[i] = "READ_WRITE" + } + roConfig := fmt.Sprintf(config, ro...) + roConfig = mgr.ConfigReplace(roConfig, ri) + rwConfig := fmt.Sprintf(config, rw...) + rwConfig = mgr.ConfigReplace(rwConfig, ri) + resource.Test(t, resource.TestCase{ + PreCheck: testAccPreCheck(t), + ErrorCheck: testAccErrorChecks(t), + ProviderFactories: testAccProvidersFactories, + CheckDestroy: createCheckResourceDestroy(appUserSchemaProperty, testAppUserSchemaExists), + Steps: []resource.TestStep{ + { + Config: roConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("okta_app_user_schema_property.one", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_app_user_schema_property.two", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_app_user_schema_property.three", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_app_user_schema_property.four", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_app_user_schema_property.five", "permissions", "READ_ONLY"), + ), + }, + { + Config: rwConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("okta_app_user_schema_property.one", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_app_user_schema_property.two", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_app_user_schema_property.three", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_app_user_schema_property.four", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_app_user_schema_property.five", "permissions", "READ_WRITE"), + ), + }, + }, + }) +} diff --git a/okta/resource_okta_auth_server_claim_default.go b/okta/resource_okta_auth_server_claim_default.go index d5a8f8bd8..995a368a7 100644 --- a/okta/resource_okta_auth_server_claim_default.go +++ b/okta/resource_okta_auth_server_claim_default.go @@ -15,7 +15,7 @@ func resourceAuthServerClaimDefault() *schema.Resource { CreateContext: resourceAuthServerClaimDefaultUpdate, ReadContext: resourceAuthServerClaimDefaultRead, UpdateContext: resourceAuthServerClaimDefaultUpdate, - DeleteContext: resourceAuthServerClaimDefaultDelete, + DeleteContext: resourceFuncNoOp, Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { parts := strings.Split(d.Id(), "/") @@ -150,11 +150,6 @@ func resourceAuthServerClaimDefaultUpdate(ctx context.Context, d *schema.Resourc return resourceAuthServerClaimDefaultRead(ctx, d, m) } -// Default claims are immutable. -func resourceAuthServerClaimDefaultDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return nil -} - func buildAuthServerClaimDefault(d *schema.ResourceData) okta.OAuth2Claim { return okta.OAuth2Claim{ Status: d.Get("status").(string), diff --git a/okta/resource_okta_default_auth_server.go b/okta/resource_okta_default_auth_server.go index 802f7f430..268aec451 100644 --- a/okta/resource_okta_default_auth_server.go +++ b/okta/resource_okta_default_auth_server.go @@ -12,7 +12,7 @@ func resourceAuthServerDefault() *schema.Resource { CreateContext: resourceAuthServerDefaultUpdate, ReadContext: resourceAuthServerDefaultRead, UpdateContext: resourceAuthServerDefaultUpdate, - DeleteContext: resourceAuthServerDefaultDelete, + DeleteContext: resourceFuncNoOp, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -139,8 +139,3 @@ func resourceAuthServerDefaultUpdate(ctx context.Context, d *schema.ResourceData d.SetId(authServer.Id) return resourceAuthServerDefaultRead(ctx, d, m) } - -// Default authorization server can not be removed -func resourceAuthServerDefaultDelete(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics { - return nil -} diff --git a/okta/resource_okta_domain_certificate.go b/okta/resource_okta_domain_certificate.go index 63a41d102..f6f67998f 100644 --- a/okta/resource_okta_domain_certificate.go +++ b/okta/resource_okta_domain_certificate.go @@ -13,9 +13,9 @@ import ( func resourceDomainCertificate() *schema.Resource { return &schema.Resource{ CreateContext: resourceDomainCertificateCreate, - ReadContext: resourceDomainCertificateRead, + ReadContext: resourceFuncNoOp, UpdateContext: resourceDomainCertificateUpdate, - DeleteContext: resourceDomainCertificateDelete, + DeleteContext: resourceFuncNoOp, Importer: nil, Schema: map[string]*schema.Schema{ "domain_id": { @@ -80,10 +80,6 @@ func resourceDomainCertificateCreate(ctx context.Context, d *schema.ResourceData return nil } -func resourceDomainCertificateRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return nil -} - func resourceDomainCertificateUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := buildDomainCertificate(d) _, err := getOktaClientFromMetadata(m).Domain.CreateCertificate(ctx, d.Get("domain_id").(string), c) @@ -93,11 +89,6 @@ func resourceDomainCertificateUpdate(ctx context.Context, d *schema.ResourceData return nil } -// nothing to do here, since domain's certificate can be deleted -func resourceDomainCertificateDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return nil -} - func buildDomainCertificate(d *schema.ResourceData) okta.DomainCertificate { return okta.DomainCertificate{ Certificate: d.Get("certificate").(string), diff --git a/okta/resource_okta_domain_verification.go b/okta/resource_okta_domain_verification.go index 88eb76273..b00b1afa1 100644 --- a/okta/resource_okta_domain_verification.go +++ b/okta/resource_okta_domain_verification.go @@ -13,8 +13,8 @@ import ( func resourceDomainVerification() *schema.Resource { return &schema.Resource{ CreateContext: resourceDomainVerificationCreate, - ReadContext: resourceDomainVerificationRead, - DeleteContext: resourceDomainVerificationDelete, + ReadContext: resourceFuncNoOp, + DeleteContext: resourceFuncNoOp, Importer: nil, Schema: map[string]*schema.Schema{ "domain_id": { @@ -48,16 +48,6 @@ func resourceDomainVerificationCreate(ctx context.Context, d *schema.ResourceDat return nil } -// nothing to do here, since domain should be already verified during creation. -func resourceDomainVerificationRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return nil -} - -// nothing to do here, since domain cannot be re-verified -func resourceDomainVerificationDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return nil -} - // Status of the domain. Accepted values: NOT_STARTED, IN_PROGRESS, VERIFIED, COMPLETED func isDomainValidated(validationStatus string) bool { switch validationStatus { diff --git a/okta/resource_okta_email_sender_verification.go b/okta/resource_okta_email_sender_verification.go index 85ff8bb54..c99e9d07f 100644 --- a/okta/resource_okta_email_sender_verification.go +++ b/okta/resource_okta_email_sender_verification.go @@ -11,8 +11,8 @@ import ( func resourceEmailSenderVerification() *schema.Resource { return &schema.Resource{ CreateContext: resourceEmailSenderVerificationCreate, - ReadContext: resourceEmailSenderVerificationRead, - DeleteContext: resourceEmailSenderVerificationDelete, + ReadContext: resourceFuncNoOp, + DeleteContext: resourceFuncNoOp, Importer: nil, Schema: map[string]*schema.Schema{ "sender_id": { @@ -44,11 +44,3 @@ func resourceEmailSenderVerificationCreate(ctx context.Context, d *schema.Resour d.SetId(d.Get("sender_id").(string)) return nil } - -func resourceEmailSenderVerificationRead(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics { - return nil -} - -func resourceEmailSenderVerificationDelete(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics { - return nil -} diff --git a/okta/resource_okta_event_hook_verification.go b/okta/resource_okta_event_hook_verification.go index a1256336d..e2cea2da3 100644 --- a/okta/resource_okta_event_hook_verification.go +++ b/okta/resource_okta_event_hook_verification.go @@ -10,8 +10,8 @@ import ( func resourceEventHookVerification() *schema.Resource { return &schema.Resource{ CreateContext: resourceEventHookVerificationCreate, - ReadContext: resourceEventHookVerificationRead, - DeleteContext: resourceEventHookVerificationDelete, + ReadContext: resourceFuncNoOp, + DeleteContext: resourceFuncNoOp, Importer: nil, Schema: map[string]*schema.Schema{ "event_hook_id": { @@ -32,11 +32,3 @@ func resourceEventHookVerificationCreate(ctx context.Context, d *schema.Resource d.SetId(d.Get("event_hook_id").(string)) return nil } - -func resourceEventHookVerificationRead(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics { - return nil -} - -func resourceEventHookVerificationDelete(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics { - return nil -} diff --git a/okta/resource_okta_group_custom_schema_property.go b/okta/resource_okta_group_custom_schema_property.go index 895e2a976..3ea74618f 100644 --- a/okta/resource_okta_group_custom_schema_property.go +++ b/okta/resource_okta_group_custom_schema_property.go @@ -127,7 +127,6 @@ func alterCustomGroupSchema(ctx context.Context, m interface{}, index string, sc stringifyGroupSchemaPropertyEnums(schema) if err != nil { - logger(m).Error(err.Error()) if resp != nil && resp.StatusCode == 500 { return fmt.Errorf("updating group custom schema property caused 500 error: %w", err) } @@ -146,9 +145,11 @@ func alterCustomGroupSchema(ctx context.Context, m interface{}, index string, sc } else if schemaAttribute != nil && reflect.DeepEqual(schemaAttribute, updated.Definitions.Custom.Properties[index]) { return nil } - logger(m).Error("failed to apply changes after several retries") return errors.New("failed to apply changes after several retries") }, bc) + if err != nil { + logger(m).Error("failed to apply changes after several retries %+v", err) + } return schemaAttribute, err } diff --git a/okta/resource_okta_group_custom_schema_property_test.go b/okta/resource_okta_group_custom_schema_property_test.go index f48c871a0..e9af0bd20 100644 --- a/okta/resource_okta_group_custom_schema_property_test.go +++ b/okta/resource_okta_group_custom_schema_property_test.go @@ -899,3 +899,80 @@ func TestAccResourceOktaGroupSchema_enum_string(t *testing.T) { }, }) } + +// TestAccResourceOktaGroupSchema_parallel_api_calls test coverage to ensure +// backoff in create and update for okta_group_schema_property resource is +// operating correctly. +func TestAccResourceOktaGroupSchema_parallel_api_calls(t *testing.T) { + ri := acctest.RandInt() + mgr := newFixtureManager(groupSchemaProperty) + config := ` +resource "okta_group_schema_property" "one" { + index = "testAcc_replace_with_uuid_one" + title = "one" + type = "string" + permissions = "%s" +} +resource "okta_group_schema_property" "two" { + index = "testAcc_replace_with_uuid_two" + title = "two" + type = "string" + permissions = "%s" +} +resource "okta_group_schema_property" "three" { + index = "testAcc_replace_with_uuid_three" + title = "three" + type = "string" + permissions = "%s" +} +resource "okta_group_schema_property" "four" { + index = "testAcc_replace_with_uuid_four" + title = "four" + type = "string" + permissions = "%s" +} +resource "okta_group_schema_property" "five" { + index = "testAcc_replace_with_uuid_five" + title = "five" + type = "string" + permissions = "%s" +} +` + ro := make([]interface{}, 5) + for i := 0; i < 5; i++ { + ro[i] = "READ_ONLY" + } + rw := make([]interface{}, 5) + for i := 0; i < 5; i++ { + rw[i] = "READ_WRITE" + } + roConfig := mgr.ConfigReplace(fmt.Sprintf(config, ro...), ri) + rwConfig := mgr.ConfigReplace(fmt.Sprintf(config, rw...), ri) + resource.Test(t, resource.TestCase{ + PreCheck: testAccPreCheck(t), + ErrorCheck: testAccErrorChecks(t), + ProviderFactories: testAccProvidersFactories, + Steps: []resource.TestStep{ + { + Config: roConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("okta_group_schema_property.one", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_group_schema_property.two", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_group_schema_property.three", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_group_schema_property.four", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_group_schema_property.five", "permissions", "READ_ONLY"), + ), + }, + { + Config: rwConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("okta_group_schema_property.one", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_group_schema_property.two", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_group_schema_property.three", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_group_schema_property.four", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_group_schema_property.five", "permissions", "READ_WRITE"), + ), + }, + }, + }) +} diff --git a/okta/resource_okta_link_definition.go b/okta/resource_okta_link_definition.go index f8583efbf..278344719 100644 --- a/okta/resource_okta_link_definition.go +++ b/okta/resource_okta_link_definition.go @@ -56,6 +56,12 @@ func resourceLinkDefinition() *schema.Resource { } func resourceLinkDefinitionCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + // NOTE: Okta API will ignore parallel calls to `POST + // /api/v1/meta/schemas/user/linkedObjects` so a mutex to affect TF + // `-parallelism=1` behavior is needed here. + oktaMutexKV.Lock(linkDefinition) + defer oktaMutexKV.Unlock(linkDefinition) + linkedObject := okta.LinkedObject{ Primary: &okta.LinkedObjectDetails{ Name: d.Get("primary_name").(string), @@ -100,6 +106,12 @@ func resourceLinkDefinitionRead(ctx context.Context, d *schema.ResourceData, m i } func resourceLinkDefinitionDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + // NOTE: Okta API will ignore parallel calls to `DELETE + // /api/v1/meta/schemas/user/linkedObjects` so a mutex to affect TF + // `-parallelism=1` behavior is needed here. + oktaMutexKV.Lock(linkDefinition) + defer oktaMutexKV.Unlock(linkDefinition) + resp, err := getOktaClientFromMetadata(m).LinkedObject.DeleteLinkedObjectDefinition(ctx, d.Id()) if err := suppressErrorOn404(resp, err); err != nil { return diag.Errorf("failed to remove linked object: %v", err) diff --git a/okta/resource_okta_link_definition_test.go b/okta/resource_okta_link_definition_test.go index 4d25e8bd1..58934ed74 100644 --- a/okta/resource_okta_link_definition_test.go +++ b/okta/resource_okta_link_definition_test.go @@ -39,3 +39,76 @@ func doesLinkDefinitionExist(id string) (bool, error) { _, response, err := getOktaClientFromMetadata(testAccProvider.Meta()).LinkedObject.GetLinkedObjectDefinition(context.Background(), id) return doesResourceExist(response, err) } + +// TestAccOktaUserBaseSchemaLogin_multiple_properties This test would fail +// before the fix was implemented. The fix is to put a calling mutex on create +// and delete for the `okta_link_definition` resource. The Okta management API +// ignores parallel calls to `POST /api/v1/meta/schemas/user/linkedObjects` and +// `DELETE /api/v1/meta/schemas/user/linkedObjects` and our fix is to use a +// calling mutex in the resource to impose the equivelent of `terraform +// -parallelism=1` +func TestAccResourceOktaLinkDefinition_parallel_api_calls(t *testing.T) { + ri := acctest.RandInt() + mgr := newFixtureManager(linkDefinition) + config := ` +resource "okta_link_definition" "one" { + primary_name = "testAcc_replace_with_uuid_one" + primary_title = "one" + primary_description = "one" + associated_name = "testAcc_replace_with_uuid_one_a" + associated_title = "one_a" + associated_description = "one_a" +} +resource "okta_link_definition" "two" { + primary_name = "testAcc_replace_with_uuid_two" + primary_title = "two" + primary_description = "two" + associated_name = "testAcc_replace_with_uuid_two_a" + associated_title = "two_a" + associated_description = "two_a" +} +resource "okta_link_definition" "three" { + primary_name = "testAcc_replace_with_uuid_three" + primary_title = "three" + primary_description = "three" + associated_name = "testAcc_replace_with_uuid_three_a" + associated_title = "three_a" + associated_description = "three_a" +} +resource "okta_link_definition" "four" { + primary_name = "testAcc_replace_with_uuid_four" + primary_title = "four" + primary_description = "four" + associated_name = "testAcc_replace_with_uuid_four_a" + associated_title = "four_a" + associated_description = "four_a" +} +resource "okta_link_definition" "five" { + primary_name = "testAcc_replace_with_uuid_five" + primary_title = "five" + primary_description = "five" + associated_name = "testAcc_replace_with_uuid_five_a" + associated_title = "five_a" + associated_description = "five_a" +} +` + config = mgr.ConfigReplace(config, ri) + resource.Test(t, resource.TestCase{ + PreCheck: testAccPreCheck(t), + ErrorCheck: testAccErrorChecks(t), + ProviderFactories: testAccProvidersFactories, + CheckDestroy: createCheckResourceDestroy(linkDefinition, doesLinkDefinitionExist), + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("okta_link_definition.one", "primary_title", "one"), + resource.TestCheckResourceAttr("okta_link_definition.two", "primary_title", "two"), + resource.TestCheckResourceAttr("okta_link_definition.three", "primary_title", "three"), + resource.TestCheckResourceAttr("okta_link_definition.four", "primary_title", "four"), + resource.TestCheckResourceAttr("okta_link_definition.five", "primary_title", "five"), + ), + }, + }, + }) +} diff --git a/okta/resource_okta_org_configuration.go b/okta/resource_okta_org_configuration.go index d51801413..b9a8bcc72 100644 --- a/okta/resource_okta_org_configuration.go +++ b/okta/resource_okta_org_configuration.go @@ -14,7 +14,7 @@ func resourceOrgConfiguration() *schema.Resource { CreateContext: resourceOrgSettingsCreate, ReadContext: resourceOrgSettingsRead, UpdateContext: resourceOrgSettingsUpdate, - DeleteContext: resourceOrgSettingsDelete, + DeleteContext: resourceFuncNoOp, Importer: &schema.ResourceImporter{StateContext: schema.ImportStatePassthroughContext}, Schema: map[string]*schema.Schema{ "company_name": { @@ -206,10 +206,6 @@ func resourceOrgSettingsUpdate(ctx context.Context, d *schema.ResourceData, m in return resourceOrgSettingsRead(ctx, d, m) } -func resourceOrgSettingsDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return nil -} - func updateCommunicationSettings(ctx context.Context, d *schema.ResourceData, m interface{}) error { comm, _, err := getOktaClientFromMetadata(m).OrgSetting.GetOktaCommunicationSettings(ctx) if err != nil { diff --git a/okta/resource_okta_policy_mfa_default.go b/okta/resource_okta_policy_mfa_default.go index dffb857f8..5db8dd705 100644 --- a/okta/resource_okta_policy_mfa_default.go +++ b/okta/resource_okta_policy_mfa_default.go @@ -15,7 +15,7 @@ func resourcePolicyMfaDefault() *schema.Resource { CreateContext: resourcePolicyMfaDefaultCreateOrUpdate, ReadContext: resourcePolicyMfaDefaultRead, UpdateContext: resourcePolicyMfaDefaultCreateOrUpdate, - DeleteContext: resourcePolicyMfaDefaultDelete, + DeleteContext: resourceFuncNoOp, Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { _, err := setDefaultPolicy(ctx, d, m, sdk.MfaPolicyType) @@ -59,11 +59,6 @@ func resourcePolicyMfaDefaultRead(ctx context.Context, d *schema.ResourceData, m return nil } -// Default policy can not be removed -func resourcePolicyMfaDefaultDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return nil -} - func buildDefaultMFAPolicy(d *schema.ResourceData) sdk.Policy { policy := sdk.MfaPolicy() policy.Name = d.Get("name").(string) diff --git a/okta/resource_okta_policy_password_default.go b/okta/resource_okta_policy_password_default.go index 88a3570a6..c03840844 100644 --- a/okta/resource_okta_policy_password_default.go +++ b/okta/resource_okta_policy_password_default.go @@ -14,7 +14,7 @@ func resourcePolicyPasswordDefault() *schema.Resource { CreateContext: resourcePolicyPasswordDefaultUpdate, ReadContext: resourcePolicyPasswordDefaultRead, UpdateContext: resourcePolicyPasswordDefaultUpdate, - DeleteContext: resourcePolicyPasswordDefaultDelete, + DeleteContext: resourceFuncNoOp, Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { policy, err := setDefaultPolicy(ctx, d, m, sdk.PasswordPolicyType) @@ -250,11 +250,6 @@ func resourcePolicyPasswordDefaultRead(ctx context.Context, d *schema.ResourceDa return nil } -// Default policy can not be removed -func resourcePolicyPasswordDefaultDelete(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics { - return nil -} - // create or update a password policy func buildDefaultPasswordPolicy(d *schema.ResourceData) sdk.Policy { policy := sdk.PasswordPolicy() diff --git a/okta/resource_okta_rate_limiting.go b/okta/resource_okta_rate_limiting.go index 0ac7eaa81..f6a0a907f 100644 --- a/okta/resource_okta_rate_limiting.go +++ b/okta/resource_okta_rate_limiting.go @@ -13,7 +13,7 @@ func resourceRateLimiting() *schema.Resource { CreateContext: resourceRateLimitingCreate, ReadContext: resourceRateLimitingRead, UpdateContext: resourceRateLimitingUpdate, - DeleteContext: resourceRateLimitingDelete, + DeleteContext: resourceFuncNoOp, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -81,10 +81,6 @@ func resourceRateLimitingUpdate(ctx context.Context, d *schema.ResourceData, m i return nil } -func resourceRateLimitingDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return nil -} - func buildRateLimiter(d *schema.ResourceData) sdk.ClientRateLimitMode { return sdk.ClientRateLimitMode{ Mode: "PREVIEW", diff --git a/okta/resource_okta_role_subscription.go b/okta/resource_okta_role_subscription.go index 6a7fd8836..bb0045d7f 100644 --- a/okta/resource_okta_role_subscription.go +++ b/okta/resource_okta_role_subscription.go @@ -14,7 +14,7 @@ func resourceRoleSubscription() *schema.Resource { CreateContext: resourceRoleSubscriptionCreate, ReadContext: resourceRoleSubscriptionRead, UpdateContext: resourceRoleSubscriptionUpdate, - DeleteContext: resourceRoleSubscriptionDelete, + DeleteContext: resourceFuncNoOp, Importer: &schema.ResourceImporter{ StateContext: func(_ context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { parts := strings.Split(d.Id(), "/") @@ -124,10 +124,6 @@ func resourceRoleSubscriptionUpdate(ctx context.Context, d *schema.ResourceData, return nil } -func resourceRoleSubscriptionDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - return nil -} - func validateSubscriptions(role, notification string) error { switch { case notification == "CONNECTOR_AGENT" || notification == "APP_IMPORT" || notification == "LDAP_AGENT" || diff --git a/okta/resource_okta_user_base_schema_property.go b/okta/resource_okta_user_base_schema_property.go index 56d75e986..0b794cfa6 100644 --- a/okta/resource_okta_user_base_schema_property.go +++ b/okta/resource_okta_user_base_schema_property.go @@ -13,8 +13,8 @@ func resourceUserBaseSchemaProperty() *schema.Resource { return &schema.Resource{ CreateContext: resourceUserBaseSchemaCreate, ReadContext: resourceUserBaseSchemaRead, - UpdateContext: resourceUserBaseSchemaUpdate, - DeleteContext: resourceUserBaseSchemaDelete, + UpdateContext: resourceUserBaseSchemaCreate, + DeleteContext: resourceFuncNoOp, Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { resourceIndex := d.Id() @@ -63,6 +63,12 @@ func resourceUserBaseSchemaResourceV0() *schema.Resource { } func resourceUserBaseSchemaCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + // NOTE: Okta API will ignore parallel calls to `POST + // /api/v1/meta/schemas/user/{userId}` so a mutex to affect TF + // `-parallelism=1` behavior is needed here. + oktaMutexKV.Lock(userBaseSchemaProperty) + defer oktaMutexKV.Unlock(userBaseSchemaProperty) + if err := updateUserBaseSubschema(ctx, d, m); err != nil { return err } @@ -88,13 +94,6 @@ func resourceUserBaseSchemaRead(ctx context.Context, d *schema.ResourceData, m i return nil } -func resourceUserBaseSchemaUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - if err := updateUserBaseSubschema(ctx, d, m); err != nil { - return err - } - return resourceUserBaseSchemaRead(ctx, d, m) -} - // create or modify a subschema func updateUserBaseSubschema(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { err := validateUserBaseSchema(d) @@ -120,11 +119,6 @@ func updateUserBaseSubschema(ctx context.Context, d *schema.ResourceData, m inte return nil } -// can't delete Base schema -func resourceUserBaseSchemaDelete(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics { - return nil -} - func validateUserBaseSchema(d *schema.ResourceData) error { _, ok := d.GetOk("pattern") if d.Get("index").(string) != "login" { diff --git a/okta/resource_okta_user_base_schema_property_test.go b/okta/resource_okta_user_base_schema_property_test.go index 9d1daf053..d86a6890e 100644 --- a/okta/resource_okta_user_base_schema_property_test.go +++ b/okta/resource_okta_user_base_schema_property_test.go @@ -146,3 +146,83 @@ func testOktaUserBaseSchemasExists(name string) resource.TestCheckFunc { return nil } } + +// TestAccOktaUserBaseSchemaLogin_multiple_properties Test for issue 1217 fix. +// https://github.com/okta/terraform-provider-okta/issues/1217 This test would +// fail before the fix was implemented. The fix is to put a calling mutex on +// create and update for the `okta_user_base_schema_property` resource. The Okta +// management API ignores parallel calls to `POST +// /api/v1/meta/schemas/user/{userId}` and our fix is to use a calling mutex in +// the resource to impose the equivelent of `terraform -parallelism=1` +func TestAccOktaUserBaseSchemaLogin_multiple_properties(t *testing.T) { + config := ` +resource "okta_user_base_schema_property" "login" { + index = "login" + title = "Username" + type = "string" + required = true + permissions = "%s" +} +resource "okta_user_base_schema_property" "firstname" { + index = "firstName" + title = "First name" + type = "string" + permissions = "%s" +} +resource "okta_user_base_schema_property" "lastname" { + index = "lastName" + title = "Last name" + type = "string" + permissions = "%s" +} +resource "okta_user_base_schema_property" "email" { + index = "email" + title = "Primary email" + type = "string" + required = true + permissions = "%s" +} +resource "okta_user_base_schema_property" "mobilephone" { + index = "mobilePhone" + title = "Mobile phone" + type = "string" + permissions = "%s" +}` + ro := make([]interface{}, 5) + for i := 0; i < 5; i++ { + ro[i] = "READ_ONLY" + } + rw := make([]interface{}, 5) + for i := 0; i < 5; i++ { + rw[i] = "READ_WRITE" + } + roConfig := fmt.Sprintf(config, ro...) + rwConfig := fmt.Sprintf(config, rw...) + resource.Test(t, resource.TestCase{ + PreCheck: testAccPreCheck(t), + ErrorCheck: testAccErrorChecks(t), + ProviderFactories: testAccProvidersFactories, + Steps: []resource.TestStep{ + { + Config: roConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("okta_user_base_schema_property.login", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_user_base_schema_property.firstname", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_user_base_schema_property.lastname", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_user_base_schema_property.email", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_user_base_schema_property.mobilephone", "permissions", "READ_ONLY"), + ), + }, + { + Config: rwConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("okta_user_base_schema_property.login", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_user_base_schema_property.firstname", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_user_base_schema_property.lastname", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_user_base_schema_property.email", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_user_base_schema_property.mobilephone", "permissions", "READ_WRITE"), + ), + }, + }, + }) +} diff --git a/okta/resource_okta_user_custom_schema_property.go b/okta/resource_okta_user_custom_schema_property.go index 8fc9681cf..5b6f4097a 100644 --- a/okta/resource_okta_user_custom_schema_property.go +++ b/okta/resource_okta_user_custom_schema_property.go @@ -167,7 +167,6 @@ func alterCustomUserSchema(ctx context.Context, m interface{}, userType, index s stringifyUserSchemaPropertyEnums(schema) if err != nil { - logger(m).Error(err.Error()) if resp != nil && resp.StatusCode == 500 { return fmt.Errorf("updating user custom schema property caused 500 error: %w", err) } @@ -186,9 +185,11 @@ func alterCustomUserSchema(ctx context.Context, m interface{}, userType, index s } else if schemaAttribute != nil && reflect.DeepEqual(schemaAttribute, updated.Definitions.Custom.Properties[index]) { return nil } - logger(m).Error("failed to apply changes after several retries") return errors.New("failed to apply changes after several retries") }, bc) + if err != nil { + logger(m).Error("failed to apply changes after several retries %+v", err) + } return schemaAttribute, err } diff --git a/okta/resource_okta_user_custom_schema_property_test.go b/okta/resource_okta_user_custom_schema_property_test.go index dda8dde32..46042cd8b 100644 --- a/okta/resource_okta_user_custom_schema_property_test.go +++ b/okta/resource_okta_user_custom_schema_property_test.go @@ -969,3 +969,80 @@ func testUserSchemaPropertyExists(schemaUserType, index, resolutionScope string) return false, fmt.Errorf("resolution scope can be only 'base' or 'custom'") } } + +// TestAccResourceOktaUserSchema_parallel_api_calls test coverage to ensure +// backoff in create and update for okta_ser_schema_property resource is +// operating correctly. +func TestAccResourceOktaUserSchema_parallel_api_calls(t *testing.T) { + ri := acctest.RandInt() + mgr := newFixtureManager(userSchemaProperty) + config := ` +resource "okta_user_schema_property" "one" { + index = "testAcc_replace_with_uuid_one" + title = "one" + type = "string" + permissions = "%s" +} +resource "okta_user_schema_property" "two" { + index = "testAcc_replace_with_uuid_two" + title = "two" + type = "string" + permissions = "%s" +} +resource "okta_user_schema_property" "three" { + index = "testAcc_replace_with_uuid_three" + title = "three" + type = "string" + permissions = "%s" +} +resource "okta_user_schema_property" "four" { + index = "testAcc_replace_with_uuid_four" + title = "four" + type = "string" + permissions = "%s" +} +resource "okta_user_schema_property" "five" { + index = "testAcc_replace_with_uuid_five" + title = "five" + type = "string" + permissions = "%s" +} +` + ro := make([]interface{}, 5) + for i := 0; i < 5; i++ { + ro[i] = "READ_ONLY" + } + rw := make([]interface{}, 5) + for i := 0; i < 5; i++ { + rw[i] = "READ_WRITE" + } + roConfig := mgr.ConfigReplace(fmt.Sprintf(config, ro...), ri) + rwConfig := mgr.ConfigReplace(fmt.Sprintf(config, rw...), ri) + resource.Test(t, resource.TestCase{ + PreCheck: testAccPreCheck(t), + ErrorCheck: testAccErrorChecks(t), + ProviderFactories: testAccProvidersFactories, + Steps: []resource.TestStep{ + { + Config: roConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("okta_user_schema_property.one", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_user_schema_property.two", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_user_schema_property.three", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_user_schema_property.four", "permissions", "READ_ONLY"), + resource.TestCheckResourceAttr("okta_user_schema_property.five", "permissions", "READ_ONLY"), + ), + }, + { + Config: rwConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("okta_user_schema_property.one", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_user_schema_property.two", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_user_schema_property.three", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_user_schema_property.four", "permissions", "READ_WRITE"), + resource.TestCheckResourceAttr("okta_user_schema_property.five", "permissions", "READ_WRITE"), + ), + }, + }, + }) +}