Repository: SAP/terraform-provider-btp (go)

Issue:
[BUG] Import of default IdP not possible
The import of the default IdP fails due to the constraint on the identity provider field. For the default IdP this field must be empty, but the provider validates for a length > 1

Pull request:
fix: handling of trust configuration for SAP default idp
## Purpose
<!-- Describe the intention of the changes being proposed. What problem does it solve or functionality does it add? -->
* This PR enables the handling of the default trust configuration with origin `sap.default`. 
* In order to enable a consistent handling several changes needed to be made in the resource:
  * The static validation of the field `identity_provider` was removed to allow the provisioning of an empty string ( sap.default IdP does not fill this field)
  * The validation is postponed to runtime
  * The `CREATE` always requires a non-empty  `identity_provider` field. The SAP default IdP cannot be created the API throws an error when the IdP would be created a second time
  * The `UPDATE` requires the validation if the origin is not `sap.default`
  * The `DELETE` needs a special handling as the sap.default IdP cannot be deleted (API error), hence, we just remove the resource from the state
* Closes #504 

## Does this introduce a breaking change?
<!-- Mark one with an "x". -->
```
[ ] Yes
[X] No
```

## Pull Request Type

What kind of change does this Pull Request introduce?
<!-- Please check the one that applies to this PR using "X". -->
```
[X] Bugfix
[ ] Feature
[ ] Refactoring (no functional changes, no api changes)
[ ] Documentation content changes
[ ] Other... Please describe:
```

## How to Test

* Test the code via automated test

```bash
go test ./...
```

## What to Check

Verify that the following are valid:

* Automated tests are executed successfully

## Other Information
<!-- Add any other helpful information that may be needed here. -->

From a customer perspective this change enables two scenarios to update the sap.default IdP via Terraform:

* Import of existing subaccounts including the sap.default IdP to Terraform
* Import of automatically created sap.default IdP even if subaccount was created via Terraform. This comprises a two step process:
  1. Creation of subaccount via Terraform (automatic creation of IdP)
  2. Import of existing/automatically created IdP

## Checklist for reviewer

<!-- This checklist needs to completed by the reviewer of the PR -->
The following organizational tasks must be completed before merging this PR:

* [X] The PR is assigned to the Terraform project and a status is set (typically "in review").
* [X] The PR has the matching labels assigned to it.
* [X] The PR has a milestone assigned to it.
* [X] If the PR closes an issue, the issue is referenced.
* [X] Possible follow-up items are created and linked.


