From 00ffdbfa5d9a25bc104ac97c1b3e87fe8efd2ffe Mon Sep 17 00:00:00 2001 From: GitHub Terraform Date: Thu, 31 Oct 2024 11:12:55 -0700 Subject: [PATCH] fix: Fixed ZPA Specialized Application Segments Update Functions --- CHANGELOG.md | 14 + GNUmakefile | 6 +- docs/guides/release-notes.md | 16 +- docs/resources/zpa_application_segment_pra.md | 3 - go.mod | 2 +- go.sum | 4 +- zpa/data_source_zpa_application_segment.go | 21 -- ..._zpa_application_segment_browser_access.go | 8 +- ...urce_zpa_application_segment_inspection.go | 32 +- zpa/provider_sweeper_test.go | 6 +- ..._zpa_application_segment_browser_access.go | 75 ++-- ...application_segment_browser_access_test.go | 10 +- ...urce_zpa_application_segment_inspection.go | 347 ++++++++++++------ ...zpa_application_segment_inspection_test.go | 1 - zpa/resource_zpa_application_segment_pra.go | 336 ++++++++--------- ...source_zpa_application_segment_pra_test.go | 2 - zpa/resource_zpa_policy_access_rule.go | 2 +- zpa/utils.go | 15 +- 18 files changed, 507 insertions(+), 393 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55992b61..818f171e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 3.33.9 (October, 31 2024) + +### Notes + +- Release date: **(October, 31 2024)** +- Supported Terraform version: **v1.x** + +### Bug Fixes +- [PR #500](https://github.com/zscaler/terraform-provider-zpa/pull/500) - Implemented a fix to the update function across all specialized application segment resources: + - `zpa_application_segment_browser_access` - The fix now automatically includes the attributes `app_id` and `ba_app_id` in the payload during updates + - `zpa_application_segment_inspection` - The fix now automatically includes the attributes `app_id` and `inspect_app_id` in the payload during updates + - `zpa_application_segment_pra` - The fix now automatically includes the attributes `app_id` and `pra_app_id` in the payload during updates. + **NOTE:** This update/fix is required to ensure the ZPA API can properly identify the Browser Access, Inspection App and PRA App, based on its specific custom ID. The fix also includes the removal of the `ForceNew` option previously included in the schema to force the resource replacement in case of changes. Issue [PR #498](https://github.com/zscaler/terraform-provider-zpa/pull/498) + ## 3.33.8 (October, 29 2024) ### Notes diff --git a/GNUmakefile b/GNUmakefile index 1020a61e..2235ef5b 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -55,14 +55,14 @@ test\:integration\:zpa: build13: GOOS=$(shell go env GOOS) build13: GOARCH=$(shell go env GOARCH) ifeq ($(OS),Windows_NT) # is Windows_NT on XP, 2000, 7, Vista, 10... -build13: DESTINATION=$(APPDATA)/terraform.d/plugins/$(ZPA_PROVIDER_NAMESPACE)/3.33.8/$(GOOS)_$(GOARCH) +build13: DESTINATION=$(APPDATA)/terraform.d/plugins/$(ZPA_PROVIDER_NAMESPACE)/3.33.9/$(GOOS)_$(GOARCH) else -build13: DESTINATION=$(HOME)/.terraform.d/plugins/$(ZPA_PROVIDER_NAMESPACE)/3.33.8/$(GOOS)_$(GOARCH) +build13: DESTINATION=$(HOME)/.terraform.d/plugins/$(ZPA_PROVIDER_NAMESPACE)/3.33.9/$(GOOS)_$(GOARCH) endif build13: fmtcheck @echo "==> Installing plugin to $(DESTINATION)" @mkdir -p $(DESTINATION) - go build -o $(DESTINATION)/terraform-provider-zpa_v3.33.8 + go build -o $(DESTINATION)/terraform-provider-zpa_v3.33.9 vet: @echo "==> Checking source code against go vet and staticcheck" diff --git a/docs/guides/release-notes.md b/docs/guides/release-notes.md index 9cdfa8b5..d7ee6b38 100644 --- a/docs/guides/release-notes.md +++ b/docs/guides/release-notes.md @@ -12,10 +12,24 @@ Track all ZPA Terraform provider's releases. New resources, features, and bug fi --- -``Last updated: v3.33.8`` +``Last updated: v3.33.9`` --- +## 3.33.9 (October, 31 2024) + +### Notes + +- Release date: **(October, 31 2024)** +- Supported Terraform version: **v1.x** + +### Bug Fixes +- [PR #500](https://github.com/zscaler/terraform-provider-zpa/pull/500) - Implemented a fix to the update function across all specialized application segment resources: + - `zpa_application_segment_browser_access` - The fix now automatically includes the attributes `app_id` and `ba_app_id` in the payload during updates + - `zpa_application_segment_inspection` - The fix now automatically includes the attributes `app_id` and `inspect_app_id` in the payload during updates + - `zpa_application_segment_pra` - The fix now automatically includes the attributes `app_id` and `pra_app_id` in the payload during updates. + **NOTE:** This update/fix is required to ensure the ZPA API can properly identify the Browser Access, Inspection App and PRA App, based on its specific custom ID. The fix also includes the removal of the `ForceNew` option previously included in the schema to force the resource replacement in case of changes. Issue [PR #498](https://github.com/zscaler/terraform-provider-zpa/pull/498) + ## 3.33.8 (October, 29 2024) ### Notes diff --git a/docs/resources/zpa_application_segment_pra.md b/docs/resources/zpa_application_segment_pra.md index 0ff4639e..fc9ce167 100644 --- a/docs/resources/zpa_application_segment_pra.md +++ b/docs/resources/zpa_application_segment_pra.md @@ -64,7 +64,6 @@ The following arguments are supported: - `segment_group_id` - (String) The unique identifier of the Segment Group. - `common_apps_dto` (Block Set, Min: 1) List of applications (e.g., Inspection, Browser Access or Privileged Remote Access) - `apps_config:` (Block Set, Min: 1) List of applications to be configured - - `name` - (String) Name of the Privileged Remote Access - `domain` - (String) Domain name of the Privileged Remote Access - `application_protocol` - (String) Protocol for the Privileged Remote Access. Supported values: `RDP` and `SSH` - `application_port` - (String) Port for the Privileged Remote Access @@ -74,8 +73,6 @@ The following arguments are supported: - `tcp_port_ranges` - (List of String) TCP port ranges used to access the app. - `udp_port_ranges` - (List of String) UDP port ranges used to access the app. -!> **WARNING:** Removing PRA applications from the `common_apps_dto.apps_config` block will cause the provider to force a replacement of the application segment. - -> **NOTE:** TCP and UDP ports can also be defined using the following model: - `tcp_port_range` - (Block Set) TCP port ranges used to access the app. diff --git a/go.mod b/go.mod index cb8c67ec..ecf4e594 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/hashicorp/terraform-plugin-docs v0.19.4 github.com/hashicorp/terraform-plugin-sdk v1.17.2 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 - github.com/zscaler/zscaler-sdk-go/v2 v2.72.5 + github.com/zscaler/zscaler-sdk-go/v2 v2.732.0 ) require ( diff --git a/go.sum b/go.sum index 00a9c730..27162778 100644 --- a/go.sum +++ b/go.sum @@ -441,8 +441,8 @@ github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgr github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= github.com/zclconf/go-cty-yaml v1.0.2/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= -github.com/zscaler/zscaler-sdk-go/v2 v2.72.5 h1:IL9eFbcvDEoS6x17ipy/kThgubdiknIeXtA37hn7SQY= -github.com/zscaler/zscaler-sdk-go/v2 v2.72.5/go.mod h1:ugDudbyESUrANGw74moJypgVnWuOyLm8NyIJgfUzNNo= +github.com/zscaler/zscaler-sdk-go/v2 v2.732.0 h1:oyESzPJJswG9dTSH0VcCeZH1ebYUmhIOKeTQg0sLu+w= +github.com/zscaler/zscaler-sdk-go/v2 v2.732.0/go.mod h1:ugDudbyESUrANGw74moJypgVnWuOyLm8NyIJgfUzNNo= go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= diff --git a/zpa/data_source_zpa_application_segment.go b/zpa/data_source_zpa_application_segment.go index 053b654b..8cf7735e 100644 --- a/zpa/data_source_zpa_application_segment.go +++ b/zpa/data_source_zpa_application_segment.go @@ -224,24 +224,3 @@ func dataSourceApplicationSegmentRead(d *schema.ResourceData, meta interface{}) return nil } - -/* -func flattenAppServerGroups(serverGroup *applicationsegment.ApplicationSegmentResource) []interface{} { - serverGroups := make([]interface{}, len(serverGroup.ServerGroups)) - for i, val := range serverGroup.ServerGroups { - serverGroups[i] = map[string]interface{}{ - "name": val.Name, - "id": val.ID, - "config_space": val.ConfigSpace, - "creation_time": val.CreationTime, - "description": val.Description, - "enabled": val.Enabled, - "dynamic_discovery": val.DynamicDiscovery, - "modifiedby": val.ModifiedBy, - "modified_time": val.ModifiedTime, - } - } - - return serverGroups -} -*/ diff --git a/zpa/data_source_zpa_application_segment_browser_access.go b/zpa/data_source_zpa_application_segment_browser_access.go index e9d2237e..af0831b4 100644 --- a/zpa/data_source_zpa_application_segment_browser_access.go +++ b/zpa/data_source_zpa_application_segment_browser_access.go @@ -5,7 +5,7 @@ import ( "log" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/browseraccess" + "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/applicationsegmentbrowseraccess" ) func dataSourceApplicationSegmentBrowserAccess() *schema.Resource { @@ -208,11 +208,11 @@ func dataSourceApplicationSegmentBrowserAccessRead(d *schema.ResourceData, meta service = service.WithMicroTenant(microTenantID) } - var resp *browseraccess.BrowserAccess + var resp *applicationsegmentbrowseraccess.BrowserAccess id, ok := d.Get("id").(string) if ok && id != "" { log.Printf("[INFO] Getting data for browser access application %s\n", id) - res, _, err := browseraccess.Get(service, id) + res, _, err := applicationsegmentbrowseraccess.Get(service, id) if err != nil { return err } @@ -221,7 +221,7 @@ func dataSourceApplicationSegmentBrowserAccessRead(d *schema.ResourceData, meta name, ok := d.Get("name").(string) if id == "" && ok && name != "" { log.Printf("[INFO] Getting data for browser access application name %s\n", name) - res, _, err := browseraccess.GetByName(service, name) + res, _, err := applicationsegmentbrowseraccess.GetByName(service, name) if err != nil { return err } diff --git a/zpa/data_source_zpa_application_segment_inspection.go b/zpa/data_source_zpa_application_segment_inspection.go index e5b650e0..88a44d86 100644 --- a/zpa/data_source_zpa_application_segment_inspection.go +++ b/zpa/data_source_zpa_application_segment_inspection.go @@ -255,16 +255,26 @@ func dataSourceApplicationSegmentInspectionRead(d *schema.ResourceData, meta int return nil } -/* -func flattenInspectionAppServerGroups(appServerGroup []applicationsegmentinspection.AppServerGroups) []interface{} { - result := make([]interface{}, 1) - mapIds := make(map[string]interface{}) - ids := make([]string, len(appServerGroup)) - for i, serverGroup := range appServerGroup { - ids[i] = serverGroup.ID +func flattenInspectionApps(apps []applicationsegmentinspection.InspectionAppDto) []interface{} { + if len(apps) == 0 { + return []interface{}{} } - mapIds["id"] = ids - result[0] = mapIds - return result + + appsConfig := make([]interface{}, len(apps)) + for i, app := range apps { + appConfigMap := map[string]interface{}{ + "id": app.ID, + "name": app.Name, + "enabled": app.Enabled, + "application_port": app.ApplicationPort, + "application_protocol": app.ApplicationProtocol, + "certificate_id": app.CertificateID, + "certificate_name": app.CertificateName, + "domain": app.Domain, + "app_id": app.AppID, + "trusted_untrusted_cert": app.TrustUntrustedCert, + } + appsConfig[i] = appConfigMap + } + return appsConfig } -*/ diff --git a/zpa/provider_sweeper_test.go b/zpa/provider_sweeper_test.go index e3e6edb6..901de43f 100644 --- a/zpa/provider_sweeper_test.go +++ b/zpa/provider_sweeper_test.go @@ -13,11 +13,11 @@ import ( "github.com/zscaler/terraform-provider-zpa/v3/zpa/common/resourcetype" "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/appconnectorgroup" "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/applicationsegment" + "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/applicationsegmentbrowseraccess" "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/applicationsegmentinspection" "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/applicationsegmentpra" "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/appservercontroller" "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/bacertificate" - "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/browseraccess" "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/cloudbrowserisolation/cbibannercontroller" "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/cloudbrowserisolation/cbicertificatecontroller" "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/cloudbrowserisolation/cbiprofilecontroller" @@ -210,7 +210,7 @@ func sweepTestApplicationSegment(client *testClient) error { func sweepTestApplicationSegmentBA(client *testClient) error { var errorList []error - appSegmentBA, _, err := browseraccess.GetAll(client.sdkClient.BrowserAccess) + appSegmentBA, _, err := applicationsegmentbrowseraccess.GetAll(client.sdkClient.BrowserAccess) if err != nil { return err } @@ -219,7 +219,7 @@ func sweepTestApplicationSegmentBA(client *testClient) error { for _, b := range appSegmentBA { // Check if the resource name has the required prefix before deleting it if strings.HasPrefix(b.Name, testResourcePrefix) || strings.HasPrefix(b.Name, updateResourcePrefix) { - if _, err := browseraccess.Delete(client.sdkClient.BrowserAccess, b.ID); err != nil { + if _, err := applicationsegmentbrowseraccess.Delete(client.sdkClient.BrowserAccess, b.ID); err != nil { errorList = append(errorList, err) continue } diff --git a/zpa/resource_zpa_application_segment_browser_access.go b/zpa/resource_zpa_application_segment_browser_access.go index ec1771f8..31e3c451 100644 --- a/zpa/resource_zpa_application_segment_browser_access.go +++ b/zpa/resource_zpa_application_segment_browser_access.go @@ -8,8 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" client "github.com/zscaler/zscaler-sdk-go/v2/zpa" - "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/applicationsegment" - "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/browseraccess" + "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/applicationsegmentbrowseraccess" "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/common" ) @@ -35,7 +34,7 @@ func resourceApplicationSegmentBrowserAccess() *schema.Resource { // assume if the passed value is an int _ = d.Set("id", id) } else { - resp, _, err := browseraccess.GetByName(service, id) + resp, _, err := applicationsegmentbrowseraccess.GetByName(service, id) if err == nil { d.SetId(resp.ID) _ = d.Set("id", resp.ID) @@ -123,7 +122,7 @@ func resourceApplicationSegmentBrowserAccess() *schema.Resource { "health_check_type": { Type: schema.TypeString, Optional: true, - Default: "NONE", + Default: "DEFAULT", ValidateFunc: validation.StringInSlice([]string{ "DEFAULT", "NONE", @@ -175,7 +174,6 @@ func resourceApplicationSegmentBrowserAccess() *schema.Resource { "select_connector_close_to_app": { Type: schema.TypeBool, Optional: true, - ForceNew: true, }, "use_in_dr_mode": { Type: schema.TypeBool, @@ -200,6 +198,10 @@ func resourceApplicationSegmentBrowserAccess() *schema.Resource { Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "app_id": { + Type: schema.TypeString, + Computed: true, + }, "id": { Type: schema.TypeString, Computed: true, @@ -220,13 +222,11 @@ func resourceApplicationSegmentBrowserAccess() *schema.Resource { "application_port": { Type: schema.TypeString, Required: true, - ForceNew: true, Description: "Port for the BA app.", }, "application_protocol": { Type: schema.TypeString, Required: true, - ForceNew: true, Description: "Protocol for the BA app.", ValidateFunc: validation.StringInSlice([]string{ "HTTP", @@ -235,7 +235,6 @@ func resourceApplicationSegmentBrowserAccess() *schema.Resource { }, "certificate_id": { Type: schema.TypeString, - ForceNew: true, Optional: true, Description: "ID of the BA certificate.", }, @@ -305,7 +304,7 @@ func resourceApplicationSegmentBrowserAccessCreate(d *schema.ResourceData, meta return fmt.Errorf("please provide a valid segment group for the application segment") } - browseraccess, _, err := browseraccess.Create(service, req) + browseraccess, _, err := applicationsegmentbrowseraccess.Create(service, req) if err != nil { return err } @@ -325,7 +324,7 @@ func resourceApplicationSegmentBrowserAccessRead(d *schema.ResourceData, meta in service = service.WithMicroTenant(microTenantID) } - resp, _, err := browseraccess.Get(service, d.Id()) + resp, _, err := applicationsegmentbrowseraccess.Get(service, d.Id()) if err != nil { if errResp, ok := err.(*client.ErrorResponse); ok && errResp.IsObjectNotFound() { log.Printf("[WARN] Removing browser access %s from state because it no longer exists in ZPA", d.Id()) @@ -392,28 +391,47 @@ func resourceApplicationSegmentBrowserAccessUpdate(d *schema.ResourceData, meta id := d.Id() log.Printf("[INFO] Updating browser access ID: %v\n", id) + + // Step 1: Retrieve existing configuration to get app_id and clientless_apps.id + existingSegment, _, err := applicationsegmentbrowseraccess.Get(service, id) + if err != nil { + if respErr, ok := err.(*client.ErrorResponse); ok && respErr.IsObjectNotFound() { + d.SetId("") + return nil + } + return err + } + + // Step 2: Build the update payload req := expandBrowserAccess(d, zClient, "") + // Step 3: Inject app_id and clientless_apps.id from the existing configuration + req.ID = existingSegment.ID // Assign app_id to the parent application + for i, clientlessApp := range req.ClientlessApps { + if i < len(existingSegment.ClientlessApps) { + clientlessApp.ID = existingSegment.ClientlessApps[i].ID // Existing clientless_app id + clientlessApp.AppID = existingSegment.ID // Assign parent app_id to clientless_app + req.ClientlessApps[i] = clientlessApp + } + } + + // Validate the update request if err := validateAppPorts(req.SelectConnectorCloseToApp, req.UDPAppPortRange, req.UDPPortRanges); err != nil { return err } + // Step 4: Ensure segment_group_id is valid if changed if d.HasChange("segment_group_id") && req.SegmentGroupID == "" { log.Println("[ERROR] Please provide a valid segment group for the browser access application segment") return fmt.Errorf("please provide a valid segment group for the browser access application segment") } - if _, _, err := browseraccess.Get(service, id); err != nil { - if respErr, ok := err.(*client.ErrorResponse); ok && respErr.IsObjectNotFound() { - d.SetId("") - return nil - } - } - - if _, err := browseraccess.Update(service, id, &req); err != nil { + // Step 5: Perform the update + if _, err := applicationsegmentbrowseraccess.Update(service, id, &req); err != nil { return err } + // Step 6: Refresh the state after updating return resourceApplicationSegmentBrowserAccessRead(d, meta) } @@ -424,21 +442,21 @@ func resourceApplicationSegmentBrowserAccessDelete(d *schema.ResourceData, meta id := d.Id() log.Printf("[INFO] Deleting browser access application with id %v\n", id) - if _, err := browseraccess.Delete(service, id); err != nil { + if _, err := applicationsegmentbrowseraccess.Delete(service, id); err != nil { return err } return nil } -func expandBrowserAccess(d *schema.ResourceData, zClient *Client, id string) browseraccess.BrowserAccess { +func expandBrowserAccess(d *schema.ResourceData, zClient *Client, id string) applicationsegmentbrowseraccess.BrowserAccess { microTenantID := GetString(d.Get("microtenant_id")) service := zClient.BrowserAccess if microTenantID != "" { service = service.WithMicroTenant(microTenantID) } - details := browseraccess.BrowserAccess{ + details := applicationsegmentbrowseraccess.BrowserAccess{ ID: d.Id(), Name: d.Get("name").(string), SegmentGroupID: d.Get("segment_group_id").(string), @@ -469,7 +487,7 @@ func expandBrowserAccess(d *schema.ResourceData, zClient *Client, id string) bro remoteTCPAppPortRanges := []string{} remoteUDPAppPortRanges := []string{} if service != nil && id != "" { - resource, _, err := applicationsegment.Get(service, id) + resource, _, err := applicationsegmentbrowseraccess.Get(service, id) if err == nil { remoteTCPAppPortRanges = resource.TCPPortRanges remoteUDPAppPortRanges = resource.UDPPortRanges @@ -509,16 +527,18 @@ func expandBrowserAccess(d *schema.ResourceData, zClient *Client, id string) bro return details } -func expandClientlessApps(d *schema.ResourceData) []browseraccess.ClientlessApps { +func expandClientlessApps(d *schema.ResourceData) []applicationsegmentbrowseraccess.ClientlessApps { clientlessInterface, ok := d.GetOk("clientless_apps") if ok { clientless := clientlessInterface.([]interface{}) log.Printf("[INFO] clientless apps data: %+v\n", clientless) - var clientlessApps []browseraccess.ClientlessApps + var clientlessApps []applicationsegmentbrowseraccess.ClientlessApps for _, clientlessApp := range clientless { clientlessApp, ok := clientlessApp.(map[string]interface{}) if ok { - clientlessApps = append(clientlessApps, browseraccess.ClientlessApps{ + clientlessApps = append(clientlessApps, applicationsegmentbrowseraccess.ClientlessApps{ + ID: clientlessApp["id"].(string), + AppID: clientlessApp["app_id"].(string), AllowOptions: clientlessApp["allow_options"].(bool), ApplicationPort: clientlessApp["application_port"].(string), ApplicationProtocol: clientlessApp["application_protocol"].(string), @@ -535,14 +555,15 @@ func expandClientlessApps(d *schema.ResourceData) []browseraccess.ClientlessApps return clientlessApps } - return []browseraccess.ClientlessApps{} + return []applicationsegmentbrowseraccess.ClientlessApps{} } -func flattenBaClientlessApps(clientlessApp *browseraccess.BrowserAccess) []interface{} { +func flattenBaClientlessApps(clientlessApp *applicationsegmentbrowseraccess.BrowserAccess) []interface{} { clientlessApps := make([]interface{}, len(clientlessApp.ClientlessApps)) for i, clientlessApp := range clientlessApp.ClientlessApps { clientlessApps[i] = map[string]interface{}{ "id": clientlessApp.ID, + "app_id": clientlessApp.AppID, "name": clientlessApp.Name, "microtenant_id": clientlessApp.MicroTenantID, "allow_options": clientlessApp.AllowOptions, diff --git a/zpa/resource_zpa_application_segment_browser_access_test.go b/zpa/resource_zpa_application_segment_browser_access_test.go index 8827f0f3..4ec2b2e2 100644 --- a/zpa/resource_zpa_application_segment_browser_access_test.go +++ b/zpa/resource_zpa_application_segment_browser_access_test.go @@ -11,11 +11,11 @@ import ( "github.com/zscaler/terraform-provider-zpa/v3/zpa/common/resourcetype" "github.com/zscaler/terraform-provider-zpa/v3/zpa/common/testing/method" "github.com/zscaler/terraform-provider-zpa/v3/zpa/common/testing/variable" - "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/browseraccess" + "github.com/zscaler/zscaler-sdk-go/v2/zpa/services/applicationsegmentbrowseraccess" ) func TestAccResourceApplicationSegmentBrowserAccess_Basic(t *testing.T) { - var browserAccess browseraccess.BrowserAccess + var browserAccess applicationsegmentbrowseraccess.BrowserAccess browserAccessTypeAndName, _, browserAccessGeneratedName := method.GenerateRandomSourcesTypeAndName(resourcetype.ZPAApplicationSegmentBrowserAccess) rDomain := acctest.RandomWithPrefix("tf-acc-test") rDescription := acctest.RandomWithPrefix("tf-acc-test") @@ -79,7 +79,7 @@ func testAccCheckApplicationSegmentBrowserAccessDestroy(s *terraform.State) erro continue } - _, _, err := browseraccess.GetByName(apiClient.BrowserAccess, rs.Primary.Attributes["name"]) + _, _, err := applicationsegmentbrowseraccess.GetByName(apiClient.BrowserAccess, rs.Primary.Attributes["name"]) if err == nil { return fmt.Errorf("Broser Access still exists") } @@ -89,7 +89,7 @@ func testAccCheckApplicationSegmentBrowserAccessDestroy(s *terraform.State) erro return nil } -func testAccCheckApplicationSegmentBrowserAccessExists(resource string, segment *browseraccess.BrowserAccess) resource.TestCheckFunc { +func testAccCheckApplicationSegmentBrowserAccessExists(resource string, segment *applicationsegmentbrowseraccess.BrowserAccess) resource.TestCheckFunc { return func(state *terraform.State) error { rs, ok := state.RootModule().Resources[resource] if !ok { @@ -100,7 +100,7 @@ func testAccCheckApplicationSegmentBrowserAccessExists(resource string, segment } apiClient := testAccProvider.Meta().(*Client) - receivedSegment, _, err := browseraccess.Get(apiClient.BrowserAccess, rs.Primary.ID) + receivedSegment, _, err := applicationsegmentbrowseraccess.Get(apiClient.BrowserAccess, rs.Primary.ID) if err != nil { return fmt.Errorf("failed fetching resource %s. Recevied error: %s", resource, err) } diff --git a/zpa/resource_zpa_application_segment_inspection.go b/zpa/resource_zpa_application_segment_inspection.go index 709c873e..bf621faa 100644 --- a/zpa/resource_zpa_application_segment_inspection.go +++ b/zpa/resource_zpa_application_segment_inspection.go @@ -1,8 +1,10 @@ package zpa import ( + "context" "fmt" "log" + "strconv" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -14,11 +16,37 @@ import ( func resourceApplicationSegmentInspection() *schema.Resource { return &schema.Resource{ - Create: resourceApplicationSegmentInspectionCreate, - Read: resourceApplicationSegmentInspectionRead, - Update: resourceApplicationSegmentInspectionUpdate, - Delete: resourceApplicationSegmentInspectionDelete, - Importer: &schema.ResourceImporter{}, + Create: resourceApplicationSegmentInspectionCreate, + Read: resourceApplicationSegmentInspectionRead, + Update: resourceApplicationSegmentInspectionUpdate, + Delete: resourceApplicationSegmentInspectionDelete, + CustomizeDiff: customizeDiffApplicationSegmentInspection, + Importer: &schema.ResourceImporter{ + State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + client := meta.(*Client) + service := client.ApplicationSegmentInspection + + microTenantID := GetString(d.Get("microtenant_id")) + if microTenantID != "" { + service = service.WithMicroTenant(microTenantID) + } + + id := d.Id() + _, parseIDErr := strconv.ParseInt(id, 10, 64) + if parseIDErr == nil { + _ = d.Set("id", id) + } else { + resp, _, err := applicationsegmentinspection.GetByName(service, id) + if err == nil { + d.SetId(resp.ID) + _ = d.Set("id", resp.ID) + } else { + return []*schema.ResourceData{d}, err + } + } + return []*schema.ResourceData{d}, nil + }, + }, Schema: map[string]*schema.Schema{ "id": { @@ -35,6 +63,18 @@ func resourceApplicationSegmentInspection() *schema.Resource { Type: schema.TypeString, Required: true, }, + "adp_enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "Indicates if Active Directory Inspection is enabled or not for the application. This allows the application segment's traffic to be inspected by Active Directory (AD) Protection.", + }, + "auto_app_protect_enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "If autoAppProtectEnabled is set to true, this field indicates if the application segment’s traffic is inspected by AppProtection.", + }, "bypass_on_reauth": { Type: schema.TypeBool, Optional: true, @@ -61,6 +101,20 @@ func resourceApplicationSegmentInspection() *schema.Resource { Description: "TCP port ranges used to access the app.", Elem: &schema.Schema{Type: schema.TypeString}, }, + "tcp_protocols": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Description: "TCP port ranges used to access the app.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "udp_protocols": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Description: "TCP port ranges used to access the app.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, "udp_port_ranges": { Type: schema.TypeList, Optional: true, @@ -98,7 +152,7 @@ func resourceApplicationSegmentInspection() *schema.Resource { "health_check_type": { Type: schema.TypeString, Optional: true, - Default: "NONE", + Default: "DEFAULT", ValidateFunc: validation.StringInSlice([]string{ "DEFAULT", "NONE", @@ -167,72 +221,33 @@ func resourceApplicationSegmentInspection() *schema.Resource { "0", "1", }, false), }, - "inspection_apps": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Computed: true, - }, - "name": { - Type: schema.TypeString, - Computed: true, - }, - "enabled": { - Type: schema.TypeBool, - Computed: true, - }, - "application_port": { - Type: schema.TypeString, - Computed: true, - }, - "application_protocol": { - Type: schema.TypeString, - Computed: true, - }, - "certificate_id": { - Type: schema.TypeString, - Computed: true, - }, - "certificate_name": { - Type: schema.TypeString, - Computed: true, - }, - "domain": { - Type: schema.TypeString, - Computed: true, - }, - "app_id": { - Type: schema.TypeString, - Computed: true, - }, - "trusted_untrusted_cert": { - Type: schema.TypeBool, - Computed: true, - }, - }, - }, - }, "common_apps_dto": { Type: schema.TypeSet, Optional: true, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "apps_config": { Type: schema.TypeSet, Optional: true, - ForceNew: true, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "id": { + "app_id": { + Type: schema.TypeString, + Computed: true, + }, + "inspect_app_id": { Type: schema.TypeString, Computed: true, }, "name": { Type: schema.TypeString, - Optional: true, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, }, "enabled": { Type: schema.TypeBool, @@ -242,34 +257,36 @@ func resourceApplicationSegmentInspection() *schema.Resource { "app_types": { Type: schema.TypeSet, Optional: true, + Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "application_port": { Type: schema.TypeString, Optional: true, + Computed: true, }, "application_protocol": { Type: schema.TypeString, Optional: true, - // ForceNew: true, + Computed: true, ValidateFunc: validation.StringInSlice([]string{ "HTTP", "HTTPS", }, false), }, "certificate_id": { - Type: schema.TypeString, - // ForceNew: true, + Type: schema.TypeString, + Computed: true, Optional: true, }, "domain": { - Type: schema.TypeString, - // ForceNew: true, + Type: schema.TypeString, + Computed: true, Optional: true, }, "trust_untrusted_cert": { - Type: schema.TypeBool, - // ForceNew: true, + Type: schema.TypeBool, + Computed: true, Optional: true, }, }, @@ -308,9 +325,7 @@ func resourceApplicationSegmentInspectionCreate(d *schema.ResourceData, meta int if err := validateAppPorts(req.SelectConnectorCloseToApp, req.UDPAppPortRange, req.UDPPortRanges); err != nil { return err } - if err := validateProtocolAndCertID(d); err != nil { - return err - } + log.Printf("[INFO] Creating application segment request\n%+v\n", req) if req.SegmentGroupID == "" { log.Println("[ERROR] Please provide a valid segment group for the application segment") @@ -344,14 +359,16 @@ func resourceApplicationSegmentInspectionRead(d *schema.ResourceData, meta inter log.Printf("[INFO] Getting sra application segment:\n%+v\n", resp) d.SetId(resp.ID) + _ = d.Set("name", resp.Name) + _ = d.Set("enabled", resp.Enabled) + _ = d.Set("adp_enabled", resp.AdpEnabled) + _ = d.Set("auto_app_protect_enabled", resp.AutoAppProtectEnabled) _ = d.Set("segment_group_id", resp.SegmentGroupID) _ = d.Set("bypass_type", resp.BypassType) _ = d.Set("bypass_on_reauth", resp.BypassOnReauth) _ = d.Set("config_space", resp.ConfigSpace) _ = d.Set("domain_names", resp.DomainNames) - _ = d.Set("name", resp.Name) _ = d.Set("description", resp.Description) - _ = d.Set("enabled", resp.Enabled) _ = d.Set("passive_health_enabled", resp.PassiveHealthEnabled) _ = d.Set("double_encrypt", resp.DoubleEncrypt) _ = d.Set("health_check_type", resp.HealthCheckType) @@ -363,10 +380,13 @@ func resourceApplicationSegmentInspectionRead(d *schema.ResourceData, meta inter _ = d.Set("tcp_keep_alive", resp.TCPKeepAlive) _ = d.Set("ip_anchored", resp.IPAnchored) _ = d.Set("health_reporting", resp.HealthReporting) + _ = d.Set("tcp_protocols", resp.TCPProtocols) + _ = d.Set("udp_protocols", resp.UDPProtocols) _ = d.Set("server_groups", flattenCommonAppServerGroups(resp.AppServerGroups)) - if err := d.Set("inspection_apps", flattenInspectionApps(resp.InspectionAppDto)); err != nil { - return fmt.Errorf("failed to read inspection apps in application segment %s", err) + // Map inspect to common_apps_dto.apps_config for state management + if err := mapInspectAppsToCommonApps(d, resp.InspectionAppDto); err != nil { + return err } _ = d.Set("tcp_port_ranges", convertPortsToListString(resp.TCPAppPortRange)) @@ -393,30 +413,33 @@ func resourceApplicationSegmentInspectionUpdate(d *schema.ResourceData, meta int id := d.Id() log.Printf("[INFO] Updating inspection application segment ID: %v\n", id) - req := expandInspectionApplicationSegment(d, zClient, "") - if err := validateAppPorts(req.SelectConnectorCloseToApp, req.UDPAppPortRange, req.UDPPortRanges); err != nil { - return err - } - - if d.HasChange("segment_group_id") && req.SegmentGroupID == "" { - log.Println("[ERROR] Please provide a valid segment group for the inspection application segment") - return fmt.Errorf("please provide a valid segment group for the inspection application segment") - } - if err := validateProtocolAndCertID(d); err != nil { - return err - } - if _, _, err := applicationsegmentinspection.Get(service, id); err != nil { + // Retrieve the current resource to get app_id and pra_app_id + resp, _, err := applicationsegmentinspection.Get(service, id) + if err != nil { if respErr, ok := err.(*client.ErrorResponse); ok && respErr.IsObjectNotFound() { d.SetId("") return nil } + return fmt.Errorf("error retrieving application segment: %v", err) } - if _, err := applicationsegmentinspection.Update(service, id, &req); err != nil { + // Extract app_id and inspect_app_id from praApps and set in common_apps_dto in state + if err := setInspectionAppIDsInCommonAppsDto(d, resp.InspectionAppDto); err != nil { + return fmt.Errorf("error setting app_id and inspect_app_id in common_apps_dto: %v", err) + } + + req := expandInspectionApplicationSegment(d, zClient, "") + + if err := validateAppPorts(req.SelectConnectorCloseToApp, req.UDPAppPortRange, req.UDPPortRanges); err != nil { return err } + _, err = applicationsegmentinspection.Update(service, id, &req) + if err != nil { + return fmt.Errorf("error updating application segment inspection: %v", err) + } + return resourceApplicationSegmentInspectionRead(d, meta) } @@ -461,9 +484,11 @@ func expandInspectionApplicationSegment(d *schema.ResourceData, zClient *Client, Description: d.Get("description").(string), HealthReporting: d.Get("health_reporting").(string), HealthCheckType: d.Get("health_check_type").(string), + AdpEnabled: d.Get("adp_enabled").(bool), + AutoAppProtectEnabled: d.Get("auto_app_protect_enabled").(bool), PassiveHealthEnabled: d.Get("passive_health_enabled").(bool), - DoubleEncrypt: d.Get("double_encrypt").(bool), Enabled: d.Get("enabled").(bool), + DoubleEncrypt: d.Get("double_encrypt").(bool), IPAnchored: d.Get("ip_anchored").(bool), IsCnameEnabled: d.Get("is_cname_enabled").(bool), SelectConnectorCloseToApp: d.Get("select_connector_close_to_app").(bool), @@ -471,6 +496,8 @@ func expandInspectionApplicationSegment(d *schema.ResourceData, zClient *Client, TCPKeepAlive: d.Get("tcp_keep_alive").(string), IsIncompleteDRConfig: d.Get("is_incomplete_dr_config").(bool), DomainNames: expandStringInSlice(d, "domain_names"), + TCPProtocols: expandStringInSlice(d, "tcp_protocols"), + UDPProtocols: expandStringInSlice(d, "udp_protocols"), AppServerGroups: expandCommonServerGroups(d), CommonAppsDto: expandInspectionCommonAppsDto(d), @@ -539,66 +566,144 @@ func expandInspectionAppsConfig(appsConfigInterface interface{}) []applicationse log.Printf("[INFO] apps config data: %+v\n", appsConfig) var commonAppConfigDto []applicationsegmentinspection.AppsConfig for _, commonAppConfig := range appsConfig.List() { - commonAppConfig, ok := commonAppConfig.(map[string]interface{}) + appConfigMap, ok := commonAppConfig.(map[string]interface{}) if ok { - appTypesSet, ok := commonAppConfig["app_types"].(*schema.Set) + // Automatically set `name` to match `domain` to prevent drift + appConfigMap["name"] = appConfigMap["domain"].(string) + + appTypesSet, ok := appConfigMap["app_types"].(*schema.Set) if !ok { continue } appTypes := SetToStringSlice(appTypesSet) + + // Retrieve protocols as a slice of strings + // protocolsSet, ok := appConfigMap["protocols"].(*schema.Set) + // var protocols []string + // if ok { + // protocols = SetToStringSlice(protocolsSet) + // } + commonAppConfigDto = append(commonAppConfigDto, applicationsegmentinspection.AppsConfig{ - Name: commonAppConfig["name"].(string), - Enabled: commonAppConfig["enabled"].(bool), - ApplicationPort: commonAppConfig["application_port"].(string), - ApplicationProtocol: commonAppConfig["application_protocol"].(string), - CertificateID: commonAppConfig["certificate_id"].(string), - Domain: commonAppConfig["domain"].(string), - TrustUntrustedCert: commonAppConfig["trust_untrusted_cert"].(bool), - AppTypes: appTypes, + AppID: appConfigMap["app_id"].(string), + InspectAppID: appConfigMap["inspect_app_id"].(string), + Name: appConfigMap["name"].(string), + Description: appConfigMap["description"].(string), + Enabled: appConfigMap["enabled"].(bool), + ApplicationPort: appConfigMap["application_port"].(string), + ApplicationProtocol: appConfigMap["application_protocol"].(string), + CertificateID: appConfigMap["certificate_id"].(string), + Domain: appConfigMap["domain"].(string), + TrustUntrustedCert: appConfigMap["trust_untrusted_cert"].(bool), + // Protocols: protocols, // Set protocols here + AppTypes: appTypes, }) } } return commonAppConfigDto } -func flattenInspectionApps(apps []applicationsegmentinspection.InspectionAppDto) []interface{} { - if len(apps) == 0 { - return []interface{}{} - } - - appsConfig := make([]interface{}, len(apps)) - for i, app := range apps { - appConfigMap := map[string]interface{}{ - "id": app.ID, - "name": app.Name, - "enabled": app.Enabled, - "application_port": app.ApplicationPort, - "application_protocol": app.ApplicationProtocol, - "certificate_id": app.CertificateID, - "certificate_name": app.CertificateName, - "domain": app.Domain, - "app_id": app.AppID, - "trusted_untrusted_cert": app.TrustUntrustedCert, +func mapInspectAppsToCommonApps(d *schema.ResourceData, inspectionApps []applicationsegmentinspection.InspectionAppDto) error { + // If the API returned any Inspection Apps, map them to common_apps_dto.apps_config + if len(inspectionApps) == 0 { + return nil + } + + // Create a single common_apps_dto with multiple apps_config blocks + commonAppsConfig := make([]interface{}, len(inspectionApps)) + for i, app := range inspectionApps { + commonAppMap := map[string]interface{}{ + "app_id": app.AppID, // Populate app_id from InspectionAppDto + "app_types": []interface{}{"INSPECT"}, + "application_protocol": app.ApplicationProtocol, + "application_port": app.ApplicationPort, + "certificate_id": app.CertificateID, + "description": app.Description, + "domain": app.Domain, + "enabled": app.Enabled, + "name": app.Name, + // "protocols": app.Protocols, + "trust_untrusted_cert": app.TrustUntrustedCert, } - appsConfig[i] = appConfigMap + // Only set inspect_app_id if it's present in the response + if app.ID != "" { + commonAppMap["inspect_app_id"] = app.ID // Populate inspect_app_id from InspectAppID + } + commonAppsConfig[i] = commonAppMap + } + + // Wrap commonAppsConfig in the common_apps_dto block + commonAppsDto := []interface{}{ + map[string]interface{}{ + "apps_config": commonAppsConfig, + }, + } + + // Set common_apps_dto in the resource data + if err := d.Set("common_apps_dto", commonAppsDto); err != nil { + return fmt.Errorf("failed to set common_apps_dto: %s", err) + } + return nil +} + +func setInspectionAppIDsInCommonAppsDto(d *schema.ResourceData, inspectionApps []applicationsegmentinspection.InspectionAppDto) error { + if len(inspectionApps) == 0 { + return nil + } + + // Extract app_id and inspect_app_id from the first Inspect app in the list + appID := inspectionApps[0].AppID + inspectAppID := inspectionApps[0].ID + + // Update the common_apps_dto with extracted app_id and inspect_app_id values + commonAppsDto := d.Get("common_apps_dto").(*schema.Set).List() + if len(commonAppsDto) == 0 { + return fmt.Errorf("common_apps_dto block is missing") } - return appsConfig + + // Update the first entry in commonAppsDto.appsConfig with app_id and inspect_app_id + commonAppConfig := commonAppsDto[0].(map[string]interface{}) + appsConfig := commonAppConfig["apps_config"].(*schema.Set).List() + + if len(appsConfig) > 0 { + appConfig := appsConfig[0].(map[string]interface{}) + appConfig["app_id"] = appID + appConfig["inspect_app_id"] = inspectAppID + } + + // Write the updated config back to the resource data + if err := d.Set("common_apps_dto", commonAppsDto); err != nil { + return fmt.Errorf("failed to set common_apps_dto: %v", err) + } + + return nil } -func validateProtocolAndCertID(d *schema.ResourceData) error { + +func customizeDiffApplicationSegmentInspection(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + // Validation for adp_enabled and auto_app_protect_enabled attributes + adpEnabled := d.Get("adp_enabled").(bool) + autoAppProtectEnabled := d.Get("auto_app_protect_enabled").(bool) + if adpEnabled && autoAppProtectEnabled { + return fmt.Errorf("if 'adp_enabled' is set to true, 'auto_app_protect_enabled' cannot be true") + } + + // Validation for common_apps_dto.apps_config fields commonAppsDto, ok := d.GetOk("common_apps_dto") if !ok || len(commonAppsDto.(*schema.Set).List()) == 0 { - return nil // or handle it as per your logic + return nil // If there's no common_apps_dto, skip further validation } appsConfig := commonAppsDto.(*schema.Set).List()[0].(map[string]interface{})["apps_config"].(*schema.Set).List() for _, config := range appsConfig { appConfig := config.(map[string]interface{}) protocol := appConfig["application_protocol"].(string) - certID := appConfig["certificate_id"].(string) + certID, hasCertID := appConfig["certificate_id"] - if protocol == "HTTP" && certID != "" { - return fmt.Errorf("certificate ID should not be set when application protocol is HTTP") + // Check if protocol is HTTP and certificate ID is set + if protocol == "HTTP" && hasCertID && certID.(string) != "" { + return fmt.Errorf("certificate ID should not be set when 'application_protocol' is HTTP") } } + return nil } diff --git a/zpa/resource_zpa_application_segment_inspection_test.go b/zpa/resource_zpa_application_segment_inspection_test.go index 0c218914..6a3bc9a2 100644 --- a/zpa/resource_zpa_application_segment_inspection_test.go +++ b/zpa/resource_zpa_application_segment_inspection_test.go @@ -152,7 +152,6 @@ resource "%s" "%s" { segment_group_id = "${%s.id}" common_apps_dto { apps_config { - name = "sales.bd-hashicorp.com" domain = "sales.bd-hashicorp.com" application_protocol = "HTTPS" application_port = "443" diff --git a/zpa/resource_zpa_application_segment_pra.go b/zpa/resource_zpa_application_segment_pra.go index 98d2db36..7f0e5747 100644 --- a/zpa/resource_zpa_application_segment_pra.go +++ b/zpa/resource_zpa_application_segment_pra.go @@ -5,10 +5,7 @@ import ( "errors" "fmt" "log" - "math" - "math/rand" "strconv" - "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -133,7 +130,7 @@ func resourceApplicationSegmentPRA() *schema.Resource { "health_check_type": { Type: schema.TypeString, Optional: true, - Default: "NONE", + Default: "DEFAULT", ValidateFunc: validation.StringInSlice([]string{ "DEFAULT", "NONE", @@ -178,7 +175,6 @@ func resourceApplicationSegmentPRA() *schema.Resource { "select_connector_close_to_app": { Type: schema.TypeBool, Optional: true, - ForceNew: true, }, "use_in_dr_mode": { Type: schema.TypeBool, @@ -204,76 +200,29 @@ func resourceApplicationSegmentPRA() *schema.Resource { "0", "1", }, false), }, - // "pra_apps": { - // Type: schema.TypeSet, - // Computed: true, - // Elem: &schema.Resource{ - // Schema: map[string]*schema.Schema{ - // "id": { - // Type: schema.TypeString, - // Computed: true, - // }, - // "name": { - // Type: schema.TypeString, - // Computed: true, - // }, - // "enabled": { - // Type: schema.TypeBool, - // Computed: true, - // }, - // "application_port": { - // Type: schema.TypeString, - // Computed: true, - // }, - // "application_protocol": { - // Type: schema.TypeString, - // Computed: true, - // }, - // "connection_security": { - // Type: schema.TypeString, - // Computed: true, - // }, - // "domain": { - // Type: schema.TypeString, - // Computed: true, - // }, - // "app_id": { - // Type: schema.TypeString, - // Computed: true, - // }, - // "hidden": { - // Type: schema.TypeBool, - // Computed: true, - // }, - // "microtenant_id": { - // Type: schema.TypeString, - // Computed: true, - // }, - // }, - // }, - // }, "common_apps_dto": { Type: schema.TypeSet, Optional: true, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "apps_config": { Type: schema.TypeSet, Optional: true, - ForceNew: true, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "id": { + "app_id": { Type: schema.TypeString, Computed: true, }, - "microtenant_id": { + "pra_app_id": { Type: schema.TypeString, - Optional: true, + Computed: true, }, "name": { Type: schema.TypeString, - Optional: true, + Computed: true, }, "enabled": { Type: schema.TypeBool, @@ -283,15 +232,18 @@ func resourceApplicationSegmentPRA() *schema.Resource { "app_types": { Type: schema.TypeSet, Optional: true, + Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, "application_port": { Type: schema.TypeString, Optional: true, + Computed: true, }, "application_protocol": { Type: schema.TypeString, Optional: true, + Computed: true, ValidateFunc: validation.StringInSlice([]string{ "RDP", "SSH", "VNC", }, false), @@ -299,6 +251,7 @@ func resourceApplicationSegmentPRA() *schema.Resource { "connection_security": { Type: schema.TypeString, Optional: true, + Computed: true, ValidateFunc: validation.StringInSlice([]string{ "ANY", "NLA", "NLA_EXT", "TLS", "VM_CONNECT", "RDP", }, false), @@ -306,6 +259,7 @@ func resourceApplicationSegmentPRA() *schema.Resource { "domain": { Type: schema.TypeString, Optional: true, + Computed: true, }, }, }, @@ -346,9 +300,6 @@ func resourceApplicationSegmentPRACreate(d *schema.ResourceData, meta interface{ } req := expandSRAApplicationSegment(d, zClient, "") - if err := checkForPRAPortsOverlap(zClient, req); err != nil { - return err - } if err := validateAppPorts(req.SelectConnectorCloseToApp, req.UDPAppPortRange, req.UDPPortRanges); err != nil { return err @@ -364,18 +315,6 @@ func resourceApplicationSegmentPRACreate(d *schema.ResourceData, meta interface{ log.Printf("[INFO] Created application segment request. ID: %v\n", resp.ID) d.SetId(resp.ID) - // Introduce a brief delay to allow the backend to fully process the creation - //lintignore:R018 - time.Sleep(5 * time.Second) - - // Explicitly call GET using the ID to fetch the latest resource state - _, _, err = applicationsegmentpra.Get(service, resp.ID) - if err != nil { - log.Printf("[ERROR] Failed to fetch application segment after creation: %s", err) - return err - } - - // Now, update Terraform state with the latest fetched details return resourceApplicationSegmentPRARead(d, meta) } @@ -422,20 +361,15 @@ func resourceApplicationSegmentPRARead(d *schema.ResourceData, meta interface{}) _ = d.Set("health_reporting", resp.HealthReporting) _ = d.Set("server_groups", flattenCommonAppServerGroups(resp.ServerGroups)) - // Set pra_apps but suppress diffs during plan/apply - // if err := d.Set("pra_apps", flattenPRAApps(resp.PRAApps)); err != nil { - // return fmt.Errorf("failed to read pra_apps in application segment %s", err) - // } - // Map pra_apps to common_apps_dto.apps_config for state management if err := mapPRAAppsToCommonApps(d, resp.PRAApps); err != nil { return err } // Map pra_apps back to common_apps_dto.apps_config - if err := mapPRAAppsToCommonApps(d, resp.PRAApps); err != nil { - return err - } + // if err := mapPRAAppsToCommonApps(d, resp.PRAApps); err != nil { + // return err + // } _ = d.Set("tcp_port_ranges", convertPortsToListString(resp.TCPAppPortRange)) _ = d.Set("udp_port_ranges", convertPortsToListString(resp.UDPAppPortRange)) @@ -451,43 +385,6 @@ func resourceApplicationSegmentPRARead(d *schema.ResourceData, meta interface{}) return nil } -func mapPRAAppsToCommonApps(d *schema.ResourceData, praApps []applicationsegmentpra.PRAApps) error { - // If the API returned any PRA Apps, map them to common_apps_dto.apps_config - if len(praApps) == 0 { - return nil - } - - // Create a single common_apps_dto with multiple apps_config blocks - commonAppsConfig := make([]interface{}, len(praApps)) - for i, app := range praApps { - commonAppMap := map[string]interface{}{ - "name": app.Name, - "domain": app.Domain, - "application_protocol": app.ApplicationProtocol, - "application_port": app.ApplicationPort, - "enabled": app.Enabled, - "app_types": []interface{}{"SECURE_REMOTE_ACCESS"}, // Adjust app types as needed - "id": nil, // Set to null as required - "microtenant_id": nil, // Set to null as required - "connection_security": app.ConnectionSecurity, - } - commonAppsConfig[i] = commonAppMap - } - - // Wrap commonAppsConfig in the common_apps_dto block - commonAppsDto := []interface{}{ - map[string]interface{}{ - "apps_config": commonAppsConfig, - }, - } - - // Set common_apps_dto in the resource data - if err := d.Set("common_apps_dto", commonAppsDto); err != nil { - return fmt.Errorf("failed to set common_apps_dto: %s", err) - } - return nil -} - func resourceApplicationSegmentPRAUpdate(d *schema.ResourceData, meta interface{}) error { zClient := meta.(*Client) service := zClient.ApplicationSegmentPRA @@ -498,42 +395,37 @@ func resourceApplicationSegmentPRAUpdate(d *schema.ResourceData, meta interface{ } id := d.Id() - log.Printf("[INFO] Updating pra application segment ID: %v\n", id) - req := expandSRAApplicationSegment(d, zClient, "") - - if err := checkForPRAPortsOverlap(zClient, req); err != nil { - return err - } + log.Printf("[INFO] Updating PRA application segment ID: %v\n", id) - if err := validateAppPorts(req.SelectConnectorCloseToApp, req.UDPAppPortRange, req.UDPPortRanges); err != nil { - return err - } - - if _, _, err := applicationsegmentpra.Get(service, id); err != nil { + // Retrieve the current resource to get app_id and pra_app_id + resp, _, err := applicationsegmentpra.Get(service, id) + if err != nil { if respErr, ok := err.(*client.ErrorResponse); ok && respErr.IsObjectNotFound() { d.SetId("") return nil } + return fmt.Errorf("error retrieving application segment: %v", err) } - // Perform the update - _, err := applicationsegmentpra.Update(service, id, &req) - if err != nil { - return err + // Extract app_id and pra_app_id from praApps and set in common_apps_dto in state + if err := setAppIDsInCommonAppsDto(d, resp.PRAApps); err != nil { + return fmt.Errorf("error setting app_id and pra_app_id in common_apps_dto: %v", err) } - // Introduce a brief delay to allow the backend to fully process the update - //lintignore:R018 - time.Sleep(5 * time.Second) + // Prepare the request payload for the update + req := expandSRAApplicationSegment(d, zClient, "") - // Fetch the latest resource state after the update - _, _, err = applicationsegmentpra.Get(service, id) - if err != nil { - log.Printf("[ERROR] Failed to fetch application segment after update: %s", err) + if err := validateAppPorts(req.SelectConnectorCloseToApp, req.UDPAppPortRange, req.UDPPortRanges); err != nil { return err } - // Now, update Terraform state with the latest fetched details + // Perform the update + _, err = applicationsegmentpra.Update(service, id, &req) + if err != nil { + return fmt.Errorf("error updating application segment: %v", err) + } + + // Refresh the state after the update to ensure correctness return resourceApplicationSegmentPRARead(d, meta) } @@ -651,21 +543,25 @@ func expandAppsConfig(appsConfigInterface interface{}) []applicationsegmentpra.A log.Printf("[INFO] apps config data: %+v\n", appsConfig) var commonAppConfigDto []applicationsegmentpra.AppsConfig for _, commonAppConfig := range appsConfig.List() { - commonAppConfig, ok := commonAppConfig.(map[string]interface{}) + appConfigMap, ok := commonAppConfig.(map[string]interface{}) if ok { - appTypesSet, ok := commonAppConfig["app_types"].(*schema.Set) + // Automatically set `name` to match `domain` to prevent drift + appConfigMap["name"] = appConfigMap["domain"].(string) + + appTypesSet, ok := appConfigMap["app_types"].(*schema.Set) if !ok { continue } appTypes := SetToStringSlice(appTypesSet) commonAppConfigDto = append(commonAppConfigDto, applicationsegmentpra.AppsConfig{ - ID: commonAppConfig["id"].(string), - Name: commonAppConfig["name"].(string), - Enabled: commonAppConfig["enabled"].(bool), - Domain: commonAppConfig["domain"].(string), - ApplicationPort: commonAppConfig["application_port"].(string), - ApplicationProtocol: commonAppConfig["application_protocol"].(string), - ConnectionSecurity: commonAppConfig["connection_security"].(string), + AppID: appConfigMap["app_id"].(string), + PRAAppID: appConfigMap["pra_app_id"].(string), + Name: appConfigMap["name"].(string), + Enabled: appConfigMap["enabled"].(bool), + Domain: appConfigMap["domain"].(string), + ApplicationPort: appConfigMap["application_port"].(string), + ApplicationProtocol: appConfigMap["application_protocol"].(string), + ConnectionSecurity: appConfigMap["connection_security"].(string), AppTypes: appTypes, }) } @@ -673,6 +569,111 @@ func expandAppsConfig(appsConfigInterface interface{}) []applicationsegmentpra.A return commonAppConfigDto } +func customizeDiffApplicationSegmentPRA(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + + // Clear any diffs related to pra_apps to prevent it from being included in the plan or state + if d.HasChange("pra_apps") { + d.Clear("pra_apps") + } + + commonAppsDto := d.Get("common_apps_dto").(*schema.Set).List() + + for _, dto := range commonAppsDto { + dtoMap := dto.(map[string]interface{}) + appsConfig := dtoMap["apps_config"].(*schema.Set).List() + + for _, appConfig := range appsConfig { + appConfigMap := appConfig.(map[string]interface{}) + appProtocol := appConfigMap["application_protocol"].(string) + connSecurity, connSecurityExists := appConfigMap["connection_security"] + + if appProtocol == "RDP" { + if !connSecurityExists || connSecurity.(string) == "" { + return errors.New("connection_security is required when application_protocol is RDP") + } + } else { + if connSecurityExists && connSecurity.(string) != "" { + return errors.New("connection_security can only be set when application_protocol is RDP") + } + } + } + } + return nil +} + +func mapPRAAppsToCommonApps(d *schema.ResourceData, praApps []applicationsegmentpra.PRAApps) error { + // If the API returned any PRA Apps, map them to common_apps_dto.apps_config + if len(praApps) == 0 { + return nil + } + + // Create a single common_apps_dto with multiple apps_config blocks + commonAppsConfig := make([]interface{}, len(praApps)) + for i, app := range praApps { + commonAppMap := map[string]interface{}{ + "name": app.Name, + "domain": app.Domain, + "application_protocol": app.ApplicationProtocol, + "application_port": app.ApplicationPort, + "enabled": app.Enabled, + "app_types": []interface{}{"SECURE_REMOTE_ACCESS"}, + "app_id": app.AppID, // Populate app_id from praApps + "connection_security": app.ConnectionSecurity, + } + // Only set pra_app_id if it's present in the response + if app.ID != "" { + commonAppMap["pra_app_id"] = app.ID // Populate pra_app_id from praApps + } + commonAppsConfig[i] = commonAppMap + } + + // Wrap commonAppsConfig in the common_apps_dto block + commonAppsDto := []interface{}{ + map[string]interface{}{ + "apps_config": commonAppsConfig, + }, + } + + // Set common_apps_dto in the resource data + if err := d.Set("common_apps_dto", commonAppsDto); err != nil { + return fmt.Errorf("failed to set common_apps_dto: %s", err) + } + return nil +} + +func setAppIDsInCommonAppsDto(d *schema.ResourceData, praApps []applicationsegmentpra.PRAApps) error { + if len(praApps) == 0 { + return nil + } + + // Extract app_id and pra_app_id from the first PRA app in the list + appID := praApps[0].AppID + praAppID := praApps[0].ID + + // Update the common_apps_dto with extracted app_id and pra_app_id values + commonAppsDto := d.Get("common_apps_dto").(*schema.Set).List() + if len(commonAppsDto) == 0 { + return fmt.Errorf("common_apps_dto block is missing") + } + + // Update the first entry in commonAppsDto.appsConfig with app_id and pra_app_id + commonAppConfig := commonAppsDto[0].(map[string]interface{}) + appsConfig := commonAppConfig["apps_config"].(*schema.Set).List() + + if len(appsConfig) > 0 { + appConfig := appsConfig[0].(map[string]interface{}) + appConfig["app_id"] = appID + appConfig["pra_app_id"] = praAppID + } + + // Write the updated config back to the resource data + if err := d.Set("common_apps_dto", commonAppsDto); err != nil { + return fmt.Errorf("failed to set common_apps_dto: %v", err) + } + + return nil +} + /* func flattenPRAApps(apps []applicationsegmentpra.PRAApps) []interface{} { if len(apps) == 0 { @@ -697,7 +698,7 @@ func flattenPRAApps(apps []applicationsegmentpra.PRAApps) []interface{} { } return appsConfig } -*/ + func checkForPRAPortsOverlap(client *Client, app applicationsegmentpra.AppSegmentPRA) error { //lintignore:R018 @@ -756,38 +757,7 @@ func PRAPortOverlap(s1, s2 []string) (bool, []string, []string) { } return false, nil, nil } - -func customizeDiffApplicationSegmentPRA(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { - - // Clear any diffs related to pra_apps to prevent it from being included in the plan or state - if d.HasChange("pra_apps") { - d.Clear("pra_apps") - } - - commonAppsDto := d.Get("common_apps_dto").(*schema.Set).List() - - for _, dto := range commonAppsDto { - dtoMap := dto.(map[string]interface{}) - appsConfig := dtoMap["apps_config"].(*schema.Set).List() - - for _, appConfig := range appsConfig { - appConfigMap := appConfig.(map[string]interface{}) - appProtocol := appConfigMap["application_protocol"].(string) - connSecurity, connSecurityExists := appConfigMap["connection_security"] - - if appProtocol == "RDP" { - if !connSecurityExists || connSecurity.(string) == "" { - return errors.New("connection_security is required when application_protocol is RDP") - } - } else { - if connSecurityExists && connSecurity.(string) != "" { - return errors.New("connection_security can only be set when application_protocol is RDP") - } - } - } - } - return nil -} +*/ /* func expandPRAAppServerGroups(d *schema.ResourceData) []applicationsegmentpra.AppServerGroups { diff --git a/zpa/resource_zpa_application_segment_pra_test.go b/zpa/resource_zpa_application_segment_pra_test.go index e46f1598..f3dea66a 100644 --- a/zpa/resource_zpa_application_segment_pra_test.go +++ b/zpa/resource_zpa_application_segment_pra_test.go @@ -148,7 +148,6 @@ resource "%s" "%s" { tcp_keep_alive = "1" common_apps_dto { apps_config { - name = "ssh_pra.example.com" domain = "ssh_pra.example.com" application_protocol = "SSH" application_port = "22" @@ -156,7 +155,6 @@ resource "%s" "%s" { app_types = ["SECURE_REMOTE_ACCESS"] } apps_config { - name = "rdp_pra.example.com" domain = "rdp_pra.example.com" application_protocol = "RDP" connection_security = "ANY" diff --git a/zpa/resource_zpa_policy_access_rule.go b/zpa/resource_zpa_policy_access_rule.go index b43dbefd..9f28d797 100644 --- a/zpa/resource_zpa_policy_access_rule.go +++ b/zpa/resource_zpa_policy_access_rule.go @@ -165,7 +165,7 @@ func resourcePolicyAccessRead(d *schema.ResourceData, meta interface{}) error { _ = d.Set("default_rule", resp.DefaultRule) _ = d.Set("operator", resp.Operator) _ = d.Set("policy_set_id", policySetID) - _ = d.Set("policy_type", "ACCESS_POLICY") + _ = d.Set("policy_type", resp.PolicyType) _ = d.Set("priority", resp.Priority) _ = d.Set("lss_default_rule", resp.LSSDefaultRule) _ = d.Set("microtenant_id", microTenantID) diff --git a/zpa/utils.go b/zpa/utils.go index e69963a7..e595d86f 100755 --- a/zpa/utils.go +++ b/zpa/utils.go @@ -237,11 +237,18 @@ func GetString(v interface{}) string { } // Helper to safely extract bool values from map -func GetBool(v interface{}) bool { - if b, ok := v.(bool); ok { - return b +// func GetBool(v interface{}) bool { +// if b, ok := v.(bool); ok { +// return b +// } +// return false +// } + +func GetBool(input interface{}) bool { + if input == nil { + return false } - return false + return input.(bool) } // Converts an epoch time (in seconds, represented as a string) to a human-readable format.