Patch:
diff --git a/internal/provider/fixtures/resource_subaccount_trust_configuration.error_missing_identityprovider.yaml b/internal/provider/fixtures/resource_subaccount_trust_configuration.error_missing_identityprovider.yaml
new file mode 100644
index 0000000..a4f61cd
--- /dev/null
+++ b/internal/provider/fixtures/resource_subaccount_trust_configuration.error_missing_identityprovider.yaml
@@ -0,0 +1,195 @@
+---
+version: 2
+interactions:
+    - id: 0
+      request:
+        proto: HTTP/1.1
+        proto_major: 1
+        proto_minor: 1
+        content_length: 118
+        transfer_encoding: []
+        trailer: {}
+        host: canary.cli.btp.int.sap
+        remote_addr: ""
+        request_uri: ""
+        body: |
+            {"customIdp":"identityProvider","subdomain":"terraformintcanary","userName":"john.doe@int.test","password":"testUserPassword"}
+        form: {}
+        headers:
+            Content-Type:
+                - application/json
+            User-Agent:
+                - Terraform/1.6.2 terraform-provider-btp/dev
+            X-Correlationid:
+                - 3930a1a4-4118-bf53-3662-2aae0993736a
+            X-Cpcli-Format:
+                - json
+        url: https://canary.cli.btp.int.sap/login/v2.49.0
+        method: POST
+      response:
+        proto: HTTP/2.0
+        proto_major: 2
+        proto_minor: 0
+        transfer_encoding: []
+        trailer: {}
+        content_length: 153
+        uncompressed: false
+        body: '{"issuer":"identity.provider.test","refreshToken":"redacted","user":"john.doe@int.test","mail":"john.doe@int.test"}'
+        headers:
+            Cache-Control:
+                - no-cache, no-store, max-age=0, must-revalidate
+            Content-Length:
+                - "153"
+            Content-Type:
+                - application/json
+            Date:
+                - Thu, 26 Oct 2023 13:59:16 GMT
+            Expires:
+                - "0"
+            Pragma:
+                - no-cache
+            Referrer-Policy:
+                - no-referrer
+            Strict-Transport-Security:
+                - max-age=31536000; includeSubDomains; preload;
+            X-Content-Type-Options:
+                - nosniff
+            X-Cpcli-Sessionid:
+                - redacted
+            X-Frame-Options:
+                - DENY
+            X-Vcap-Request-Id:
+                - 84e0f77f-8011-464d-5dc7-1088fca1f527
+            X-Xss-Protection:
+                - "0"
+        status: 200 OK
+        code: 200
+        duration: 311.6576ms
+    - id: 1
+      request:
+        proto: HTTP/1.1
+        proto_major: 1
+        proto_minor: 1
+        content_length: 118
+        transfer_encoding: []
+        trailer: {}
+        host: canary.cli.btp.int.sap
+        remote_addr: ""
+        request_uri: ""
+        body: |
+            {"customIdp":"identityProvider","subdomain":"terraformintcanary","userName":"john.doe@int.test","password":"testUserPassword"}
+        form: {}
+        headers:
+            Content-Type:
+                - application/json
+            User-Agent:
+                - Terraform/1.6.2 terraform-provider-btp/dev
+            X-Correlationid:
+                - 59fbe020-f927-0c04-9886-8210f975955c
+            X-Cpcli-Format:
+                - json
+        url: https://canary.cli.btp.int.sap/login/v2.49.0
+        method: POST
+      response:
+        proto: HTTP/2.0
+        proto_major: 2
+        proto_minor: 0
+        transfer_encoding: []
+        trailer: {}
+        content_length: 153
+        uncompressed: false
+        body: '{"issuer":"identity.provider.test","refreshToken":"redacted","user":"john.doe@int.test","mail":"john.doe@int.test"}'
+        headers:
+            Cache-Control:
+                - no-cache, no-store, max-age=0, must-revalidate
+            Content-Length:
+                - "153"
+            Content-Type:
+                - application/json
+            Date:
+                - Thu, 26 Oct 2023 13:59:17 GMT
+            Expires:
+                - "0"
+            Pragma:
+                - no-cache
+            Referrer-Policy:
+                - no-referrer
+            Strict-Transport-Security:
+                - max-age=31536000; includeSubDomains; preload;
+            X-Content-Type-Options:
+                - nosniff
+            X-Cpcli-Sessionid:
+                - redacted
+            X-Frame-Options:
+                - DENY
+            X-Vcap-Request-Id:
+                - 5ec6a141-b6db-4525-4135-ba60749f4ff8
+            X-Xss-Protection:
+                - "0"
+        status: 200 OK
+        code: 200
+        duration: 294.5529ms
+    - id: 2
+      request:
+        proto: HTTP/1.1
+        proto_major: 1
+        proto_minor: 1
+        content_length: 118
+        transfer_encoding: []
+        trailer: {}
+        host: canary.cli.btp.int.sap
+        remote_addr: ""
+        request_uri: ""
+        body: |
+            {"customIdp":"identityProvider","subdomain":"terraformintcanary","userName":"john.doe@int.test","password":"testUserPassword"}
+        form: {}
+        headers:
+            Content-Type:
+                - application/json
+            User-Agent:
+                - Terraform/1.6.2 terraform-provider-btp/dev
+            X-Correlationid:
+                - 3326c8af-3eb7-dca4-837e-95284d49701b
+            X-Cpcli-Format:
+                - json
+        url: https://canary.cli.btp.int.sap/login/v2.49.0
+        method: POST
+      response:
+        proto: HTTP/2.0
+        proto_major: 2
+        proto_minor: 0
+        transfer_encoding: []
+        trailer: {}
+        content_length: 153
+        uncompressed: false
+        body: '{"issuer":"identity.provider.test","refreshToken":"redacted","user":"john.doe@int.test","mail":"john.doe@int.test"}'
+        headers:
+            Cache-Control:
+                - no-cache, no-store, max-age=0, must-revalidate
+            Content-Length:
+                - "153"
+            Content-Type:
+                - application/json
+            Date:
+                - Thu, 26 Oct 2023 13:59:17 GMT
+            Expires:
+                - "0"
+            Pragma:
+                - no-cache
+            Referrer-Policy:
+                - no-referrer
+            Strict-Transport-Security:
+                - max-age=31536000; includeSubDomains; preload;
+            X-Content-Type-Options:
+                - nosniff
+            X-Cpcli-Sessionid:
+                - redacted
+            X-Frame-Options:
+                - DENY
+            X-Vcap-Request-Id:
+                - 0bc1291b-a5a4-4f16-4b22-80f0663ec066
+            X-Xss-Protection:
+                - "0"
+        status: 200 OK
+        code: 200
+        duration: 394.2156ms
diff --git a/internal/provider/resource_subaccount_trust_configuration.go b/internal/provider/resource_subaccount_trust_configuration.go
index 7a28f71..8ec323c 100644
--- a/internal/provider/resource_subaccount_trust_configuration.go
+++ b/internal/provider/resource_subaccount_trust_configuration.go
@@ -19,6 +19,8 @@ import (
 	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
 )
 
+const OriginSapDefault = "sap.default"
+
 func newSubaccountTrustConfigurationResource() resource.Resource {
 	return &subaccountTrustConfigurationResource{}
 }
@@ -59,9 +61,8 @@ __Further documentation:__
 			"identity_provider": schema.StringAttribute{
 				MarkdownDescription: "The name of the Identity Authentication tenant that you want to connect to the subaccount.",
 				Required:            true,
-				Validators: []validator.String{
-					stringvalidator.LengthAtLeast(1),
-				},
+				// No validation for the identity provider name, it is validated by the API
+				// Needed for handling of sap.default IdP which has no value for this field
 			},
 			"domain": schema.StringAttribute{
 				MarkdownDescription: "The tenant's domain which should be used for user logon.",
@@ -193,6 +194,13 @@ func (rs *subaccountTrustConfigurationResource) Create(ctx context.Context, req
 		return
 	}
 
+	// Manual check of IdentityProvider field - not possible via schema validation due to handling of sap.default
+	// Create only possible for custom IdP -> value for field must be provided
+	if plan.IdentityProvider.ValueString() == "" {
+		resp.Diagnostics.AddAttributeError(path.Root("identity_provider"), "Empty Identity Provider", "To create a trust configuration you must provide a non-empty value for the identity provider")
+		return
+	}
+
 	cliCreateReq := btpcli.TrustConfigurationCreateInput{
 		IdentityProvider: plan.IdentityProvider.ValueString(),
 	}
@@ -263,6 +271,13 @@ func (rs *subaccountTrustConfigurationResource) Update(ctx context.Context, req
 		return
 	}
 
+	// sap.default and custom IdP must be handled in different ways
+	// Manual check for identity provider needed if the origin is not sap.default
+	if state.Origin.ValueString() != OriginSapDefault && plan.IdentityProvider.ValueString() == "" {
+		resp.Diagnostics.AddAttributeError(path.Root("identity_provider"), "Empty Identity Provider", "To update a trust configuration you must provide a non-empty value for the identity provider")
+		return
+	}
+
 	idp := plan.IdentityProvider.ValueString()
 	availableForUserLogon := plan.AvailableForUserLogon.ValueBool()
 	autoCreateShadowUsers := plan.AutoCreateShadowUsers.ValueBool()
@@ -317,6 +332,15 @@ func (rs *subaccountTrustConfigurationResource) Delete(ctx context.Context, req
 		return
 	}
 
+	//If the sap.default IdP is managed via Terraform we must skip the explicit deletion
+	//It cannot be deleted and it is sufficient to remove it from the state
+	if state.Origin.ValueString() == OriginSapDefault {
+		resp.Diagnostics.AddWarning("SAP Default cannot be deleted",
+			"It is not possible to delete the trust configuration for origin 'sap.default'. "+
+				"Skipping the deletion")
+		return
+	}
+
 	_, _, err := rs.cli.Security.Trust.DeleteBySubaccount(ctx, state.SubaccountId.ValueString(), state.Origin.ValueString())
 	if err != nil {
 		resp.Diagnostics.AddError("API Error Deleting Resource Trust Configuration (Subaccount)", fmt.Sprintf("%s", err))


Test patch:
diff --git a/internal/provider/resource_subaccount_trust_configuration_test.go b/internal/provider/resource_subaccount_trust_configuration_test.go
index bfaa54a..8161c25 100644
--- a/internal/provider/resource_subaccount_trust_configuration_test.go
+++ b/internal/provider/resource_subaccount_trust_configuration_test.go
@@ -145,6 +145,22 @@ func TestResourceSubaccountTrustConfiguration(t *testing.T) {
 			},
 		})
 	})
+
+	t.Run("error path - missing identity provider", func(t *testing.T) {
+		rec, user := setupVCR(t, "fixtures/resource_subaccount_trust_configuration.error_missing_identityprovider")
+		defer stopQuietly(rec)
+
+		resource.Test(t, resource.TestCase{
+			IsUnitTest:               true,
+			ProtoV6ProviderFactories: getProviders(rec.GetDefaultClient()),
+			Steps: []resource.TestStep{
+				{
+					Config:      hclProviderFor(user) + hclResourceSubaccountTrustConfigurationMinimal("uut", "ef23ace8-6ade-4d78-9c1f-8df729548bbf", ""),
+					ExpectError: regexp.MustCompile(`Empty Identity Provider`),
+				},
+			},
+		})
+	})
 }
 
 func hclResourceSubaccountTrustConfigurationComplete(resourceName string, subaccountId string, identityProvider string, domain string, name string, description string, linkText string, availableForUserLogin bool, autoCreateShadowUsers bool, status string) string {