diff --git a/.github/workflows/gotest-cloud-eu.yml b/.github/workflows/gotest-cloud-eu.yml new file mode 100644 index 0000000..c9a95db --- /dev/null +++ b/.github/workflows/gotest-cloud-eu.yml @@ -0,0 +1,150 @@ +# This is a Go Test workflow that is manually triggered + +name: Go Test - Cloud - EU + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: + pull_request: + branches: + - main + paths: + - 'internal/**' + - '**.go' + types: [opened, reopened, synchronize] + workflow_dispatch: + # Inputs the workflow accepts. + inputs: + version: + # Friendly description to be shown in the UI + description: 'Release version that is tested against.' + # Default value if no value is explicitly provided + default: 'Latest' + # Input has to be provided for the workflow to run + required: false + # The data type of the input + type: string + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a job for preparing test env, and a job for running go tests + gotest-prep: + # GitHub environment for the job + environment: Go Test - Cloud - EU + # The type of runner that the job will run on + runs-on: ubuntu-latest + + steps: + # Checkout repo + - name: Checkout Repo + uses: actions/checkout@v4 + + - name: Run Azure PowerShell inline script + uses: azure/powershell@v2 + with: + inlineScript: | + ./scripts/test-env-power-management/azure_env.ps1 ` + -ClientId ${{secrets.CLIENT_ID}} ` + -ClientSecret ${{secrets.CLIENT_SECRET}} ` + -DomainFqdn ${{secrets.DOMAIN_FQDN}} ` + -Hostname ${{secrets.HOSTNAME}} ` + -AzureClientId ${{secrets.AZURE_CLIENT_ID}} ` + -AzureClientSecret ${{secrets.AZURE_CLIENT_SECRET}} ` + -AzureTenantId ${{secrets.AZURE_TENANT_ID}} ` + -AzureSubscriptionId ${{secrets.AZURE_SUBSCRIPTION_ID}} ` + -AzureResourceGroupName ${{secrets.AZURE_RESOURCE_GROUP_NAME}} ` + -AzureAdVmName ${{secrets.AZURE_AD_VM_NAME}} ` + -AzureConnectorVm1Name ${{secrets.AZURE_CONNECTOR_VM1_NAME}} ` + -AzureConnectorVm2Name ${{secrets.AZURE_CONNECTOR_VM2_NAME}} ` + -OnPremises $false + azPSVersion: "latest" + + gotest-cloud-eu: + needs: gotest-prep + # GitHub environment for the job + environment: Go Test - Cloud - EU + # The type of runner that the job will run on + runs-on: ubuntu-latest + + steps: + # Checkout repo + - name: Checkout Repo + uses: actions/checkout@v4 + + # Set up Go + - name: Setup Go 1.21.x + uses: actions/setup-go@v5 + with: + # Semantic version range syntax or exact version of Go + go-version: '1.21.x' + + # Install dependencies + - name: Install dependencies + run: | + go get . + + # Set env + - name: Set env + run: | + echo "TF_ACC=1" >> $GITHUB_ENV + echo "GOTEST_CONTEXT=${{vars.GOTEST_CONTEXT}}" >> $GITHUB_ENV + echo "CITRIX_CUSTOMER_ID=${{secrets.CITRIX_CUSTOMER_ID}}" >> $GITHUB_ENV + echo "CITRIX_CLIENT_ID=${{secrets.CITRIX_CLIENT_ID}}" >> $GITHUB_ENV + echo "CITRIX_CLIENT_SECRET=${{secrets.CITRIX_CLIENT_SECRET}}" >> $GITHUB_ENV + echo "CITRIX_HOSTNAME=${{secrets.CITRIX_HOSTNAME}}" >> $GITHUB_ENV + echo "CITRIX_ENVIRONMENT=${{secrets.CITRIX_ENVIRONMENT}}" >> $GITHUB_ENV + echo "TEST_ZONE_INPUT_AZURE=${{secrets.TEST_ZONE_INPUT_AZURE}}" >> $GITHUB_ENV + echo "TEST_ZONE_DESCRIPTION=${{vars.TEST_ZONE_DESCRIPTION}}" >> $GITHUB_ENV + echo "TEST_RESOURCE_LOCATION_NAME=${{vars.TEST_RESOURCE_LOCATION_NAME}}" >> $GITHUB_ENV + echo "TEST_HYPERV_NAME_AZURE=${{vars.TEST_HYPERV_NAME_AZURE}}" >> $GITHUB_ENV + echo "TEST_HYPERV_AD_ID=${{secrets.TEST_HYPERV_AD_ID}}" >> $GITHUB_ENV + echo "TEST_HYPERV_SUBSCRIPTION_ID=${{secrets.TEST_HYPERV_SUBSCRIPTION_ID}}" >> $GITHUB_ENV + echo "TEST_HYPERV_APPLICATION_ID=${{secrets.TEST_HYPERV_APPLICATION_ID}}" >> $GITHUB_ENV + echo "TEST_HYPERV_APPLICATION_SECRET=${{secrets.TEST_HYPERV_APPLICATION_SECRET}}" >> $GITHUB_ENV + echo "TEST_HYPERV_RP_NAME=${{vars.TEST_HYPERV_RP_NAME}}" >> $GITHUB_ENV + echo "TEST_HYPERV_RP_REGION=${{secrets.TEST_HYPERV_RP_REGION}}" >> $GITHUB_ENV + echo "TEST_HYPERV_RP_VIRTUAL_NETWORK_RESOURCE_GROUP=${{secrets.TEST_HYPERV_RP_VIRTUAL_NETWORK_RESOURCE_GROUP}}" >> $GITHUB_ENV + echo "TEST_HYPERV_RP_VIRTUAL_NETWORK=${{secrets.TEST_HYPERV_RP_VIRTUAL_NETWORK}}" >> $GITHUB_ENV + echo "Test_HYPERV_RP_SUBNETS=${{secrets.Test_HYPERV_RP_SUBNETS}}" >> $GITHUB_ENV + echo "TEST_MC_NAME=${{vars.TEST_MC_NAME}}" >> $GITHUB_ENV + echo "TEST_MC_SERVICE_ACCOUNT=${{secrets.TEST_MC_SERVICE_ACCOUNT}}" >> $GITHUB_ENV + echo "TEST_MC_SERVICE_ACCOUNT_PASS=${{secrets.TEST_MC_SERVICE_ACCOUNT_PASS}}" >> $GITHUB_ENV + echo "TEST_MC_SERVICE_OFFERING=${{secrets.TEST_MC_SERVICE_OFFERING}}" >> $GITHUB_ENV + echo "TEST_MC_MASTER_IMAGE=${{secrets.TEST_MC_MASTER_IMAGE}}" >> $GITHUB_ENV + echo "TEST_MC_MASTER_IMAGE_UPDATED=${{secrets.TEST_MC_MASTER_IMAGE_UPDATED}}" >> $GITHUB_ENV + echo "TEST_MC_IMAGE_RESOURCE_GROUP=${{secrets.TEST_MC_IMAGE_RESOURCE_GROUP}}" >> $GITHUB_ENV + echo "TEST_MC_IMAGE_STORAGE_ACCOUNT=${{secrets.TEST_MC_IMAGE_STORAGE_ACCOUNT}}" >> $GITHUB_ENV + echo "TEST_MC_IMAGE_CONTAINER=${{secrets.TEST_MC_IMAGE_CONTAINER}}" >> $GITHUB_ENV + echo "TEST_MC_SUBNET=${{secrets.TEST_MC_SUBNET}}" >> $GITHUB_ENV + echo "TEST_MC_DOMAIN=${{secrets.TEST_MC_DOMAIN}}" >> $GITHUB_ENV + echo "TEST_MC_NAME_MANUAL=${{vars.TEST_MC_NAME_MANUAL}}" >> $GITHUB_ENV + echo "TEST_MC_ALLOCATION_TYPE_MANUAL_POWER_MANAGED=${{vars.TEST_MC_ALLOCATION_TYPE_MANUAL_POWER_MANAGED}}" >> $GITHUB_ENV + echo "TEST_MC_SESSION_SUPPORT_MANUAL_POWER_MANAGED=${{vars.TEST_MC_SESSION_SUPPORT_MANUAL_POWER_MANAGED}}" >> $GITHUB_ENV + echo "TEST_MC_REGION_MANUAL_POWER_MANAGED=${{secrets.TEST_MC_REGION_MANUAL_POWER_MANAGED}}" >> $GITHUB_ENV + echo "TEST_MC_RESOURCE_GROUP_MANUAL_POWER_MANAGED=${{secrets.TEST_MC_RESOURCE_GROUP_MANUAL_POWER_MANAGED}}" >> $GITHUB_ENV + echo "TEST_MC_MACHINE_NAME_MANUAL_AZURE=${{secrets.TEST_MC_MACHINE_NAME_MANUAL_AZURE}}" >> $GITHUB_ENV + echo "TEST_MC_MACHINE_ACCOUNT_MANUAL_AZURE=${{secrets.TEST_MC_MACHINE_ACCOUNT_MANUAL_AZURE}}" >> $GITHUB_ENV + echo "TEST_MC_MACHINE_PROFILE_VM_NAME=${{secrets.TEST_MC_MACHINE_PROFILE_VM_NAME}}" >> $GITHUB_ENV + echo "TEST_MC_MACHINE_PROFILE_RESOURCE_GROUP=${{secrets.TEST_MC_MACHINE_PROFILE_RESOURCE_GROUP}}" >> $GITHUB_ENV + echo "TEST_DG_NAME=${{vars.TEST_DG_NAME}}" >> $GITHUB_ENV + echo "TEST_POLICY_SET_WITHOUT_DG_NAME=${{vars.TEST_POLICY_SET_WITHOUT_DG_NAME}}" >> $GITHUB_ENV + echo "TEST_POLICY_SET_NAME=${{vars.TEST_POLICY_SET_NAME}}" >> $GITHUB_ENV + echo "TEST_APP_NAME=${{vars.TEST_APP_NAME}}" >> $GITHUB_ENV + echo "TEST_ADMIN_FOLDER_NAME=${{vars.TEST_ADMIN_FOLDER_NAME}}" >> $GITHUB_ENV + echo "TEST_ROLE_NAME=${{vars.TEST_ROLE_NAME}}" >> $GITHUB_ENV + echo "TEST_ADMIN_SCOPE_NAME=${{vars.TEST_ADMIN_SCOPE_NAME}}" >> $GITHUB_ENV + echo "TEST_ADMIN_USER_NAME=${{secrets.TEST_ADMIN_USER_NAME}}" >> $GITHUB_ENV + echo "TEST_ADMIN_USER_DOMAIN=${{secrets.TEST_ADMIN_USER_DOMAIN}}" >> $GITHUB_ENV + + # Test PreCheck + - name: Test Precheck + run: go test -v ./internal/test -run "^TestAzureMcsSuitePreCheck$" + + # Test + - name: Test + run: go test -v ./internal/test -run "^TestAzureMcs$" -timeout 1h + + # Sweep + - name: Sweep + if: always() + run: go test -v ./internal/test -run "^TestAzureMcs$" -timeout 1h -sweep-run "citrix_zone,citrix_zone,citrix_admin_folder,citrix_admin_role,citrix_admin_scope" -sweep="azure" \ No newline at end of file diff --git a/DEVELOPER.md b/DEVELOPER.md index f09f16a..000495c 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -104,18 +104,21 @@ In order to reduce errors this project has introduced a system to convert betwee | From | To | Function | Notes | |------|----|----------|-------| -| `types.Object` | `T` | `ObjectValueToTypedObject` | `T` must implement `ModelWithAttributes` | -| `T` | `types.Object` | `TypedObjectToObjectValue` | `T` must implement `ModelWithAttributes` | -| `types.List` | `T[]` | `ObjectListToTypedArray[T]` | `T` must implement `ModelWithAttributes`. For a list of nested objects | -| `T[]` | `types.List` | `TypedArrayToObjectList[T]` | `T` must implement `ModelWithAttributes`. For a list of nested objects | -| `types.Set` | `T[]` | `ObjectSetToTypedArray[T]` | `T` must implement `ModelWithAttributes`. For a set of nested objects | -| `T[]` | `types.Set` | `TypedArrayToObjectSet[T]` | `T` must implement `ModelWithAttributes`. For a set of nested objects | +| `types.Object` | `T` | `ObjectValueToTypedObject` | `T` must implement `ResourceModelWithAttributes` or `DataSourceModelWithAttributes` | +| `T` | `types.Object` | `TypedObjectToObjectValue` | `T` must implement `ResourceModelWithAttributes` | +| `T` | `types.Object` | `DataSourceTypedObjectToObjectValue` | `T` must implement `DataSourceModelWithAttributes` | +| `types.List` | `T[]` | `ObjectListToTypedArray[T]` | `T` must implement `ResourceModelWithAttributes` or `DataSourceModelWithAttributes`. For a list of nested objects | +| `T[]` | `types.List` | `TypedArrayToObjectList[T]` | `T` must implement `ResourceModelWithAttributes`. For a list of nested resource objects | +| `T[]` | `types.List` | `DataSourceTypedArrayToObjectSet[T]` | `T` must implement `DataSourceModelWithAttributes`. For a list of nested data source objects | +| `types.Set` | `T[]` | `ObjectSetToTypedArray[T]` | `T` must implement `ResourceModelWithAttributes`. For a set of nested objects | +| `T[]` | `types.Set` | `TypedArrayToObjectSet[T]` | `T` must implement `ResourceModelWithAttributes`. For a set of nested resource objects | +| `T[]` | `types.Set` | `DataSourceTypedArrayToObjectSet[T]` | `T` must implement `DataSourceModelWithAttributes`. For a set of nested data source objects | | `types.List` | `string[]` | `StringListToStringArray` | For a list of strings | | `string[]` | `types.List` | `StringArrayToStringList` | For a list of strings | | `types.Set` | `string[]` | `StringSetToStringArray` | For a set of strings | | `string[]` | `types.Set` | `StringArrayToStringSet` | For a set of strings | -In order to use the first 6 of these methods, the struct `T` needs to implement the [ModelWithAttributes](internal/util/types.go) interface which is ultimately populated from the attribute's Schema. This gives the Terraform type system the necessary information to populate a `types.Object` or `types.List` with a nested object. +In order to use the first 9 of these methods, the struct `T` needs to implement the [ResourceModelWithAttributes](internal/util/types.go) or [DataSourceModelWithAttributes](internal/util/types.go) interface which is ultimately populated from the attribute's Schema. This gives the Terraform type system the necessary information to populate a `types.Object` or `types.List` with a nested object. ### Initalizing Terraform types When dealing with a struct that contains nested `types.List/Set/Object`, it is important to never work with an empty struct, but instead start with one that has all of the nested objects initialized to Terraform's `ListNull/SetNull/ObjectNull`. There are helpers to assist with this: @@ -126,7 +129,7 @@ tfObject := ComplexTerraformObject{} tfObject ComplexTerraformObject // Instead: -if attributesMap, err := util.AttributeMapFromObject(ComplexTerraformObject{}); err == nil { +if attributesMap, err := util.ResourceAttributeMapFromObject(ComplexTerraformObject{}); err == nil { tfObject := types.ObjectNull(attributesMap) } else { diagnostics.AddWarning("Error when creating null ComplexTerraformObject", err.Error()) diff --git a/docs/data-sources/admin_role.md b/docs/data-sources/admin_role.md new file mode 100644 index 0000000..4506023 --- /dev/null +++ b/docs/data-sources/admin_role.md @@ -0,0 +1,47 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "citrix_admin_role Data Source - citrix" +subcategory: "CVAD" +description: |- + Data source of an administrator role. +--- + +# citrix_admin_role (Data Source) + +Data source of an administrator role. + +## Example Usage + +```terraform +# Get Admin Scope resource by name +data "citrix_admin_role" "example_admin_role" { + name = "ExampleAdminRole" +} + +# Get Admin Scope resource by id +data "citrix_admin_role" "example_admin_role" { + id = "00000000-0000-0000-0000-000000000000" +} +``` + + +## Schema + +### Optional + +- `id` (String) ID of the admin role. +- `name` (String) Name of the admin role. + +### Read-Only + +- `can_launch_manage` (Boolean) Flag to determine if the user will have access to the Manage tab on the console. Defaults to `true`. + +~> **Please Note** This field is only applicable for cloud admins. For on-premise admins, the only acceptable value is `true`. +- `can_launch_monitor` (Boolean) Flag to determine if the user will have access to the Monitor tab on the console. Defaults to `true`. + +~> **Please Note** This field is only applicable for cloud admins. For on-premise admins, the only acceptable value is `true`. +- `description` (String) Description of the admin role. +- `is_built_in` (Boolean) Flag to determine if the role was built-in or user defined +- `permissions` (Set of String) Permissions to be associated with the admin role. + +-> **Note** To get a list of supported permissions, please refer to [Admin Predefined Permissions for Cloud](https://developer-docs.citrix.com/en-us/citrix-daas-service-apis/citrix-daas-rest-apis/apis/#/Admin-APIs/Admin-GetPredefinedPermissions) and [Admin Predefined Permissions for On-Premise](https://developer-docs.citrix.com/en-us/citrix-virtual-apps-desktops/citrix-cvad-rest-apis/apis/#/Admin-APIs/Admin-GetPredefinedPermissions). \ No newline at end of file diff --git a/docs/data-sources/admin_user.md b/docs/data-sources/admin_user.md new file mode 100644 index 0000000..11d0193 --- /dev/null +++ b/docs/data-sources/admin_user.md @@ -0,0 +1,42 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "citrix_admin_user Data Source - citrix" +subcategory: "CVAD" +description: |- + Data source of an administrator user for on-premise environment. +--- + +# citrix_admin_user (Data Source) + +Data source of an administrator user for on-premise environment. + +## Example Usage + +```terraform +# Get Admin Scope resource by id +data "citrix_admin_user" "example_admin_user" { + id = "00000000-0000-0000-0000-000000000000" +} +``` + + +## Schema + +### Required + +- `id` (String) ID of the admin user. + +### Read-Only + +- `domain_name` (String) Name of the domain that the user is a part of. For example, if the domain is `example.com`, then provide the value `example` for this field. +- `is_enabled` (Boolean) Flag to determine if the administrator is to be enabled or not. +- `name` (String) Name of an existing user in the active directory. +- `rights` (Attributes List) Rights to be associated with the admin user. (see [below for nested schema](#nestedatt--rights)) + + +### Nested Schema for `rights` + +Read-Only: + +- `role` (String) Name of the role to be associated with the admin user. +- `scope` (String) Name of the scope to be associated with the admin user. \ No newline at end of file diff --git a/docs/data-sources/cloud_okta_identity_provider.md b/docs/data-sources/cloud_okta_identity_provider.md index fd9050e..58c1813 100644 --- a/docs/data-sources/cloud_okta_identity_provider.md +++ b/docs/data-sources/cloud_okta_identity_provider.md @@ -34,4 +34,7 @@ data "citrix_cloud_okta_identity_provider" "example_okta_identity_provider" { ### Read-Only +- `okta_api_token` (String) Okta API token for configuring Okta Identity Provider. +- `okta_client_id` (String) ID of the Okta client for configuring Okta Identity Provider. +- `okta_client_secret` (String) Secret of the Okta client for configuring Okta Identity Provider. - `okta_domain` (String) Okta domain name for configuring Okta Identity Provider. \ No newline at end of file diff --git a/docs/data-sources/cloud_resource_location.md b/docs/data-sources/cloud_resource_location.md index 2f468f2..cbc753f 100644 --- a/docs/data-sources/cloud_resource_location.md +++ b/docs/data-sources/cloud_resource_location.md @@ -28,4 +28,6 @@ data "citrix_cloud_resource_location" "example-resource-location" { ### Read-Only -- `id` (String) ID of the resource location. \ No newline at end of file +- `id` (String) ID of the resource location. +- `internal_only` (Boolean) Flag to determine if the resource location can only be used internally. Defaults to `false`. +- `time_zone` (String) Timezone associated with the resource location. Please refer to the `Timezone` column in the following [table](https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones?view=windows-11#time-zones) for allowed values. \ No newline at end of file diff --git a/docs/data-sources/cloud_saml_identity_provider.md b/docs/data-sources/cloud_saml_identity_provider.md index 714cf62..f88fa92 100644 --- a/docs/data-sources/cloud_saml_identity_provider.md +++ b/docs/data-sources/cloud_saml_identity_provider.md @@ -40,6 +40,7 @@ data "citrix_cloud_saml_identity_provider" "example_saml_identity_provider" { - `authentication_context_comparison` (String) The authentication context comparison type. - `cert_common_name` (String) The common name of the SAML certificate. - `cert_expiration` (String) The expiration date time of the SAML certificate. +- `cert_file_path` (String) The file path of the certificate. - `entity_id` (String) The entity ID of the SAML 2.0 Identity Provider. - `logout_binding` (String) The binding of the logout service. - `logout_url` (String) The URL of the logout service. diff --git a/docs/data-sources/delivery_group.md b/docs/data-sources/delivery_group.md index 0618303..b81a731 100644 --- a/docs/data-sources/delivery_group.md +++ b/docs/data-sources/delivery_group.md @@ -13,25 +13,29 @@ Read data of an existing delivery group. ## Example Usage ```terraform +# Get Citrix Delivery Group data source by name data "citrix_delivery_group" "example_delivery_group" { name = "exampleDeliveryGroupName" } + +# Get Citrix Delivery Group data source by ID +data "citrix_delivery_group" "example_delivery_group" { + id = "00000000-0000-0000-0000-000000000000" +} ``` ## Schema -### Required - -- `name` (String) Name of the delivery group. - ### Optional - `delivery_group_folder_path` (String) The path to the folder in which the delivery group is located. +- `id` (String) GUID identifier of the delivery group. +- `name` (String) Name of the delivery group. ### Read-Only -- `id` (String) GUID identifier of the delivery group. +- `delivery_type` (String) The delivery type of the delivery group. - `tags` (Set of String) A set of identifiers of tags to associate with the delivery group. - `tenants` (Set of String) A set of identifiers of tenants to associate with the delivery group. - `vdas` (Attributes List) The VDAs associated with the delivery group. (see [below for nested schema](#nestedatt--vdas)) @@ -44,4 +48,5 @@ Read-Only: - `associated_delivery_group` (String) Delivery group which the VDA is associated with. - `associated_machine_catalog` (String) Machine catalog which the VDA is associated with. - `hosted_machine_id` (String) Machine ID within the hypervisor hosting unit. +- `id` (String) Id of the VDA. - `machine_name` (String) Machine name of the VDA. \ No newline at end of file diff --git a/docs/data-sources/hypervisor.md b/docs/data-sources/hypervisor.md index 4090241..359251e 100644 --- a/docs/data-sources/hypervisor.md +++ b/docs/data-sources/hypervisor.md @@ -17,16 +17,21 @@ Read data of an existing hypervisor. data "citrix_hypervisor" "azure-hypervisor" { name = "azure-hyperv" } + +# Get Hypervisor resource of any connection type by id +data "citrix_hypervisor" "azure-hypervisor" { + id = "00000000-0000-0000-0000-000000000000" +} ``` ## Schema -### Required +### Optional +- `id` (String) GUID identifier of the hypervisor. - `name` (String) Name of the hypervisor. ### Read-Only -- `id` (String) GUID identifier of the hypervisor. - `tenants` (Set of String) A set of identifiers of tenants to associate with the hypervisor connection. \ No newline at end of file diff --git a/docs/data-sources/hypervisor_resource_pool.md b/docs/data-sources/hypervisor_resource_pool.md index 504ea18..b9eb993 100644 --- a/docs/data-sources/hypervisor_resource_pool.md +++ b/docs/data-sources/hypervisor_resource_pool.md @@ -18,6 +18,12 @@ data "citrix_hypervisor_resource_pool" "azure-resource-pool" { name = "azure-rp" hypervisor_name = "azure-hyperv" } + +# Get Hypervisor Resource Pool of any connection type resource by id and the hypervisor name it belongs to +data "citrix_hypervisor_resource_pool" "azure-resource-pool" { + id = "00000000-0000-0000-0000-000000000000" + hypervisor_name = "azure-hyperv" +} ``` @@ -26,9 +32,12 @@ data "citrix_hypervisor_resource_pool" "azure-resource-pool" { ### Required - `hypervisor_name` (String) Name of the hypervisor to which the resource pool belongs. + +### Optional + +- `id` (String) GUID identifier of the hypervisor resource pool. - `name` (String) Name of the hypervisor resource pool. ### Read-Only -- `id` (String) GUID identifier of the hypervisor resource pool. - `networks` (List of String) Networks available in the hypervisor resource pool. \ No newline at end of file diff --git a/docs/data-sources/machine_catalog.md b/docs/data-sources/machine_catalog.md index 2f71047..167af60 100644 --- a/docs/data-sources/machine_catalog.md +++ b/docs/data-sources/machine_catalog.md @@ -13,25 +13,28 @@ Read data of an existing machine catalog. ## Example Usage ```terraform +# Get Citrix Machine Catalog data source by name data "citrix_machine_catalog" "example_machine_catalog" { name = "example-catalog" } + +# Get Citrix Machine Catalog data source by ID +data "citrix_machine_catalog" "example_machine_catalog" { + id = "00000000-0000-0000-0000-000000000000" +} ``` ## Schema -### Required - -- `name` (String) Name of the machine catalog. - ### Optional +- `id` (String) GUID identifier of the machine catalog. - `machine_catalog_folder_path` (String) The path to the folder in which the machine catalog is located. +- `name` (String) Name of the machine catalog. ### Read-Only -- `id` (String) GUID identifier of the machine catalog. - `tags` (Set of String) A set of identifiers of tags to associate with the machine catalog. - `tenants` (Set of String) A set of identifiers of tenants to associate with the machine catalog. - `vdas` (Attributes List) The VDAs associated with the machine catalog. (see [below for nested schema](#nestedatt--vdas)) @@ -44,4 +47,5 @@ Read-Only: - `associated_delivery_group` (String) Delivery group which the VDA is associated with. - `associated_machine_catalog` (String) Machine catalog which the VDA is associated with. - `hosted_machine_id` (String) Machine ID within the hypervisor hosting unit. +- `id` (String) Id of the VDA. - `machine_name` (String) Machine name of the VDA. \ No newline at end of file diff --git a/docs/data-sources/policy_set.md b/docs/data-sources/policy_set.md new file mode 100644 index 0000000..870f4b6 --- /dev/null +++ b/docs/data-sources/policy_set.md @@ -0,0 +1,175 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "citrix_policy_set Data Source - citrix" +subcategory: "CVAD" +description: |- + Data source of a policy set and the policies within it. The order of the policies in this data source reflects the policy priority. +--- + +# citrix_policy_set (Data Source) + +Data source of a policy set and the policies within it. The order of the policies in this data source reflects the policy priority. + +## Example Usage + +```terraform +# Get Policy Set data source by id +data "citrix_policy_set" "example_policy_set_data_source_with_id" { + id = "00000000-0000-0000-0000-000000000000" +} + +# Get Policy Set data source by name +data "citrix_policy_set" "example_policy_set_data_source_with_name" { + name = "example-policy-set" +} +``` + + +## Schema + +### Optional + +- `id` (String) GUID identifier of the policy set. +- `name` (String) Name of the policy set. + +### Read-Only + +- `assigned` (Boolean) Indicate whether the policy set is being assigned to delivery groups. +- `description` (String) Description of the policy set. +- `policies` (Attributes List) Ordered list of policies. + +-> **Note** The order of policies in the list determines the priority of the policies. (see [below for nested schema](#nestedatt--policies)) +- `scopes` (Set of String) The IDs of the scopes for the policy set to be a part of. +- `type` (String) Type of the policy set. Type can be one of `SitePolicies`, `DeliveryGroupPolicies`, `SiteTemplates`, or `CustomTemplates`. + + +### Nested Schema for `policies` + +Optional: + +- `branch_repeater_filter` (Attributes) Definition of branch repeater policy filter. (see [below for nested schema](#nestedatt--policies--branch_repeater_filter)) + +Read-Only: + +- `access_control_filters` (Attributes Set) Set of Access control policy filters. (see [below for nested schema](#nestedatt--policies--access_control_filters)) +- `client_ip_filters` (Attributes Set) Set of Client ip policy filters. (see [below for nested schema](#nestedatt--policies--client_ip_filters)) +- `client_name_filters` (Attributes Set) Set of Client name policy filters. (see [below for nested schema](#nestedatt--policies--client_name_filters)) +- `delivery_group_filters` (Attributes Set) Set of Delivery group policy filters. (see [below for nested schema](#nestedatt--policies--delivery_group_filters)) +- `delivery_group_type_filters` (Attributes Set) Set of Delivery group type policy filters. (see [below for nested schema](#nestedatt--policies--delivery_group_type_filters)) +- `description` (String) Description of the policy. +- `enabled` (Boolean) Indicate whether the policy is being enabled. +- `id` (String) Id of the policy. +- `name` (String) Name of the policy. +- `ou_filters` (Attributes Set) Set of Organizational unit policy filters. (see [below for nested schema](#nestedatt--policies--ou_filters)) +- `policy_settings` (Attributes Set) Set of policy settings. (see [below for nested schema](#nestedatt--policies--policy_settings)) +- `tag_filters` (Attributes Set) Set of Tag policy filters. (see [below for nested schema](#nestedatt--policies--tag_filters)) +- `user_filters` (Attributes Set) Set of User policy filters. (see [below for nested schema](#nestedatt--policies--user_filters)) + + +### Nested Schema for `policies.branch_repeater_filter` + +Read-Only: + +- `allowed` (Boolean) Indicate the filtered policy is allowed or denied if the filter condition is met. +- `id` (String) Id of the branch repeater policy filter. + + + +### Nested Schema for `policies.access_control_filters` + +Read-Only: + +- `allowed` (Boolean) Indicate the filtered policy is allowed or denied if the filter condition is met. +- `condition` (String) Gateway condition for the policy filter. +- `connection` (String) Gateway connection for the policy filter. +- `enabled` (Boolean) Indicate whether the filter is being enabled. +- `gateway` (String) Gateway for the policy filter. +- `id` (String) Id of the policy filter. + + + +### Nested Schema for `policies.client_ip_filters` + +Read-Only: + +- `allowed` (Boolean) Indicate the filtered policy is allowed or denied if the filter condition is met. +- `enabled` (Boolean) Indicate whether the filter is being enabled. +- `id` (String) Id of the client ip policy filter. +- `ip_address` (String) IP Address of the client to be filtered. + + + +### Nested Schema for `policies.client_name_filters` + +Read-Only: + +- `allowed` (Boolean) Indicate the filtered policy is allowed or denied if the filter condition is met. +- `client_name` (String) Name of the client to be filtered. +- `enabled` (Boolean) Indicate whether the filter is being enabled. +- `id` (String) Id of the client name policy filter. + + + +### Nested Schema for `policies.delivery_group_filters` + +Read-Only: + +- `allowed` (Boolean) Indicate the filtered policy is allowed or denied if the filter condition is met. +- `delivery_group_id` (String) Id of the delivery group to be filtered. +- `enabled` (Boolean) Indicate whether the filter is being enabled. +- `id` (String) Id of the delivery group policy filter. + + + +### Nested Schema for `policies.delivery_group_type_filters` + +Read-Only: + +- `allowed` (Boolean) Indicate the filtered policy is allowed or denied if the filter condition is met. +- `delivery_group_type` (String) Type of the delivery groups to be filtered. +- `enabled` (Boolean) Indicate whether the filter is being enabled. +- `id` (String) Id of the delivery group type policy filter. + + + +### Nested Schema for `policies.ou_filters` + +Read-Only: + +- `allowed` (Boolean) Indicate the filtered policy is allowed or denied if the filter condition is met. +- `enabled` (Boolean) Indicate whether the filter is being enabled. +- `id` (String) Id of the organizational unit policy filter. +- `ou` (String) Organizational Unit to be filtered. + + + +### Nested Schema for `policies.policy_settings` + +Read-Only: + +- `enabled` (Boolean) Whether of the policy setting has enabled or allowed value. +- `name` (String) Name of the policy setting. +- `use_default` (Boolean) Indicate whether using default value for the policy setting. +- `value` (String) Value of the policy setting. + + + +### Nested Schema for `policies.tag_filters` + +Read-Only: + +- `allowed` (Boolean) Indicate the filtered policy is allowed or denied if the filter condition is met. +- `enabled` (Boolean) Indicate whether the filter is being enabled. +- `id` (String) Id of the tag policy filter. +- `tag` (String) Tag to be filtered. + + + +### Nested Schema for `policies.user_filters` + +Read-Only: + +- `allowed` (Boolean) Indicate the filtered policy is allowed or denied if the filter condition is met. +- `enabled` (Boolean) Indicate whether the filter is being enabled. +- `id` (String) Id of the user policy filter. +- `sid` (String) SID of the user or user group to be filtered. \ No newline at end of file diff --git a/docs/data-sources/quickcreate_aws_workspaces_image.md b/docs/data-sources/quickcreate_aws_workspaces_image.md index e3f28a2..20676ee 100644 --- a/docs/data-sources/quickcreate_aws_workspaces_image.md +++ b/docs/data-sources/quickcreate_aws_workspaces_image.md @@ -40,7 +40,8 @@ data "citrix_quickcreate_aws_workspaces_image" "example_aws_workspaces_image" { ### Read-Only -- `aws_image_id` (String) Id of the image on AWS. +- `aws_image_id` (String) Id of the image to be imported in AWS. +- `aws_imported_image_id` (String) The Id of the image imported in AWS WorkSpaces. - `description` (String) Description of the image. - `ingestion_process` (String) The type of ingestion process of the image. Possible values are `BYOL_REGULAR_BYOP` and `BYOL_GRAPHICS_G4DN_BYOP`. - `operating_system` (String) The type of operating system of the image. Possible values are `WINDOWS` and `LINUX`. diff --git a/docs/data-sources/storefront_server.md b/docs/data-sources/storefront_server.md new file mode 100644 index 0000000..c4e4614 --- /dev/null +++ b/docs/data-sources/storefront_server.md @@ -0,0 +1,39 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "citrix_storefront_server Data Source - citrix" +subcategory: "CVAD" +description: |- + Data source of a StoreFront server. +--- + +# citrix_storefront_server (Data Source) + +Data source of a StoreFront server. + +## Example Usage + +```terraform +# Get StoreFront Server data source by name +data "citrix_storefront_server" "example_storefront_server_by_name" { + name = "ExampleStoreFrontServer" +} + +# Get StoreFront Server data source by id +data "citrix_storefront_server" "example_storefront_server_by_id" { + id = "00000000-0000-0000-0000-000000000000" +} +``` + + +## Schema + +### Optional + +- `id` (String) GUID identifier of the StoreFront server. +- `name` (String) Name of the StoreFront server. + +### Read-Only + +- `description` (String) Description of the StoreFront server. +- `enabled` (Boolean) Indicates if the StoreFront server is enabled. Default is `true`. +- `url` (String) URL for connecting to the StoreFront server. \ No newline at end of file diff --git a/docs/data-sources/vda.md b/docs/data-sources/vda.md index d77eef1..4a1e26f 100644 --- a/docs/data-sources/vda.md +++ b/docs/data-sources/vda.md @@ -44,4 +44,5 @@ Read-Only: - `associated_delivery_group` (String) Delivery group which the VDA is associated with. - `associated_machine_catalog` (String) Machine catalog which the VDA is associated with. - `hosted_machine_id` (String) Machine ID within the hypervisor hosting unit. +- `id` (String) Id of the VDA. - `machine_name` (String) Machine name of the VDA. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index cce790b..393e51c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -86,6 +86,7 @@ provider "citrix" { - `cvad_config` (Attributes) Configuration for CVAD service. (see [below for nested schema](#nestedatt--cvad_config)) - `storefront_remote_host` (Attributes) StoreFront Remote Host for Citrix DaaS service.
Only applicable for Citrix on-premises StoreFront. Use this to specify StoreFront Remote Host.
(see [below for nested schema](#nestedatt--storefront_remote_host)) +- `wem_on_prem_config` (Attributes) Configuration for WEM on-premises service. (see [below for nested schema](#nestedatt--wem_on_prem_config)) ### Nested Schema for `cvad_config` @@ -134,6 +135,11 @@ For Citrix Cloud customers: Use this to force override the Citrix DaaS service h -> **Note** Can be set via Environment Variable **CITRIX_HOSTNAME**. ~> **Please Note** This parameter is required for on-premises customers to be specified in the provider configuration or via environment variable. +- `wem_region` (String) WEM Hosting Region of the Citrix Cloud customer. Available values are `US`, `EU`, and `APS`. + +-> **Note** Can be set via Environment Variable **CITRIX_WEM_REGION**. + +~> **Please Note** Only applicable for Citrix Workspace Environment Management (WEM) Cloud customers. @@ -141,7 +147,18 @@ For Citrix Cloud customers: Use this to force override the Citrix DaaS service h Optional: -- `ad_admin_password` (String) Active Directory Admin Password to connect to storefront server
Use this to specify AD admin password
Can be set via Environment Variable **SF_AD_ADMIN_PASSWORD**.
This parameter is **required** to be specified in the provider configuration or via environment variable. +- `ad_admin_password` (String, Sensitive) Active Directory Admin Password to connect to storefront server
Use this to specify AD admin password
Can be set via Environment Variable **SF_AD_ADMIN_PASSWORD**.
This parameter is **required** to be specified in the provider configuration or via environment variable. - `ad_admin_username` (String) Active Directory Admin Username to connect to storefront server
Use this to specify AD admin username
Can be set via Environment Variable **SF_AD_ADMIN_USERNAME**.
This parameter is **required** to be specified in the provider configuration or via environment variable. - `computer_name` (String) StoreFront server computer Name
Use this to specify StoreFront server computer name
Can be set via Environment Variable **SF_COMPUTER_NAME**.
This parameter is **required** to be specified in the provider configuration or via environment variable. - `disable_ssl_verification` (Boolean) Disable SSL verification against the target storefront server.
Only applicable to customers connecting to storefront server remotely. Customers should omit this option when running storefront provider locally. Set to true to skip SSL verification only when the target DDC does not have a valid SSL certificate issued by a trusted CA.
When set to true, please make sure that your provider storefront_remote_host is set for a known storefront hostname.
Can be set via Environment Variable **SF_DISABLE_SSL_VERIFICATION**. + + + +### Nested Schema for `wem_on_prem_config` + +Optional: + +- `admin_password` (String, Sensitive) WEM Admin Password to connect to WEM service
Use this to specify WEM admin password
Can be set via Environment Variable **WEM_ADMIN_PASSWORD**.
This parameter is **required** to be specified in the provider configuration or via environment variable. +- `admin_username` (String) WEM Admin Username to connect to WEM service
Use this to specify WEM admin username
Can be set via Environment Variable **WEM_ADMIN_USERNAME**.
This parameter is **required** to be specified in the provider configuration or via environment variable. +- `disable_ssl_verification` (Boolean) Disable SSL verification against the target WEM service.
Set to true to skip SSL verification only when the target WEM service does not have a valid SSL certificate issued by a trusted CA.
When set to true, please make sure that your provider config is set for a known WEM hostname.
Can be set via Environment Variable **WEM_DISABLE_SSL_VERIFICATION**. +- `hostname` (String) Name of server hosting Citrix WEM service.
Use this to specify WEM service hostname.
Can be set via Environment Variable **WEM_HOSTNAME**.
This parameter is **required** to be specified in the provider configuration or via environment variable. diff --git a/docs/resources/admin_folder.md b/docs/resources/admin_folder.md index 4f5c945..1eb9338 100644 --- a/docs/resources/admin_folder.md +++ b/docs/resources/admin_folder.md @@ -47,6 +47,10 @@ resource "citrix_admin_folder" "example-admin-folder-3" { - `id` (String) Identifier of the admin folder. - `path` (String) Path to the admin folder. +- `total_application_groups` (Number) Number of application groups contained in the admin folder. +- `total_applications` (Number) Number of applications contained in the admin folder. +- `total_delivery_groups` (Number) Number of delivery groups contained in the admin folder. +- `total_machine_catalogs` (Number) Number of machine catalogs contained in the admin folder. ## Import diff --git a/docs/resources/admin_scope.md b/docs/resources/admin_scope.md index 39be782..05c3386 100644 --- a/docs/resources/admin_scope.md +++ b/docs/resources/admin_scope.md @@ -34,6 +34,10 @@ resource "citrix_admin_scope" "example-admin-scope" { ### Read-Only - `id` (String) ID of the admin scope. +- `is_all_scope` (Boolean) Indicates whether the Admin Scope is all scope or not. +- `is_built_in` (Boolean) Indicates whether the Admin Scope is built-in or not. +- `tenant_id` (String) ID of the tenant to which the Admin Scope belongs. +- `tenant_name` (String) Name of the tenant to which the Admin Scope belongs. ## Import diff --git a/docs/resources/application.md b/docs/resources/application.md index fae2cdf..0d624a7 100644 --- a/docs/resources/application.md +++ b/docs/resources/application.md @@ -75,6 +75,7 @@ resource "citrix_application" "example-application" { -> **Note** The order of delivery group in the `delivery_groups` list determines the priority of the delivery group. Alternatively, you can use the `delivery_groups_priority` attribute to selectively set the priority of delivery groups. - `delivery_groups_priority` (Attributes Set) Set of delivery groups with their corresponding priority. (see [below for nested schema](#nestedatt--delivery_groups_priority)) - `description` (String) Description of the application. +- `enabled` (Boolean) Indicates whether the application is enabled or disabled. Default is `true`. - `icon` (String) The Id of the icon to be associated with the application. - `limit_visibility_to_users` (Set of String) By default, the application is visible to all users within a delivery group. However, you can restrict its visibility to only certain users by specifying them in the `limit_visibility_to_users` list. diff --git a/docs/resources/application_icon.md b/docs/resources/application_icon.md index 728723f..4f0274b 100644 --- a/docs/resources/application_icon.md +++ b/docs/resources/application_icon.md @@ -36,8 +36,8 @@ resource "citrix_application_icon" "example-application-icon" { Import is supported using the following syntax: ```shell -# Application icon can be imported by specifying the GUID -terraform import citrix_application_icon.example-application-icon 4cec0568-1c91-407f-a32e-cc487822defc +# Application icon can be imported by specifying the ID +terraform import citrix_application_icon.example-application-icon 1 ``` ## Generating Raw Data of an icon diff --git a/docs/resources/delivery_group.md b/docs/resources/delivery_group.md index 65ba165..100a0df 100644 --- a/docs/resources/delivery_group.md +++ b/docs/resources/delivery_group.md @@ -40,6 +40,9 @@ resource "citrix_delivery_group" "example-delivery-group" { ] autoscale_settings = { autoscale_enabled = true + restrict_autoscale_tag = "example-tag" + peak_restrict_min_idle_untagged_percent = 10 + off_peak_restrict_min_idle_untagged_percent = 10 disconnect_peak_idle_session_after_seconds = 3600 log_off_peak_disconnected_session_after_seconds = 3600 peak_log_off_action = "Nothing" @@ -198,6 +201,7 @@ resource "citrix_delivery_group" "example-delivery-group" { -> **Note** Use `Citrix Gateway connections` as the name for the default policy that is Via Access Gateway and `Non-Citrix Gateway connections` as the name for the default policy that is Not Via Access Gateway. (see [below for nested schema](#nestedatt--default_access_policies)) - `default_desktop_icon` (String) The id of the icon to be used as the default icon for the desktops in the delivery group. - `delivery_group_folder_path` (String) The path of the folder in which the delivery group is located. +- `delivery_type` (String) Delivery type of the delivery group. Available values are `DesktopsOnly`, `AppsOnly`, and `DesktopsAndApps`. Defaults to `DesktopsOnly` for Delivery Groups with associated Machine Catalogs that have `allocation_type` set to `Static` and for Delivery Groups that have `sharing_kind` set to `private`. Otherwise defaults to `DesktopsAndApps - `description` (String) Description of the delivery group. - `desktops` (Attributes List) A list of Desktop resources to publish on the delivery group. Only 1 desktop can be added to a Remote PC Delivery Group. (see [below for nested schema](#nestedatt--desktops)) - `make_resources_available_in_lhc` (Boolean) In the event of a service disruption or loss of connectivity, select if you want Local Host Cache to keep resources in the delivery group available to launch new sessions. Existing sessions are not impacted. @@ -211,7 +215,7 @@ resource "citrix_delivery_group" "example-delivery-group" { - `reboot_schedules` (Attributes List) The reboot schedule for the delivery group. (see [below for nested schema](#nestedatt--reboot_schedules)) - `restricted_access_users` (Attributes) Restrict access to this Delivery Group by specifying users and groups in the allow and block list. If no value is specified, all authenticated users will have access to this Delivery Group. To give access to unauthenticated users, use the `allow_anonymous_access` property. (see [below for nested schema](#nestedatt--restricted_access_users)) - `scopes` (Set of String) The IDs of the scopes for the delivery group to be a part of. -- `session_support` (String) The session support for the delivery group. Can only be set to `SingleSession` or `MultiSession`. Specify only if you want to create a Delivery Group wthout any `associated_machine_catalogs`. Ensure session support is same as that of the prospective Machine Catalogs you will associate this Delivery Group with. +- `session_support` (String) The session support for the delivery group. Can only be set to `SingleSession` or `MultiSession`. Specify only if you want to create a Delivery Group without any `associated_machine_catalogs`. Ensure session support is same as that of the prospective Machine Catalogs you will associate this Delivery Group with. - `sharing_kind` (String) The sharing kind for the delivery group. Can only be set to `Shared` or `Private`. Specify only if you want to create a Delivery Group wthout any `associated_machine_catalogs`. - `storefront_servers` (Set of String) A list of GUID identifiers of StoreFront Servers to associate with the delivery group. - `tags` (Set of String) A set of identifiers of tags to associate with the delivery group. @@ -279,6 +283,9 @@ Optional: - `off_peak_extended_disconnect_timeout_minutes` (Number) The number of minutes before the second configured action should be performed after a user session disconnects outside peak hours. - `off_peak_log_off_action` (String) The action to be performed after a configurable period of a user session ending outside peak hours. Choose between `Nothing`, `Suspend`, and `Shutdown`. Default is `Nothing`. - `off_peak_log_off_timeout_minutes` (Number) The number of minutes before the configured action should be performed after a user session ends outside peak hours. +- `off_peak_restrict_min_idle_untagged_percent` (Number) Specifies the percentage of remaining untagged capacity to fall below to start powering on tagged machines during off peak hours. + +~> **Please Note** This setting is only applicable when the `restrict_autoscale_tag` is set. - `peak_autoscale_assigned_power_on_idle_action` (String) The action to be performed on an assigned machine previously started by autoscale that subsequently remains unused. Choose between `Nothing`, `Suspend`, and `Shutdown`. Default is `Nothing`. - `peak_autoscale_assigned_power_on_idle_timeout_minutes` (Number) The number of minutes before the configured action is performed on an assigned machine previously started by autoscale that subsequently remains unused. - `peak_buffer_size_percent` (Number) The percentage of machines in the delivery group that should be kept available in an idle state in peak hours. @@ -288,6 +295,9 @@ Optional: - `peak_extended_disconnect_timeout_minutes` (Number) The number of minutes before the second configured action should be performed after a user session disconnects in peak hours. - `peak_log_off_action` (String) The action to be performed after a configurable period of a user session ending in peak hours. Choose between `Nothing`, `Suspend`, and `Shutdown`. Default is `Nothing`. - `peak_log_off_timeout_minutes` (Number) The number of minutes before the configured action should be performed after a user session ends in peak hours. +- `peak_restrict_min_idle_untagged_percent` (Number) Specifies the percentage of remaining untagged capacity to fall below to start powering on tagged machines during peak hours. + +~> **Please Note** This setting is only applicable when the `restrict_autoscale_tag` is set. - `power_off_delay_minutes` (Number) Delay before machines are powered off, when scaling down. Specified in minutes. ~> **Please Note** Applies only to multi-session machines. @@ -296,6 +306,7 @@ Optional: - `power_time_schemes` (Attributes List) Power management time schemes. ~> **Please Note** It is not allowed to have more than one power time scheme that cover the same day of the week for the same delivery group. (see [below for nested schema](#nestedatt--autoscale_settings--power_time_schemes)) +- `restrict_autoscale_tag` (String) Name of the tag on the machines that autoscale will apply on. - `timezone` (String) The time zone in which this delivery group's machines reside. @@ -416,14 +427,14 @@ Required: Required: -- `enable_session_roaming` (Boolean) When enabled, if the user launches this desktop and then moves to another device, the same session is used, and applications are available on both devices. When disabled, the session no longer roams between devices. - -~> **Please Note** Session roaming should be set to `false` for Remote PC Delivery Group. - `published_name` (String) A display name for the desktop. Optional: - `description` (String) A description for the published desktop. The name and description are shown in Citrix Workspace app. +- `enable_session_roaming` (Boolean) When enabled, if the user launches this desktop and then moves to another device, the same session is used, and applications are available on both devices. When disabled, the session no longer roams between devices. + +~> **Please Note** Session roaming should be set to `false` for Remote PC Delivery Group. - `enabled` (Boolean) Specify whether to enable the delivery of this desktop. Default is `true`. - `restrict_to_tag` (String) Restrict session launch to machines with tag specified in GUID. - `restricted_access_users` (Attributes) Restrict access to this Desktop by specifying users and groups in the allow and block list. If no value is specified, all users that have access to this Delivery Group will have access to the Desktop. diff --git a/docs/resources/desktop_icon.md b/docs/resources/desktop_icon.md index 54ca4c7..5d71d07 100644 --- a/docs/resources/desktop_icon.md +++ b/docs/resources/desktop_icon.md @@ -36,6 +36,6 @@ resource "citrix_desktop_icon" "example-desktop-icon" { Import is supported using the following syntax: ```shell -# Desktop icon can be imported by specifying the GUID -terraform import citrix_desktop_icon.example-desktop-icon 4cec0568-1c91-407f-a32e-cc487822d0a0 +# Desktop icon can be imported by specifying the ID +terraform import citrix_desktop_icon.example-desktop-icon 1 ``` \ No newline at end of file diff --git a/docs/resources/gac_discovery.md b/docs/resources/gac_discovery.md new file mode 100644 index 0000000..64397fe --- /dev/null +++ b/docs/resources/gac_discovery.md @@ -0,0 +1,42 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "citrix_gac_discovery Resource - citrix" +subcategory: "Citrix Cloud" +description: |- + Manages the Global App Configuration Discovery. +--- + +# citrix_gac_discovery (Resource) + +Manages the Global App Configuration Discovery. + +## Example Usage + +```terraform +resource "citrix_gac_discovery" "example-gac-discovery" { + domain = "example-domain.com" + service_urls = ["https://example.com:443", "https://example2.com:80"] + allowed_web_store_urls = ["https://example.com", "https://example2.com"] +} +``` + + +## Schema + +### Required + +- `domain` (String) Domain name of the discovery record. The domain must not contain uppercase letters. +- `service_urls` (Set of String) The list of store URLs that are returned for email-based discovery. Each URL must end with a port number like example.com:443. + +### Optional + +- `allowed_web_store_urls` (Set of String) The list of custom Web URLs, URL needs to match the domain claimed (Optional). + +## Import + +Import is supported using the following syntax: + +```shell +# Global App Configuration Discovery can be imported by specifying the domain +terraform import citrix_gac_discovery.example-gac-discovery example-domain.com +``` \ No newline at end of file diff --git a/docs/resources/machine_catalog.md b/docs/resources/machine_catalog.md index fb781cd..dc27abd 100644 --- a/docs/resources/machine_catalog.md +++ b/docs/resources/machine_catalog.md @@ -4,12 +4,15 @@ page_title: "citrix_machine_catalog Resource - citrix" subcategory: "CVAD" description: |- Manages a machine catalog. + -> Note To bind a machine catalog to a Workspace Environment Management (WEM) configuration set, use citrix_wem_directory_object resource. --- # citrix_machine_catalog (Resource) Manages a machine catalog. +-> **Note** To bind a machine catalog to a Workspace Environment Management (WEM) configuration set, use `citrix_wem_directory_object` resource. + ## Example Usage ```terraform @@ -37,8 +40,10 @@ resource "citrix_machine_catalog" "example-azure-mtsession" { azure_master_image = { # shared_subscription = var.azure_image_subscription # Uncomment if the image is from a subscription outside of the hypervisor's subscription - # For Azure master image from managed disk or snapshot + # Resource Group is required for any type of Azure master image resource_group = var.azure_resource_group + + # For Azure master image from managed disk or snapshot master_image = var.azure_master_image # For Azure image gallery @@ -318,7 +323,7 @@ resource "citrix_machine_catalog" "example-manual-power-managed-mtsession" { { region = "East US" resource_group_name = "machine-resource-group-name" - machine_account = "DOMAIN\\MachineName" + machine_account = "domain\\machine-name" machine_name = "MachineName" } ] @@ -397,8 +402,10 @@ resource "citrix_machine_catalog" "example-non-domain-joined-azure-mcs" { azure_master_image = { # shared_subscription = var.azure_image_subscription # Uncomment if the image is from a subscription outside of the hypervisor's subscription - # For Azure master image from managed disk or snapshot + # Resource Group is required for any type of Azure master image resource_group = var.azure_resource_group + + # For Azure master image from managed disk or snapshot master_image = var.azure_master_image # For Azure image gallery @@ -447,6 +454,7 @@ resource "citrix_machine_catalog" "example-non-domain-joined-azure-mcs" { - `machine_catalog_folder_path` (String) The path to the folder in which the machine catalog is located. - `metadata` (Attributes List) Metadata for the Machine Catalog. (see [below for nested schema](#nestedatt--metadata)) - `minimum_functional_level` (String) Specifies the minimum functional level for the VDA machines in the catalog. Defaults to `L7_20`. +- `persist_user_changes` (String) Specify if user changes are persisted on the machines in the machine catalog. Choose between `Discard` and `OnLocal`. Defaults to OnLocal for manual or non-PVS single session static catalogs, Discard otherwise. - `provisioning_scheme` (Attributes) Machine catalog provisioning scheme. Required when `provisioning_type = MCS` or `provisioning_type = PVS_STREAMING`. (see [below for nested schema](#nestedatt--provisioning_scheme)) - `remote_pc_ous` (Attributes List) Organizational Units to be included in the Remote PC machine catalog. Only to be used when `is_remote_pc = true`. For adding machines, use `machine_accounts`. (see [below for nested schema](#nestedatt--remote_pc_ous)) - `scopes` (Set of String) The IDs of the scopes for the machine catalog to be a part of. @@ -476,7 +484,7 @@ Optional: Required: -- `machine_account` (String) The Computer AD Account for the machine. Must be in the format DOMAIN\MACHINE. +- `machine_account` (String) The computer AD account for the machine must be in the format \, all in lowercase. Optional: @@ -747,13 +755,16 @@ Required: Required: -- `domain` (String) The AD domain name for the pool. Specify this in FQDN format; for example, MyDomain.com. +- `domain` (String) The AD domain where machine accounts will be created. Specify this in FQDN format; for example, MyDomain.com. - `service_account` (String) Service account for the domain. Only the username is required; do not include the domain name. - `service_account_password` (String, Sensitive) Service account password for the domain. Optional: - `domain_ou` (String) The organization unit that computer accounts will be created into. +- `service_account_domain` (String) The domain name of the service account. Specify this in FQDN format; for example, MyServiceDomain.com. + +~> **Please Note** Use this property if domain of the service account which is used to create the machine accounts resides in a domain different from what's specified in property `domain` where the machine accounts are created. diff --git a/docs/resources/machine_properties.md b/docs/resources/machine_properties.md new file mode 100644 index 0000000..3b8821c --- /dev/null +++ b/docs/resources/machine_properties.md @@ -0,0 +1,42 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "citrix_machine_properties Resource - citrix" +subcategory: "CVAD" +description: |- + Manages the properties of a machine. +--- + +# citrix_machine_properties (Resource) + +Manages the properties of a machine. + +## Example Usage + +```terraform +resource "citrix_machine_properties" "example_machine_properties" { + name = "domain\\machine-name" // For workgroup machines, use machine-name only + machine_catalog_id = "00000000-0000-0000-0000-000000000000" // Id of the machine catalog the machine belongs to + tags = [ "11111111-1111-1111-1111-111111111111" ] // Tags to be assigned to the machine +} +``` + + +## Schema + +### Required + +- `machine_catalog_id` (String) The ID of the machine catalog to which the machine belongs. +- `name` (String) The Name of the machine. For domain joined machines, the name must be in format \ format. Must be all in lowercase. + +### Optional + +- `tags` (Set of String) A set of identifiers of tags to associate with the machine. + +## Import + +Import is supported using the following syntax: + +```shell +# citrix_machine_properties resource can be imported with the Machine Name +terraform import citrix_machine_properties.example_machine_properties domain\machine-name +``` \ No newline at end of file diff --git a/docs/resources/quickcreate_aws_workspaces_image.md b/docs/resources/quickcreate_aws_workspaces_image.md index a1f6668..d4c6b04 100644 --- a/docs/resources/quickcreate_aws_workspaces_image.md +++ b/docs/resources/quickcreate_aws_workspaces_image.md @@ -42,7 +42,7 @@ resource "citrix_quickcreate_aws_workspaces_image" "example_aws_workspaces_image ### Required - `account_id` (String) GUID identifier of the account. -- `aws_image_id` (String) Id of the image on AWS. +- `aws_image_id` (String) Id of the image to be imported in AWS. - `description` (String) Description of the image. - `ingestion_process` (String) The type of ingestion process of the image. Possible values are `BYOL_REGULAR_BYOP` and `BYOL_GRAPHICS_G4DN_BYOP`. - `name` (String) Name of the image. @@ -51,6 +51,7 @@ resource "citrix_quickcreate_aws_workspaces_image" "example_aws_workspaces_image ### Read-Only +- `aws_imported_image_id` (String) The Id of the image imported in AWS WorkSpaces. - `id` (String) GUID identifier of the image. - `state` (String) The state of ingestion process of the image. - `tenancy` (String) The type of tenancy of the image. diff --git a/docs/resources/wem_configuration_set.md b/docs/resources/wem_configuration_set.md index e35c3bf..e1029a1 100644 --- a/docs/resources/wem_configuration_set.md +++ b/docs/resources/wem_configuration_set.md @@ -4,15 +4,12 @@ page_title: "citrix_wem_configuration_set Resource - citrix" subcategory: "WEM" description: |- Manages configuration sets within a WEM deployment. - ~> Disclaimer This feature is supported for Citrix Cloud customers, and will be made available for On-Premises soon. --- # citrix_wem_configuration_set (Resource) Manages configuration sets within a WEM deployment. -~> **Disclaimer** This feature is supported for Citrix Cloud customers, and will be made available for On-Premises soon. - ## Example Usage ```terraform diff --git a/examples/basic_azure_mcs_vda/machine_catalogs.tf b/examples/basic_azure_mcs_vda/machine_catalogs.tf index d6d2b7a..beac725 100644 --- a/examples/basic_azure_mcs_vda/machine_catalogs.tf +++ b/examples/basic_azure_mcs_vda/machine_catalogs.tf @@ -20,8 +20,10 @@ resource "citrix_machine_catalog" "example-catalog" { azure_master_image = { # shared_subscription = var.azure_image_subscription # Uncomment if the image is from a subscription outside of the hypervisor's subscription - # For Azure master image from managed disk or snapshot + # Resource Group is required for any type of Azure master image resource_group = var.azure_resource_group + + # For Azure master image from managed disk or snapshot master_image = var.azure_master_image # For Azure image gallery diff --git a/examples/non_domain_joined_azure_mcs_vda/machine_catalogs.tf b/examples/non_domain_joined_azure_mcs_vda/machine_catalogs.tf index 0dbf9cc..1fbe416 100644 --- a/examples/non_domain_joined_azure_mcs_vda/machine_catalogs.tf +++ b/examples/non_domain_joined_azure_mcs_vda/machine_catalogs.tf @@ -15,8 +15,10 @@ resource "citrix_machine_catalog" "example-catalog" { azure_master_image = { # shared_subscription = var.azure_image_subscription # Uncomment if the image is from a subscription outside of the hypervisor's subscription - # For Azure master image from managed disk or snapshot + # Resource Group is required for any type of Azure master image resource_group = var.azure_resource_group + + # For Azure master image from managed disk or snapshot master_image = var.azure_master_image # For Azure image gallery diff --git a/go.mod b/go.mod index 6b5fabc..f08369e 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,22 @@ module github.com/citrix/terraform-provider-citrix -go 1.22.0 +go 1.22.7 toolchain go1.23.1 require ( - github.com/citrix/citrix-daas-rest-go v1.0.7 + github.com/citrix/citrix-daas-rest-go v1.0.8 github.com/google/uuid v1.6.0 github.com/hashicorp/go-azure-helpers v0.71.0 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/terraform-plugin-docs v0.14.1 - github.com/hashicorp/terraform-plugin-framework v1.12.0 - github.com/hashicorp/terraform-plugin-framework-validators v0.14.0 - github.com/hashicorp/terraform-plugin-go v0.24.0 + github.com/hashicorp/terraform-plugin-framework v1.13.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 + github.com/hashicorp/terraform-plugin-go v0.25.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.8.0 - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c - golang.org/x/mod v0.21.0 + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f + golang.org/x/mod v0.22.0 ) require ( @@ -38,14 +38,15 @@ require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.6.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect - github.com/hashicorp/hc-install v0.6.4 // indirect + github.com/hashicorp/hc-install v0.9.0 // indirect github.com/hashicorp/hcl/v2 v2.22.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect - github.com/hashicorp/terraform-json v0.22.1 // indirect - github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect + github.com/hashicorp/terraform-json v0.23.0 // indirect + github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.2 // indirect @@ -68,15 +69,15 @@ require ( github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect - golang.org/x/crypto v0.28.0 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect - golang.org/x/tools v0.26.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/tools v0.27.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/grpc v1.67.1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/grpc v1.68.0 // indirect google.golang.org/protobuf v1.35.1 // indirect ) diff --git a/go.sum b/go.sum index e757f69..e4fb4ab 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= -github.com/citrix/citrix-daas-rest-go v1.0.7 h1:KMflWuN6dR5ZqFVWiksqFRtZD5IDrVoq6U2CtPH/5Dk= -github.com/citrix/citrix-daas-rest-go v1.0.7/go.mod h1:4Me0VHpyxMYfPwpU2XWV0jOE2Jdz8MHNpge3MLD5B2E= +github.com/citrix/citrix-daas-rest-go v1.0.8 h1:I164Uw1DerU6pIkU712wBxnPx8kP2uYT98vXeFoQuc8= +github.com/citrix/citrix-daas-rest-go v1.0.8/go.mod h1:4Me0VHpyxMYfPwpU2XWV0jOE2Jdz8MHNpge3MLD5B2E= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= @@ -83,33 +83,35 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.6.4 h1:QLqlM56/+SIIGvGcfFiwMY3z5WGXT066suo/v9Km8e0= -github.com/hashicorp/hc-install v0.6.4/go.mod h1:05LWLy8TD842OtgcfBbOT0WMoInBMUSHjmDx10zuBIA= +github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE= +github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg= github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M= github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= -github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= -github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= +github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= +github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= github.com/hashicorp/terraform-plugin-docs v0.14.1 h1:MikFi59KxrP/ewrZoaowrB9he5Vu4FtvhamZFustiA4= github.com/hashicorp/terraform-plugin-docs v0.14.1/go.mod h1:k2NW8+t113jAus6bb5tQYQgEAX/KueE/u8X2Z45V1GM= -github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ= -github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE= -github.com/hashicorp/terraform-plugin-framework-validators v0.14.0 h1:3PCn9iyzdVOgHYOBmncpSSOxjQhCTYmc+PGvbdlqSaI= -github.com/hashicorp/terraform-plugin-framework-validators v0.14.0/go.mod h1:LwDKNdzxrDY/mHBrlC6aYfE2fQ3Dk3gaJD64vNiXvo4= -github.com/hashicorp/terraform-plugin-go v0.24.0 h1:2WpHhginCdVhFIrWHxDEg6RBn3YaWzR2o6qUeIEat2U= -github.com/hashicorp/terraform-plugin-go v0.24.0/go.mod h1:tUQ53lAsOyYSckFGEefGC5C8BAaO0ENqzFd3bQeuYQg= +github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= +github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU= +github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 h1:RXMmu7JgpFjnI1a5QjMCBb11usrW2OtAG+iOTIj5c9Y= +github.com/hashicorp/terraform-plugin-framework-validators v0.15.0/go.mod h1:Bh89/hNmqsEWug4/XWKYBwtnw3tbz5BAy1L1OgvbIaY= +github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks= +github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 h1:wyKCCtn6pBBL46c1uIIBNUOWlNfYXfXpVo16iDyLp8Y= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0/go.mod h1:B0Al8NyYVr8Mp/KLwssKXG1RqnTk7FySqSn4fRuLNgw= github.com/hashicorp/terraform-plugin-testing v1.8.0 h1:wdYIgwDk4iO933gC4S8KbKdnMQShu6BXuZQPScmHvpk= github.com/hashicorp/terraform-plugin-testing v1.8.0/go.mod h1:o2kOgf18ADUaZGhtOl0YCkfIxg01MAiMATT2EtIHlZk= github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= @@ -213,25 +215,25 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -246,8 +248,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -257,22 +259,22 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= diff --git a/internal/citrixcloud/admin_user/admin_user_resource_model.go b/internal/citrixcloud/admin_user/admin_user_resource_model.go index 6d2e074..c242e69 100644 --- a/internal/citrixcloud/admin_user/admin_user_resource_model.go +++ b/internal/citrixcloud/admin_user/admin_user_resource_model.go @@ -235,7 +235,7 @@ func (r CCAdminUserResourceModel) RefreshPropertyValues(ctx context.Context, dia return r } -func (r CCAdminPolicyResourceModel) RefreshListItem(ctx context.Context, diags *diag.Diagnostics, adminAccessPolicy ccadmins.AdministratorAccessPolicyModel) util.ModelWithAttributes { +func (r CCAdminPolicyResourceModel) RefreshListItem(ctx context.Context, diags *diag.Diagnostics, adminAccessPolicy ccadmins.AdministratorAccessPolicyModel) util.ResourceModelWithAttributes { // Update the name and service name from the admin access policy r.Name = types.StringValue(adminAccessPolicy.GetDisplayName()) r.ServiceName = types.StringValue(adminAccessPolicy.GetServiceName()) diff --git a/internal/citrixcloud/admin_user/admin_user_utils.go b/internal/citrixcloud/admin_user/admin_user_utils.go index d45518e..3e0e438 100644 --- a/internal/citrixcloud/admin_user/admin_user_utils.go +++ b/internal/citrixcloud/admin_user/admin_user_utils.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "strings" + "time" ccadmins "github.com/citrix/citrix-daas-rest-go/ccadmins" citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" @@ -112,25 +113,73 @@ func getAdminUserPolicies(ctx context.Context, diagnostics *diag.Diagnostics, cl // If access type is Custom, retrieve and add policies if strings.EqualFold(adminUserResourceModel.AccessType.ValueString(), string(ccadmins.ADMINISTRATORACCESSTYPE_CUSTOM)) { - accessPolicies, err := getListOfAllAccessPolicies(ctx, client) + return fetchAdminPoliciesWithRetry(ctx, diagnostics, client, adminUserResourceModel) + } + return nil, fmt.Errorf("invalid access type") +} + +func fetchAdminPoliciesWithRetry(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, adminUserResourceModel CCAdminUserResourceModel) ([]ccadmins.AdministratorAccessPolicyModel, error) { + adminId, err := getAdminIdFromAuthToken(client) + if err != nil { + err = fmt.Errorf("Unable to verify access of the admin user\n" + err.Error()) + return nil, err + } + + if adminId == "" { + err = fmt.Errorf("admin user not found") + return nil, err + } + + // Adding a retry logic to fetch the admin policies when a role name or scope name is not found. This is necessary because when a new role or scope is created using the orchestration API, the CC Admin API may take up to 3 minutes + // to reflect the updated list. Therefore, we need to wait before throwing an error indicating that the value is not found. + const ( + maxRetries = 7 + retryInterval = 30 * time.Second + ) + + var ( + accessPolicies *ccadmins.AdministratorAccessModel + adminPolicyAccessModels []ccadmins.AdministratorAccessPolicyModel + itemFound bool + ) + + for retryCount := 0; retryCount < maxRetries; retryCount++ { + accessPolicies, err = getAccessPolicies(ctx, client, adminId) if err != nil { return nil, err } - policies := util.ObjectListToTypedArray[CCAdminPolicyResourceModel](ctx, diagnostics, adminUserResourceModel.Policies) - adminPolicyAccessModels := []ccadmins.AdministratorAccessPolicyModel{} - for _, policy := range policies { - adminAccessPolicyModel, err := getAdminAccessPolicy(ctx, diagnostics, policy, accessPolicies.GetPolicies()) - if err != nil { - return nil, err - } - adminPolicyAccessModels = append(adminPolicyAccessModels, adminAccessPolicyModel) + + adminPolicyAccessModels, itemFound, err = fetchAdminPolicyAccessModels(ctx, diagnostics, adminUserResourceModel, accessPolicies) + // If no error or item found, break the loop + if err == nil || itemFound { + break } - return adminPolicyAccessModels, nil + // Sleep for the retry interval before trying again + time.Sleep(retryInterval) } - return nil, fmt.Errorf("invalid access type") + + if err != nil { + err = fmt.Errorf("error fetching admin policy access models\n" + err.Error()) + return nil, err + } + + return adminPolicyAccessModels, nil } -func getAdminAccessPolicy(ctx context.Context, diagnostics *diag.Diagnostics, adminPolicyResourceModel CCAdminPolicyResourceModel, remoteAdminPolicies []ccadmins.AdministratorAccessPolicyModel) (ccadmins.AdministratorAccessPolicyModel, error) { +func fetchAdminPolicyAccessModels(ctx context.Context, diagnostics *diag.Diagnostics, adminUserResourceModel CCAdminUserResourceModel, accessPolicies *ccadmins.AdministratorAccessModel) ([]ccadmins.AdministratorAccessPolicyModel, bool, error) { + policies := util.ObjectListToTypedArray[CCAdminPolicyResourceModel](ctx, diagnostics, adminUserResourceModel.Policies) + adminPolicyAccessModels := []ccadmins.AdministratorAccessPolicyModel{} + for _, policy := range policies { + adminAccessPolicyModel, itemFound, err := getAdminAccessPolicy(ctx, diagnostics, policy, accessPolicies.GetPolicies()) + if err != nil { + return nil, itemFound, err + } + adminPolicyAccessModels = append(adminPolicyAccessModels, adminAccessPolicyModel) + } + return adminPolicyAccessModels, false, nil +} + +func getAdminAccessPolicy(ctx context.Context, diagnostics *diag.Diagnostics, adminPolicyResourceModel CCAdminPolicyResourceModel, remoteAdminPolicies []ccadmins.AdministratorAccessPolicyModel) (ccadmins.AdministratorAccessPolicyModel, bool, error) { policyDisplayName := adminPolicyResourceModel.Name.ValueString() serviceName := adminPolicyResourceModel.ServiceName.ValueString() scopes := util.StringSetToStringArray(ctx, diagnostics, adminPolicyResourceModel.Scopes) @@ -173,13 +222,13 @@ func getAdminAccessPolicy(ctx context.Context, diagnostics *diag.Diagnostics, ad } if !scopeNameExists { err := fmt.Errorf("scope with name: %s not found", scope) - return createAdminPolicyModel, err + return createAdminPolicyModel, false, err } createAdminPolicyModel.SetScopeChoices(createScopeChoices) } } else if len(scopes) > 0 { err := fmt.Errorf("policy with name: %s does not contain any scopes", policyDisplayName) - return createAdminPolicyModel, err + return createAdminPolicyModel, true, err } else { createAdminPolicyModel.SetScopeChoices(remotePolicyScopeChoices) } @@ -187,33 +236,18 @@ func getAdminAccessPolicy(ctx context.Context, diagnostics *diag.Diagnostics, ad } if !policyNameExists { err := fmt.Errorf("policy with name: %s not found", policyDisplayName) - return createAdminPolicyModel, err + return createAdminPolicyModel, false, err } if len(serviceNameList) > 1 { err := fmt.Errorf("policy with name: %s is associated with multiple services %v. Please specify one of the services in the 'service_name' attribute", policyDisplayName, strings.Join(serviceNameList, ", ")) - return createAdminPolicyModel, err + return createAdminPolicyModel, true, err } if createAdminPolicyModel.GetServiceName() == ADMINISTRATORSERVICENAME_XENDESKTOP && len(scopes) == 0 { err := fmt.Errorf("policy '%s' with service name '%s' has no scopes; please add scope values", policyDisplayName, ADMINISTRATORSERVICENAME_XENDESKTOP) - return createAdminPolicyModel, err - } - return createAdminPolicyModel, nil -} - -func getListOfAllAccessPolicies(ctx context.Context, client *citrixdaasclient.CitrixDaasClient) (*ccadmins.AdministratorAccessModel, error) { - adminId, err := getAdminIdFromAuthToken(client) - if err != nil { - err = fmt.Errorf("Unable to verify access of the admin user\n" + err.Error()) - return nil, err - } - - if adminId == "" { - err = fmt.Errorf("admin user not found") - return nil, err + return createAdminPolicyModel, true, err } - // Get all access policies for the admin user - return getAccessPolicies(ctx, client, adminId) + return createAdminPolicyModel, true, nil } func getAccessPolicies(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, adminId string) (*ccadmins.AdministratorAccessModel, error) { diff --git a/internal/citrixcloud/global_app_configuration/gac_discovery_resource.go b/internal/citrixcloud/global_app_configuration/gac_discovery_resource.go new file mode 100644 index 0000000..7a5b9be --- /dev/null +++ b/internal/citrixcloud/global_app_configuration/gac_discovery_resource.go @@ -0,0 +1,312 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package global_app_configuration + +import ( + "context" + + citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" + globalappconfiguration "github.com/citrix/citrix-daas-rest-go/globalappconfiguration" + "github.com/citrix/terraform-provider-citrix/internal/util" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &gacDiscoveryResource{} + _ resource.ResourceWithConfigure = &gacDiscoveryResource{} + _ resource.ResourceWithImportState = &gacDiscoveryResource{} + _ resource.ResourceWithModifyPlan = &gacDiscoveryResource{} + _ resource.ResourceWithValidateConfig = &gacDiscoveryResource{} +) + +// NewGACDiscoveryResource is a helper function to simplify the provider implementation. +func NewGacDiscoveryResource() resource.Resource { + return &gacDiscoveryResource{} +} + +// GACDiscoveryResource is the resource implementation. +type gacDiscoveryResource struct { + client *citrixdaasclient.CitrixDaasClient +} + +// Metadata returns the resource type name. +func (r *gacDiscoveryResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_gac_discovery" +} + +// Schema defines the schema for the resource. +func (r *gacDiscoveryResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = GACDiscoveryResourceModel{}.GetSchema() +} + +// Configure adds the provider configured client to the resource. +func (r *gacDiscoveryResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*citrixdaasclient.CitrixDaasClient) +} + +// Create creates the resource and sets the initial Terraform state. +func (r *gacDiscoveryResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + var plan GACDiscoveryResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var app globalappconfiguration.Apps + var workspace globalappconfiguration.Workspace + var domain globalappconfiguration.Domain + domain.SetName(plan.Domain.ValueString()) + + var aURLs []globalappconfiguration.AllowedWebStoreURL + var sURLs []globalappconfiguration.DiscoveryServiceURL + + // Set the allowed web store urls + urls := util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.AllowedWebStoreURLs) + for _, url := range urls { + var aURL globalappconfiguration.AllowedWebStoreURL + aURL.SetUrl(url) + aURLs = append(aURLs, aURL) + } + + // Set the service urls + urls = util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.ServiceURLs) + for _, url := range urls { + var sURL globalappconfiguration.DiscoveryServiceURL + sURL.SetUrl(url) + sURLs = append(sURLs, sURL) + } + + workspace.SetAllowedWebStoreURLs(aURLs) + workspace.SetServiceURLs(sURLs) + app.SetWorkspace(workspace) + + var body globalappconfiguration.DiscoveryRecordModel + + body.SetApp(app) + body.SetDomain(domain) + + // Call the API + createDiscoveryRequest := r.client.GacClient.DiscoveryControllerDAAS.PostDiscoveryApiUsingPOST(ctx, util.GacAppName) + createDiscoveryRequest = createDiscoveryRequest.DiscoveryRecord(body) + _, httpResp, err := citrixdaasclient.AddRequestData(createDiscoveryRequest, r.client).Execute() + + //In case of error, add it to diagnostics and return + if err != nil { + resp.Diagnostics.AddError( + "Error creating GAC Discovery for Domain: "+plan.Domain.ValueString(), + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadGacError(err), + ) + return + } + + //Try to get the new discovery configuration from remote + discoveryConfiguration, err := getDiscoveryConfiguration(ctx, r.client, &resp.Diagnostics, plan.Domain.ValueString()) + if err != nil { + return + } + + // Map response body to schema and populate computed attribute values + if len(discoveryConfiguration.GetItems()) == 0 { + resp.Diagnostics.AddError("Error fetching discovery configuration for domain: "+plan.Domain.ValueString(), "No discovery configuration found for domain: "+plan.Domain.ValueString()) + return + } else { + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, discoveryConfiguration.GetItems()[0]) + } + + // Set state to fully populated data + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read refreshes the Terraform state with the latest data. +func (r *gacDiscoveryResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + var state GACDiscoveryResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Try to get Service Url discovery from remote + discoveryConfiguration, err := readDiscoveryConfiguration(ctx, r.client, resp, state.Domain.ValueString()) + if err != nil { + return + } + + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, discoveryConfiguration.GetItems()[0]) + + // Set refreshed state + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *gacDiscoveryResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + var plan GACDiscoveryResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var app globalappconfiguration.Apps + var workspace globalappconfiguration.Workspace + var domain globalappconfiguration.Domain + domain.SetName(plan.Domain.ValueString()) + + var aURLs []globalappconfiguration.AllowedWebStoreURL + var sURLs []globalappconfiguration.DiscoveryServiceURL + + // Set the allowed web store urls + urls := util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.AllowedWebStoreURLs) + for _, url := range urls { + var aURL globalappconfiguration.AllowedWebStoreURL + aURL.SetUrl(url) + aURLs = append(aURLs, aURL) + } + + // Set the service urls + urls = util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.ServiceURLs) + for _, url := range urls { + var sURL globalappconfiguration.DiscoveryServiceURL + sURL.SetUrl(url) + sURLs = append(sURLs, sURL) + } + + workspace.SetAllowedWebStoreURLs(aURLs) + workspace.SetServiceURLs(sURLs) + app.SetWorkspace(workspace) + + var body globalappconfiguration.DiscoveryRecordModel + + body.SetApp(app) + body.SetDomain(domain) + + // Call the API + updateDiscoveryRequest := r.client.GacClient.DiscoveryControllerDAAS.PutDiscoveryApiUsingPUT(ctx, util.GacAppName, plan.Domain.ValueString()) + updateDiscoveryRequest = updateDiscoveryRequest.DiscoveryRecord(body) + httpResp, err := citrixdaasclient.AddRequestData(updateDiscoveryRequest, r.client).Execute() + + if err != nil { + resp.Diagnostics.AddError( + "Error updating discovery configuration: "+plan.Domain.ValueString(), + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadGacError(err), + ) + return + } + + // Try to get Service Url discovery from remote + updateddiscoveryConfiguration, err := getDiscoveryConfiguration(ctx, r.client, &resp.Diagnostics, plan.Domain.ValueString()) + if err != nil { + return + } + + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, updateddiscoveryConfiguration.GetItems()[0]) + + // Set state to fully populated data + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *gacDiscoveryResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + // Retrieve values from state + var state GACDiscoveryResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + //Delete discovery configuration for the domain + deleteDiscoveryRequest := r.client.GacClient.DiscoveryControllerDAAS.DeleteDiscoveryApiUsingDELETE(ctx, util.GacAppName, state.Domain.ValueString()) + httpResp, err := citrixdaasclient.AddRequestData(deleteDiscoveryRequest, r.client).Execute() + + if err != nil { + resp.Diagnostics.AddError( + "Error deleting gac discovery configuration for domain: "+state.Domain.ValueString(), + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadGacError(err), + ) + return + } + +} + +func (r *gacDiscoveryResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // Retrieve import ID and save to id attribute + resource.ImportStatePassthroughID(ctx, path.Root("domain"), req, resp) +} + +func getDiscoveryConfiguration(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, domain string) (*globalappconfiguration.GetAllDiscoveryResponse, error) { + getDiscoveryRequest := client.GacClient.DiscoveryControllerDAAS.GetDiscoveryApiUsingGET(ctx, util.GacAppName, domain) + getDiscoveryResponse, httpResp, err := citrixdaasclient.ExecuteWithRetry[*globalappconfiguration.GetAllDiscoveryResponse](getDiscoveryRequest, client) + if err != nil { + diagnostics.AddError( + "Error fetching discovery configuration for doamin: "+domain, + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadGacError(err), + ) + return nil, err + } + + return getDiscoveryResponse, nil +} + +func readDiscoveryConfiguration(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, resp *resource.ReadResponse, domain string) (*globalappconfiguration.GetAllDiscoveryResponse, error) { + getDiscoveryRequest := client.GacClient.DiscoveryControllerDAAS.GetDiscoveryApiUsingGET(ctx, util.GacAppName, domain) + getDiscoveryResponse, _, err := util.ReadResource[*globalappconfiguration.GetAllDiscoveryResponse](getDiscoveryRequest, ctx, client, resp, "Discovery Configuration", domain) + return getDiscoveryResponse, err +} + +func (r *gacDiscoveryResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + if r.client != nil && r.client.GacClient == nil { + resp.Diagnostics.AddError(util.ProviderInitializationErrorMsg, util.MissingProviderClientIdAndSecretErrorMsg) + return + } +} + +func (r *gacDiscoveryResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + var data GACDiscoveryResourceModel + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + schemaType, configValuesForSchema := util.GetConfigValuesForSchema(ctx, &resp.Diagnostics, &data) + tflog.Debug(ctx, "Validate Config - "+schemaType, configValuesForSchema) +} diff --git a/internal/citrixcloud/global_app_configuration/gac_discovery_resource_model.go b/internal/citrixcloud/global_app_configuration/gac_discovery_resource_model.go new file mode 100644 index 0000000..343970c --- /dev/null +++ b/internal/citrixcloud/global_app_configuration/gac_discovery_resource_model.go @@ -0,0 +1,77 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package global_app_configuration + +import ( + "context" + "regexp" + + globalappconfiguration "github.com/citrix/citrix-daas-rest-go/globalappconfiguration" + "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type GACDiscoveryResourceModel struct { + Domain types.String `tfsdk:"domain"` + ServiceURLs types.Set `tfsdk:"service_urls"` + AllowedWebStoreURLs types.Set `tfsdk:"allowed_web_store_urls"` // +} + +func (GACDiscoveryResourceModel) GetAttributes() map[string]schema.Attribute { + return GACDiscoveryResourceModel{}.GetSchema().Attributes +} + +func (GACDiscoveryResourceModel) GetSchema() schema.Schema { + return schema.Schema{ + Description: "Citrix Cloud --- Manages the Global App Configuration Discovery.", + Attributes: map[string]schema.Attribute{ + "domain": schema.StringAttribute{ + Description: "Domain name of the discovery record. The domain must not contain uppercase letters.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^[^A-Z]*$`), "The domain must not contain uppercase letters."), + }, + }, + "service_urls": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The list of store URLs that are returned for email-based discovery. Each URL must end with a port number like example.com:443.", + Required: true, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.RegexMatches(regexp.MustCompile(`:\d+$`), "Each URL must end with a port number."), + ), + }, + }, + "allowed_web_store_urls": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The list of custom Web URLs, URL needs to match the domain claimed (Optional).", + Optional: true, + }, + }, + } +} + +func (r GACDiscoveryResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, discoveryRecordModel globalappconfiguration.DiscoveryRecordModel) GACDiscoveryResourceModel { + + r.Domain = types.StringValue(discoveryRecordModel.Domain.GetName()) + + var sURLs []string + for _, serviceURL := range discoveryRecordModel.App.Workspace.ServiceURLs { + sURLs = append(sURLs, *serviceURL.Url) + } + r.ServiceURLs = util.StringArrayToStringSet(ctx, diagnostics, sURLs) + + var aURLs []string + for _, allowedWebStoreURL := range discoveryRecordModel.App.Workspace.AllowedWebStoreURLs { + aURLs = append(aURLs, *allowedWebStoreURL.Url) + } + r.AllowedWebStoreURLs = util.StringArrayToStringSet(ctx, diagnostics, aURLs) + + return r +} diff --git a/internal/citrixcloud/gac_settings/gac_settings_resource.go b/internal/citrixcloud/global_app_configuration/gac_settings_resource.go similarity index 99% rename from internal/citrixcloud/gac_settings/gac_settings_resource.go rename to internal/citrixcloud/global_app_configuration/gac_settings_resource.go index 820253f..f35f50f 100644 --- a/internal/citrixcloud/gac_settings/gac_settings_resource.go +++ b/internal/citrixcloud/global_app_configuration/gac_settings_resource.go @@ -1,6 +1,6 @@ // Copyright © 2024. Citrix Systems, Inc. -package gac_settings +package global_app_configuration import ( "context" @@ -198,6 +198,7 @@ func (r *gacSettingsResource) Update(ctx context.Context, req resource.UpdateReq "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ "\nError message: "+util.ReadGacError(err), ) + return } // Try to get Service Url settings from remote diff --git a/internal/citrixcloud/gac_settings/gac_settings_resource_model.go b/internal/citrixcloud/global_app_configuration/gac_settings_resource_model.go similarity index 99% rename from internal/citrixcloud/gac_settings/gac_settings_resource_model.go rename to internal/citrixcloud/global_app_configuration/gac_settings_resource_model.go index e3accfb..85d2ce9 100644 --- a/internal/citrixcloud/gac_settings/gac_settings_resource_model.go +++ b/internal/citrixcloud/global_app_configuration/gac_settings_resource_model.go @@ -1,6 +1,6 @@ // Copyright © 2024. Citrix Systems, Inc. -package gac_settings +package global_app_configuration import ( "context" diff --git a/internal/citrixcloud/gac_settings/gac_settings_util.go b/internal/citrixcloud/global_app_configuration/gac_settings_util.go similarity index 91% rename from internal/citrixcloud/gac_settings/gac_settings_util.go rename to internal/citrixcloud/global_app_configuration/gac_settings_util.go index 1c46801..d73bf77 100644 --- a/internal/citrixcloud/gac_settings/gac_settings_util.go +++ b/internal/citrixcloud/global_app_configuration/gac_settings_util.go @@ -1,6 +1,6 @@ // Copyright © 2024. Citrix Systems, Inc. -package gac_settings +package global_app_configuration import ( "context" @@ -345,7 +345,7 @@ func verifyStruct(mapData map[string]interface{}, goType interface{}) bool { func WindowsSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnostics, windowsSetting *WindowsSettings) { windowsSetting.ValueList = types.ListNull(types.StringType) //setting null for LocalAppAllowList - localAppAllowListAttributesMap, err := util.AttributeMapFromObject(LocalAppAllowListModel{}) + localAppAllowListAttributesMap, err := util.ResourceAttributeMapFromObject(LocalAppAllowListModel{}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return @@ -353,7 +353,7 @@ func WindowsSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnos windowsSetting.LocalAppAllowList = types.ListNull(types.ObjectType{AttrTypes: localAppAllowListAttributesMap}) //setting null for ExtensionInstallAllowList - installAllowListAttributesMap, err := util.AttributeMapFromObject(ExtensionInstallAllowListModel{}) + installAllowListAttributesMap, err := util.ResourceAttributeMapFromObject(ExtensionInstallAllowListModel{}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return @@ -361,7 +361,7 @@ func WindowsSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnos windowsSetting.ExtensionInstallAllowList = types.ListNull(types.ObjectType{AttrTypes: installAllowListAttributesMap}) //setting null for EnterpriseBroswerSSO - enterpriseBrowserAttributesMap, err := util.AttributeMapFromObject(CitrixEnterpriseBrowserModel{}) + enterpriseBrowserAttributesMap, err := util.ResourceAttributeMapFromObject(CitrixEnterpriseBrowserModel{}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return @@ -369,7 +369,7 @@ func WindowsSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnos windowsSetting.EnterpriseBroswerSSO = types.ObjectNull(enterpriseBrowserAttributesMap) //setting null for AutoLaunchProtocolsFromOrigins - autoLaunchProtocolAttributesMap, err := util.AttributeMapFromObject(AutoLaunchProtocolsFromOriginsModel{AllowedOrigins: types.ListNull(types.StringType)}) + autoLaunchProtocolAttributesMap, err := util.ResourceAttributeMapFromObject(AutoLaunchProtocolsFromOriginsModel{AllowedOrigins: types.ListNull(types.StringType)}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return @@ -377,7 +377,7 @@ func WindowsSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnos windowsSetting.AutoLaunchProtocolsFromOrigins = types.ListNull(types.ObjectType{AttrTypes: autoLaunchProtocolAttributesMap}) //setting null for ManagedBookmarks - bookMarkAttributesMap, err := util.AttributeMapFromObject(BookMarkValueModel{}) + bookMarkAttributesMap, err := util.ResourceAttributeMapFromObject(BookMarkValueModel{}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return @@ -388,7 +388,7 @@ func WindowsSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnos func MacosSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnostics, macosSetting *MacosSettings) { macosSetting.ValueList = types.ListNull(types.StringType) //setting null for AutoLaunchProtocolsFromOrigins - autoLaunchProtocolAttributesMap, err := util.AttributeMapFromObject(AutoLaunchProtocolsFromOriginsModel{AllowedOrigins: types.ListNull(types.StringType)}) + autoLaunchProtocolAttributesMap, err := util.ResourceAttributeMapFromObject(AutoLaunchProtocolsFromOriginsModel{AllowedOrigins: types.ListNull(types.StringType)}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return @@ -396,7 +396,7 @@ func MacosSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnosti macosSetting.AutoLaunchProtocolsFromOrigins = types.ListNull(types.ObjectType{AttrTypes: autoLaunchProtocolAttributesMap}) //setting null for ManagedBookmarks - bookMarkAttributesMap, err := util.AttributeMapFromObject(BookMarkValueModel{}) + bookMarkAttributesMap, err := util.ResourceAttributeMapFromObject(BookMarkValueModel{}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return @@ -404,7 +404,7 @@ func MacosSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnosti macosSetting.ManagedBookmarks = types.ListNull(types.ObjectType{AttrTypes: bookMarkAttributesMap}) //setting null for ExtensionInstallAllowList - installAllowListAttributesMap, err := util.AttributeMapFromObject(ExtensionInstallAllowListModel{}) + installAllowListAttributesMap, err := util.ResourceAttributeMapFromObject(ExtensionInstallAllowListModel{}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return @@ -412,7 +412,7 @@ func MacosSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnosti macosSetting.ExtensionInstallAllowList = types.ListNull(types.ObjectType{AttrTypes: installAllowListAttributesMap}) //setting null for EnterpriseBroswerSSO - enterpriseBrowserAttributesMap, err := util.AttributeMapFromObject(CitrixEnterpriseBrowserModel{}) + enterpriseBrowserAttributesMap, err := util.ResourceAttributeMapFromObject(CitrixEnterpriseBrowserModel{}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return @@ -423,7 +423,7 @@ func MacosSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnosti func LinuxSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnostics, linuxSetting *LinuxSettings) { linuxSetting.ValueList = types.ListNull(types.StringType) //setting null for AutoLaunchProtocolsFromOrigins - autoLaunchProtocolAttributesMap, err := util.AttributeMapFromObject(AutoLaunchProtocolsFromOriginsModel{AllowedOrigins: types.ListNull(types.StringType)}) + autoLaunchProtocolAttributesMap, err := util.ResourceAttributeMapFromObject(AutoLaunchProtocolsFromOriginsModel{AllowedOrigins: types.ListNull(types.StringType)}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return @@ -431,7 +431,7 @@ func LinuxSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnosti linuxSetting.AutoLaunchProtocolsFromOrigins = types.ListNull(types.ObjectType{AttrTypes: autoLaunchProtocolAttributesMap}) //setting null for ManagedBookmarks - bookMarkAttributesMap, err := util.AttributeMapFromObject(BookMarkValueModel{}) + bookMarkAttributesMap, err := util.ResourceAttributeMapFromObject(BookMarkValueModel{}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return @@ -439,7 +439,7 @@ func LinuxSettingsDefaultValues(ctx context.Context, diagnostics *diag.Diagnosti linuxSetting.ManagedBookmarks = types.ListNull(types.ObjectType{AttrTypes: bookMarkAttributesMap}) //setting null for ExtensionInstallAllowList - installAllowListAttributesMap, err := util.AttributeMapFromObject(ExtensionInstallAllowListModel{}) + installAllowListAttributesMap, err := util.ResourceAttributeMapFromObject(ExtensionInstallAllowListModel{}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return diff --git a/internal/citrixcloud/identity_providers/google_identity_provider_data_source.go b/internal/citrixcloud/identity_providers/google_identity_provider_data_source.go index afc43a3..98bdc88 100644 --- a/internal/citrixcloud/identity_providers/google_identity_provider_data_source.go +++ b/internal/citrixcloud/identity_providers/google_identity_provider_data_source.go @@ -59,9 +59,9 @@ func (d *GoogleIdentityProviderDataSource) Read(ctx context.Context, req datasou // Read the data from the API var idpStatus *citrixcws.IdpStatusModel var err error - if data.Id.ValueString() != "" { + if !data.Id.IsNull() { idpStatus, err = getIdentityProviderById(ctx, d.client, &resp.Diagnostics, d.idpType, data.Id.ValueString()) - } else if data.Name.ValueString() != "" { + } else { idpStatus, err = getIdentityProviderByName(ctx, d.client, &resp.Diagnostics, d.idpType, data.Name.ValueString()) } diff --git a/internal/citrixcloud/identity_providers/google_identity_provider_data_source_model.go b/internal/citrixcloud/identity_providers/google_identity_provider_data_source_model.go index cf9a09c..d77e83e 100644 --- a/internal/citrixcloud/identity_providers/google_identity_provider_data_source_model.go +++ b/internal/citrixcloud/identity_providers/google_identity_provider_data_source_model.go @@ -30,11 +30,15 @@ func (GoogleIdentityProviderDataSourceModel) GetSchema() schema.Schema { Optional: true, Validators: []validator.String{ stringvalidator.ExactlyOneOf(path.MatchRoot("name")), + stringvalidator.LengthAtLeast(1), }, }, "name": schema.StringAttribute{ Description: "Name of the Citrix Cloud Google Cloud Identity Provider instance.", Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, }, "auth_domain_name": schema.StringAttribute{ Description: "User authentication domain name for Google Cloud Identity Provider.", diff --git a/internal/citrixcloud/identity_providers/okta_identity_provider_data_source.go b/internal/citrixcloud/identity_providers/okta_identity_provider_data_source.go index 0c63dc6..db56a84 100644 --- a/internal/citrixcloud/identity_providers/okta_identity_provider_data_source.go +++ b/internal/citrixcloud/identity_providers/okta_identity_provider_data_source.go @@ -28,7 +28,7 @@ func (d *OktaIdentityProviderDataSource) Metadata(_ context.Context, req datasou } func (d *OktaIdentityProviderDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = OktaIdentityProviderDataSourceModel{}.GetSchema() + resp.Schema = OktaIdentityProviderModel{}.GetDataSourceSchema() } func (d *OktaIdentityProviderDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { @@ -49,7 +49,7 @@ func (d *OktaIdentityProviderDataSource) Read(ctx context.Context, req datasourc return } - var data OktaIdentityProviderDataSourceModel + var data OktaIdentityProviderModel // Read Terraform configuration data into the model resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -59,9 +59,9 @@ func (d *OktaIdentityProviderDataSource) Read(ctx context.Context, req datasourc // Read the data from the API var idpStatus *citrixcws.IdpStatusModel var err error - if data.Id.ValueString() != "" { + if !data.Id.IsNull() { idpStatus, err = getIdentityProviderById(ctx, d.client, &resp.Diagnostics, d.idpType, data.Id.ValueString()) - } else if data.Name.ValueString() != "" { + } else { idpStatus, err = getIdentityProviderByName(ctx, d.client, &resp.Diagnostics, d.idpType, data.Name.ValueString()) } @@ -69,7 +69,7 @@ func (d *OktaIdentityProviderDataSource) Read(ctx context.Context, req datasourc return } - data = data.RefreshPropertyValues(idpStatus) + data = data.RefreshPropertyValues(false, idpStatus) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) diff --git a/internal/citrixcloud/identity_providers/okta_identity_provider_data_source_model.go b/internal/citrixcloud/identity_providers/okta_identity_provider_data_source_model.go index 9226fd8..ef4f62b 100644 --- a/internal/citrixcloud/identity_providers/okta_identity_provider_data_source_model.go +++ b/internal/citrixcloud/identity_providers/okta_identity_provider_data_source_model.go @@ -2,24 +2,13 @@ package cc_identity_providers import ( - "strings" - - "github.com/citrix/citrix-daas-rest-go/citrixcws" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" ) -type OktaIdentityProviderDataSourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - OktaDomain types.String `tfsdk:"okta_domain"` -} - -func (OktaIdentityProviderDataSourceModel) GetSchema() schema.Schema { +func (OktaIdentityProviderModel) GetDataSourceSchema() schema.Schema { return schema.Schema{ // This description is used by the documentation generator and the language server. Description: "Citrix Cloud --- Data source of a Citrix Cloud Okta Identity Provider instance. Note that this feature is in Tech Preview.", @@ -30,34 +19,36 @@ func (OktaIdentityProviderDataSourceModel) GetSchema() schema.Schema { Optional: true, Validators: []validator.String{ stringvalidator.ExactlyOneOf(path.MatchRoot("name")), + stringvalidator.LengthAtLeast(1), }, }, "name": schema.StringAttribute{ Description: "Name of the Citrix Cloud Identity Provider instance.", Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, }, "okta_domain": schema.StringAttribute{ Description: "Okta domain name for configuring Okta Identity Provider.", Computed: true, }, + "okta_client_id": schema.StringAttribute{ + Description: "ID of the Okta client for configuring Okta Identity Provider.", + Computed: true, + }, + "okta_client_secret": schema.StringAttribute{ + Description: "Secret of the Okta client for configuring Okta Identity Provider.", + Computed: true, + }, + "okta_api_token": schema.StringAttribute{ + Description: "Okta API token for configuring Okta Identity Provider.", + Computed: true, + }, }, } } -func (OktaIdentityProviderDataSourceModel) GetAttributes() map[string]schema.Attribute { - return OktaIdentityProviderDataSourceModel{}.GetSchema().Attributes -} - -func (r OktaIdentityProviderDataSourceModel) RefreshPropertyValues(oktaIdp *citrixcws.IdpStatusModel) OktaIdentityProviderDataSourceModel { - - // Overwrite Okta Identity Provider Data Source with refreshed state - r.Id = types.StringValue(oktaIdp.GetIdpInstanceId()) - r.Name = types.StringValue(oktaIdp.GetIdpNickname()) - - additionalInfo := oktaIdp.GetAdditionalStatusInfo() - if additionalInfo != nil { - r.OktaDomain = types.StringValue(strings.ReplaceAll(additionalInfo["oktaDomain"], "https://", "")) - } - - return r +func (OktaIdentityProviderModel) GetDataSourceAttributes() map[string]schema.Attribute { + return OktaIdentityProviderModel{}.GetDataSourceSchema().Attributes } diff --git a/internal/citrixcloud/identity_providers/okta_identity_provider_resource.go b/internal/citrixcloud/identity_providers/okta_identity_provider_resource.go index 607d5b6..ced023a 100644 --- a/internal/citrixcloud/identity_providers/okta_identity_provider_resource.go +++ b/internal/citrixcloud/identity_providers/okta_identity_provider_resource.go @@ -43,7 +43,7 @@ func (r *OktaIdentityProviderResource) Metadata(_ context.Context, req resource. // Schema defines the schema for the resource. func (r *OktaIdentityProviderResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = OktaIdentityProviderResourceModel{}.GetSchema() + resp.Schema = OktaIdentityProviderModel{}.GetSchema() } // Configure adds the provider configured client to the resource. @@ -61,7 +61,7 @@ func (r *OktaIdentityProviderResource) Create(ctx context.Context, req resource. defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan OktaIdentityProviderResourceModel + var plan OktaIdentityProviderModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -75,7 +75,7 @@ func (r *OktaIdentityProviderResource) Create(ctx context.Context, req resource. } // Refresh state with created Identity Provider before Identity Provider configuration - plan = plan.RefreshPropertyValues(idpStatus) + plan = plan.RefreshPropertyValues(true, idpStatus) diags = resp.State.Set(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -110,7 +110,7 @@ func (r *OktaIdentityProviderResource) Create(ctx context.Context, req resource. } // Refresh plan - plan = plan.RefreshPropertyValues(idpStatus) + plan = plan.RefreshPropertyValues(true, idpStatus) // Set state with fully populated data diags = resp.State.Set(ctx, &plan) @@ -125,7 +125,7 @@ func (r *OktaIdentityProviderResource) Read(ctx context.Context, req resource.Re defer util.PanicHandler(&resp.Diagnostics) // Get current state - var state OktaIdentityProviderResourceModel + var state OktaIdentityProviderModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -138,7 +138,7 @@ func (r *OktaIdentityProviderResource) Read(ctx context.Context, req resource.Re return } - state = state.RefreshPropertyValues(idpStatus) + state = state.RefreshPropertyValues(true, idpStatus) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -153,7 +153,7 @@ func (r *OktaIdentityProviderResource) Update(ctx context.Context, req resource. defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan OktaIdentityProviderResourceModel + var plan OktaIdentityProviderModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -171,7 +171,7 @@ func (r *OktaIdentityProviderResource) Update(ctx context.Context, req resource. return } - plan = plan.RefreshPropertyValues(idpStatus) + plan = plan.RefreshPropertyValues(true, idpStatus) // Set refreshed state diags = resp.State.Set(ctx, &plan) @@ -186,7 +186,7 @@ func (r *OktaIdentityProviderResource) Delete(ctx context.Context, req resource. defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from state - var state OktaIdentityProviderResourceModel + var state OktaIdentityProviderModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -204,7 +204,7 @@ func (r *OktaIdentityProviderResource) ImportState(ctx context.Context, req reso func (r *OktaIdentityProviderResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { defer util.PanicHandler(&resp.Diagnostics) - var data OktaIdentityProviderResourceModel + var data OktaIdentityProviderModel diags := req.Config.Get(ctx, &data) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -230,7 +230,7 @@ func (r *OktaIdentityProviderResource) ModifyPlan(ctx context.Context, req resou // Retrieve values from plan if !req.Plan.Raw.IsNull() { - var plan OktaIdentityProviderResourceModel + var plan OktaIdentityProviderModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/internal/citrixcloud/identity_providers/okta_identity_provider_resource_model.go b/internal/citrixcloud/identity_providers/okta_identity_provider_resource_model.go index 16791de..32c9214 100644 --- a/internal/citrixcloud/identity_providers/okta_identity_provider_resource_model.go +++ b/internal/citrixcloud/identity_providers/okta_identity_provider_resource_model.go @@ -16,7 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type OktaIdentityProviderResourceModel struct { +type OktaIdentityProviderModel struct { Id types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` OktaDomain types.String `tfsdk:"okta_domain"` @@ -25,7 +25,7 @@ type OktaIdentityProviderResourceModel struct { OktaApiToken types.String `tfsdk:"okta_api_token"` } -func (OktaIdentityProviderResourceModel) GetSchema() schema.Schema { +func (OktaIdentityProviderModel) GetSchema() schema.Schema { return schema.Schema{ // This description is used by the documentation generator and the language server. Description: "Citrix Cloud --- Manages a Citrix Cloud Okta Identity Provider instance. Note that this feature is in Tech Preview.", @@ -79,19 +79,27 @@ func (OktaIdentityProviderResourceModel) GetSchema() schema.Schema { } } -func (OktaIdentityProviderResourceModel) GetAttributes() map[string]schema.Attribute { - return OktaIdentityProviderResourceModel{}.GetSchema().Attributes +func (OktaIdentityProviderModel) GetAttributes() map[string]schema.Attribute { + return OktaIdentityProviderModel{}.GetSchema().Attributes } -func (r OktaIdentityProviderResourceModel) RefreshPropertyValues(oktaIdp *citrixcws.IdpStatusModel) OktaIdentityProviderResourceModel { +func (r OktaIdentityProviderModel) RefreshPropertyValues(isResource bool, oktaIdp *citrixcws.IdpStatusModel) OktaIdentityProviderModel { // Overwrite Okta Identity Provider Resource with refreshed state r.Id = types.StringValue(oktaIdp.GetIdpInstanceId()) r.Name = types.StringValue(oktaIdp.GetIdpNickname()) + r.OktaClientId = types.StringValue(oktaIdp.GetClientId()) additionalInfo := oktaIdp.GetAdditionalStatusInfo() if additionalInfo != nil { r.OktaDomain = types.StringValue(strings.ReplaceAll(additionalInfo["oktaDomain"], "https://", "")) + } else if !isResource { + r.OktaDomain = types.StringNull() + } + + if !isResource { + r.OktaClientSecret = types.StringNull() + r.OktaApiToken = types.StringNull() } return r diff --git a/internal/citrixcloud/identity_providers/saml_identity_provider_data_source.go b/internal/citrixcloud/identity_providers/saml_identity_provider_data_source.go index b5fc379..fd529f2 100644 --- a/internal/citrixcloud/identity_providers/saml_identity_provider_data_source.go +++ b/internal/citrixcloud/identity_providers/saml_identity_provider_data_source.go @@ -28,7 +28,7 @@ func (d *SamlIdentityProviderDataSource) Metadata(_ context.Context, req datasou } func (d *SamlIdentityProviderDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = SamlIdentityProviderDataSourceModel{}.GetSchema() + resp.Schema = SamlIdentityProviderModel{}.GetDataSourceSchema() } func (d *SamlIdentityProviderDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { @@ -49,7 +49,7 @@ func (d *SamlIdentityProviderDataSource) Read(ctx context.Context, req datasourc return } - var data SamlIdentityProviderDataSourceModel + var data SamlIdentityProviderModel // Read Terraform configuration data into the model resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -59,16 +59,16 @@ func (d *SamlIdentityProviderDataSource) Read(ctx context.Context, req datasourc // Read the data from the API var idpStatus *citrixcws.IdpStatusModel var err error - if data.Id.ValueString() != "" { + if !data.Id.IsNull() { idpStatus, err = getIdentityProviderById(ctx, d.client, &resp.Diagnostics, d.idpType, data.Id.ValueString()) - } else if data.Name.ValueString() != "" { + } else { idpStatus, err = getIdentityProviderByName(ctx, d.client, &resp.Diagnostics, d.idpType, data.Name.ValueString()) } if err != nil { return } - data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, idpStatus, nil) + data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, false, idpStatus, nil) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) // Get SAML Configuration @@ -77,7 +77,7 @@ func (d *SamlIdentityProviderDataSource) Read(ctx context.Context, req datasourc return } - data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, idpStatus, samlConfig) + data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, false, idpStatus, samlConfig) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } diff --git a/internal/citrixcloud/identity_providers/saml_identity_provider_data_source_model.go b/internal/citrixcloud/identity_providers/saml_identity_provider_data_source_model.go index 62a6ab2..99346ac 100644 --- a/internal/citrixcloud/identity_providers/saml_identity_provider_data_source_model.go +++ b/internal/citrixcloud/identity_providers/saml_identity_provider_data_source_model.go @@ -2,32 +2,13 @@ package cc_identity_providers import ( - "context" - - "github.com/citrix/citrix-daas-rest-go/citrixcws" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" ) -type SamlAttributeNameMappingsDataSourceModel struct { - UserDisplayName types.String `tfsdk:"user_display_name"` - UserGivenName types.String `tfsdk:"user_given_name"` - UserFamilyName types.String `tfsdk:"user_family_name"` - SecurityIdentifier types.String `tfsdk:"security_identifier"` - UserPrincipalName types.String `tfsdk:"user_principal_name"` - Email types.String `tfsdk:"email"` - AdObjectIdentifier types.String `tfsdk:"ad_object_identifier"` - AdForest types.String `tfsdk:"ad_forest"` - AdDomain types.String `tfsdk:"ad_domain"` -} - -func (SamlAttributeNameMappingsDataSourceModel) GetSchema() schema.SingleNestedAttribute { +func (SamlAttributeNameMappings) GetDataSourceSchema() schema.SingleNestedAttribute { return schema.SingleNestedAttribute{ Description: "Defines the attribute mappings for SAML 2.0 Identity Provider.", Computed: true, @@ -72,32 +53,11 @@ func (SamlAttributeNameMappingsDataSourceModel) GetSchema() schema.SingleNestedA } } -func (SamlAttributeNameMappingsDataSourceModel) GetAttributes() map[string]schema.Attribute { - return SamlAttributeNameMappingsDataSourceModel{}.GetSchema().Attributes +func (SamlAttributeNameMappings) GetDataSourceAttributes() map[string]schema.Attribute { + return SamlAttributeNameMappings{}.GetDataSourceSchema().Attributes } -type SamlIdentityProviderDataSourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - AuthDomainName types.String `tfsdk:"auth_domain_name"` - EntityId types.String `tfsdk:"entity_id"` - UseScopedEntityId types.Bool `tfsdk:"use_scoped_entity_id"` - SignAuthRequest types.String `tfsdk:"sign_auth_request"` - SingleSignOnServiceUrl types.String `tfsdk:"single_sign_on_service_url"` - SingleSignOnServiceBinding types.String `tfsdk:"single_sign_on_service_binding"` - SamlResponse types.String `tfsdk:"saml_response"` - AuthenticationContext types.String `tfsdk:"authentication_context"` - AuthenticationContextComparison types.String `tfsdk:"authentication_context_comparison"` - LogoutUrl types.String `tfsdk:"logout_url"` - SignLogoutRequest types.String `tfsdk:"sign_logout_request"` - LogoutBinding types.String `tfsdk:"logout_binding"` - AttributeNames types.Object `tfsdk:"attribute_names"` // SamlAttributeNameMappingsDataSourceModel - CertCommonName types.String `tfsdk:"cert_common_name"` - CertExpiration types.String `tfsdk:"cert_expiration"` - ScopedEntityIdSuffix types.String `tfsdk:"scoped_entity_id_suffix"` -} - -func (SamlIdentityProviderDataSourceModel) GetSchema() schema.Schema { +func (SamlIdentityProviderModel) GetDataSourceSchema() schema.Schema { return schema.Schema{ Description: "Citrix Cloud --- Data Source of a SAML 2.0 Identity Provider instance. Note that this feature is in Tech Preview.", @@ -106,12 +66,16 @@ func (SamlIdentityProviderDataSourceModel) GetSchema() schema.Schema { Description: "Id of the SAML 2.0 Identity Provider instance.", Optional: true, Validators: []validator.String{ - stringvalidator.ExactlyOneOf(path.MatchRoot("id"), path.MatchRoot("name")), + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), + stringvalidator.LengthAtLeast(1), }, }, "name": schema.StringAttribute{ Description: "Name of the SAML 2.0 Identity Provider instance.", Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, }, "auth_domain_name": schema.StringAttribute{ Description: "Auth Domain name of the SAML 2.0 Identity Provider instance.", @@ -141,6 +105,10 @@ func (SamlIdentityProviderDataSourceModel) GetSchema() schema.Schema { Description: "The SAML response.", Computed: true, }, + "cert_file_path": schema.StringAttribute{ + Description: "The file path of the certificate.", + Computed: true, + }, "authentication_context": schema.StringAttribute{ Description: "The authentication context.", Computed: true, @@ -161,7 +129,7 @@ func (SamlIdentityProviderDataSourceModel) GetSchema() schema.Schema { Description: "The binding of the logout service.", Computed: true, }, - "attribute_names": SamlAttributeNameMappingsDataSourceModel{}.GetSchema(), + "attribute_names": SamlAttributeNameMappings{}.GetDataSourceSchema(), "cert_common_name": schema.StringAttribute{ Description: "The common name of the SAML certificate.", Computed: true, @@ -178,76 +146,6 @@ func (SamlIdentityProviderDataSourceModel) GetSchema() schema.Schema { } } -func (SamlIdentityProviderDataSourceModel) GetResourceAttributes() map[string]schema.Attribute { - return SamlIdentityProviderDataSourceModel{}.GetSchema().Attributes -} - -func (r SamlIdentityProviderDataSourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, samlIdp *citrixcws.IdpStatusModel, samlConfig *citrixcws.SamlConfigModel) SamlIdentityProviderDataSourceModel { - - // Overwrite SAML 2.0 Identity Provider Data Source with refreshed state - r.Id = types.StringValue(samlIdp.GetIdpInstanceId()) - r.Name = types.StringValue(samlIdp.GetIdpNickname()) - r.AuthDomainName = types.StringValue(samlIdp.GetAuthDomainName()) - - r.EntityId = types.StringValue(samlConfig.GetSamlEntityId()) - - useScopedEntityId := false - if samlConfig.GetSamlSpEntityIdSuffix() != "" { - useScopedEntityId = true - } - r.UseScopedEntityId = types.BoolValue(useScopedEntityId) - - if samlConfig == nil { - return r - } - - r.SignAuthRequest = types.StringValue(samlConfig.GetSamlSignAuthRequest()) - r.SingleSignOnServiceUrl = types.StringValue(samlConfig.GetSamlSingleSignOnServiceUrl()) - r.SingleSignOnServiceBinding = types.StringValue(samlConfig.GetSamlSingleSignOnServiceBinding()) - r.SamlResponse = types.StringValue(samlConfig.GetSamlResponse()) - r.AuthenticationContext = types.StringValue(samlConfig.GetSamlAuthenticationContext()) - r.AuthenticationContextComparison = types.StringValue(samlConfig.GetSamlAuthenticationContextComparison()) - r.LogoutUrl = types.StringValue(samlConfig.GetSamlLogoutUrl()) - if samlConfig.GetSamlLogoutUrl() != "" { - r.SignLogoutRequest = types.StringValue(samlConfig.GetSamlSignLogoutRequest()) - r.LogoutBinding = types.StringValue(samlConfig.GetSamlLogoutRequestBinding()) - } - - // Refresh Attribute Name Mappings - attributeNamesAttributesMap := map[string]attr.Type{ - "user_display_name": types.StringType, - "user_given_name": types.StringType, - "user_family_name": types.StringType, - "security_identifier": types.StringType, - "user_principal_name": types.StringType, - "email": types.StringType, - "ad_object_identifier": types.StringType, - "ad_forest": types.StringType, - "ad_domain": types.StringType, - } - updatedAttributeNames := SamlAttributeNameMappingsDataSourceModel{ - UserDisplayName: types.StringValue(samlConfig.GetSamlAttributeNameForUserDisplayName()), - UserGivenName: types.StringValue(samlConfig.GetSamlAttributeNameForUserGivenName()), - UserFamilyName: types.StringValue(samlConfig.GetSamlAttributeNameForUserFamilyName()), - SecurityIdentifier: types.StringValue(samlConfig.GetSamlAttributeNameForSid()), - UserPrincipalName: types.StringValue(samlConfig.GetSamlAttributeNameForUpn()), - Email: types.StringValue(samlConfig.GetSamlAttributeNameForEmail()), - AdObjectIdentifier: types.StringValue(samlConfig.GetSamlAttributeNameForAdOid()), - AdForest: types.StringValue(samlConfig.GetSamlAttributeNameForAdForest()), - AdDomain: types.StringValue(samlConfig.GetSamlAttributeNameForAdDomain()), - } - attributeNames, diags := types.ObjectValueFrom(ctx, attributeNamesAttributesMap, updatedAttributeNames) - if diags != nil { - diagnostics.Append(diags...) - attributeNames = types.ObjectUnknown(attributeNamesAttributesMap) - } - - r.AttributeNames = attributeNames - - // Refresh Computed Values - r.CertCommonName = types.StringValue(samlConfig.GetSamlCertCN()) - r.CertExpiration = types.StringValue(samlConfig.GetSamlCertExpiration()) - r.ScopedEntityIdSuffix = types.StringValue(samlConfig.GetSamlSpEntityIdSuffix()) - - return r +func (SamlIdentityProviderModel) GetDataSourceAttributes() map[string]schema.Attribute { + return SamlIdentityProviderModel{}.GetDataSourceSchema().Attributes } diff --git a/internal/citrixcloud/identity_providers/saml_identity_provider_resource.go b/internal/citrixcloud/identity_providers/saml_identity_provider_resource.go index 00b43ad..b8310bd 100644 --- a/internal/citrixcloud/identity_providers/saml_identity_provider_resource.go +++ b/internal/citrixcloud/identity_providers/saml_identity_provider_resource.go @@ -46,7 +46,7 @@ func (r *SamlIdentityProviderResource) Metadata(_ context.Context, req resource. // Schema defines the schema for the resource. func (r *SamlIdentityProviderResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = SamlIdentityProviderResourceModel{}.GetSchema() + resp.Schema = SamlIdentityProviderModel{}.GetSchema() } // Configure adds the provider configured client to the resource. @@ -64,7 +64,7 @@ func (r *SamlIdentityProviderResource) Create(ctx context.Context, req resource. defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan SamlIdentityProviderResourceModel + var plan SamlIdentityProviderModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -119,7 +119,7 @@ func (r *SamlIdentityProviderResource) Create(ctx context.Context, req resource. } // Refresh state with created Identity Provider before Identity Provider configuration - plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, idpStatus, nil) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, true, idpStatus, nil) diags = resp.State.Set(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -181,7 +181,7 @@ func (r *SamlIdentityProviderResource) Create(ctx context.Context, req resource. } // Refresh plan - plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, idpStatus, samlConfig) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, true, idpStatus, samlConfig) // Set state with fully populated data diags = resp.State.Set(ctx, &plan) @@ -196,7 +196,7 @@ func (r *SamlIdentityProviderResource) Read(ctx context.Context, req resource.Re defer util.PanicHandler(&resp.Diagnostics) // Get current state - var state SamlIdentityProviderResourceModel + var state SamlIdentityProviderModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -216,7 +216,7 @@ func (r *SamlIdentityProviderResource) Read(ctx context.Context, req resource.Re return } - state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, idpStatus, samlConfig) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, true, idpStatus, samlConfig) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -231,7 +231,7 @@ func (r *SamlIdentityProviderResource) Update(ctx context.Context, req resource. defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan SamlIdentityProviderResourceModel + var plan SamlIdentityProviderModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -239,7 +239,7 @@ func (r *SamlIdentityProviderResource) Update(ctx context.Context, req resource. } // Retrieve values from state - var state SamlIdentityProviderResourceModel + var state SamlIdentityProviderModel diags = req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -272,7 +272,7 @@ func (r *SamlIdentityProviderResource) Update(ctx context.Context, req resource. return } - state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, idpStatus, samlConfig) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, true, idpStatus, samlConfig) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -287,7 +287,7 @@ func (r *SamlIdentityProviderResource) Delete(ctx context.Context, req resource. defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from state - var state SamlIdentityProviderResourceModel + var state SamlIdentityProviderModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -305,7 +305,7 @@ func (r *SamlIdentityProviderResource) ImportState(ctx context.Context, req reso func (r *SamlIdentityProviderResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { defer util.PanicHandler(&resp.Diagnostics) - var data SamlIdentityProviderResourceModel + var data SamlIdentityProviderModel diags := req.Config.Get(ctx, &data) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -331,7 +331,7 @@ func (r *SamlIdentityProviderResource) ModifyPlan(ctx context.Context, req resou // Retrieve values from plan if !req.Plan.Raw.IsNull() { - var plan SamlIdentityProviderResourceModel + var plan SamlIdentityProviderModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/internal/citrixcloud/identity_providers/saml_identity_provider_resource_model.go b/internal/citrixcloud/identity_providers/saml_identity_provider_resource_model.go index 1fd9ee5..3a17c77 100644 --- a/internal/citrixcloud/identity_providers/saml_identity_provider_resource_model.go +++ b/internal/citrixcloud/identity_providers/saml_identity_provider_resource_model.go @@ -109,7 +109,7 @@ func (SamlAttributeNameMappings) GetAttributes() map[string]schema.Attribute { return SamlAttributeNameMappings{}.GetSchema().Attributes } -type SamlIdentityProviderResourceModel struct { +type SamlIdentityProviderModel struct { Id types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` AuthDomainName types.String `tfsdk:"auth_domain_name"` @@ -135,7 +135,7 @@ type SamlIdentityProviderResourceModel struct { ScopedEntityIdSuffix types.String `tfsdk:"scoped_entity_id_suffix"` } -func (SamlIdentityProviderResourceModel) GetSchema() schema.Schema { +func (SamlIdentityProviderModel) GetSchema() schema.Schema { return schema.Schema{ Description: "Citrix Cloud --- Manages a SAML 2.0 Identity Provider instance. Note that this feature is in Tech Preview.", @@ -322,11 +322,11 @@ func (SamlIdentityProviderResourceModel) GetSchema() schema.Schema { } } -func (SamlIdentityProviderResourceModel) GetAttributes() map[string]schema.Attribute { - return SamlIdentityProviderResourceModel{}.GetSchema().Attributes +func (SamlIdentityProviderModel) GetAttributes() map[string]schema.Attribute { + return SamlIdentityProviderModel{}.GetSchema().Attributes } -func (r SamlIdentityProviderResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, samlIdp *citrixcws.IdpStatusModel, samlConfig *citrixcws.SamlConfigModel) SamlIdentityProviderResourceModel { +func (r SamlIdentityProviderModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, isResource bool, samlIdp *citrixcws.IdpStatusModel, samlConfig *citrixcws.SamlConfigModel) SamlIdentityProviderModel { // Overwrite SAML 2.0 Identity Provider Resource with refreshed state r.Id = types.StringValue(samlIdp.GetIdpInstanceId()) @@ -353,6 +353,9 @@ func (r SamlIdentityProviderResourceModel) RefreshPropertyValues(ctx context.Con r.SingleSignOnServiceUrl = types.StringValue(samlConfig.GetSamlSingleSignOnServiceUrl()) r.SingleSignOnServiceBinding = types.StringValue(samlConfig.GetSamlSingleSignOnServiceBinding()) r.SamlResponse = types.StringValue(samlConfig.GetSamlResponse()) + if !isResource { + r.CertFilePath = types.StringNull() + } r.AuthenticationContext = types.StringValue(samlConfig.GetSamlAuthenticationContext()) r.AuthenticationContextComparison = types.StringValue(samlConfig.GetSamlAuthenticationContextComparison()) r.LogoutUrl = types.StringValue(samlConfig.GetSamlLogoutUrl()) @@ -373,7 +376,12 @@ func (r SamlIdentityProviderResourceModel) RefreshPropertyValues(ctx context.Con samlAttributeNameMappings.UserGivenName = types.StringValue(samlConfig.GetSamlAttributeNameForUserGivenName()) samlAttributeNameMappings.UserFamilyName = types.StringValue(samlConfig.GetSamlAttributeNameForUserFamilyName()) - samlAttributeNameMappingsObject := util.TypedObjectToObjectValue(ctx, diagnostics, samlAttributeNameMappings) + var samlAttributeNameMappingsObject types.Object + if isResource { + samlAttributeNameMappingsObject = util.TypedObjectToObjectValue(ctx, diagnostics, samlAttributeNameMappings) + } else { + samlAttributeNameMappingsObject = util.DataSourceTypedObjectToObjectValue(ctx, diagnostics, samlAttributeNameMappings) + } r.AttributeNames = samlAttributeNameMappingsObject // Refresh Computed Values diff --git a/internal/citrixcloud/resource_locations/resource_locations_data_source.go b/internal/citrixcloud/resource_locations/resource_locations_data_source.go index 3ab2be1..1a4eabb 100644 --- a/internal/citrixcloud/resource_locations/resource_locations_data_source.go +++ b/internal/citrixcloud/resource_locations/resource_locations_data_source.go @@ -33,7 +33,7 @@ func (d *ResourceLocationsDataSource) Metadata(ctx context.Context, req datasour } func (d *ResourceLocationsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = ResourceLocationsDataSourceModel{}.GetSchema() + resp.Schema = ResourceLocationModel{}.GetDataSourceSchema() } func (d *ResourceLocationsDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { @@ -53,7 +53,7 @@ func (d *ResourceLocationsDataSource) Read(ctx context.Context, req datasource.R return } - var data ResourceLocationsDataSourceModel + var data ResourceLocationModel // Read Terraform configuration data into the model resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) diff --git a/internal/citrixcloud/resource_locations/resource_locations_data_source_model.go b/internal/citrixcloud/resource_locations/resource_locations_data_source_model.go index 542340f..0163cdf 100644 --- a/internal/citrixcloud/resource_locations/resource_locations_data_source_model.go +++ b/internal/citrixcloud/resource_locations/resource_locations_data_source_model.go @@ -3,17 +3,10 @@ package resource_locations import ( - ccresourcelocations "github.com/citrix/citrix-daas-rest-go/ccresourcelocations" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" ) -type ResourceLocationsDataSourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` -} - -func (ResourceLocationsDataSourceModel) GetSchema() schema.Schema { +func (ResourceLocationModel) GetDataSourceSchema() schema.Schema { return schema.Schema{ Description: "Citrix Cloud --- Read data of an existing resource location.", @@ -26,14 +19,14 @@ func (ResourceLocationsDataSourceModel) GetSchema() schema.Schema { Description: "Name of the resource location.", Required: true, }, + "internal_only": schema.BoolAttribute{ + Description: "Flag to determine if the resource location can only be used internally. Defaults to `false`.", + Computed: true, + }, + "time_zone": schema.StringAttribute{ + Description: "Timezone associated with the resource location. Please refer to the `Timezone` column in the following [table](https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones?view=windows-11#time-zones) for allowed values.", + Computed: true, + }, }, } } - -func (r ResourceLocationsDataSourceModel) RefreshPropertyValues(ccResourceLocation *ccresourcelocations.CitrixCloudServicesRegistryApiModelsLocationsResourceLocationModel) ResourceLocationsDataSourceModel { - // Overwrite resource location with refreshed state - r.Id = types.StringValue(ccResourceLocation.GetId()) - r.Name = types.StringValue(ccResourceLocation.GetName()) - - return r -} diff --git a/internal/citrixcloud/resource_locations/resource_locations_resource.go b/internal/citrixcloud/resource_locations/resource_locations_resource.go index 0efb2e1..5501865 100644 --- a/internal/citrixcloud/resource_locations/resource_locations_resource.go +++ b/internal/citrixcloud/resource_locations/resource_locations_resource.go @@ -40,7 +40,7 @@ func (r *resourceLocationResource) Metadata(_ context.Context, req resource.Meta // Schema defines the schema for the resource. func (r *resourceLocationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = ResourceLocationResourceModel{}.GetSchema() + resp.Schema = ResourceLocationModel{}.GetSchema() } // Configure adds the provider configured client to the resource. @@ -57,7 +57,7 @@ func (r *resourceLocationResource) Create(ctx context.Context, req resource.Crea defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan ResourceLocationResourceModel + var plan ResourceLocationModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -108,7 +108,7 @@ func (r *resourceLocationResource) Read(ctx context.Context, req resource.ReadRe defer util.PanicHandler(&resp.Diagnostics) // Get current state - var state ResourceLocationResourceModel + var state ResourceLocationModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -137,7 +137,7 @@ func (r *resourceLocationResource) Update(ctx context.Context, req resource.Upda defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan ResourceLocationResourceModel + var plan ResourceLocationModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -184,7 +184,7 @@ func (r *resourceLocationResource) Delete(ctx context.Context, req resource.Dele defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from state - var state ResourceLocationResourceModel + var state ResourceLocationModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -217,7 +217,7 @@ func readResourceLocation(ctx context.Context, client *citrixdaasclient.CitrixDa func (r *resourceLocationResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { defer util.PanicHandler(&resp.Diagnostics) - var data ResourceLocationResourceModel + var data ResourceLocationModel diags := req.Config.Get(ctx, &data) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -243,7 +243,7 @@ func (r *resourceLocationResource) ModifyPlan(ctx context.Context, req resource. // Retrieve values from plan if !req.Plan.Raw.IsNull() { - var plan ResourceLocationResourceModel + var plan ResourceLocationModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/internal/citrixcloud/resource_locations/resource_locations_resource_model.go b/internal/citrixcloud/resource_locations/resource_locations_resource_model.go index 58866e8..63259b6 100644 --- a/internal/citrixcloud/resource_locations/resource_locations_resource_model.go +++ b/internal/citrixcloud/resource_locations/resource_locations_resource_model.go @@ -13,15 +13,15 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// ResourceLocationResourceModel maps the resource schema data. -type ResourceLocationResourceModel struct { +// ResourceLocationModel maps the resource schema data. +type ResourceLocationModel struct { Id types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` InternalOnly types.Bool `tfsdk:"internal_only"` TimeZone types.String `tfsdk:"time_zone"` } -func (ResourceLocationResourceModel) GetSchema() schema.Schema { +func (ResourceLocationModel) GetSchema() schema.Schema { return schema.Schema{ // This description is used by the documentation generator and the language server. Description: "Citrix Cloud --- Manages a Citrix Cloud resource location.", @@ -54,11 +54,11 @@ func (ResourceLocationResourceModel) GetSchema() schema.Schema { } } -func (ResourceLocationResourceModel) GetAttributes() map[string]schema.Attribute { - return ResourceLocationResourceModel{}.GetSchema().Attributes +func (ResourceLocationModel) GetAttributes() map[string]schema.Attribute { + return ResourceLocationModel{}.GetSchema().Attributes } -func (r ResourceLocationResourceModel) RefreshPropertyValues(ccResourceLocation *ccresourcelocations.CitrixCloudServicesRegistryApiModelsLocationsResourceLocationModel) ResourceLocationResourceModel { +func (r ResourceLocationModel) RefreshPropertyValues(ccResourceLocation *ccresourcelocations.CitrixCloudServicesRegistryApiModelsLocationsResourceLocationModel) ResourceLocationModel { // Overwrite resource location with refreshed state r.Id = types.StringValue(ccResourceLocation.GetId()) diff --git a/internal/daas/admin_folder/admin_folder_data_source.go b/internal/daas/admin_folder/admin_folder_data_source.go index d9e81e0..2c7cffe 100644 --- a/internal/daas/admin_folder/admin_folder_data_source.go +++ b/internal/daas/admin_folder/admin_folder_data_source.go @@ -3,7 +3,6 @@ package admin_folder import ( "context" - "strings" citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" "github.com/citrix/terraform-provider-citrix/internal/util" @@ -27,7 +26,7 @@ func (d *AdminFolderDataSource) Metadata(_ context.Context, req datasource.Metad } func (d *AdminFolderDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = AdminFolderDataSourceModel{}.GetSchema() + resp.Schema = AdminFolderModel{}.GetDataSourceSchema() } func (d *AdminFolderDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { @@ -47,7 +46,7 @@ func (d *AdminFolderDataSource) Read(ctx context.Context, req datasource.ReadReq return } - var data AdminFolderDataSourceModel + var data AdminFolderModel // Read Terraform configuration data into the model resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) @@ -59,12 +58,10 @@ func (d *AdminFolderDataSource) Read(ctx context.Context, req datasource.ReadReq // Read the data from the API var adminFolderIdOrPath string - if data.Id.ValueString() != "" { + if !data.Id.IsNull() { adminFolderIdOrPath = data.Id.ValueString() - } - if data.Path.ValueString() != "" { - adminFolderIdOrPath = data.Path.ValueString() + "\\" - adminFolderIdOrPath = strings.ReplaceAll(adminFolderIdOrPath, "\\", "|") + } else { + adminFolderIdOrPath = util.BuildResourcePathForGetRequest(data.Path.ValueString(), "") } adminFolder, err := getAdminFolder(ctx, d.client, &resp.Diagnostics, adminFolderIdOrPath) diff --git a/internal/daas/admin_folder/admin_folder_data_source_model.go b/internal/daas/admin_folder/admin_folder_data_source_model.go index 77ff99e..4fd4a36 100644 --- a/internal/daas/admin_folder/admin_folder_data_source_model.go +++ b/internal/daas/admin_folder/admin_folder_data_source_model.go @@ -2,33 +2,17 @@ package admin_folder import ( - "context" "regexp" - "strings" - citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" "github.com/citrix/terraform-provider-citrix/internal/util" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) -type AdminFolderDataSourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Type types.Set `tfsdk:"type"` // Set[String] - Path types.String `tfsdk:"path"` - ParentPath types.String `tfsdk:"parent_path"` - TotalApplications types.Int64 `tfsdk:"total_applications"` - TotalMachineCatalogs types.Int64 `tfsdk:"total_machine_catalogs"` - TotalApplicationGroups types.Int64 `tfsdk:"total_application_groups"` - TotalDeliveryGroups types.Int64 `tfsdk:"total_delivery_groups"` -} - -func (AdminFolderDataSourceModel) GetSchema() schema.Schema { +func (AdminFolderModel) GetDataSourceSchema() schema.Schema { return schema.Schema{ // This description is used by the documentation generator and the language server. Description: "CVAD --- Data source to get details regarding a specific admin folder.", @@ -38,7 +22,8 @@ func (AdminFolderDataSourceModel) GetSchema() schema.Schema { Description: "Identifier of the admin folder.", Optional: true, Validators: []validator.String{ - stringvalidator.ExactlyOneOf(path.MatchRoot("id"), path.MatchRoot("path")), // Ensures that only one of either Id or Path is provided. It will also cause a validation error if none are specified. + stringvalidator.ExactlyOneOf(path.MatchRoot("path")), // Ensures that only one of either Id or Path is provided. It will also cause a validation error if none are specified. + stringvalidator.LengthAtLeast(1), }, }, "path": schema.StringAttribute{ @@ -81,35 +66,3 @@ func (AdminFolderDataSourceModel) GetSchema() schema.Schema { }, } } - -func (r AdminFolderDataSourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, adminFolder *citrixorchestration.AdminFolderResponseModel) AdminFolderDataSourceModel { - // Overwrite application folder with refreshed state - r.Id = types.StringValue(adminFolder.GetId()) - r.Name = types.StringValue(adminFolder.GetName()) - - r.Path = types.StringValue(strings.TrimSuffix(adminFolder.GetPath(), "\\")) - - adminFolderTypes := []string{} - adminFolderMetadata := adminFolder.GetMetadata() - for _, metadata := range adminFolderMetadata { - typeInfo := metadata.GetName() - adminFolderTypes = append(adminFolderTypes, typeInfo) - } - adminFolderTypeSet := util.StringArrayToStringSet(ctx, diagnostics, adminFolderTypes) - r.Type = adminFolderTypeSet - - var parentPath = strings.TrimSuffix(adminFolder.GetPath(), adminFolder.GetName()+"\\") - parentPath = strings.TrimSuffix(parentPath, "\\") - if parentPath != "" { - r.ParentPath = types.StringValue(parentPath) - } else { - r.ParentPath = types.StringNull() - } - - r.TotalApplications = types.Int64Value(int64(adminFolder.GetTotalApplications())) - r.TotalMachineCatalogs = types.Int64Value(int64(adminFolder.GetTotalMachineCatalogs())) - r.TotalApplicationGroups = types.Int64Value(int64(adminFolder.GetTotalApplicationGroups())) - r.TotalDeliveryGroups = types.Int64Value(int64(adminFolder.GetTotalDesktopGroups())) - - return r -} diff --git a/internal/daas/admin_folder/admin_folder_resource.go b/internal/daas/admin_folder/admin_folder_resource.go index 2e59b4a..da19ea9 100644 --- a/internal/daas/admin_folder/admin_folder_resource.go +++ b/internal/daas/admin_folder/admin_folder_resource.go @@ -52,7 +52,7 @@ func (r *adminFolderResource) Configure(_ context.Context, req resource.Configur // Schema defines the schema for the data source. func (r *adminFolderResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = AdminFolderResourceModel{}.GetSchema() + resp.Schema = AdminFolderModel{}.GetSchema() } // Create creates the resource and sets the initial Terraform state. @@ -60,7 +60,7 @@ func (r *adminFolderResource) Create(ctx context.Context, req resource.CreateReq defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan AdminFolderResourceModel + var plan AdminFolderModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -112,7 +112,7 @@ func (r *adminFolderResource) Read(ctx context.Context, req resource.ReadRequest defer util.PanicHandler(&resp.Diagnostics) // Get current state - var state AdminFolderResourceModel + var state AdminFolderModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -140,7 +140,7 @@ func (r *adminFolderResource) Update(ctx context.Context, req resource.UpdateReq defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan AdminFolderResourceModel + var plan AdminFolderModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -194,7 +194,7 @@ func (r *adminFolderResource) Delete(ctx context.Context, req resource.DeleteReq defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from state - var state AdminFolderResourceModel + var state AdminFolderModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -230,7 +230,7 @@ func (r *adminFolderResource) ImportState(ctx context.Context, req resource.Impo func (r *adminFolderResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { defer util.PanicHandler(&resp.Diagnostics) - var data AdminFolderResourceModel + var data AdminFolderModel diags := req.Config.Get(ctx, &data) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/internal/daas/admin_folder/admin_folder_resource_model.go b/internal/daas/admin_folder/admin_folder_resource_model.go index 3c60c15..43bb4de 100644 --- a/internal/daas/admin_folder/admin_folder_resource_model.go +++ b/internal/daas/admin_folder/admin_folder_resource_model.go @@ -18,15 +18,19 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type AdminFolderResourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Type types.Set `tfsdk:"type"` // Set[String] - Path types.String `tfsdk:"path"` - ParentPath types.String `tfsdk:"parent_path"` +type AdminFolderModel struct { + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Type types.Set `tfsdk:"type"` // Set[String] + Path types.String `tfsdk:"path"` + ParentPath types.String `tfsdk:"parent_path"` + TotalApplications types.Int64 `tfsdk:"total_applications"` + TotalMachineCatalogs types.Int64 `tfsdk:"total_machine_catalogs"` + TotalApplicationGroups types.Int64 `tfsdk:"total_application_groups"` + TotalDeliveryGroups types.Int64 `tfsdk:"total_delivery_groups"` } -func (AdminFolderResourceModel) GetSchema() schema.Schema { +func (AdminFolderModel) GetSchema() schema.Schema { return schema.Schema{ Description: "CVAD --- Manages an admin folder.", Attributes: map[string]schema.Attribute{ @@ -71,15 +75,31 @@ func (AdminFolderResourceModel) GetSchema() schema.Schema { Description: "Path to the admin folder.", Computed: true, }, + "total_applications": schema.Int64Attribute{ + Description: "Number of applications contained in the admin folder.", + Computed: true, + }, + "total_machine_catalogs": schema.Int64Attribute{ + Description: "Number of machine catalogs contained in the admin folder.", + Computed: true, + }, + "total_application_groups": schema.Int64Attribute{ + Description: "Number of application groups contained in the admin folder.", + Computed: true, + }, + "total_delivery_groups": schema.Int64Attribute{ + Description: "Number of delivery groups contained in the admin folder.", + Computed: true, + }, }, } } -func (AdminFolderResourceModel) GetAttributes() map[string]schema.Attribute { - return AdminFolderResourceModel{}.GetSchema().Attributes +func (AdminFolderModel) GetAttributes() map[string]schema.Attribute { + return AdminFolderModel{}.GetSchema().Attributes } -func (r AdminFolderResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, adminFolder *citrixorchestration.AdminFolderResponseModel) AdminFolderResourceModel { +func (r AdminFolderModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, adminFolder *citrixorchestration.AdminFolderResponseModel) AdminFolderModel { // Overwrite application folder with refreshed state r.Id = types.StringValue(adminFolder.GetId()) r.Name = types.StringValue(adminFolder.GetName()) @@ -104,5 +124,10 @@ func (r AdminFolderResourceModel) RefreshPropertyValues(ctx context.Context, dia r.ParentPath = types.StringNull() } + r.TotalApplications = types.Int64Value(int64(adminFolder.GetTotalApplications())) + r.TotalMachineCatalogs = types.Int64Value(int64(adminFolder.GetTotalMachineCatalogs())) + r.TotalApplicationGroups = types.Int64Value(int64(adminFolder.GetTotalApplicationGroups())) + r.TotalDeliveryGroups = types.Int64Value(int64(adminFolder.GetTotalDesktopGroups())) + return r } diff --git a/internal/daas/admin_role/admin_role_data_source.go b/internal/daas/admin_role/admin_role_data_source.go new file mode 100644 index 0000000..ea10c4f --- /dev/null +++ b/internal/daas/admin_role/admin_role_data_source.go @@ -0,0 +1,77 @@ +// Copyright © 2024. Citrix Systems, Inc. +package admin_role + +import ( + "context" + + citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" + "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +var ( + _ datasource.DataSource = &AdminRoleDataSource{} +) + +func NewAdminRoleDataSource() datasource.DataSource { + return &AdminRoleDataSource{} +} + +type AdminRoleDataSource struct { + client *citrixdaasclient.CitrixDaasClient +} + +func (d *AdminRoleDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_admin_role" +} + +func (d *AdminRoleDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = AdminRoleModel{}.GetDataSourceSchema() +} + +func (d *AdminRoleDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*citrixdaasclient.CitrixDaasClient) +} + +func (d *AdminRoleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + if d.client != nil && d.client.ApiClient == nil { + resp.Diagnostics.AddError(util.ProviderInitializationErrorMsg, util.MissingProviderClientIdAndSecretErrorMsg) + return + } + + var data AdminRoleModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Read the data from the API + var adminRoleNameOrId string + + if data.Id.ValueString() != "" { + adminRoleNameOrId = data.Id.ValueString() + } else if data.Name.ValueString() != "" { + adminRoleNameOrId = data.Name.ValueString() + } + + adminRole, err := getAdminRole(ctx, d.client, &resp.Diagnostics, adminRoleNameOrId) + + if err != nil { + return + } + + data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, adminRole) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/daas/admin_role/admin_role_data_source_model.go b/internal/daas/admin_role/admin_role_data_source_model.go new file mode 100644 index 0000000..56477e5 --- /dev/null +++ b/internal/daas/admin_role/admin_role_data_source_model.go @@ -0,0 +1,63 @@ +// Copyright © 2024. Citrix Systems, Inc. +package admin_role + +import ( + "regexp" + + "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (AdminRoleModel) GetDataSourceSchema() schema.Schema { + return schema.Schema{ + // This description is used by the documentation generator and the language server. + Description: "CVAD --- Data source of an administrator role.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "ID of the admin role.", + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), // Ensures that only one of either Id or Path is provided. It will also cause a validation error if none are specified. + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the admin role.", + Optional: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the admin role.", + Computed: true, + }, + "is_built_in": schema.BoolAttribute{ + Description: "Flag to determine if the role was built-in or user defined", + Computed: true, + }, + "can_launch_manage": schema.BoolAttribute{ + Description: "Flag to determine if the user will have access to the Manage tab on the console. Defaults to `true`. " + + "\n\n~> **Please Note** This field is only applicable for cloud admins. For on-premise admins, the only acceptable value is `true`.", + Computed: true, + }, + "can_launch_monitor": schema.BoolAttribute{ + Description: "Flag to determine if the user will have access to the Monitor tab on the console. Defaults to `true`. " + + "\n\n~> **Please Note** This field is only applicable for cloud admins. For on-premise admins, the only acceptable value is `true`.", + Computed: true, + }, + "permissions": schema.SetAttribute{ + ElementType: types.StringType, + Description: "Permissions to be associated with the admin role. " + + "\n\n-> **Note** To get a list of supported permissions, please refer to [Admin Predefined Permissions for Cloud](https://developer-docs.citrix.com/en-us/citrix-daas-service-apis/citrix-daas-rest-apis/apis/#/Admin-APIs/Admin-GetPredefinedPermissions) and [Admin Predefined Permissions for On-Premise](https://developer-docs.citrix.com/en-us/citrix-virtual-apps-desktops/citrix-cvad-rest-apis/apis/#/Admin-APIs/Admin-GetPredefinedPermissions).", + Computed: true, + }, + }, + } +} + +func (AdminRoleModel) GetDataSourceAttributes() map[string]schema.Attribute { + return AdminRoleModel{}.GetDataSourceSchema().Attributes +} diff --git a/internal/daas/admin_role/admin_role_resource.go b/internal/daas/admin_role/admin_role_resource.go index 7ef9804..62c0070 100644 --- a/internal/daas/admin_role/admin_role_resource.go +++ b/internal/daas/admin_role/admin_role_resource.go @@ -42,7 +42,7 @@ func (r *adminRoleResource) Metadata(_ context.Context, req resource.MetadataReq // Schema defines the schema for the resource. func (r *adminRoleResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = AdminRoleResourceModel{}.GetSchema() + resp.Schema = AdminRoleModel{}.GetSchema() } // Configure adds the provider configured client to the resource. @@ -59,7 +59,7 @@ func (r *adminRoleResource) Create(ctx context.Context, req resource.CreateReque defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan AdminRoleResourceModel + var plan AdminRoleModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -120,7 +120,7 @@ func (r *adminRoleResource) Read(ctx context.Context, req resource.ReadRequest, defer util.PanicHandler(&resp.Diagnostics) // Get current state - var state AdminRoleResourceModel + var state AdminRoleModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -148,7 +148,7 @@ func (r *adminRoleResource) Update(ctx context.Context, req resource.UpdateReque defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan AdminRoleResourceModel + var plan AdminRoleModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -200,7 +200,7 @@ func (r *adminRoleResource) Delete(ctx context.Context, req resource.DeleteReque defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from state - var state AdminRoleResourceModel + var state AdminRoleModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -251,7 +251,7 @@ func readAdminRole(ctx context.Context, client *citrixdaasclient.CitrixDaasClien func (r *adminRoleResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { defer util.PanicHandler(&resp.Diagnostics) - var data AdminRoleResourceModel + var data AdminRoleModel diags := req.Config.Get(ctx, &data) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -272,7 +272,7 @@ func (r *adminRoleResource) ModifyPlan(ctx context.Context, req resource.ModifyP // Retrieve values from plan if !req.Plan.Raw.IsNull() { - var plan AdminRoleResourceModel + var plan AdminRoleModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/internal/daas/admin_role/admin_role_resource_model.go b/internal/daas/admin_role/admin_role_resource_model.go index 082c1fa..0bfab29 100644 --- a/internal/daas/admin_role/admin_role_resource_model.go +++ b/internal/daas/admin_role/admin_role_resource_model.go @@ -20,8 +20,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// AdminRoleResourceModel maps the resource schema data. -type AdminRoleResourceModel struct { +// AdminRoleModel maps the resource schema data. +type AdminRoleModel struct { Id types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` IsBuiltIn types.Bool `tfsdk:"is_built_in"` @@ -31,7 +31,7 @@ type AdminRoleResourceModel struct { Permissions types.Set `tfsdk:"permissions"` //Set[string] } -func (r AdminRoleResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, adminRole *citrixorchestration.RoleResponseModel) AdminRoleResourceModel { +func (r AdminRoleModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, adminRole *citrixorchestration.RoleResponseModel) AdminRoleModel { // Overwrite admin role with refreshed state r.Id = types.StringValue(adminRole.GetId()) @@ -50,7 +50,7 @@ func (r AdminRoleResourceModel) RefreshPropertyValues(ctx context.Context, diagn return r } -func (AdminRoleResourceModel) GetSchema() schema.Schema { +func (AdminRoleModel) GetSchema() schema.Schema { return schema.Schema{ // This description is used by the documentation generator and the language server. Description: "CVAD --- Manages an administrator role.", @@ -107,6 +107,6 @@ func (AdminRoleResourceModel) GetSchema() schema.Schema { } } -func (AdminRoleResourceModel) GetAttributes() map[string]schema.Attribute { - return AdminRoleResourceModel{}.GetSchema().Attributes +func (AdminRoleModel) GetAttributes() map[string]schema.Attribute { + return AdminRoleModel{}.GetSchema().Attributes } diff --git a/internal/daas/admin_scope/admin_scope_data_source.go b/internal/daas/admin_scope/admin_scope_data_source.go index 118867c..c46934d 100644 --- a/internal/daas/admin_scope/admin_scope_data_source.go +++ b/internal/daas/admin_scope/admin_scope_data_source.go @@ -28,7 +28,7 @@ func (d *AdminScopeDataSource) Metadata(_ context.Context, req datasource.Metada } func (d *AdminScopeDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = GetAdminScopeDataSourceSchema() + resp.Schema = AdminScopeModel{}.GetDataSourceSchema() } func (d *AdminScopeDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { @@ -48,7 +48,7 @@ func (d *AdminScopeDataSource) Read(ctx context.Context, req datasource.ReadRequ return } - var data AdminScopeDataSourceModel + var data AdminScopeModel // Read Terraform configuration data into the model resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) @@ -60,10 +60,9 @@ func (d *AdminScopeDataSource) Read(ctx context.Context, req datasource.ReadRequ // Read the data from the API var adminScopeNameOrId string - if data.Id.ValueString() != "" { + if !data.Id.IsNull() { adminScopeNameOrId = data.Id.ValueString() - } - if data.Name.ValueString() != "" { + } else { adminScopeNameOrId = data.Name.ValueString() } diff --git a/internal/daas/admin_scope/admin_scope_data_source_model.go b/internal/daas/admin_scope/admin_scope_data_source_model.go index 4e5a0ab..fc64e90 100644 --- a/internal/daas/admin_scope/admin_scope_data_source_model.go +++ b/internal/daas/admin_scope/admin_scope_data_source_model.go @@ -3,43 +3,13 @@ package admin_scope import ( - "context" - - "github.com/citrix/citrix-daas-rest-go/citrixorchestration" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" ) -type AdminScopeDataSourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - IsBuiltIn types.Bool `tfsdk:"is_built_in"` - IsAllScope types.Bool `tfsdk:"is_all_scope"` - IsTenantScope types.Bool `tfsdk:"is_tenant_scope"` - TenantId types.String `tfsdk:"tenant_id"` - TenantName types.String `tfsdk:"tenant_name"` -} - -func (r AdminScopeDataSourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, adminScope *citrixorchestration.ScopeResponseModel) AdminScopeDataSourceModel { - - r.Id = types.StringValue(adminScope.GetId()) - r.Name = types.StringValue(adminScope.GetName()) - r.Description = types.StringValue(adminScope.GetDescription()) - r.IsBuiltIn = types.BoolValue(adminScope.GetIsBuiltIn()) - r.IsAllScope = types.BoolValue(adminScope.GetIsAllScope()) - r.IsTenantScope = types.BoolValue(adminScope.GetIsTenantScope()) - r.TenantId = types.StringValue(adminScope.GetTenantId()) - r.TenantName = types.StringValue(adminScope.GetTenantName()) - - return r -} - -func GetAdminScopeDataSourceSchema() schema.Schema { +func (AdminScopeModel) GetDataSourceSchema() schema.Schema { return schema.Schema{ // This description is used by the documentation generator and the language server. Description: "CVAD --- Data source to get details regarding a specific Administrator scope.", @@ -50,11 +20,15 @@ func GetAdminScopeDataSourceSchema() schema.Schema { Optional: true, Validators: []validator.String{ stringvalidator.ExactlyOneOf(path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. + stringvalidator.LengthAtLeast(1), }, }, "name": schema.StringAttribute{ Description: "Name of the Admin Scope.", Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, }, "description": schema.StringAttribute{ Description: "Description of the Admin Scope.", @@ -82,5 +56,8 @@ func GetAdminScopeDataSourceSchema() schema.Schema { }, }, } +} +func (AdminScopeModel) GetDataSourceAttributes() map[string]schema.Attribute { + return AdminScopeModel{}.GetDataSourceSchema().Attributes } diff --git a/internal/daas/admin_scope/admin_scope_resource.go b/internal/daas/admin_scope/admin_scope_resource.go index d066f3e..d9a19ac 100644 --- a/internal/daas/admin_scope/admin_scope_resource.go +++ b/internal/daas/admin_scope/admin_scope_resource.go @@ -42,7 +42,7 @@ func (r *adminScopeResource) Metadata(_ context.Context, req resource.MetadataRe // Schema defines the schema for the resource. func (r *adminScopeResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = AdminScopeResourceModel{}.GetSchema() + resp.Schema = AdminScopeModel{}.GetSchema() } // Configure adds the provider configured client to the resource. @@ -59,7 +59,7 @@ func (r *adminScopeResource) Create(ctx context.Context, req resource.CreateRequ defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan AdminScopeResourceModel + var plan AdminScopeModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -108,7 +108,7 @@ func (r *adminScopeResource) Read(ctx context.Context, req resource.ReadRequest, defer util.PanicHandler(&resp.Diagnostics) // Get current state - var state AdminScopeResourceModel + var state AdminScopeModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -136,7 +136,7 @@ func (r *adminScopeResource) Update(ctx context.Context, req resource.UpdateRequ defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan AdminScopeResourceModel + var plan AdminScopeModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -185,7 +185,7 @@ func (r *adminScopeResource) Delete(ctx context.Context, req resource.DeleteRequ defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from state - var state AdminScopeResourceModel + var state AdminScopeModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -235,7 +235,7 @@ func readAdminScope(ctx context.Context, client *citrixdaasclient.CitrixDaasClie func (r *adminScopeResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { defer util.PanicHandler(&resp.Diagnostics) - var data AdminScopeResourceModel + var data AdminScopeModel diags := req.Config.Get(ctx, &data) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -261,7 +261,7 @@ func (r *adminScopeResource) ModifyPlan(ctx context.Context, req resource.Modify create := req.State.Raw.IsNull() - var plan AdminScopeResourceModel + var plan AdminScopeModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/internal/daas/admin_scope/admin_scope_resource_model.go b/internal/daas/admin_scope/admin_scope_resource_model.go index c4444de..a817159 100644 --- a/internal/daas/admin_scope/admin_scope_resource_model.go +++ b/internal/daas/admin_scope/admin_scope_resource_model.go @@ -17,15 +17,19 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// AdminScopeResourceModel maps the resource schema data. -type AdminScopeResourceModel struct { +// AdminScopeModel maps the resource schema data. +type AdminScopeModel struct { Id types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` IsTenantScope types.Bool `tfsdk:"is_tenant_scope"` + IsBuiltIn types.Bool `tfsdk:"is_built_in"` + IsAllScope types.Bool `tfsdk:"is_all_scope"` + TenantId types.String `tfsdk:"tenant_id"` + TenantName types.String `tfsdk:"tenant_name"` } -func (r AdminScopeResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, adminScope *citrixorchestration.ScopeResponseModel) AdminScopeResourceModel { +func (r AdminScopeModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, adminScope *citrixorchestration.ScopeResponseModel) AdminScopeModel { // Overwrite admin scope with refreshed state r.Id = types.StringValue(adminScope.GetId()) @@ -33,10 +37,15 @@ func (r AdminScopeResourceModel) RefreshPropertyValues(ctx context.Context, diag r.Description = types.StringValue(adminScope.GetDescription()) r.IsTenantScope = types.BoolValue(adminScope.GetIsTenantScope()) + r.IsBuiltIn = types.BoolValue(adminScope.GetIsBuiltIn()) + r.IsAllScope = types.BoolValue(adminScope.GetIsAllScope()) + r.TenantId = types.StringValue(adminScope.GetTenantId()) + r.TenantName = types.StringValue(adminScope.GetTenantName()) + return r } -func (AdminScopeResourceModel) GetSchema() schema.Schema { +func (AdminScopeModel) GetSchema() schema.Schema { return schema.Schema{ // This description is used by the documentation generator and the language server. Description: "CVAD --- Manages an administrator scope.", @@ -67,10 +76,26 @@ func (AdminScopeResourceModel) GetSchema() schema.Schema { boolplanmodifier.RequiresReplace(), }, }, + "is_built_in": schema.BoolAttribute{ + Description: "Indicates whether the Admin Scope is built-in or not.", + Computed: true, + }, + "is_all_scope": schema.BoolAttribute{ + Description: "Indicates whether the Admin Scope is all scope or not.", + Computed: true, + }, + "tenant_id": schema.StringAttribute{ + Description: "ID of the tenant to which the Admin Scope belongs.", + Computed: true, + }, + "tenant_name": schema.StringAttribute{ + Description: "Name of the tenant to which the Admin Scope belongs.", + Computed: true, + }, }, } } -func (AdminScopeResourceModel) GetAttributes() map[string]schema.Attribute { - return AdminScopeResourceModel{}.GetSchema().Attributes +func (AdminScopeModel) GetAttributes() map[string]schema.Attribute { + return AdminScopeModel{}.GetSchema().Attributes } diff --git a/internal/daas/admin_user/admin_user_data_source.go b/internal/daas/admin_user/admin_user_data_source.go new file mode 100644 index 0000000..de03014 --- /dev/null +++ b/internal/daas/admin_user/admin_user_data_source.go @@ -0,0 +1,73 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package admin_user + +import ( + "context" + + citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" + "github.com/citrix/terraform-provider-citrix/internal/util" + + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +var ( + _ datasource.DataSource = &AdminUserDataSource{} +) + +func NewAdminUserDataSource() datasource.DataSource { + return &AdminUserDataSource{} +} + +type AdminUserDataSource struct { + client *citrixdaasclient.CitrixDaasClient +} + +func (d *AdminUserDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_admin_user" +} + +func (d *AdminUserDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = AdminUserResourceModel{}.GetDataSourceSchema() +} + +func (d *AdminUserDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*citrixdaasclient.CitrixDaasClient) +} + +func (d *AdminUserDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + if d.client != nil && d.client.ApiClient == nil { + resp.Diagnostics.AddError(util.ProviderInitializationErrorMsg, util.MissingProviderClientIdAndSecretErrorMsg) + return + } + + if !d.client.AuthConfig.OnPremises { + resp.Diagnostics.AddError("Environment Not Supported", "This terraform resource is only supported for on-premise deployments") + } + + var data AdminUserResourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + adminUser, err := getAdminUser(ctx, d.client, &resp.Diagnostics, data.Id.ValueString()) + if err != nil { + return + } + + data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, false, adminUser) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/daas/admin_user/admin_user_data_source_model.go b/internal/daas/admin_user/admin_user_data_source_model.go new file mode 100644 index 0000000..bba3bfa --- /dev/null +++ b/internal/daas/admin_user/admin_user_data_source_model.go @@ -0,0 +1,61 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package admin_user + +import ( + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +func (RightsModel) GetDataSourceSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "role": schema.StringAttribute{ + Description: "Name of the role to be associated with the admin user.", + Computed: true, + }, + "scope": schema.StringAttribute{ + Description: "Name of the scope to be associated with the admin user.", + Computed: true, + }, + }, + } +} + +func (RightsModel) GetDataSourceAttributes() map[string]schema.Attribute { + return RightsModel{}.GetDataSourceSchema().Attributes +} + +func (AdminUserResourceModel) GetDataSourceSchema() schema.Schema { + return schema.Schema{ + // This description is used by the documentation generator and the language server. + Description: "CVAD --- Data source of an administrator user for on-premise environment.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "ID of the admin user.", + Required: true, + }, + "name": schema.StringAttribute{ + Description: "Name of an existing user in the active directory.", + Computed: true, + }, + "domain_name": schema.StringAttribute{ + Description: "Name of the domain that the user is a part of. For example, if the domain is `example.com`, then provide the value `example` for this field.", + Computed: true, + }, + "rights": schema.ListNestedAttribute{ + Description: "Rights to be associated with the admin user.", + Computed: true, + NestedObject: RightsModel{}.GetDataSourceSchema(), + }, + "is_enabled": schema.BoolAttribute{ + Description: "Flag to determine if the administrator is to be enabled or not.", + Computed: true, + }, + }, + } +} + +func (AdminUserResourceModel) GetDataSourceAttributes() map[string]schema.Attribute { + return AdminUserResourceModel{}.GetDataSourceSchema().Attributes +} diff --git a/internal/daas/admin_user/admin_user_resource.go b/internal/daas/admin_user/admin_user_resource.go index e3384b1..23fdfb8 100644 --- a/internal/daas/admin_user/admin_user_resource.go +++ b/internal/daas/admin_user/admin_user_resource.go @@ -103,7 +103,7 @@ func (r *adminUserResource) Create(ctx context.Context, req resource.CreateReque } // Map response body to schema and populate computed attribute values - plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, adminUser) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, true, adminUser) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -131,7 +131,7 @@ func (r *adminUserResource) Read(ctx context.Context, req resource.ReadRequest, return } - state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, adminUser) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, true, adminUser) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -190,7 +190,7 @@ func (r *adminUserResource) Update(ctx context.Context, req resource.UpdateReque } // Update resource state with updated property values - plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, updatedAdminUser) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, true, updatedAdminUser) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) diff --git a/internal/daas/admin_user/admin_user_resource_model.go b/internal/daas/admin_user/admin_user_resource_model.go index 38fc9a6..04450da 100644 --- a/internal/daas/admin_user/admin_user_resource_model.go +++ b/internal/daas/admin_user/admin_user_resource_model.go @@ -32,7 +32,7 @@ type RightsModel struct { Scope types.String `tfsdk:"scope"` } -func (r AdminUserResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, adminUser *citrixorchestration.AdministratorResponseModel) AdminUserResourceModel { +func (r AdminUserResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, isResource bool, adminUser *citrixorchestration.AdministratorResponseModel) AdminUserResourceModel { // Overwrite admin user data with refreshed state userDetails := adminUser.GetUser() userFQDN := strings.Split(userDetails.GetSamName(), "\\") @@ -40,7 +40,11 @@ func (r AdminUserResourceModel) RefreshPropertyValues(ctx context.Context, diagn r.Id = types.StringValue(userDetails.GetSid()) r.Name = types.StringValue(userFQDN[len(userFQDN)-1]) r.DomainName = types.StringValue(userDetails.GetDomain()) - r.Rights = util.TypedArrayToObjectList[RightsModel](ctx, diagnostics, r.refreshRights(ctx, diagnostics, adminUser.GetScopesAndRoles())) + if isResource { + r.Rights = util.TypedArrayToObjectList[RightsModel](ctx, diagnostics, r.refreshRights(ctx, diagnostics, adminUser.GetScopesAndRoles())) + } else { + r.Rights = util.DataSourceTypedArrayToObjectList[RightsModel](ctx, diagnostics, r.refreshRights(ctx, diagnostics, adminUser.GetScopesAndRoles())) + } r.IsEnabled = types.BoolValue(adminUser.GetEnabled()) return r diff --git a/internal/daas/application/application_folder_details_data_source.go b/internal/daas/application/application_folder_details_data_source.go index f26aa05..11688c3 100644 --- a/internal/daas/application/application_folder_details_data_source.go +++ b/internal/daas/application/application_folder_details_data_source.go @@ -4,12 +4,14 @@ package application import ( "context" - "strings" + "regexp" citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -39,6 +41,10 @@ func (d *ApplicationDataSource) Schema(ctx context.Context, req datasource.Schem "path": schema.StringAttribute{ Description: "The path of the folder to get the applications from.", Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.AdminFolderPathWithBackslashRegex), "Application Folder Path must not start or end with a backslash"), + stringvalidator.RegexMatches(regexp.MustCompile(util.AdminFolderPathSpecialCharactersRegex), "Application Folder Path must not contain any of the following special characters: / ; : # . * ? = < > | [ ] ( ) { } \" ' ` ~ "), + }, }, "total_applications": schema.Int64Attribute{ Description: "The total number of applications in the folder.", @@ -122,14 +128,13 @@ func (d *ApplicationDataSource) Read(ctx context.Context, req datasource.ReadReq } // Get the list of applications using the path - path := data.Path.ValueString() - if path != "" { - applicationFolderPath := strings.ReplaceAll(path, "\\", "|") + if !data.Path.IsNull() { + applicationFolderPath := util.BuildResourcePathForGetRequest(data.Path.ValueString(), "") getApplicationsRequest := d.client.ApiClient.AdminFoldersAPIsDAAS.AdminFoldersGetAdminFolderApplications(ctx, applicationFolderPath) apps, httpResp, err := citrixdaasclient.AddRequestData(getApplicationsRequest, d.client).Execute() if err != nil { resp.Diagnostics.AddError( - "Error getting Applications from folder "+path, + "Error getting Applications from folder "+data.Path.ValueString(), "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ "\nError message: "+util.ReadClientError(err), ) diff --git a/internal/daas/application/application_resource.go b/internal/daas/application/application_resource.go index 71ad1a1..228c491 100644 --- a/internal/daas/application/application_resource.go +++ b/internal/daas/application/application_resource.go @@ -82,6 +82,7 @@ func (r *applicationResource) Create(ctx context.Context, req resource.CreateReq createApplicationRequest.SetApplicationFolder(plan.ApplicationFolderPath.ValueString()) createApplicationRequest.SetIcon(plan.Icon.ValueString()) createApplicationRequest.SetClientFolder(plan.ApplicationCategoryPath.ValueString()) + createApplicationRequest.SetEnabled(plan.Enabled.ValueBool()) if plan.LimitVisibilityToUsers.IsNull() { createApplicationRequest.SetIncludedUserFilterEnabled(false) @@ -134,24 +135,18 @@ func (r *applicationResource) Create(ctx context.Context, req resource.CreateReq // Try getting the new application with application name - applicationName := plan.Name.ValueString() - - // If the application is present in an application folder, we specify the name in this format: {application folder path plus application name}.For example, FolderName1|FolderName2|ApplicationName. - if plan.ApplicationFolderPath.ValueString() != "" { - applicationName = strings.ReplaceAll(plan.ApplicationFolderPath.ValueString(), "\\", "|") + "|" + applicationName - } - - application, err := getApplication(ctx, r.client, &resp.Diagnostics, applicationName) + applicationPath := util.BuildResourcePathForGetRequest(plan.ApplicationFolderPath.ValueString(), plan.Name.ValueString()) + application, err := getApplication(ctx, r.client, &resp.Diagnostics, applicationPath) if err != nil { return } - applicationDeliveryGroups, err := getApplicationDeliveryGroups(ctx, r.client, &resp.Diagnostics, applicationName) + applicationDeliveryGroups, err := getApplicationDeliveryGroups(ctx, r.client, &resp.Diagnostics, applicationPath) if err != nil { return } - tags := getApplicationTags(ctx, &resp.Diagnostics, r.client, applicationName) + tags := getApplicationTags(ctx, &resp.Diagnostics, r.client, applicationPath) // Map response body to schema and populate Computed attribute values plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, application, applicationDeliveryGroups, tags) @@ -230,6 +225,7 @@ func (r *applicationResource) Update(ctx context.Context, req resource.UpdateReq editApplicationRequestBody.SetApplicationFolder(plan.ApplicationFolderPath.ValueString()) editApplicationRequestBody.SetIcon(plan.Icon.ValueString()) editApplicationRequestBody.SetClientFolder(plan.ApplicationCategoryPath.ValueString()) + editApplicationRequestBody.SetEnabled(plan.Enabled.ValueBool()) if plan.LimitVisibilityToUsers.IsNull() { editApplicationRequestBody.SetIncludedUserFilterEnabled(false) diff --git a/internal/daas/application/application_resource_model.go b/internal/daas/application/application_resource_model.go index c7961e4..78347da 100644 --- a/internal/daas/application/application_resource_model.go +++ b/internal/daas/application/application_resource_model.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" @@ -116,6 +117,7 @@ type ApplicationResourceModel struct { ApplicationCategoryPath types.String `tfsdk:"application_category_path"` Metadata types.List `tfsdk:"metadata"` // List[NameValueStringPairModel] Tags types.Set `tfsdk:"tags"` // Set[string] + Enabled types.Bool `tfsdk:"enabled"` } // Schema defines the schema for the data source. @@ -216,6 +218,12 @@ func (ApplicationResourceModel) GetSchema() schema.Schema { ), }, }, + "enabled": schema.BoolAttribute{ + Description: "Indicates whether the application is enabled or disabled. Default is `true`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, }, } } @@ -232,6 +240,7 @@ func (r ApplicationResourceModel) RefreshPropertyValues(ctx context.Context, dia r.Description = types.StringValue(application.GetDescription()) r.Icon = types.StringValue(application.GetIconId()) r.ApplicationCategoryPath = types.StringValue(application.GetClientFolder()) + r.Enabled = types.BoolValue(application.GetEnabled()) // Set optional values adminFolder := application.GetApplicationFolder() diff --git a/internal/daas/delivery_group/delivery_group_data_source.go b/internal/daas/delivery_group/delivery_group_data_source.go index 4061788..d23b29d 100644 --- a/internal/daas/delivery_group/delivery_group_data_source.go +++ b/internal/daas/delivery_group/delivery_group_data_source.go @@ -4,7 +4,6 @@ package delivery_group import ( "context" - "strings" citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" "github.com/citrix/terraform-provider-citrix/internal/util" @@ -61,36 +60,39 @@ func (d *DeliveryGroupDataSource) Read(ctx context.Context, req datasource.ReadR } // Get refreshed delivery group state from Orchestration - deliveryGroupName := data.Name.ValueString() - deliveryGroupPath := strings.ReplaceAll(data.DeliveryGroupFolderPath.ValueString(), "\\", "|") - if deliveryGroupPath != "" { - deliveryGroupPath = deliveryGroupPath + "|" + deliveryGroupName + var deliveryGroupPathOrId string + var deliveryGroupNameOrId string + if !data.Id.IsNull() { + deliveryGroupPathOrId = data.Id.ValueString() + deliveryGroupNameOrId = data.Id.ValueString() } else { - deliveryGroupPath = deliveryGroupName + deliveryGroupNameOrId = data.Name.ValueString() + deliveryGroupPathOrId = util.BuildResourcePathForGetRequest(data.DeliveryGroupFolderPath.ValueString(), deliveryGroupNameOrId) } - getDeliveryGroupRequest := d.client.ApiClient.DeliveryGroupsAPIsDAAS.DeliveryGroupsGetDeliveryGroup(ctx, deliveryGroupPath) + + getDeliveryGroupRequest := d.client.ApiClient.DeliveryGroupsAPIsDAAS.DeliveryGroupsGetDeliveryGroup(ctx, deliveryGroupPathOrId) deliveryGroup, httpResp, err := citrixdaasclient.AddRequestData(getDeliveryGroupRequest, d.client).Execute() if err != nil { resp.Diagnostics.AddError( - "Error reading Delivery Group "+deliveryGroupName, + "Error reading Delivery Group "+deliveryGroupNameOrId, "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ "\nError message: "+util.ReadClientError(err), ) } // Get VDAs associated with the delivery group - getDeliveryGroupMachinesRequest := d.client.ApiClient.DeliveryGroupsAPIsDAAS.DeliveryGroupsGetDeliveryGroupMachines(ctx, deliveryGroupName) + getDeliveryGroupMachinesRequest := d.client.ApiClient.DeliveryGroupsAPIsDAAS.DeliveryGroupsGetDeliveryGroupMachines(ctx, deliveryGroupPathOrId) deliveryGroupVdas, httpResp, err := citrixdaasclient.AddRequestData(getDeliveryGroupMachinesRequest, d.client).Execute() if err != nil { resp.Diagnostics.AddError( - "Error listing VDAs in Delivery Group "+deliveryGroupName, + "Error listing VDAs in Delivery Group "+deliveryGroupNameOrId, "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ "\nError message: "+util.ReadClientError(err), ) } - tags := getDeliveryGroupTags(ctx, &resp.Diagnostics, d.client, deliveryGroupName) + tags := getDeliveryGroupTags(ctx, &resp.Diagnostics, d.client, deliveryGroupPathOrId) data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, deliveryGroup, deliveryGroupVdas, tags) diff --git a/internal/daas/delivery_group/delivery_group_data_source_model.go b/internal/daas/delivery_group/delivery_group_data_source_model.go index d6b2ab4..5a6af57 100644 --- a/internal/daas/delivery_group/delivery_group_data_source_model.go +++ b/internal/daas/delivery_group/delivery_group_data_source_model.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -21,6 +22,7 @@ import ( type DeliveryGroupDataSourceModel struct { Id types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` + DeliveryType types.String `tfsdk:"delivery_type"` DeliveryGroupFolderPath types.String `tfsdk:"delivery_group_folder_path"` Vdas []vda.VdaModel `tfsdk:"vdas"` // List[VdaModel] Tenants types.Set `tfsdk:"tenants"` // Set[string] @@ -33,11 +35,22 @@ func (DeliveryGroupDataSourceModel) GetSchema() schema.Schema { Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: "GUID identifier of the delivery group.", - Computed: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "Id must be a valid GUID"), + }, }, "name": schema.StringAttribute{ Description: "Name of the delivery group.", - Required: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "delivery_type": schema.StringAttribute{ + Description: "The delivery type of the delivery group.", + Computed: true, }, "delivery_group_folder_path": schema.StringAttribute{ Description: "The path to the folder in which the delivery group is located.", @@ -45,6 +58,7 @@ func (DeliveryGroupDataSourceModel) GetSchema() schema.Schema { Validators: []validator.String{ stringvalidator.RegexMatches(regexp.MustCompile(util.AdminFolderPathWithBackslashRegex), "Admin Folder Path must not start or end with a backslash"), stringvalidator.RegexMatches(regexp.MustCompile(util.AdminFolderPathSpecialCharactersRegex), "Admin Folder Path must not contain any of the following special characters: / ; : # . * ? = < > | [ ] ( ) { } \" ' ` ~ "), + stringvalidator.AlsoRequires(path.MatchRoot("name")), }, }, "vdas": schema.ListNestedAttribute{ @@ -78,6 +92,9 @@ func (r DeliveryGroupDataSourceModel) RefreshPropertyValues(ctx context.Context, r.DeliveryGroupFolderPath = types.StringNull() } + deliveryType := string(deliveryGroup.GetDeliveryType()) + r.DeliveryType = types.StringValue(deliveryType) + res := []vda.VdaModel{} for _, model := range vdas.GetItems() { machineName := model.GetName() @@ -89,6 +106,7 @@ func (r DeliveryGroupDataSourceModel) RefreshPropertyValues(ctx context.Context, deliveryGroupId := deliveryGroup.GetId() res = append(res, vda.VdaModel{ + Id: types.StringValue(model.GetId()), MachineName: types.StringValue(machineName), HostedMachineId: types.StringValue(hostedMachineId), AssociatedMachineCatalog: types.StringValue(machineCatalogId), diff --git a/internal/daas/delivery_group/delivery_group_resource.go b/internal/daas/delivery_group/delivery_group_resource.go index d1ea27c..b93a387 100644 --- a/internal/daas/delivery_group/delivery_group_resource.go +++ b/internal/daas/delivery_group/delivery_group_resource.go @@ -626,7 +626,7 @@ func (r *deliveryGroupResource) ModifyPlan(ctx context.Context, req resource.Mod if plan.AssociatedMachineCatalogs.IsNull() { errorSummary := fmt.Sprintf("Error %s Delivery Group", operation) feature := "Delivery Groups without associated machine catalogs" - isFeatureSupportedForCurrentDDC := util.CheckProductVersion(r.client, &resp.Diagnostics, 118, 7, 42, errorSummary, feature) + isFeatureSupportedForCurrentDDC := util.CheckProductVersion(r.client, &resp.Diagnostics, 118, 118, 7, 42, errorSummary, feature) if !isFeatureSupportedForCurrentDDC { return @@ -648,6 +648,29 @@ func (r *deliveryGroupResource) ModifyPlan(ctx context.Context, req resource.Mod return } + // Validate Delivery Type + if !plan.DeliveryType.IsNull() && !plan.DeliveryType.IsUnknown() && !plan.SessionSupport.IsUnknown() && !plan.SharingKind.IsUnknown() { + deliveryType := plan.DeliveryType.ValueString() + sharingKind := plan.SharingKind.ValueString() + if (associatedMachineCatalogProperties.AllocationType == citrixorchestration.ALLOCATIONTYPE_STATIC || sharingKind == string(citrixorchestration.SHARINGKIND_PRIVATE)) && + deliveryType == string(citrixorchestration.DELIVERYKIND_DESKTOPS_AND_APPS) { + resp.Diagnostics.AddAttributeError( + path.Root("delivery_type"), + "Incorrect Attribute Configuration", + fmt.Sprintf("`delivery_type` can only be `%s` or `%s` when allocation type of the associated machine catalog is `%s` or `sharing_kind` is `%s`.", string(citrixorchestration.DELIVERYKIND_DESKTOPS_ONLY), string(citrixorchestration.DELIVERYKIND_APPS_ONLY), string(citrixorchestration.ALLOCATIONTYPE_STATIC), string(citrixorchestration.SHARINGKIND_PRIVATE)), + ) + return + } + + if deliveryType == string(citrixorchestration.DELIVERYKIND_APPS_ONLY) && !plan.Desktops.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("delivery_type"), + "Incorrect Attribute Configuration", + fmt.Sprintf("`delivery_type` cannot be `%s` when `desktops` is specified.", string(citrixorchestration.DELIVERYKIND_APPS_ONLY)), + ) + } + } + isValid, errMsg := validatePowerManagementSettings(ctx, &resp.Diagnostics, plan, associatedMachineCatalogProperties.AllocationType, associatedMachineCatalogProperties.SessionSupport) if !isValid { @@ -702,5 +725,39 @@ func (r *deliveryGroupResource) ModifyPlan(ctx context.Context, req resource.Mod "Incorrect Attribute Configuration", "make_resources_available_in_lhc can only be set for power managed Single Session OS Random (pooled) VDAs.", ) + return + } + + if !plan.Desktops.IsNull() { + sessionRoamingShouldBeSet := true + if associatedMachineCatalogProperties.AllocationType == citrixorchestration.ALLOCATIONTYPE_STATIC { + sessionRoamingShouldBeSet = false + } + + if !plan.SharingKind.IsNull() { + sharingKind := plan.SharingKind.ValueString() + if sharingKind == string(citrixorchestration.SHARINGKIND_PRIVATE) { + sessionRoamingShouldBeSet = false + } + } + + desktops := util.ObjectListToTypedArray[DeliveryGroupDesktop](ctx, &resp.Diagnostics, plan.Desktops) + for _, desktop := range desktops { + if desktop.EnableSessionRoaming.IsUnknown() { + continue + } else if !desktop.EnableSessionRoaming.IsNull() && !sessionRoamingShouldBeSet { + resp.Diagnostics.AddError( + "Error "+operation+" Delivery Group "+plan.Name.ValueString(), + "`enable_session_roaming` cannot be set when `sharing_kind` is `Private` or associated machine catalogs have `Static` allocation type.", + ) + return + } else if desktop.EnableSessionRoaming.IsNull() && sessionRoamingShouldBeSet { + resp.Diagnostics.AddError( + "Error "+operation+" Delivery Group "+plan.Name.ValueString(), + "`enable_session_roaming` should be set when `sharing_kind` is `Shared` or associated machine catalogs have `Random` allocation type.", + ) + return + } + } } } diff --git a/internal/daas/delivery_group/delivery_group_resource_model.go b/internal/daas/delivery_group/delivery_group_resource_model.go index afc5732..ea0f75e 100644 --- a/internal/daas/delivery_group/delivery_group_resource_model.go +++ b/internal/daas/delivery_group/delivery_group_resource_model.go @@ -13,6 +13,7 @@ import ( "github.com/citrix/terraform-provider-citrix/internal/util" "github.com/citrix/terraform-provider-citrix/internal/validators" "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" @@ -352,30 +353,33 @@ func (DeliveryGroupRebootSchedule) GetAttributes() map[string]schema.Attribute { } type DeliveryGroupPowerManagementSettings struct { - AutoscaleEnabled types.Bool `tfsdk:"autoscale_enabled"` - Timezone types.String `tfsdk:"timezone"` - PeakDisconnectTimeoutMinutes types.Int64 `tfsdk:"peak_disconnect_timeout_minutes"` - PeakLogOffAction types.String `tfsdk:"peak_log_off_action"` - PeakLogOffTimeoutMinutes types.Int64 `tfsdk:"peak_log_off_timeout_minutes"` - PeakDisconnectAction types.String `tfsdk:"peak_disconnect_action"` - PeakExtendedDisconnectAction types.String `tfsdk:"peak_extended_disconnect_action"` - PeakExtendedDisconnectTimeoutMinutes types.Int64 `tfsdk:"peak_extended_disconnect_timeout_minutes"` - OffPeakDisconnectTimeoutMinutes types.Int64 `tfsdk:"off_peak_disconnect_timeout_minutes"` - OffPeakLogOffAction types.String `tfsdk:"off_peak_log_off_action"` - OffPeakLogOffTimeoutMinutes types.Int64 `tfsdk:"off_peak_log_off_timeout_minutes"` - OffPeakDisconnectAction types.String `tfsdk:"off_peak_disconnect_action"` - OffPeakExtendedDisconnectAction types.String `tfsdk:"off_peak_extended_disconnect_action"` - OffPeakExtendedDisconnectTimeoutMinutes types.Int64 `tfsdk:"off_peak_extended_disconnect_timeout_minutes"` - PeakBufferSizePercent types.Int64 `tfsdk:"peak_buffer_size_percent"` - OffPeakBufferSizePercent types.Int64 `tfsdk:"off_peak_buffer_size_percent"` - PowerOffDelayMinutes types.Int64 `tfsdk:"power_off_delay_minutes"` - PeakAutoscaleAssignedPowerOnIdleAction types.String `tfsdk:"peak_autoscale_assigned_power_on_idle_action"` - PeakAutoscaleAssignedPowerOnIdleTimeoutMinutes types.Int64 `tfsdk:"peak_autoscale_assigned_power_on_idle_timeout_minutes"` - DisconnectPeakIdleSessionAfterSeconds types.Int64 `tfsdk:"disconnect_peak_idle_session_after_seconds"` - DisconnectOffPeakIdleSessionAfterSeconds types.Int64 `tfsdk:"disconnect_off_peak_idle_session_after_seconds"` - LogoffPeakDisconnectedSessionAfterSeconds types.Int64 `tfsdk:"log_off_peak_disconnected_session_after_seconds"` - LogoffOffPeakDisconnectedSessionAfterSeconds types.Int64 `tfsdk:"log_off_off_peak_disconnected_session_after_seconds"` - PowerTimeSchemes types.List `tfsdk:"power_time_schemes"` //List[DeliveryGroupPowerTimeScheme] + AutoscaleEnabled types.Bool `tfsdk:"autoscale_enabled"` + RestrictAutoscaleTag types.String `tfsdk:"restrict_autoscale_tag"` + RestrictAutoscaleMinIdleUntaggedPercentDuringPeak types.Int32 `tfsdk:"peak_restrict_min_idle_untagged_percent"` + RestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak types.Int32 `tfsdk:"off_peak_restrict_min_idle_untagged_percent"` + Timezone types.String `tfsdk:"timezone"` + PeakDisconnectTimeoutMinutes types.Int64 `tfsdk:"peak_disconnect_timeout_minutes"` + PeakLogOffAction types.String `tfsdk:"peak_log_off_action"` + PeakLogOffTimeoutMinutes types.Int64 `tfsdk:"peak_log_off_timeout_minutes"` + PeakDisconnectAction types.String `tfsdk:"peak_disconnect_action"` + PeakExtendedDisconnectAction types.String `tfsdk:"peak_extended_disconnect_action"` + PeakExtendedDisconnectTimeoutMinutes types.Int64 `tfsdk:"peak_extended_disconnect_timeout_minutes"` + OffPeakDisconnectTimeoutMinutes types.Int64 `tfsdk:"off_peak_disconnect_timeout_minutes"` + OffPeakLogOffAction types.String `tfsdk:"off_peak_log_off_action"` + OffPeakLogOffTimeoutMinutes types.Int64 `tfsdk:"off_peak_log_off_timeout_minutes"` + OffPeakDisconnectAction types.String `tfsdk:"off_peak_disconnect_action"` + OffPeakExtendedDisconnectAction types.String `tfsdk:"off_peak_extended_disconnect_action"` + OffPeakExtendedDisconnectTimeoutMinutes types.Int64 `tfsdk:"off_peak_extended_disconnect_timeout_minutes"` + PeakBufferSizePercent types.Int64 `tfsdk:"peak_buffer_size_percent"` + OffPeakBufferSizePercent types.Int64 `tfsdk:"off_peak_buffer_size_percent"` + PowerOffDelayMinutes types.Int64 `tfsdk:"power_off_delay_minutes"` + PeakAutoscaleAssignedPowerOnIdleAction types.String `tfsdk:"peak_autoscale_assigned_power_on_idle_action"` + PeakAutoscaleAssignedPowerOnIdleTimeoutMinutes types.Int64 `tfsdk:"peak_autoscale_assigned_power_on_idle_timeout_minutes"` + DisconnectPeakIdleSessionAfterSeconds types.Int64 `tfsdk:"disconnect_peak_idle_session_after_seconds"` + DisconnectOffPeakIdleSessionAfterSeconds types.Int64 `tfsdk:"disconnect_off_peak_idle_session_after_seconds"` + LogoffPeakDisconnectedSessionAfterSeconds types.Int64 `tfsdk:"log_off_peak_disconnected_session_after_seconds"` + LogoffOffPeakDisconnectedSessionAfterSeconds types.Int64 `tfsdk:"log_off_off_peak_disconnected_session_after_seconds"` + PowerTimeSchemes types.List `tfsdk:"power_time_schemes"` //List[DeliveryGroupPowerTimeScheme] } func (DeliveryGroupPowerManagementSettings) GetSchema() schema.SingleNestedAttribute { @@ -387,6 +391,31 @@ func (DeliveryGroupPowerManagementSettings) GetSchema() schema.SingleNestedAttri Description: "Whether auto-scale is enabled for the delivery group.", Required: true, }, + "restrict_autoscale_tag": schema.StringAttribute{ + Description: "Name of the tag on the machines that autoscale will apply on.", + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "peak_restrict_min_idle_untagged_percent": schema.Int32Attribute{ + Description: "Specifies the percentage of remaining untagged capacity to fall below to start powering on tagged machines during peak hours. " + + "\n\n~> **Please Note** This setting is only applicable when the `restrict_autoscale_tag` is set.", + Optional: true, + Validators: []validator.Int32{ + int32validator.Between(1, 100), + int32validator.AlsoRequires(path.MatchRelative().AtParent().AtName("restrict_autoscale_tag"), path.MatchRelative().AtParent().AtName("off_peak_restrict_min_idle_untagged_percent")), + }, + }, + "off_peak_restrict_min_idle_untagged_percent": schema.Int32Attribute{ + Description: "Specifies the percentage of remaining untagged capacity to fall below to start powering on tagged machines during off peak hours. " + + "\n\n~> **Please Note** This setting is only applicable when the `restrict_autoscale_tag` is set.", + Optional: true, + Validators: []validator.Int32{ + int32validator.Between(1, 100), + int32validator.AlsoRequires(path.MatchRelative().AtParent().AtName("restrict_autoscale_tag"), path.MatchRelative().AtParent().AtName("peak_restrict_min_idle_untagged_percent")), + }, + }, "timezone": schema.StringAttribute{ Description: "The time zone in which this delivery group's machines reside.", Optional: true, @@ -668,7 +697,7 @@ func (DeliveryGroupDesktop) GetSchema() schema.NestedAttributeObject { "enable_session_roaming": schema.BoolAttribute{ Description: "When enabled, if the user launches this desktop and then moves to another device, the same session is used, and applications are available on both devices. When disabled, the session no longer roams between devices. " + "\n\n~> **Please Note** Session roaming should be set to `false` for Remote PC Delivery Group.", - Required: true, + Optional: true, }, "restricted_access_users": restrictedAccessUsers.GetSchema(), }, @@ -907,6 +936,7 @@ type DeliveryGroupResourceModel struct { Id types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` + DeliveryType types.String `tfsdk:"delivery_type"` SessionSupport types.String `tfsdk:"session_support"` SharingKind types.String `tfsdk:"sharing_kind"` RestrictedAccessUsers types.Object `tfsdk:"restricted_access_users"` @@ -954,8 +984,15 @@ func (DeliveryGroupResourceModel) GetSchema() schema.Schema { Computed: true, Default: stringdefault.StaticString(""), }, + "delivery_type": schema.StringAttribute{ + Description: "Delivery type of the delivery group. Available values are `DesktopsOnly`, `AppsOnly`, and `DesktopsAndApps`. Defaults to `DesktopsOnly` for Delivery Groups with associated Machine Catalogs that have `allocation_type` set to `Static` and for Delivery Groups that have `sharing_kind` set to `private`. Otherwise defaults to `DesktopsAndApps", + Optional: true, + Validators: []validator.String{ + util.GetValidatorFromEnum(citrixorchestration.AllowedDeliveryKindEnumValues), + }, + }, "session_support": schema.StringAttribute{ - Description: "The session support for the delivery group. Can only be set to `SingleSession` or `MultiSession`. Specify only if you want to create a Delivery Group wthout any `associated_machine_catalogs`. Ensure session support is same as that of the prospective Machine Catalogs you will associate this Delivery Group with.", + Description: "The session support for the delivery group. Can only be set to `SingleSession` or `MultiSession`. Specify only if you want to create a Delivery Group without any `associated_machine_catalogs`. Ensure session support is same as that of the prospective Machine Catalogs you will associate this Delivery Group with.", Optional: true, Validators: []validator.String{ util.GetValidatorFromEnum(citrixorchestration.AllowedSessionSupportEnumValues), @@ -1164,6 +1201,12 @@ func (r DeliveryGroupResourceModel) RefreshPropertyValues(ctx context.Context, d r.PolicySetId = types.StringNull() } + if !r.DeliveryType.IsNull() { + r.DeliveryType = types.StringValue(string(deliveryGroup.GetDeliveryType())) + } else { + r.DeliveryType = types.StringNull() + } + minimumFunctionalLevel := deliveryGroup.GetMinimumFunctionalLevel() r.MinimumFunctionalLevel = types.StringValue(string(minimumFunctionalLevel)) @@ -1198,8 +1241,18 @@ func (r DeliveryGroupResourceModel) RefreshPropertyValues(ctx context.Context, d r = r.updatePlanWithAutoscaleSettings(ctx, diagnostics, deliveryGroup, dgPowerTimeSchemes) r = r.updatePlanWithRebootSchedule(ctx, diagnostics, dgRebootSchedule) r = r.updatePlanWithAppProtection(ctx, diagnostics, deliveryGroup) - r = r.updatePlanWithDefaultAccessPolicies(ctx, diagnostics, deliveryGroup.GetAdvancedAccessPolicy()) - r = r.updatePlanWithCustomAccessPolicies(ctx, diagnostics, deliveryGroup.GetAdvancedAccessPolicy()) + + var defaultAccessPolicies []citrixorchestration.AdvancedAccessPolicyResponseModel + var customAccessPolicies []citrixorchestration.AdvancedAccessPolicyResponseModel + for _, policy := range deliveryGroup.GetAdvancedAccessPolicy() { + if policy.GetIsBuiltIn() { + defaultAccessPolicies = append(defaultAccessPolicies, policy) + } else { + customAccessPolicies = append(customAccessPolicies, policy) + } + } + r = r.updatePlanWithDefaultAccessPolicies(ctx, diagnostics, defaultAccessPolicies) + r = r.updatePlanWithCustomAccessPolicies(ctx, diagnostics, customAccessPolicies) if len(deliveryGroup.GetStoreFrontServersForHostedReceiver()) > 0 || !r.StoreFrontServers.IsNull() { var remoteAssociatedStoreFrontServers []string diff --git a/internal/daas/delivery_group/delivery_group_utils.go b/internal/daas/delivery_group/delivery_group_utils.go index e1c7271..f4fcc00 100644 --- a/internal/daas/delivery_group/delivery_group_utils.go +++ b/internal/daas/delivery_group/delivery_group_utils.go @@ -694,10 +694,9 @@ func getRequestModelForDeliveryGroupCreate(ctx context.Context, diagnostics *dia } body.SetMinimumFunctionalLevel(*functionalLevel) - deliveryKind := citrixorchestration.DELIVERYKIND_DESKTOPS_AND_APPS - if plan.SessionSupport.ValueString() == string(citrixorchestration.SESSIONSUPPORT_SINGLE_SESSION) || - associatedMachineCatalogProperties.SessionSupport == citrixorchestration.SESSIONSUPPORT_SINGLE_SESSION { - deliveryKind = citrixorchestration.DELIVERYKIND_DESKTOPS_ONLY + deliveryKind, err := getDeliveryGroupDeliveryType(diagnostics, plan.DeliveryType, plan.SessionSupport.ValueString(), associatedMachineCatalogProperties.SessionSupport) + if err != nil { + return body, err } body.SetDeliveryType(deliveryKind) @@ -723,6 +722,22 @@ func getRequestModelForDeliveryGroupCreate(ctx context.Context, diagnostics *dia if !plan.AutoscaleSettings.IsNull() { autoscale := util.ObjectValueToTypedObject[DeliveryGroupPowerManagementSettings](ctx, diagnostics, plan.AutoscaleSettings) body.SetAutoScaleEnabled(autoscale.AutoscaleEnabled.ValueBool()) + if !autoscale.RestrictAutoscaleTag.IsNull() { + body.SetRestrictAutoscaleTag(autoscale.RestrictAutoscaleTag.ValueString()) + } + + if !autoscale.RestrictAutoscaleMinIdleUntaggedPercentDuringPeak.IsNull() { + body.SetRestrictAutoscaleMinIdleUntaggedPercentDuringPeak(autoscale.RestrictAutoscaleMinIdleUntaggedPercentDuringPeak.ValueInt32()) + } else { + body.SetRestrictAutoscaleMinIdleUntaggedPercentDuringPeak(-1) + } + + if !autoscale.RestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak.IsNull() { + body.SetRestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak(autoscale.RestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak.ValueInt32()) + } else { + body.SetRestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak(-1) + } + body.SetTimeZone(autoscale.Timezone.ValueString()) body.SetPeakDisconnectTimeoutMinutes(int32(autoscale.PeakDisconnectTimeoutMinutes.ValueInt64())) body.SetPeakLogOffAction(getSessionChangeHostingActionValue(autoscale.PeakLogOffAction.ValueString())) @@ -913,6 +928,19 @@ func getRequestModelForDeliveryGroupUpdate(ctx context.Context, diagnostics *dia var editDeliveryGroupRequestBody citrixorchestration.EditDeliveryGroupRequestModel editDeliveryGroupRequestBody.SetName(plan.Name.ValueString()) editDeliveryGroupRequestBody.SetDescription(plan.Description.ValueString()) + + if !plan.DeliveryType.IsNull() { + deliveryKind, err := citrixorchestration.NewDeliveryKindFromValue(plan.DeliveryType.ValueString()) + if err != nil { + diagnostics.AddError( + "Error creating Delivery Group", + fmt.Sprintf("Unsupported delivery type %s.", plan.DeliveryType.ValueString()), + ) + return editDeliveryGroupRequestBody, err + } + editDeliveryGroupRequestBody.SetDeliveryType(*deliveryKind) + } + editDeliveryGroupRequestBody.SetDesktops(deliveryGroupDesktopsArray) editDeliveryGroupRequestBody.SetRebootSchedules(deliveryGroupRebootScheduleArray) editDeliveryGroupRequestBody.SetAdvancedAccessPolicy(advancedAccessPolicies) @@ -939,6 +967,23 @@ func getRequestModelForDeliveryGroupUpdate(ctx context.Context, diagnostics *dia } editDeliveryGroupRequestBody.SetAutoScaleEnabled(autoscale.AutoscaleEnabled.ValueBool()) + if !autoscale.RestrictAutoscaleTag.IsNull() { + editDeliveryGroupRequestBody.SetRestrictAutoscaleTag(autoscale.RestrictAutoscaleTag.ValueString()) + } else { + editDeliveryGroupRequestBody.SetRestrictAutoscaleTag("") + } + + if !autoscale.RestrictAutoscaleMinIdleUntaggedPercentDuringPeak.IsNull() { + editDeliveryGroupRequestBody.SetRestrictAutoscaleMinIdleUntaggedPercentDuringPeak(autoscale.RestrictAutoscaleMinIdleUntaggedPercentDuringPeak.ValueInt32()) + } else { + editDeliveryGroupRequestBody.SetRestrictAutoscaleMinIdleUntaggedPercentDuringPeak(-1) + } + + if !autoscale.RestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak.IsNull() { + editDeliveryGroupRequestBody.SetRestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak(autoscale.RestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak.ValueInt32()) + } else { + editDeliveryGroupRequestBody.SetRestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak(-1) + } editDeliveryGroupRequestBody.SetPeakDisconnectTimeoutMinutes(int32(autoscale.PeakDisconnectTimeoutMinutes.ValueInt64())) editDeliveryGroupRequestBody.SetPeakLogOffAction(getSessionChangeHostingActionValue(autoscale.PeakLogOffAction.ValueString())) editDeliveryGroupRequestBody.SetPeakLogOffTimeoutMinutes(int32(autoscale.PeakLogOffTimeoutMinutes.ValueInt64())) @@ -1122,7 +1167,7 @@ func parseDeliveryGroupRebootScheduleToClientModel(ctx context.Context, diags *d } -func (schedule DeliveryGroupRebootSchedule) RefreshListItem(ctx context.Context, diags *diag.Diagnostics, rebootSchedule citrixorchestration.RebootScheduleResponseModel) util.ModelWithAttributes { +func (schedule DeliveryGroupRebootSchedule) RefreshListItem(ctx context.Context, diags *diag.Diagnostics, rebootSchedule citrixorchestration.RebootScheduleResponseModel) util.ResourceModelWithAttributes { schedule.Name = types.StringValue(rebootSchedule.GetName()) if rebootSchedule.GetDescription() != "" { schedule.Description = types.StringValue(rebootSchedule.GetDescription()) @@ -1174,7 +1219,7 @@ func (schedule DeliveryGroupRebootSchedule) RefreshListItem(ctx context.Context, return schedule } -func (dgDesktop DeliveryGroupDesktop) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, desktop citrixorchestration.DesktopResponseModel) util.ModelWithAttributes { +func (dgDesktop DeliveryGroupDesktop) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, desktop citrixorchestration.DesktopResponseModel) util.ResourceModelWithAttributes { dgDesktop.PublishedName = types.StringValue(desktop.GetPublishedName()) dgDesktop.DesktopDescription = types.StringValue(desktop.GetDescription()) @@ -1186,16 +1231,20 @@ func (dgDesktop DeliveryGroupDesktop) RefreshListItem(ctx context.Context, diagn } dgDesktop.Enabled = types.BoolValue(desktop.GetEnabled()) - sessionReconnection := desktop.GetSessionReconnection() - if sessionReconnection == citrixorchestration.SESSIONRECONNECTION_ALWAYS { - dgDesktop.EnableSessionRoaming = types.BoolValue(true) + if desktop.SessionReconnection != nil { + sessionReconnection := desktop.GetSessionReconnection() + if sessionReconnection == citrixorchestration.SESSIONRECONNECTION_ALWAYS { + dgDesktop.EnableSessionRoaming = types.BoolValue(true) + } else { + dgDesktop.EnableSessionRoaming = types.BoolValue(false) + } } else { - dgDesktop.EnableSessionRoaming = types.BoolValue(false) + dgDesktop.EnableSessionRoaming = types.BoolNull() } var users RestrictedAccessUsers if !desktop.GetIncludedUserFilterEnabled() { - if attributes, err := util.AttributeMapFromObject(users); err == nil { + if attributes, err := util.ResourceAttributeMapFromObject(users); err == nil { dgDesktop.RestrictedAccessUsers = types.ObjectNull(attributes) } else { diagnostics.AddWarning("Error when creating null RestrictedAccessUsers", err.Error()) @@ -1225,7 +1274,7 @@ func (dgDesktop DeliveryGroupDesktop) RefreshListItem(ctx context.Context, diagn return dgDesktop } -func (dgAccessPolicy DeliveryGroupAccessPolicyModel) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, accessPolicy citrixorchestration.AdvancedAccessPolicyResponseModel) util.ModelWithAttributes { +func (dgAccessPolicy DeliveryGroupAccessPolicyModel) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, accessPolicy citrixorchestration.AdvancedAccessPolicyResponseModel) util.ResourceModelWithAttributes { dgAccessPolicy.Id = types.StringValue(accessPolicy.GetId()) if accessPolicy.GetIsBuiltIn() { @@ -1257,7 +1306,7 @@ func (dgAccessPolicy DeliveryGroupAccessPolicyModel) RefreshListItem(ctx context return dgAccessPolicy } -func (dgAccessPolicyTags DeliveryGroupAccessPolicyCriteriaTagsModel) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, accessPolicyTag citrixorchestration.SmartAccessTagResponseModel) util.ModelWithAttributes { +func (dgAccessPolicyTags DeliveryGroupAccessPolicyCriteriaTagsModel) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, accessPolicyTag citrixorchestration.SmartAccessTagResponseModel) util.ResourceModelWithAttributes { if !strings.EqualFold(dgAccessPolicyTags.FilterName.ValueString(), accessPolicyTag.GetFarm()) { dgAccessPolicyTags.FilterName = types.StringValue(accessPolicyTag.GetFarm()) } @@ -1268,7 +1317,7 @@ func (dgAccessPolicyTags DeliveryGroupAccessPolicyCriteriaTagsModel) RefreshList return dgAccessPolicyTags } -func (dgAppProtectionApplyContextually DeliveryGroupAppProtectionApplyContextuallyModel) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, accessPolicy citrixorchestration.AdvancedAccessPolicyResponseModel) util.ModelWithAttributes { +func (dgAppProtectionApplyContextually DeliveryGroupAppProtectionApplyContextuallyModel) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, accessPolicy citrixorchestration.AdvancedAccessPolicyResponseModel) util.ResourceModelWithAttributes { policyName := accessPolicy.GetName() if accessPolicy.GetIsBuiltIn() { policyName = dgAppProtectionApplyContextually.PolicyName.ValueString() @@ -1346,12 +1395,14 @@ func verifyUsersAndParseDeliveryGroupDesktopsToClientModel(ctx context.Context, var desktopRequest citrixorchestration.DesktopRequestModel desktopRequest.SetPublishedName(deliveryGroupDesktop.PublishedName.ValueString()) desktopRequest.SetDescription(deliveryGroupDesktop.DesktopDescription.ValueString()) - sessionReconnection := citrixorchestration.SESSIONRECONNECTION_ALWAYS - if !deliveryGroupDesktop.EnableSessionRoaming.ValueBool() { - sessionReconnection = citrixorchestration.SESSIONRECONNECTION_SAME_ENDPOINT_ONLY + if !deliveryGroupDesktop.EnableSessionRoaming.IsNull() { + sessionReconnection := citrixorchestration.SESSIONRECONNECTION_ALWAYS + if !deliveryGroupDesktop.EnableSessionRoaming.ValueBool() { + sessionReconnection = citrixorchestration.SESSIONRECONNECTION_SAME_ENDPOINT_ONLY + } + desktopRequest.SetSessionReconnection(sessionReconnection) } desktopRequest.SetEnabled(deliveryGroupDesktop.Enabled.ValueBool()) - desktopRequest.SetSessionReconnection(sessionReconnection) includedUserIds := []string{} excludedUserIds := []string{} @@ -1465,10 +1516,6 @@ func (r DeliveryGroupResourceModel) updatePlanWithDesktops(ctx context.Context, } func (r DeliveryGroupResourceModel) updatePlanWithAccessPolicies(ctx context.Context, diagnostics *diag.Diagnostics, accessPolicies []citrixorchestration.AdvancedAccessPolicyResponseModel, isBuiltIn bool) DeliveryGroupResourceModel { - accessPolicies = slices.DeleteFunc(accessPolicies, func(policy citrixorchestration.AdvancedAccessPolicyResponseModel) bool { - return policy.GetIsBuiltIn() != isBuiltIn - }) - stateAccessPolciies := r.CustomAccessPolicies if isBuiltIn { stateAccessPolciies = r.DefaultAccessPolicies @@ -1510,7 +1557,7 @@ func (r DeliveryGroupResourceModel) updatePlanWithAppProtection(ctx context.Cont appProtectionModel.EnableAntiKeyLogging = types.BoolValue(antiKeyLoggingEnabled) appProtectionModel.EnableAntiScreenCapture = types.BoolValue(antiScreenCaptureEnabled) - attributesMap, err := util.AttributeMapFromObject(DeliveryGroupAppProtectionApplyContextuallyModel{}) + attributesMap, err := util.ResourceAttributeMapFromObject(DeliveryGroupAppProtectionApplyContextuallyModel{}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) return r @@ -1528,7 +1575,7 @@ func (r DeliveryGroupResourceModel) updatePlanWithAppProtection(ctx context.Cont appProtection.EnableAntiScreenCapture = types.BoolNull() r.AppProtection = util.TypedObjectToObjectValue(ctx, diagnostics, appProtection) } else { - if attributes, err := util.AttributeMapFromObject(DeliveryGroupAppProtection{}); err == nil { + if attributes, err := util.ResourceAttributeMapFromObject(DeliveryGroupAppProtection{}); err == nil { r.AppProtection = types.ObjectNull(attributes) } else { diagnostics.AddWarning("Error when creating null DeliveryGroupAppProtection", err.Error()) @@ -1615,7 +1662,7 @@ func (r DeliveryGroupResourceModel) updatePlanWithRestrictedAccessUsers(ctx cont } if !accessPolicy.GetIncludedUserFilterEnabled() { - if attributes, err := util.AttributeMapFromObject(RestrictedAccessUsers{}); err == nil { + if attributes, err := util.ResourceAttributeMapFromObject(RestrictedAccessUsers{}); err == nil { r.RestrictedAccessUsers = types.ObjectNull(attributes) } else { diagnostics.AddWarning("Error when creating null RestrictedAccessUsers", err.Error()) @@ -1641,7 +1688,7 @@ func (r DeliveryGroupResourceModel) updatePlanWithRestrictedAccessUsers(ctx cont } if users.AllowList.IsNull() && users.BlockList.IsNull() { - if attributes, err := util.AttributeMapFromObject(users); err == nil { + if attributes, err := util.ResourceAttributeMapFromObject(users); err == nil { r.RestrictedAccessUsers = types.ObjectNull(attributes) } } else { @@ -1659,6 +1706,23 @@ func (r DeliveryGroupResourceModel) updatePlanWithAutoscaleSettings(ctx context. autoscale := util.ObjectValueToTypedObject[DeliveryGroupPowerManagementSettings](context.Background(), nil, r.AutoscaleSettings) autoscale.AutoscaleEnabled = types.BoolValue(deliveryGroup.GetAutoScaleEnabled()) + if deliveryGroup.RestrictAutoscaleTag != nil { + restrictAutoScaleTag := deliveryGroup.GetRestrictAutoscaleTag() + autoscale.RestrictAutoscaleTag = types.StringValue(restrictAutoScaleTag.GetName()) + + if deliveryGroup.GetRestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak() >= 0 { + autoscale.RestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak = types.Int32Value(deliveryGroup.GetRestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak()) + } else { + autoscale.RestrictAutoscaleMinIdleUntaggedPercentDuringOffPeak = types.Int32Null() + } + + if deliveryGroup.GetRestrictAutoscaleMinIdleUntaggedPercentDuringPeak() >= 0 { + autoscale.RestrictAutoscaleMinIdleUntaggedPercentDuringPeak = types.Int32Value(deliveryGroup.GetRestrictAutoscaleMinIdleUntaggedPercentDuringPeak()) + } else { + autoscale.RestrictAutoscaleMinIdleUntaggedPercentDuringPeak = types.Int32Null() + } + } + if !autoscale.Timezone.IsNull() { autoscale.Timezone = types.StringValue(deliveryGroup.GetTimeZone()) } @@ -1691,7 +1755,7 @@ func (r DeliveryGroupResourceModel) updatePlanWithAutoscaleSettings(ctx context. parsedPowerTimeSchemes = preserveOrderInPowerTimeSchemes(ctx, diags, autoscalePowerTimeSchemes, parsedPowerTimeSchemes) autoscale.PowerTimeSchemes = util.TypedArrayToObjectList(ctx, diags, parsedPowerTimeSchemes) } else { - if attributeMap, err := util.AttributeMapFromObject(DeliveryGroupPowerTimeScheme{}); err == nil { + if attributeMap, err := util.ResourceAttributeMapFromObject(DeliveryGroupPowerTimeScheme{}); err == nil { autoscale.PowerTimeSchemes = types.ListNull(types.ObjectType{AttrTypes: attributeMap}) } else { diags.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) @@ -2058,3 +2122,25 @@ func getDeliveryGroupTags(ctx context.Context, diagnostics *diag.Diagnostics, cl tagsResp, httpResp, err := citrixdaasclient.AddRequestData(getTagsRequest, client).Execute() return util.ProcessTagsResponseCollection(diagnostics, tagsResp, httpResp, err, "Delivery Group", deliveryGroupId) } + +func getDeliveryGroupDeliveryType(diagnostics *diag.Diagnostics, deliveryType types.String, sessionSupport string, associatedCatalogSessionSupport citrixorchestration.SessionSupport) (citrixorchestration.DeliveryKind, error) { + var deliveryKind citrixorchestration.DeliveryKind + if deliveryType.IsNull() { + deliveryKind = citrixorchestration.DELIVERYKIND_DESKTOPS_AND_APPS + if sessionSupport == string(citrixorchestration.SESSIONSUPPORT_SINGLE_SESSION) || + associatedCatalogSessionSupport == citrixorchestration.SESSIONSUPPORT_SINGLE_SESSION { + deliveryKind = citrixorchestration.DELIVERYKIND_DESKTOPS_ONLY + } + } else { + deliveryKindParsed, err := citrixorchestration.NewDeliveryKindFromValue(deliveryType.ValueString()) + if err != nil { + diagnostics.AddError( + "Error creating Delivery Group", + fmt.Sprintf("Unsupported delivery type %s.", deliveryType.ValueString()), + ) + return citrixorchestration.DELIVERYKIND_UNKNOWN, err + } + deliveryKind = *deliveryKindParsed + } + return deliveryKind, nil +} diff --git a/internal/daas/hypervisor/hypervisor_data_source.go b/internal/daas/hypervisor/hypervisor_data_source.go index 7b9ed50..2754b10 100644 --- a/internal/daas/hypervisor/hypervisor_data_source.go +++ b/internal/daas/hypervisor/hypervisor_data_source.go @@ -60,13 +60,18 @@ func (d *HypervisorDataSource) Read(ctx context.Context, req datasource.ReadRequ } // Get refreshed hypervisor state from Orchestration - hypervisorName := data.Name.ValueString() - getHypervisorRequest := d.client.ApiClient.HypervisorsAPIsDAAS.HypervisorsGetHypervisor(ctx, hypervisorName) + var hypervisorNameOrId string + if !data.Name.IsNull() { + hypervisorNameOrId = data.Name.ValueString() + } else { + hypervisorNameOrId = data.Id.ValueString() + } + getHypervisorRequest := d.client.ApiClient.HypervisorsAPIsDAAS.HypervisorsGetHypervisor(ctx, hypervisorNameOrId) hypervisor, httpResp, err := citrixdaasclient.AddRequestData(getHypervisorRequest, d.client).Execute() if err != nil { resp.Diagnostics.AddError( - "Error reading Hypervisor "+hypervisorName, + "Error reading Hypervisor "+hypervisorNameOrId, "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ "\nError message: "+util.ReadClientError(err), ) diff --git a/internal/daas/hypervisor/hypervisor_data_source_model.go b/internal/daas/hypervisor/hypervisor_data_source_model.go index f734715..dd2035a 100644 --- a/internal/daas/hypervisor/hypervisor_data_source_model.go +++ b/internal/daas/hypervisor/hypervisor_data_source_model.go @@ -4,11 +4,15 @@ package hypervisor import ( "context" + "regexp" "github.com/citrix/citrix-daas-rest-go/citrixorchestration" "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -25,11 +29,18 @@ func (HypervisorDataSourceModel) GetSchema() schema.Schema { Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: "GUID identifier of the hypervisor.", - Computed: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "Id must be a valid GUID"), + }, }, "name": schema.StringAttribute{ Description: "Name of the hypervisor.", - Required: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, }, "tenants": schema.SetAttribute{ ElementType: types.StringType, diff --git a/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_common.go b/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_common.go index 1d91240..8293d77 100644 --- a/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_common.go +++ b/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_common.go @@ -29,7 +29,7 @@ func (h HypervisorStorageModel) GetKey() string { return h.StorageName.ValueString() } -func (v HypervisorStorageModel) RefreshListItem(_ context.Context, _ *diag.Diagnostics, remote citrixorchestration.HypervisorStorageResourceResponseModel) util.ModelWithAttributes { +func (v HypervisorStorageModel) RefreshListItem(_ context.Context, _ *diag.Diagnostics, remote citrixorchestration.HypervisorStorageResourceResponseModel) util.ResourceModelWithAttributes { v.StorageName = types.StringValue(remote.GetName()) v.Superseded = types.BoolValue(remote.GetSuperseded()) return v diff --git a/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_data_source.go b/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_data_source.go index 9edd1b1..d3f0954 100644 --- a/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_data_source.go +++ b/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_data_source.go @@ -59,15 +59,22 @@ func (d *HypervisorResourcePoolDataSource) Read(ctx context.Context, req datasou return } + var resourcePoolNameOrId string + if !data.Id.IsNull() { + resourcePoolNameOrId = data.Id.ValueString() + } else { + // Get refreshed machine catalog state from Orchestration + resourcePoolNameOrId = data.Name.ValueString() + } + // Get refreshed hypervisor resource pool state from Orchestration hypervisorName := data.HypervisorName.ValueString() - resourcePoolName := data.Name.ValueString() - getHypervisorResourcePoolRequest := d.client.ApiClient.HypervisorsAPIsDAAS.HypervisorsGetHypervisorResourcePool(ctx, hypervisorName, resourcePoolName) + getHypervisorResourcePoolRequest := d.client.ApiClient.HypervisorsAPIsDAAS.HypervisorsGetHypervisorResourcePool(ctx, hypervisorName, resourcePoolNameOrId) resourcePool, httpResp, err := citrixdaasclient.AddRequestData(getHypervisorResourcePoolRequest, d.client).Execute() if err != nil { resp.Diagnostics.AddError( - "Error reading Resource Pool "+resourcePoolName+" of Hypervisor "+hypervisorName, + "Error reading Resource Pool "+resourcePoolNameOrId+" of Hypervisor "+hypervisorName, "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ "\nError message: "+util.ReadClientError(err), ) diff --git a/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_data_source_model.go b/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_data_source_model.go index 44f6a1f..087d4ed 100644 --- a/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_data_source_model.go +++ b/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_data_source_model.go @@ -4,11 +4,15 @@ package hypervisor_resource_pool import ( "context" + "regexp" "github.com/citrix/citrix-daas-rest-go/citrixorchestration" "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -26,11 +30,18 @@ func (HypervisorResourcePoolDataSourceModel) GetSchema() schema.Schema { Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: "GUID identifier of the hypervisor resource pool.", - Computed: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "Id must be a valid GUID"), + }, }, "name": schema.StringAttribute{ Description: "Name of the hypervisor resource pool.", - Required: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, }, "hypervisor_name": schema.StringAttribute{ Description: "Name of the hypervisor to which the resource pool belongs.", diff --git a/internal/daas/machine_catalog/machine_catalog_common_utils.go b/internal/daas/machine_catalog/machine_catalog_common_utils.go index c3aba70..fb4482a 100644 --- a/internal/daas/machine_catalog/machine_catalog_common_utils.go +++ b/internal/daas/machine_catalog/machine_catalog_common_utils.go @@ -59,12 +59,17 @@ func getRequestModelForCreateMachineCatalog(plan MachineCatalogResourceModel, ct return nil, err } body.SetSessionSupport(*sessionSupport) - persistChanges := citrixorchestration.PERSISTCHANGES_DISCARD - if *provisioningType == citrixorchestration.PROVISIONINGTYPE_MANUAL || - (*provisioningType != citrixorchestration.PROVISIONINGTYPE_PVS_STREAMING && *sessionSupport == citrixorchestration.SESSIONSUPPORT_SINGLE_SESSION && *allocationType == citrixorchestration.ALLOCATIONTYPE_STATIC) { - persistChanges = citrixorchestration.PERSISTCHANGES_ON_LOCAL + if !plan.PersistUserChanges.IsNull() { + body.SetPersistUserChanges(citrixorchestration.PersistChanges(plan.PersistUserChanges.ValueString())) + } else { + persistChanges := citrixorchestration.PERSISTCHANGES_DISCARD + if *provisioningType == citrixorchestration.PROVISIONINGTYPE_MANUAL || + (*provisioningType != citrixorchestration.PROVISIONINGTYPE_PVS_STREAMING && *sessionSupport == citrixorchestration.SESSIONSUPPORT_SINGLE_SESSION && *allocationType == citrixorchestration.ALLOCATIONTYPE_STATIC) { + persistChanges = citrixorchestration.PERSISTCHANGES_ON_LOCAL + } + body.SetPersistUserChanges(persistChanges) } - body.SetPersistUserChanges(persistChanges) + body.SetZone(plan.Zone.ValueString()) if !plan.VdaUpgradeType.IsNull() { body.SetVdaUpgradeType(citrixorchestration.VdaUpgradeType(plan.VdaUpgradeType.ValueString())) @@ -245,7 +250,7 @@ func generateBatchApiHeaders(ctx context.Context, diagnostics *diag.Diagnostics, } func readMachineCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, resp *resource.ReadResponse, machineCatalogId string) (*citrixorchestration.MachineCatalogDetailResponseModel, *http.Response, error) { - getMachineCatalogRequest := client.ApiClient.MachineCatalogsAPIsDAAS.MachineCatalogsGetMachineCatalog(ctx, machineCatalogId).Fields("Id,Name,Description,ProvisioningType,Zone,AllocationType,SessionSupport,TotalCount,HypervisorConnection,ProvisioningScheme,RemotePCEnrollmentScopes,IsPowerManaged,MinimumFunctionalLevel,IsRemotePC,Metadata,Scopes,UpgradeInfo,AdminFolder") + getMachineCatalogRequest := client.ApiClient.MachineCatalogsAPIsDAAS.MachineCatalogsGetMachineCatalog(ctx, machineCatalogId).Fields("Id,Name,Description,ProvisioningType,PersistChanges,Zone,AllocationType,SessionSupport,TotalCount,HypervisorConnection,ProvisioningScheme,RemotePCEnrollmentScopes,IsPowerManaged,MinimumFunctionalLevel,IsRemotePC,Metadata,Scopes,UpgradeInfo,AdminFolder") catalog, httpResp, err := util.ReadResource[*citrixorchestration.MachineCatalogDetailResponseModel](getMachineCatalogRequest, ctx, client, resp, "Machine Catalog", machineCatalogId) return catalog, httpResp, err @@ -389,7 +394,7 @@ func allocationTypeEnumToString(conn citrixorchestration.AllocationType) string } } -func (scope RemotePcOuModel) RefreshListItem(_ context.Context, _ *diag.Diagnostics, remote citrixorchestration.RemotePCEnrollmentScopeResponseModel) util.ModelWithAttributes { +func (scope RemotePcOuModel) RefreshListItem(_ context.Context, _ *diag.Diagnostics, remote citrixorchestration.RemotePCEnrollmentScopeResponseModel) util.ResourceModelWithAttributes { scope.OUName = types.StringValue(remote.GetOU()) scope.IncludeSubFolders = types.BoolValue(remote.GetIncludeSubfolders()) diff --git a/internal/daas/machine_catalog/machine_catalog_data_source.go b/internal/daas/machine_catalog/machine_catalog_data_source.go index d54ed97..b319152 100644 --- a/internal/daas/machine_catalog/machine_catalog_data_source.go +++ b/internal/daas/machine_catalog/machine_catalog_data_source.go @@ -4,7 +4,6 @@ package machine_catalog import ( "context" - "strings" citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" "github.com/citrix/terraform-provider-citrix/internal/util" @@ -60,37 +59,38 @@ func (d *MachineCatalogDataSource) Read(ctx context.Context, req datasource.Read return } - // Get refreshed machine catalog state from Orchestration - machineCatalogName := data.Name.ValueString() - machineCatalogPath := strings.ReplaceAll(data.MachineCatalogFolderPath.ValueString(), "\\", "|") - if machineCatalogPath != "" { - machineCatalogPath = machineCatalogPath + "|" + machineCatalogName + var machineCatalogPathOrId string + var machineCatalogNameOrId string + if !data.Id.IsNull() { + machineCatalogPathOrId = data.Id.ValueString() + machineCatalogNameOrId = data.Id.ValueString() } else { - machineCatalogPath += machineCatalogName + // Get refreshed machine catalog state from Orchestration + machineCatalogNameOrId = util.BuildResourcePathForGetRequest(data.MachineCatalogFolderPath.ValueString(), data.Name.ValueString()) } - getMachineCatalogRequest := d.client.ApiClient.MachineCatalogsAPIsDAAS.MachineCatalogsGetMachineCatalog(ctx, machineCatalogPath).Fields("Id,Name,Description,ProvisioningType,Zone,AllocationType,SessionSupport,TotalCount,HypervisorConnection,ProvisioningScheme,RemotePCEnrollmentScopes,IsPowerManaged,MinimumFunctionalLevel,IsRemotePC") + getMachineCatalogRequest := d.client.ApiClient.MachineCatalogsAPIsDAAS.MachineCatalogsGetMachineCatalog(ctx, machineCatalogPathOrId).Fields("Id,Name,Description,ProvisioningType,PersistChanges,Zone,AllocationType,SessionSupport,TotalCount,HypervisorConnection,ProvisioningScheme,RemotePCEnrollmentScopes,IsPowerManaged,MinimumFunctionalLevel,IsRemotePC") machineCatalog, httpResp, err := citrixdaasclient.AddRequestData(getMachineCatalogRequest, d.client).Execute() if err != nil { resp.Diagnostics.AddError( - "Error reading Machine Catalog "+machineCatalogName, + "Error reading Machine Catalog "+machineCatalogNameOrId, "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ "\nError message: "+util.ReadClientError(err), ) } // Get VDAs associated with the machine catalog - getMachineCatalogMachinesRequest := d.client.ApiClient.MachineCatalogsAPIsDAAS.MachineCatalogsGetMachineCatalogMachines(ctx, machineCatalogPath) + getMachineCatalogMachinesRequest := d.client.ApiClient.MachineCatalogsAPIsDAAS.MachineCatalogsGetMachineCatalogMachines(ctx, machineCatalogPathOrId) machineCatalogVdas, httpResp, err := citrixdaasclient.AddRequestData(getMachineCatalogMachinesRequest, d.client).Execute() if err != nil { resp.Diagnostics.AddError( - "Error listing VDAs in Machine Catalog "+machineCatalogName, + "Error listing VDAs in Machine Catalog "+machineCatalogNameOrId, "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ "\nError message: "+util.ReadClientError(err), ) } - tags := getMachineCatalogTags(ctx, &resp.Diagnostics, d.client, machineCatalogPath) + tags := getMachineCatalogTags(ctx, &resp.Diagnostics, d.client, machineCatalogPathOrId) data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, machineCatalog, machineCatalogVdas, tags) diff --git a/internal/daas/machine_catalog/machine_catalog_data_source_model.go b/internal/daas/machine_catalog/machine_catalog_data_source_model.go index 5497a0d..a281a7c 100644 --- a/internal/daas/machine_catalog/machine_catalog_data_source_model.go +++ b/internal/daas/machine_catalog/machine_catalog_data_source_model.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -33,11 +34,18 @@ func (MachineCatalogDataSourceModel) GetSchema() schema.Schema { Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: "GUID identifier of the machine catalog.", - Computed: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "Id must be a valid GUID"), + }, }, "name": schema.StringAttribute{ Description: "Name of the machine catalog.", - Required: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, }, "machine_catalog_folder_path": schema.StringAttribute{ Description: "The path to the folder in which the machine catalog is located.", @@ -45,6 +53,7 @@ func (MachineCatalogDataSourceModel) GetSchema() schema.Schema { Validators: []validator.String{ stringvalidator.RegexMatches(regexp.MustCompile(util.AdminFolderPathWithBackslashRegex), "Admin Folder Path must not start or end with a backslash"), stringvalidator.RegexMatches(regexp.MustCompile(util.AdminFolderPathSpecialCharactersRegex), "Admin Folder Path must not contain any of the following special characters: / ; : # . * ? = < > | [ ] ( ) { } \" ' ` ~ "), + stringvalidator.AlsoRequires(path.MatchRoot("name")), }, }, "vdas": schema.ListNestedAttribute{ @@ -89,6 +98,7 @@ func (r MachineCatalogDataSourceModel) RefreshPropertyValues(ctx context.Context deliveryGroupId := deliveryGroup.GetId() res = append(res, vda.VdaModel{ + Id: types.StringValue(model.GetId()), MachineName: types.StringValue(machineName), HostedMachineId: types.StringValue(hostedMachineId), AssociatedMachineCatalog: types.StringValue(machineCatalogId), diff --git a/internal/daas/machine_catalog/machine_catalog_manual_utils.go b/internal/daas/machine_catalog/machine_catalog_manual_utils.go index ffb047a..0554a3e 100644 --- a/internal/daas/machine_catalog/machine_catalog_manual_utils.go +++ b/internal/daas/machine_catalog/machine_catalog_manual_utils.go @@ -443,6 +443,7 @@ func (r MachineCatalogResourceModel) updateCatalogWithMachines(ctx context.Conte if machinesNotPresetInRemote[strings.ToLower(machine.MachineAccount.ValueString())] { continue } + machine.MachineAccount = types.StringValue(strings.ToLower(machine.MachineAccount.ValueString())) machineAccountMachines = append(machineAccountMachines, machine) } machineAccount.Machines = util.TypedArrayToObjectList[MachineCatalogMachineModel](ctx, diagnostics, machineAccountMachines) diff --git a/internal/daas/machine_catalog/machine_catalog_mcs_pvs_utils.go b/internal/daas/machine_catalog/machine_catalog_mcs_pvs_utils.go index 8005512..c1975a6 100644 --- a/internal/daas/machine_catalog/machine_catalog_mcs_pvs_utils.go +++ b/internal/daas/machine_catalog/machine_catalog_mcs_pvs_utils.go @@ -644,7 +644,11 @@ func setProvSchemePropertiesForUpdateCatalog(provisioningSchemePlan Provisioning } func generateAdminCredentialHeader(machineDomainIdentityModel MachineDomainIdentityModel) string { - credential := fmt.Sprintf("%s\\%s:%s", machineDomainIdentityModel.Domain.ValueString(), machineDomainIdentityModel.ServiceAccount.ValueString(), machineDomainIdentityModel.ServiceAccountPassword.ValueString()) + domain := machineDomainIdentityModel.Domain.ValueString() + if !machineDomainIdentityModel.ServiceAccountDomain.IsNull() { + domain = machineDomainIdentityModel.ServiceAccountDomain.ValueString() + } + credential := fmt.Sprintf("%s\\%s:%s", domain, machineDomainIdentityModel.ServiceAccount.ValueString(), machineDomainIdentityModel.ServiceAccountPassword.ValueString()) encodedData := base64.StdEncoding.EncodeToString([]byte(credential)) header := fmt.Sprintf("Basic %s", encodedData) diff --git a/internal/daas/machine_catalog/machine_catalog_resource.go b/internal/daas/machine_catalog/machine_catalog_resource.go index 9d89abb..e50833e 100644 --- a/internal/daas/machine_catalog/machine_catalog_resource.go +++ b/internal/daas/machine_catalog/machine_catalog_resource.go @@ -6,7 +6,6 @@ import ( "context" "fmt" "net/http" - "strings" citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" @@ -104,14 +103,8 @@ func (r *machineCatalogResource) Create(ctx context.Context, req resource.Create if err != nil { return } - machineCatalogName := plan.Name.ValueString() - machineCatalogPath := strings.ReplaceAll(plan.MachineCatalogFolderPath.ValueString(), "\\", "|") - if machineCatalogPath != "" { - machineCatalogPath = machineCatalogPath + "|" + machineCatalogName - } else { - machineCatalogPath += machineCatalogName - } + machineCatalogPath := util.BuildResourcePathForGetRequest(plan.MachineCatalogFolderPath.ValueString(), plan.Name.ValueString()) setMachineCatalogTags(ctx, &resp.Diagnostics, r.client, machineCatalogPath, plan.Tags) // Get the new catalog @@ -434,6 +427,15 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc ) } + if data.SessionSupport.ValueString() == string(citrixorchestration.SESSIONSUPPORT_SINGLE_SESSION) && data.AllocationType.ValueString() == string(citrixorchestration.ALLOCATIONTYPE_RANDOM) && data.PersistUserChanges.ValueString() == string(citrixorchestration.PERSISTCHANGES_ON_LOCAL) { + resp.Diagnostics.AddAttributeError( + path.Root("persist_user_changes"), + "Incorrect Attribute Configuration", + "persist_user_changes cannot be set to OnLocal when session_support is set to Static and allocation_type is set to Random.", + ) + return + } + if !data.Metadata.IsNull() { metadata := util.ObjectListToTypedArray[util.NameValueStringPairModel](ctx, &resp.Diagnostics, data.Metadata) isValid := util.ValidateMetadataConfig(ctx, &resp.Diagnostics, metadata) @@ -943,6 +945,5 @@ func (r *machineCatalogResource) ModifyPlan(ctx context.Context, req resource.Mo ) return } - } } diff --git a/internal/daas/machine_catalog/machine_catalog_resource_model.go b/internal/daas/machine_catalog/machine_catalog_resource_model.go index e5fd865..a14aa24 100644 --- a/internal/daas/machine_catalog/machine_catalog_resource_model.go +++ b/internal/daas/machine_catalog/machine_catalog_resource_model.go @@ -38,6 +38,7 @@ type MachineCatalogResourceModel struct { Description types.String `tfsdk:"description"` IsPowerManaged types.Bool `tfsdk:"is_power_managed"` IsRemotePc types.Bool `tfsdk:"is_remote_pc"` + PersistUserChanges types.String `tfsdk:"persist_user_changes"` AllocationType types.String `tfsdk:"allocation_type"` SessionSupport types.String `tfsdk:"session_support"` Zone types.String `tfsdk:"zone"` @@ -100,10 +101,11 @@ func (MachineCatalogMachineModel) GetSchema() schema.NestedAttributeObject { return schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "machine_account": schema.StringAttribute{ - Description: "The Computer AD Account for the machine. Must be in the format DOMAIN\\MACHINE.", + Description: "The computer AD account for the machine must be in the format \\, all in lowercase.", Required: true, Validators: []validator.String{ stringvalidator.RegexMatches(regexp.MustCompile(util.SamRegex), "must be in the format DOMAIN\\MACHINE"), + stringvalidator.RegexMatches(regexp.MustCompile(util.LowerCaseRegex), "must be in lowercase"), }, }, "machine_name": schema.StringAttribute{ @@ -309,6 +311,7 @@ func (CustomPropertyModel) GetAttributes() map[string]schema.Attribute { type MachineDomainIdentityModel struct { Domain types.String `tfsdk:"domain"` Ou types.String `tfsdk:"domain_ou"` + ServiceAccountDomain types.String `tfsdk:"service_account_domain"` ServiceAccount types.String `tfsdk:"service_account"` ServiceAccountPassword types.String `tfsdk:"service_account_password"` } @@ -320,7 +323,7 @@ func (MachineDomainIdentityModel) GetSchema() schema.SingleNestedAttribute { Optional: true, Attributes: map[string]schema.Attribute{ "domain": schema.StringAttribute{ - Description: "The AD domain name for the pool. Specify this in FQDN format; for example, MyDomain.com.", + Description: "The AD domain where machine accounts will be created. Specify this in FQDN format; for example, MyDomain.com.", Required: true, Validators: []validator.String{ stringvalidator.RegexMatches(regexp.MustCompile(util.DomainFqdnRegex), "must be in FQDN format"), @@ -336,6 +339,14 @@ func (MachineDomainIdentityModel) GetSchema() schema.SingleNestedAttribute { stringvalidator.LengthAtLeast(1), }, }, + "service_account_domain": schema.StringAttribute{ + Description: "The domain name of the service account. Specify this in FQDN format; for example, MyServiceDomain.com." + + "\n\n~> **Please Note** Use this property if domain of the service account which is used to create the machine accounts resides in a domain different from what's specified in property `domain` where the machine accounts are created.", + Optional: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.DomainFqdnRegex), "must be in FQDN format"), + }, + }, "service_account": schema.StringAttribute{ Description: "Service account for the domain. Only the username is required; do not include the domain name.", Required: true, @@ -459,7 +470,8 @@ func (RemotePcOuModel) GetAttributes() map[string]schema.Attribute { func (MachineCatalogResourceModel) GetSchema() schema.Schema { return schema.Schema{ - Description: "CVAD --- Manages a machine catalog.", + Description: "CVAD --- Manages a machine catalog." + + "\n\n-> **Note** To bind a machine catalog to a Workspace Environment Management (WEM) configuration set, use `citrix_wem_directory_object` resource.", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: "GUID identifier of the machine catalog.", @@ -478,6 +490,19 @@ func (MachineCatalogResourceModel) GetSchema() schema.Schema { Computed: true, Default: stringdefault.StaticString(""), }, + "persist_user_changes": schema.StringAttribute{ + Description: "Specify if user changes are persisted on the machines in the machine catalog. Choose between `Discard` and `OnLocal`. Defaults to OnLocal for manual or non-PVS single session static catalogs, Discard otherwise. ", + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "Discard", + "OnLocal", + ), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, "is_power_managed": schema.BoolAttribute{ Description: "Specify if the machines in the machine catalog will be power managed.", Optional: true, @@ -638,6 +663,12 @@ func (r MachineCatalogResourceModel) RefreshPropertyValues(ctx context.Context, sessionSupport := catalog.GetSessionSupport() r.SessionSupport = types.StringValue(string(sessionSupport)) + if r.PersistUserChanges.IsNull() { + r.PersistUserChanges = types.StringNull() + } else { + r.PersistUserChanges = types.StringValue(string(catalog.GetPersistChanges())) + } + minimumFunctionalLevel := catalog.GetMinimumFunctionalLevel() r.MinimumFunctionalLevel = types.StringValue(string(minimumFunctionalLevel)) @@ -709,7 +740,7 @@ func (r MachineCatalogResourceModel) RefreshPropertyValues(ctx context.Context, r.Tags = util.RefreshTagSet(ctx, diagnostics, tags) if catalog.ProvisioningScheme == nil { - if attributesMap, err := util.AttributeMapFromObject(ProvisioningSchemeModel{}); err == nil { + if attributesMap, err := util.ResourceAttributeMapFromObject(ProvisioningSchemeModel{}); err == nil { r.ProvisioningScheme = types.ObjectNull(attributesMap) } else { diagnostics.AddWarning("Error when creating null ProvisioningSchemeModel", err.Error()) @@ -723,7 +754,7 @@ func (r MachineCatalogResourceModel) RefreshPropertyValues(ctx context.Context, return r } -func (networkMapping NetworkMappingModel) RefreshListItem(_ context.Context, _ *diag.Diagnostics, nic citrixorchestration.NetworkMapResponseModel) util.ModelWithAttributes { +func (networkMapping NetworkMappingModel) RefreshListItem(_ context.Context, _ *diag.Diagnostics, nic citrixorchestration.NetworkMapResponseModel) util.ResourceModelWithAttributes { networkMapping.NetworkDevice = types.StringValue(nic.GetDeviceId()) network := nic.GetNetwork() segments := strings.Split(network.GetXDPath(), "\\") diff --git a/internal/daas/machine_catalog/machine_config.go b/internal/daas/machine_catalog/machine_config.go index 6b79f94..3537d96 100644 --- a/internal/daas/machine_catalog/machine_config.go +++ b/internal/daas/machine_catalog/machine_config.go @@ -1183,7 +1183,7 @@ func (mc *AzureMachineConfigModel) RefreshProperties(ctx context.Context, diagno } // Clear gallery image details - attributeMap, err := util.AttributeMapFromObject(GalleryImageModel{}) + attributeMap, err := util.ResourceAttributeMapFromObject(GalleryImageModel{}) if err != nil { diagnostics.AddWarning("Error converting schema to attribute map. Error: ", err.Error()) } else { @@ -1218,7 +1218,7 @@ func (mc *AzureMachineConfigModel) RefreshProperties(ctx context.Context, diagno machineProfileModel := parseAzureMachineProfileResponseToModel(machineProfile) mc.MachineProfile = util.TypedObjectToObjectValue(ctx, diagnostics, machineProfileModel) } else { - if attributesMap, err := util.AttributeMapFromObject(AzureMachineProfileModel{}); err == nil { + if attributesMap, err := util.ResourceAttributeMapFromObject(AzureMachineProfileModel{}); err == nil { mc.MachineProfile = types.ObjectNull(attributesMap) } else { diagnostics.AddWarning("Error when creating null AzureMachineProfileModel", err.Error()) @@ -1338,7 +1338,7 @@ func (mc *AzureMachineConfigModel) RefreshProperties(ctx context.Context, diagno } if !isDesSet && !mc.DiskEncryptionSet.IsNull() { - if attributesMap, err := util.AttributeMapFromObject(AzureDiskEncryptionSetModel{}); err == nil { + if attributesMap, err := util.ResourceAttributeMapFromObject(AzureDiskEncryptionSetModel{}); err == nil { mc.DiskEncryptionSet = types.ObjectNull(attributesMap) } else { diagnostics.AddWarning("Error when creating null AzureDiskEcryptionSetModel", err.Error()) @@ -1346,7 +1346,7 @@ func (mc *AzureMachineConfigModel) RefreshProperties(ctx context.Context, diagno } if !isUseSharedImageGallerySet && !mc.UseAzureComputeGallery.IsNull() { - if attributesMap, err := util.AttributeMapFromObject(AzureComputeGallerySettings{}); err == nil { + if attributesMap, err := util.ResourceAttributeMapFromObject(AzureComputeGallerySettings{}); err == nil { mc.UseAzureComputeGallery = types.ObjectNull(attributesMap) } else { diagnostics.AddWarning("Error when creating null AzureComputeGallerySettings", err.Error()) diff --git a/internal/daas/machine_catalog/machine_properties_resource.go b/internal/daas/machine_catalog/machine_properties_resource.go new file mode 100644 index 0000000..edfeb3d --- /dev/null +++ b/internal/daas/machine_catalog/machine_properties_resource.go @@ -0,0 +1,319 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package machine_catalog + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/citrix/citrix-daas-rest-go/citrixorchestration" + citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" + "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &MachinePropertiesResource{} + _ resource.ResourceWithConfigure = &MachinePropertiesResource{} + _ resource.ResourceWithImportState = &MachinePropertiesResource{} + _ resource.ResourceWithValidateConfig = &MachinePropertiesResource{} + _ resource.ResourceWithModifyPlan = &MachinePropertiesResource{} +) + +// NewAdminFolderResource is a helper function to simplify the provider implementation. +func NewMachinePropertiesResource() resource.Resource { + return &MachinePropertiesResource{} +} + +// MachinePropertiesResource is the resource implementation. +type MachinePropertiesResource struct { + client *citrixdaasclient.CitrixDaasClient +} + +// Metadata returns the data source type name. +func (r *MachinePropertiesResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_machine_properties" +} + +// Configure adds the provider configured client to the data source. +func (r *MachinePropertiesResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*citrixdaasclient.CitrixDaasClient) +} + +// Schema defines the schema for the data source. +func (r *MachinePropertiesResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = MachinePropertiesModel{}.GetSchema() +} + +// Create implements resource.Resource. +func (r *MachinePropertiesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + var plan MachinePropertiesModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + machineName := strings.ReplaceAll(plan.Name.ValueString(), "\\", "|") + if !plan.Tags.IsNull() { + err := setMachineTags(ctx, r.client, &resp.Diagnostics, machineName, plan.Tags, "managing tags for") + if err != nil { + return + } + } + + // Get refreshed admin properties from Orchestration + machineProperties, err := getMachineProperties(ctx, r.client, &resp.Diagnostics, machineName) + if err != nil { + return + } + + machineTagIds, err := getMachineTagIds(ctx, r.client, &resp.Diagnostics, machineName) + if err != nil { + return + } + + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, machineProperties, machineTagIds) + + // Set refreshed state + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read implements resource.Resource. +func (r *MachinePropertiesResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + var state MachinePropertiesModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + machineName := strings.ReplaceAll(state.Name.ValueString(), "\\", "|") + // Get refreshed admin properties from Orchestration + machineProperties, err := readMachineProperties(ctx, r.client, resp, machineName) + if err != nil { + return + } + + machineTagIds, err := getMachineTagIds(ctx, r.client, &resp.Diagnostics, machineName) + if err != nil { + return + } + + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, machineProperties, machineTagIds) + + // Set refreshed state + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Update implements resource.Resource. +func (r *MachinePropertiesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + var plan MachinePropertiesModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + machineName := strings.ReplaceAll(plan.Name.ValueString(), "\\", "|") + if !plan.Tags.IsNull() { + err := setMachineTags(ctx, r.client, &resp.Diagnostics, machineName, plan.Tags, "updating tags for") + if err != nil { + return + } + } + + // Get refreshed admin properties from Orchestration + machineProperties, err := getMachineProperties(ctx, r.client, &resp.Diagnostics, machineName) + if err != nil { + return + } + + machineTagIds, err := getMachineTagIds(ctx, r.client, &resp.Diagnostics, machineName) + if err != nil { + return + } + + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, machineProperties, machineTagIds) + + // Set refreshed state + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Delete implements resource.Resource. +func (r *MachinePropertiesResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + // Retrieve values from state + var state MachinePropertiesModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + machineName := strings.ReplaceAll(state.Name.ValueString(), "\\", "|") + getMachinePropertiesRequest := r.client.ApiClient.MachinesAPIsDAAS.MachinesGetMachine(ctx, machineName) + _, httpResp, err := citrixdaasclient.ExecuteWithRetry[*citrixorchestration.MachineDetailResponseModel](getMachinePropertiesRequest, r.client) + if err != nil { + if httpResp.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( + "Error checking properties of machine "+state.Name.ValueString(), + "\nTransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return + } + + // If machine still exist, remove tags + err = setMachineTags(ctx, r.client, &resp.Diagnostics, machineName, types.SetNull(types.StringType), "removing tags from") + if err != nil { + return + } + resp.State.RemoveResource(ctx) +} + +func (r *MachinePropertiesResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // Retrieve import ID and save to id attribute + resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp) +} + +func (r *MachinePropertiesResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + var data MachinePropertiesModel + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + schemaType, configValuesForSchema := util.GetConfigValuesForSchema(ctx, &resp.Diagnostics, &data) + tflog.Debug(ctx, "Validate Config - "+schemaType, configValuesForSchema) +} + +func (r *MachinePropertiesResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + if r.client != nil && r.client.ApiClient == nil { + resp.Diagnostics.AddError(util.ProviderInitializationErrorMsg, util.MissingProviderClientIdAndSecretErrorMsg) + return + } + + if req.Plan.Raw.IsNull() || !req.Plan.Raw.IsFullyKnown() { + return + } + + var plan MachinePropertiesModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + machineName := strings.ReplaceAll(plan.Name.ValueString(), "\\", "|") + getMachinePropertiesRequest := r.client.ApiClient.MachinesAPIsDAAS.MachinesGetMachine(ctx, machineName) + machineProperties, httpResp, err := citrixdaasclient.ExecuteWithRetry[*citrixorchestration.MachineDetailResponseModel](getMachinePropertiesRequest, r.client) + if err != nil { + resp.Diagnostics.AddError( + "Error reading the properties of Machine "+plan.Name.ValueString(), + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return + } + machineCatalog := machineProperties.GetMachineCatalog() + if !strings.EqualFold(machineCatalog.GetId(), plan.MachineCatalogId.ValueString()) { + resp.Diagnostics.AddError( + "Error reading the properties of Machine "+plan.Name.ValueString(), + "Machine catalog ID specified does not match the machine catalog ID of the machine", + ) + return + } +} + +func readMachineProperties(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, resp *resource.ReadResponse, machineNameOrId string) (*citrixorchestration.MachineDetailResponseModel, error) { + getMachinePropertiesRequest := client.ApiClient.MachinesAPIsDAAS.MachinesGetMachine(ctx, machineNameOrId) + machineProperties, _, err := util.ReadResource[*citrixorchestration.MachineDetailResponseModel](getMachinePropertiesRequest, ctx, client, resp, "Machine Properties", machineNameOrId) + return machineProperties, err +} + +func getMachineProperties(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, machineNameOrId string) (*citrixorchestration.MachineDetailResponseModel, error) { + getMachinePropertiesRequest := client.ApiClient.MachinesAPIsDAAS.MachinesGetMachine(ctx, machineNameOrId) + machineProperties, httpResp, err := citrixdaasclient.ExecuteWithRetry[*citrixorchestration.MachineDetailResponseModel](getMachinePropertiesRequest, client) + if err != nil { + diagnostics.AddError( + "Error reading the properties of Machine "+strings.ReplaceAll(machineNameOrId, "|", "\\"), + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return nil, err + } + return machineProperties, err +} + +func getMachineTagIds(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, machineNameOrId string) ([]string, error) { + getTagsRequest := client.ApiClient.MachinesAPIsDAAS.MachinesGetMachineTags(ctx, machineNameOrId) + machineTags, httpResp, err := citrixdaasclient.ExecuteWithRetry[*citrixorchestration.TagResponseModelCollection](getTagsRequest, client) + if err != nil { + diagnostics.AddError( + "Error reading the tags of Machine "+strings.ReplaceAll(machineNameOrId, "|", "\\"), + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return nil, err + } + tagIds := []string{} + for _, tag := range machineTags.GetItems() { + tagIds = append(tagIds, tag.GetId()) + } + return tagIds, err +} + +func setMachineTags(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, machineNameOrId string, tags types.Set, tagOperation string) error { + setMachineTagRequest := client.ApiClient.MachinesAPIsDAAS.MachinesSetMachineTags(ctx, machineNameOrId) + tagRequestModel := util.ConstructTagsRequestModel(ctx, diagnostics, tags) + setMachineTagRequest = setMachineTagRequest.TagsRequestModel(tagRequestModel) + httpResp, err := citrixdaasclient.AddRequestData(setMachineTagRequest, client).Execute() + if err != nil { + diagnostics.AddError( + fmt.Sprintf("Error %s machine %s", tagOperation, strings.ReplaceAll(machineNameOrId, "|", "\\")), + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return err + } + return nil +} diff --git a/internal/daas/machine_catalog/machine_properties_resource_model.go b/internal/daas/machine_catalog/machine_properties_resource_model.go new file mode 100644 index 0000000..d9cb9de --- /dev/null +++ b/internal/daas/machine_catalog/machine_properties_resource_model.go @@ -0,0 +1,84 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package machine_catalog + +import ( + "context" + "regexp" + "strings" + + "github.com/citrix/citrix-daas-rest-go/citrixorchestration" + "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type MachinePropertiesModel struct { + Name types.String `tfsdk:"name"` + MachineCatalogId types.String `tfsdk:"machine_catalog_id"` + Tags types.Set `tfsdk:"tags"` +} + +func (MachinePropertiesModel) GetSchema() schema.Schema { + return schema.Schema{ + Description: "CVAD --- Manages the properties of a machine.", + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "The Name of the machine. For domain joined machines, the name must be in format \\ format. Must be all in lowercase.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.LowerCaseRegex), "must be all in lowercase"), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "machine_catalog_id": schema.StringAttribute{ + Description: "The ID of the machine catalog to which the machine belongs.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "tags": schema.SetAttribute{ + ElementType: types.StringType, + Description: "A set of identifiers of tags to associate with the machine.", + Optional: true, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + validator.String( + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + ), + ), + }, + }, + }, + } +} + +func (MachinePropertiesModel) GetAttributes() map[string]schema.Attribute { + return MachinePropertiesModel{}.GetSchema().Attributes +} + +func (r MachinePropertiesModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, machine *citrixorchestration.MachineDetailResponseModel, tagIds []string) MachinePropertiesModel { + machineName := strings.ToLower(machine.GetName()) + r.Name = types.StringValue(machineName) + + machineCatalog := machine.GetMachineCatalog() + r.MachineCatalogId = types.StringValue(machineCatalog.GetId()) + + if len(tagIds) > 0 { + r.Tags = util.StringArrayToStringSet(ctx, diagnostics, tagIds) + } else { + r.Tags = types.SetNull(types.StringType) + } + + return r +} diff --git a/internal/daas/policies/policy_filter_data_source_model.go b/internal/daas/policies/policy_filter_data_source_model.go new file mode 100644 index 0000000..50320e4 --- /dev/null +++ b/internal/daas/policies/policy_filter_data_source_model.go @@ -0,0 +1,251 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package policies + +import ( + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +func (AccessControlFilterModel) GetDataSourceSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Id of the policy filter.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Computed: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Computed: true, + }, + "connection": schema.StringAttribute{ + Description: "Gateway connection for the policy filter.", + Computed: true, + }, + "condition": schema.StringAttribute{ + Description: "Gateway condition for the policy filter.", + Computed: true, + }, + "gateway": schema.StringAttribute{ + Description: "Gateway for the policy filter.", + Computed: true, + }, + }, + } +} + +func (AccessControlFilterModel) GetDataSourceAttributes() map[string]schema.Attribute { + return AccessControlFilterModel{}.GetDataSourceSchema().Attributes +} +func (BranchRepeaterFilterModel) GetDataSourceSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Definition of branch repeater policy filter.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Id of the branch repeater policy filter.", + Computed: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Computed: true, + }, + }, + } +} + +func (BranchRepeaterFilterModel) GetDataSourceAttributes() map[string]schema.Attribute { + return BranchRepeaterFilterModel{}.GetDataSourceSchema().Attributes +} + +func (ClientIPFilterModel) GetDataSourceSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Id of the client ip policy filter.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Computed: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Computed: true, + }, + "ip_address": schema.StringAttribute{ + Description: "IP Address of the client to be filtered.", + Computed: true, + }, + }, + } +} + +func (ClientIPFilterModel) GetDataSourceAttributes() map[string]schema.Attribute { + return ClientIPFilterModel{}.GetDataSourceSchema().Attributes +} + +func (ClientNameFilterModel) GetDataSourceSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Id of the client name policy filter.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Computed: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Computed: true, + }, + "client_name": schema.StringAttribute{ + Description: "Name of the client to be filtered.", + Computed: true, + }, + }, + } +} + +func (ClientNameFilterModel) GetDataSourceAttributes() map[string]schema.Attribute { + return ClientNameFilterModel{}.GetDataSourceSchema().Attributes +} + +func (DeliveryGroupFilterModel) GetDataSourceSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Id of the delivery group policy filter.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Computed: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Computed: true, + }, + "delivery_group_id": schema.StringAttribute{ + Description: "Id of the delivery group to be filtered.", + Computed: true, + }, + }, + } +} + +func (DeliveryGroupFilterModel) GetDataSourceAttributes() map[string]schema.Attribute { + return DeliveryGroupFilterModel{}.GetDataSourceSchema().Attributes +} + +func (DeliveryGroupTypeFilterModel) GetDataSourceSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Id of the delivery group type policy filter.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Computed: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Computed: true, + }, + "delivery_group_type": schema.StringAttribute{ + Description: "Type of the delivery groups to be filtered.", + Computed: true, + }, + }, + } +} + +func (DeliveryGroupTypeFilterModel) GetDataSourceAttributes() map[string]schema.Attribute { + return DeliveryGroupTypeFilterModel{}.GetDataSourceSchema().Attributes +} + +func (OuFilterModel) GetDataSourceSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Id of the organizational unit policy filter.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Computed: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Computed: true, + }, + "ou": schema.StringAttribute{ + Description: "Organizational Unit to be filtered.", + Computed: true, + }, + }, + } +} + +func (OuFilterModel) GetDataSourceAttributes() map[string]schema.Attribute { + return OuFilterModel{}.GetDataSourceSchema().Attributes +} + +func (UserFilterModel) GetDataSourceSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Id of the user policy filter.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Computed: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Computed: true, + }, + "sid": schema.StringAttribute{ + Description: "SID of the user or user group to be filtered.", + Computed: true, + }, + }, + } +} + +func (UserFilterModel) GetDataSourceAttributes() map[string]schema.Attribute { + return UserFilterModel{}.GetDataSourceSchema().Attributes +} + +func (TagFilterModel) GetDataSourceSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Id of the tag policy filter.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Computed: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Computed: true, + }, + "tag": schema.StringAttribute{ + Description: "Tag to be filtered.", + Computed: true, + }, + }, + } +} + +func (TagFilterModel) GetDataSourceAttributes() map[string]schema.Attribute { + return TagFilterModel{}.GetDataSourceSchema().Attributes +} diff --git a/internal/daas/policies/policy_set_data_source.go b/internal/daas/policies/policy_set_data_source.go new file mode 100644 index 0000000..5822f6e --- /dev/null +++ b/internal/daas/policies/policy_set_data_source.go @@ -0,0 +1,103 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package policies + +import ( + "context" + "strings" + + citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" + citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" + "github.com/citrix/terraform-provider-citrix/internal/util" + + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var ( + _ datasource.DataSource = &PolicySetDataSource{} +) + +func NewPolicySetDataSource() datasource.DataSource { + return &PolicySetDataSource{} +} + +// PolicySetDataSource defines the data source implementation. +type PolicySetDataSource struct { + client *citrixdaasclient.CitrixDaasClient +} + +func (d *PolicySetDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_policy_set" +} + +func (d *PolicySetDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = PolicySetModel{}.GetDataSourceSchema() +} + +func (d *PolicySetDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*citrixdaasclient.CitrixDaasClient) +} + +func (d *PolicySetDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + if d.client != nil && d.client.ApiClient == nil { + resp.Diagnostics.AddError(util.ProviderInitializationErrorMsg, util.MissingProviderClientIdAndSecretErrorMsg) + return + } + + var data PolicySetModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + var policySet *citrixorchestration.PolicySetResponse + var err error + if !data.Id.IsNull() { + // Get refreshed policy set from Orchestration + policySet, err = getPolicySet(ctx, d.client, &resp.Diagnostics, data.Id.ValueString()) + if err != nil { + return + } + } else { + policySets, err := getPolicySets(ctx, d.client, &resp.Diagnostics) + if err != nil { + return + } + for _, remotePolicySet := range policySets { + if strings.EqualFold(data.Name.ValueString(), remotePolicySet.GetName()) { + policySet = &remotePolicySet + break + } + } + if policySet == nil { + resp.Diagnostics.AddError("Policy Set not found", "Policy Set with name "+data.Name.ValueString()+" not found.") + return + } + } + + policies, err := getPolicies(ctx, d.client, &resp.Diagnostics, policySet.GetPolicySetGuid()) + if err != nil { + return + } + + policySetScopes, err := util.FetchScopeIdsByNames(ctx, resp.Diagnostics, d.client, policySet.GetScopes()) + if err != nil { + return + } + + data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, false, policySet, policies, policySetScopes) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/daas/policies/policy_set_data_source_model.go b/internal/daas/policies/policy_set_data_source_model.go new file mode 100644 index 0000000..a0b3fd6 --- /dev/null +++ b/internal/daas/policies/policy_set_data_source_model.go @@ -0,0 +1,163 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package policies + +import ( + "regexp" + + "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (PolicySettingModel) GetDataSourceSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "Name of the policy setting.", + Computed: true, + }, + "use_default": schema.BoolAttribute{ + Description: "Indicate whether using default value for the policy setting.", + Computed: true, + }, + "value": schema.StringAttribute{ + Description: "Value of the policy setting.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Whether of the policy setting has enabled or allowed value.", + Computed: true, + }, + }, + } +} + +func (PolicySettingModel) GetDataSourceAttributes() map[string]schema.Attribute { + return PolicySettingModel{}.GetDataSourceSchema().Attributes +} + +func (PolicyModel) GetDataSourceSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Id of the policy.", + Computed: true, + }, + "name": schema.StringAttribute{ + Description: "Name of the policy.", + Computed: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the policy.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the policy is being enabled.", + Computed: true, + }, + "policy_settings": schema.SetNestedAttribute{ + Description: "Set of policy settings.", + Computed: true, + NestedObject: PolicySettingModel{}.GetDataSourceSchema(), + }, + "access_control_filters": schema.SetNestedAttribute{ + Description: "Set of Access control policy filters.", + Computed: true, + NestedObject: AccessControlFilterModel{}.GetDataSourceSchema(), + }, + "branch_repeater_filter": BranchRepeaterFilterModel{}.GetDataSourceSchema(), + "client_ip_filters": schema.SetNestedAttribute{ + Description: "Set of Client ip policy filters.", + Computed: true, + NestedObject: ClientIPFilterModel{}.GetDataSourceSchema(), + }, + "client_name_filters": schema.SetNestedAttribute{ + Description: "Set of Client name policy filters.", + Computed: true, + NestedObject: ClientNameFilterModel{}.GetDataSourceSchema(), + }, + "delivery_group_filters": schema.SetNestedAttribute{ + Description: "Set of Delivery group policy filters.", + Computed: true, + NestedObject: DeliveryGroupFilterModel{}.GetDataSourceSchema(), + }, + "delivery_group_type_filters": schema.SetNestedAttribute{ + Description: "Set of Delivery group type policy filters.", + Computed: true, + NestedObject: DeliveryGroupTypeFilterModel{}.GetDataSourceSchema(), + }, + "ou_filters": schema.SetNestedAttribute{ + Description: "Set of Organizational unit policy filters.", + Computed: true, + NestedObject: OuFilterModel{}.GetDataSourceSchema(), + }, + "user_filters": schema.SetNestedAttribute{ + Description: "Set of User policy filters.", + Computed: true, + NestedObject: UserFilterModel{}.GetDataSourceSchema(), + }, + "tag_filters": schema.SetNestedAttribute{ + Description: "Set of Tag policy filters.", + Computed: true, + NestedObject: TagFilterModel{}.GetDataSourceSchema(), + }, + }, + } +} + +func (PolicyModel) GetDataSourceAttributes() map[string]schema.Attribute { + return PolicyModel{}.GetDataSourceSchema().Attributes +} + +func (PolicySetModel) GetDataSourceSchema() schema.Schema { + return schema.Schema{ + Description: "CVAD --- Data source of a policy set and the policies within it. The order of the policies in this data source reflects the policy priority.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the policy set.", + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the policy set.", + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "type": schema.StringAttribute{ + Description: "Type of the policy set. Type can be one of `SitePolicies`, `DeliveryGroupPolicies`, `SiteTemplates`, or `CustomTemplates`.", + Computed: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the policy set.", + Computed: true, + }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The IDs of the scopes for the policy set to be a part of.", + Computed: true, + }, + "policies": schema.ListNestedAttribute{ + Description: "Ordered list of policies. \n\n-> **Note** The order of policies in the list determines the priority of the policies.", + Computed: true, + NestedObject: PolicyModel{}.GetDataSourceSchema(), + }, + "assigned": schema.BoolAttribute{ + Description: "Indicate whether the policy set is being assigned to delivery groups.", + Computed: true, + }, + }, + } +} + +func (PolicySetModel) GetDataSourceAttributes() map[string]schema.Attribute { + return PolicySetModel{}.GetDataSourceSchema().Attributes +} diff --git a/internal/daas/policies/policy_set_resource.go b/internal/daas/policies/policy_set_resource.go index 1bbdd7b..e921fa9 100644 --- a/internal/daas/policies/policy_set_resource.go +++ b/internal/daas/policies/policy_set_resource.go @@ -59,7 +59,7 @@ func (r *policySetResource) ModifyPlan(ctx context.Context, req resource.ModifyP } // Retrieve values from plan - var plan PolicySetResourceModel + var plan PolicySetModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -69,7 +69,7 @@ func (r *policySetResource) ModifyPlan(ctx context.Context, req resource.ModifyP // Validate DDC Version errorSummary := fmt.Sprintf("Error %s Policy Set", operation) feature := "Policy Set resource" - isDdcVersionSupported := util.CheckProductVersion(r.client, &resp.Diagnostics, 120, 7, 41, errorSummary, feature) + isDdcVersionSupported := util.CheckProductVersion(r.client, &resp.Diagnostics, 120, 118, 7, 41, errorSummary, feature) if !isDdcVersionSupported { return @@ -108,8 +108,18 @@ func (r *policySetResource) ModifyPlan(ctx context.Context, req resource.ModifyP for _, policy := range plannedPolicies { policyContainsUserSetting := false + existingPolicySettingNames := map[string]bool{} policySettings := util.ObjectSetToTypedArray[PolicySettingModel](ctx, &resp.Diagnostics, policy.PolicySettings) for _, setting := range policySettings { + if existingPolicySettingNames[setting.Name.ValueString()] { + resp.Diagnostics.AddError( + "Error "+operation+" Policy Set", + "Each type of policy settings can only be specified once in the same group policy.", + ) + return + } else { + existingPolicySettingNames[setting.Name.ValueString()] = true + } if slices.ContainsFunc(userSettings, func(userSetting string) bool { return strings.EqualFold(userSetting, setting.Name.ValueString()) }) { @@ -194,7 +204,7 @@ func (r *policySetResource) Configure(_ context.Context, req resource.ConfigureR // Schema implements resource.Resource. func (*policySetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = PolicySetResourceModel{}.GetSchema() + resp.Schema = PolicySetModel{}.GetSchema() } // Create implements resource.Resource. @@ -202,7 +212,7 @@ func (r *policySetResource) Create(ctx context.Context, req resource.CreateReque defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan PolicySetResourceModel + var plan PolicySetModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -316,7 +326,7 @@ func (r *policySetResource) Create(ctx context.Context, req resource.CreateReque } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, policySet, policies, policySetScopes) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, true, policySet, policies, policySetScopes) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -330,7 +340,7 @@ func (r *policySetResource) Create(ctx context.Context, req resource.CreateReque func (r *policySetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { defer util.PanicHandler(&resp.Diagnostics) - var state PolicySetResourceModel + var state PolicySetModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -353,7 +363,7 @@ func (r *policySetResource) Read(ctx context.Context, req resource.ReadRequest, return } - state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, policySet, policies, policySetScopes) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, true, policySet, policies, policySetScopes) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -368,14 +378,14 @@ func (r *policySetResource) Update(ctx context.Context, req resource.UpdateReque defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan PolicySetResourceModel + var plan PolicySetModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - var state PolicySetResourceModel + var state PolicySetModel diags = req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -422,15 +432,16 @@ func (r *policySetResource) Update(ctx context.Context, req resource.UpdateReque } policiesInPlan := util.ObjectListToTypedArray[PolicyModel](ctx, &resp.Diagnostics, plan.Policies) - policyIdsInPlan := []string{} policiesToCreate := []PolicyModel{} policiesToUpdate := []PolicyModel{} + policiesWithUpdatedNames := []PolicyModel{} + policyIdMapInPlan := map[string]PolicyModel{} for _, policy := range policiesInPlan { if policy.Id.ValueString() == "" { policiesToCreate = append(policiesToCreate, policy) } else { - policyIdsInPlan = append(policyIdsInPlan, policy.Id.ValueString()) policiesToUpdate = append(policiesToUpdate, policy) + policyIdMapInPlan[policy.Id.ValueString()] = policy } } @@ -439,20 +450,21 @@ func (r *policySetResource) Update(ctx context.Context, req resource.UpdateReque for _, policy := range util.ObjectListToTypedArray[PolicyModel](ctx, &resp.Diagnostics, state.Policies) { policyIdMapFromState[strings.ToLower(policy.Id.ValueString())] = policy policyIdsInState = append(policyIdsInState, policy.Id.ValueString()) + if policyInPlan, ok := policyIdMapInPlan[policy.Id.ValueString()]; ok && policy.Name.ValueString() != policyInPlan.Name.ValueString() { + policiesWithUpdatedNames = append(policiesWithUpdatedNames, policy) + } } policyIdsToDelete := []string{} // Check if any policies are to be deleted for _, policyId := range policyIdsInState { - if !slices.ContainsFunc(policyIdsInPlan, func(policyIdInPlan string) bool { - return strings.EqualFold(policyId, policyIdInPlan) - }) { + if _, ok := policyIdMapInPlan[policyId]; !ok { policyIdsToDelete = append(policyIdsToDelete, policyId) } } // Rename policies to update with their policy id to avoid naming collision - if len(policiesToUpdate) > 0 { + if len(policiesWithUpdatedNames) > 0 { batchApiHeaders, httpResp, err := generateBatchApiHeaders(r.client) if err != nil { diags.AddError( @@ -464,7 +476,7 @@ func (r *policySetResource) Update(ctx context.Context, req resource.UpdateReque } batchRequestItems := []citrixorchestration.BatchRequestItemModel{} var batchRequestModel citrixorchestration.BatchRequestModel - for policyIndex, policy := range policiesToUpdate { + for policyIndex, policy := range policiesWithUpdatedNames { var updatePolicyRequest = citrixorchestration.PolicyRequest{} updatePolicyRequest.SetName(policy.Id.ValueString()) updatePolicyRequestBodyString, err := util.ConvertToString(updatePolicyRequest) @@ -780,7 +792,7 @@ func (r *policySetResource) Update(ctx context.Context, req resource.UpdateReque } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, policySet, policies, policySetScopes) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, true, policySet, policies, policySetScopes) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -795,7 +807,7 @@ func (r *policySetResource) Delete(ctx context.Context, req resource.DeleteReque defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from state - var state PolicySetResourceModel + var state PolicySetModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -905,7 +917,7 @@ func (r *policySetResource) ImportState(ctx context.Context, req resource.Import func (r *policySetResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { defer util.PanicHandler(&resp.Diagnostics) - var data PolicySetResourceModel + var data PolicySetModel diags := req.Config.Get(ctx, &data) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/internal/daas/policies/policy_set_resource_model.go b/internal/daas/policies/policy_set_resource_model.go index 01d5b04..2e51889 100644 --- a/internal/daas/policies/policy_set_resource_model.go +++ b/internal/daas/policies/policy_set_resource_model.go @@ -202,7 +202,7 @@ func (PolicyModel) GetAttributes() map[string]schema.Attribute { return PolicyModel{}.GetSchema().Attributes } -type PolicySetResourceModel struct { +type PolicySetModel struct { Id types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` Type types.String `tfsdk:"type"` @@ -212,7 +212,7 @@ type PolicySetResourceModel struct { Policies types.List `tfsdk:"policies"` // List[PolicyModel] } -func (PolicySetResourceModel) GetSchema() schema.Schema { +func (PolicySetModel) GetSchema() schema.Schema { return schema.Schema{ Description: "CVAD --- Manages a policy set and the policies within it. The order of the policies specified in this resource reflect the policy priority.", Attributes: map[string]schema.Attribute{ @@ -276,11 +276,11 @@ func (PolicySetResourceModel) GetSchema() schema.Schema { } } -func (PolicySetResourceModel) GetAttributes() map[string]schema.Attribute { - return PolicySetResourceModel{}.GetSchema().Attributes +func (PolicySetModel) GetAttributes() map[string]schema.Attribute { + return PolicySetModel{}.GetSchema().Attributes } -func (r PolicySetResourceModel) RefreshPropertyValues(ctx context.Context, diags *diag.Diagnostics, policySet *citrixorchestration.PolicySetResponse, policies *citrixorchestration.CollectionEnvelopeOfPolicyResponse, policySetScopes []string) PolicySetResourceModel { +func (r PolicySetModel) RefreshPropertyValues(ctx context.Context, diags *diag.Diagnostics, isResource bool, policySet *citrixorchestration.PolicySetResponse, policies *citrixorchestration.CollectionEnvelopeOfPolicyResponse, policySetScopes []string) PolicySetModel { // Set required values r.Id = types.StringValue(policySet.GetPolicySetGuid()) r.Name = types.StringValue(policySet.GetName()) @@ -339,12 +339,21 @@ func (r PolicySetResourceModel) RefreshPropertyValues(ctx context.Context, diags } } - policyModel.PolicySettings = util.TypedArrayToObjectSet(ctx, diags, refreshedPolicySettings) + if isResource { + policyModel.PolicySettings = util.TypedArrayToObjectSet(ctx, diags, refreshedPolicySettings) + } else { + policyModel.PolicySettings = util.DataSourceTypedArrayToObjectSet(ctx, diags, refreshedPolicySettings) + } var accessControlFilters []AccessControlFilterModel - attributes, _ := util.AttributeMapFromObject(BranchRepeaterFilterModel{}) - policyModel.BranchRepeaterFilter = types.ObjectNull(attributes) + if isResource { + attributes, _ := util.ResourceAttributeMapFromObject(BranchRepeaterFilterModel{}) + policyModel.BranchRepeaterFilter = types.ObjectNull(attributes) + } else { + attributes, _ := util.DataSourceAttributeMapFromObject(BranchRepeaterFilterModel{}) + policyModel.BranchRepeaterFilter = types.ObjectNull(attributes) + } var clientIpFilters []ClientIPFilterModel var clientNameFilters []ClientNameFilterModel @@ -430,25 +439,49 @@ func (r PolicySetResourceModel) RefreshPropertyValues(ctx context.Context, diags } } } - policyModel.AccessControlFilters = util.TypedArrayToObjectSet(ctx, diags, accessControlFilters) - policyModel.ClientIPFilters = util.TypedArrayToObjectSet(ctx, diags, clientIpFilters) - policyModel.ClientNameFilters = util.TypedArrayToObjectSet(ctx, diags, clientNameFilters) - policyModel.DeliveryGroupFilters = util.TypedArrayToObjectSet(ctx, diags, desktopGroupFilters) - policyModel.DeliveryGroupTypeFilters = util.TypedArrayToObjectSet(ctx, diags, desktopKindFilters) - policyModel.TagFilters = util.TypedArrayToObjectSet(ctx, diags, desktopTagFilters) - policyModel.OuFilters = util.TypedArrayToObjectSet(ctx, diags, ouFilters) - policyModel.UserFilters = util.TypedArrayToObjectSet(ctx, diags, userFilters) + if isResource { + policyModel.AccessControlFilters = util.TypedArrayToObjectSet(ctx, diags, accessControlFilters) + policyModel.ClientIPFilters = util.TypedArrayToObjectSet(ctx, diags, clientIpFilters) + policyModel.ClientNameFilters = util.TypedArrayToObjectSet(ctx, diags, clientNameFilters) + policyModel.DeliveryGroupFilters = util.TypedArrayToObjectSet(ctx, diags, desktopGroupFilters) + policyModel.DeliveryGroupTypeFilters = util.TypedArrayToObjectSet(ctx, diags, desktopKindFilters) + policyModel.TagFilters = util.TypedArrayToObjectSet(ctx, diags, desktopTagFilters) + policyModel.OuFilters = util.TypedArrayToObjectSet(ctx, diags, ouFilters) + policyModel.UserFilters = util.TypedArrayToObjectSet(ctx, diags, userFilters) + } else { + policyModel.AccessControlFilters = util.DataSourceTypedArrayToObjectSet(ctx, diags, accessControlFilters) + policyModel.ClientIPFilters = util.DataSourceTypedArrayToObjectSet(ctx, diags, clientIpFilters) + policyModel.ClientNameFilters = util.DataSourceTypedArrayToObjectSet(ctx, diags, clientNameFilters) + policyModel.DeliveryGroupFilters = util.DataSourceTypedArrayToObjectSet(ctx, diags, desktopGroupFilters) + policyModel.DeliveryGroupTypeFilters = util.DataSourceTypedArrayToObjectSet(ctx, diags, desktopKindFilters) + policyModel.TagFilters = util.DataSourceTypedArrayToObjectSet(ctx, diags, desktopTagFilters) + policyModel.OuFilters = util.DataSourceTypedArrayToObjectSet(ctx, diags, ouFilters) + policyModel.UserFilters = util.DataSourceTypedArrayToObjectSet(ctx, diags, userFilters) + } refreshedPolicies = append(refreshedPolicies, policyModel) } - updatedPolicies := util.TypedArrayToObjectList(ctx, diags, refreshedPolicies) + var updatedPolicies types.List + if isResource { + updatedPolicies = util.TypedArrayToObjectList(ctx, diags, refreshedPolicies) + } else { + updatedPolicies = util.DataSourceTypedArrayToObjectList(ctx, diags, refreshedPolicies) + } r.Policies = updatedPolicies } else { - attributesMap, err := util.AttributeMapFromObject(PolicyModel{}) - if err != nil { - diags.AddError("Error converting schema to attribute map. Error: ", err.Error()) + var attributesMap map[string]attr.Type + var err error + if isResource { + attributesMap, err = util.ResourceAttributeMapFromObject(PolicyModel{}) + if err != nil { + diags.AddError("Error converting schema to attribute map. Error: ", err.Error()) + } + } else { + attributesMap, err = util.DataSourceAttributeMapFromObject(PolicyModel{}) + if err != nil { + diags.AddError("Error converting schema to attribute map. Error: ", err.Error()) + } } - r.Policies = types.ListNull(types.ObjectType{AttrTypes: attributesMap}) } diff --git a/internal/daas/policies/policy_set_utils.go b/internal/daas/policies/policy_set_utils.go index 052e38d..612cc16 100644 --- a/internal/daas/policies/policy_set_utils.go +++ b/internal/daas/policies/policy_set_utils.go @@ -311,17 +311,53 @@ func constructSettingRequest(policySetting PolicySettingModel) citrixorchestrati } func updatePolicySettings(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, policyId string, policyName string, policySettingsInPlan []PolicySettingModel, policySettingsInState []PolicySettingModel) error { + // Detect deleted settings + policySettingsToDelete := []PolicySettingModel{} + for _, policySetting := range policySettingsInState { + if !slices.ContainsFunc(policySettingsInPlan, func(policySettingInPlan PolicySettingModel) bool { + return strings.EqualFold(policySetting.Name.ValueString(), policySettingInPlan.Name.ValueString()) + }) { + policySettingsToDelete = append(policySettingsToDelete, policySetting) + } + } + + policySettingsToCreate := []PolicySettingModel{} + policySettingsToUpdate := []PolicySettingModel{} + for _, policySetting := range policySettingsInPlan { + policyInStateIndex := slices.IndexFunc(policySettingsInState, func(policySettingInState PolicySettingModel) bool { + return strings.EqualFold(policySetting.Name.ValueString(), policySettingInState.Name.ValueString()) + }) + if policyInStateIndex != -1 { + policySettingInState := policySettingsInState[policyInStateIndex] + if policySetting.Enabled.ValueBool() != policySettingInState.Enabled.ValueBool() || + policySetting.Value.ValueString() != policySettingInState.Value.ValueString() || + policySetting.UseDefault.ValueBool() != policySettingInState.UseDefault.ValueBool() { + policySettingsToUpdate = append(policySettingsToUpdate, policySetting) + } + } else { + policySettingsToCreate = append(policySettingsToCreate, policySetting) + } + } + // Delete policy settings - if len(policySettingsInState) > 0 { - err := deletePolicySettings(ctx, client, diagnostics, policyId) + if len(policySettingsToDelete) > 0 { + err := deletePolicySettings(ctx, client, diagnostics, policyId, policySettingsToDelete) if err != nil { return err } } // Create policy settings - if len(policySettingsInPlan) > 0 { - err := createPolicySettings(ctx, client, diagnostics, policyId, policyName, policySettingsInPlan) + if len(policySettingsToCreate) > 0 { + err := createPolicySettings(ctx, client, diagnostics, policyId, policyName, policySettingsToCreate) + if err != nil { + return err + } + } + + // Update policy settings + if len(policySettingsToUpdate) > 0 { + err := updatePolicySettingDetails(ctx, client, diagnostics, policyId, policyName, policySettingsToUpdate) if err != nil { return err } @@ -388,7 +424,84 @@ func createPolicySettings(ctx context.Context, client *citrixdaasclient.CitrixDa return nil } -func deletePolicySettings(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, policyId string) error { +func updatePolicySettingDetails(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, policyId string, policyName string, policySettingsToUpdate []PolicySettingModel) error { + // Batch create new policy settings + updatePolicySettingBatchRequestItems := []citrixorchestration.BatchRequestItemModel{} + batchApiHeaders, httpResp, err := generateBatchApiHeaders(client) + if err != nil { + diagnostics.AddError( + "Error updating policy settings in policy "+policyName, + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nCould not update policy settings in the policy, unexpected error: "+util.ReadClientError(err), + ) + return err + } + + policySettings, err := getPolicySettings(ctx, client, diagnostics, policyId) + if err != nil { + return err + } + + policySettingIdMap := map[string]PolicySettingModel{} + for _, policySettingInPlan := range policySettingsToUpdate { + for _, policySettingInRemote := range policySettings.GetItems() { + if strings.EqualFold(policySettingInPlan.Name.ValueString(), policySettingInRemote.GetSettingName()) { + policySettingIdMap[policySettingInRemote.GetSettingGuid()] = policySettingInPlan + continue + } + } + } + + // Update policy settings + policySettingUpdateCounter := 0 + for id, policySetting := range policySettingIdMap { + relativeUrl := fmt.Sprintf("/gpo/settings/%s", id) + + settingRequest := constructSettingRequest(policySetting) + settingRequestStringBody, err := util.ConvertToString(settingRequest) + if err != nil { + diagnostics.AddError( + "Error updating policy setting in policy "+policyName, + "An unexpected error occurred: "+err.Error(), + ) + return err + } + + var batchRequestItem citrixorchestration.BatchRequestItemModel + batchRequestItem.SetReference(fmt.Sprintf("updatePolicySetting%s", strconv.Itoa(policySettingUpdateCounter))) + batchRequestItem.SetMethod(http.MethodPatch) + batchRequestItem.SetRelativeUrl(client.GetBatchRequestItemRelativeUrl(relativeUrl)) + batchRequestItem.SetHeaders(batchApiHeaders) + batchRequestItem.SetBody(settingRequestStringBody) + updatePolicySettingBatchRequestItems = append(updatePolicySettingBatchRequestItems, batchRequestItem) + policySettingUpdateCounter++ + } + + var batchRequestModel citrixorchestration.BatchRequestModel + batchRequestModel.SetItems(updatePolicySettingBatchRequestItems) + successfulJobs, txId, err := citrixdaasclient.PerformBatchOperation(ctx, client, batchRequestModel) + if err != nil { + diagnostics.AddError( + "Error updating Policy Settings in Policy "+policyName, + "TransactionId: "+txId+ + "\nError message: "+util.ReadClientError(err), + ) + return err + } + + if successfulJobs < len(updatePolicySettingBatchRequestItems) { + errMsg := fmt.Sprintf("An error occurred while updating policy settings to the Policy. %d of %d policy settings were updated.", successfulJobs, len(updatePolicySettingBatchRequestItems)) + diagnostics.AddError( + "Error updating policy settings in Policy "+policyName, + "TransactionId: "+txId+ + "\n"+errMsg, + ) + return err + } + return nil +} + +func deletePolicySettings(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, policyId string, policySettingsToDelete []PolicySettingModel) error { // Setup batch requests deletePolicySettingBatchRequestItems := []citrixorchestration.BatchRequestItemModel{} batchApiHeaders, httpResp, err := generateBatchApiHeaders(client) @@ -405,9 +518,19 @@ func deletePolicySettings(ctx context.Context, client *citrixdaasclient.CitrixDa if err != nil { return err } + + policySettingIdsToDelete := []string{} + for _, policySetting := range policySettings.GetItems() { + if slices.ContainsFunc(policySettingsToDelete, func(policySettingToDelete PolicySettingModel) bool { + return strings.EqualFold(policySetting.GetSettingName(), policySettingToDelete.Name.ValueString()) + }) { + policySettingIdsToDelete = append(policySettingIdsToDelete, policySetting.GetSettingGuid()) + } + } + // batch delete policy settings - for index, policySetting := range policySettings.GetItems() { - relativeUrl := fmt.Sprintf("/gpo/settings/%s", policySetting.GetSettingGuid()) + for index, policySettingId := range policySettingIdsToDelete { + relativeUrl := fmt.Sprintf("/gpo/settings/%s", policySettingId) var batchRequestItem citrixorchestration.BatchRequestItemModel batchRequestItem.SetReference(fmt.Sprintf("removeSetting%s", strconv.Itoa(index))) diff --git a/internal/daas/storefront_server/storefront_server_data_source.go b/internal/daas/storefront_server/storefront_server_data_source.go new file mode 100644 index 0000000..6f0e29f --- /dev/null +++ b/internal/daas/storefront_server/storefront_server_data_source.go @@ -0,0 +1,79 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package storefront_server + +import ( + "context" + + citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" + "github.com/citrix/terraform-provider-citrix/internal/util" + + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +var ( + _ datasource.DataSource = &StoreFrontServerDataSource{} +) + +func NewStoreFrontServerDataSource() datasource.DataSource { + return &StoreFrontServerDataSource{} +} + +type StoreFrontServerDataSource struct { + client *citrixdaasclient.CitrixDaasClient +} + +func (d *StoreFrontServerDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_storefront_server" +} + +func (d *StoreFrontServerDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = StoreFrontServerResourceModel{}.GetDataSourceSchema() +} + +func (d *StoreFrontServerDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*citrixdaasclient.CitrixDaasClient) +} + +func (d *StoreFrontServerDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + if d.client != nil && d.client.ApiClient == nil { + resp.Diagnostics.AddError(util.ProviderInitializationErrorMsg, util.MissingProviderClientIdAndSecretErrorMsg) + return + } + + var data StoreFrontServerResourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Read the data from the API + var storeFrontServerNameOrId string + + if data.Id.ValueString() != "" { + storeFrontServerNameOrId = data.Id.ValueString() + } else if data.Name.ValueString() != "" { + storeFrontServerNameOrId = data.Name.ValueString() + } + + // Try getting the new StoreFront server with StoreFront server name + storeFrontServer, _, err := getStoreFrontServer(ctx, d.client, storeFrontServerNameOrId) + if err != nil { + return + } + + data = data.RefreshPropertyValues(storeFrontServer) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/daas/storefront_server/storefront_server_data_source_model.go b/internal/daas/storefront_server/storefront_server_data_source_model.go new file mode 100644 index 0000000..d90b4d8 --- /dev/null +++ b/internal/daas/storefront_server/storefront_server_data_source_model.go @@ -0,0 +1,45 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package storefront_server + +import ( + "regexp" + + "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func (StoreFrontServerResourceModel) GetDataSourceSchema() schema.Schema { + return schema.Schema{ + Description: "CVAD --- Data source of a StoreFront server.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the StoreFront server.", + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the StoreFront server.", + Optional: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the StoreFront server.", + Computed: true, + }, + "url": schema.StringAttribute{ + Description: "URL for connecting to the StoreFront server.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Indicates if the StoreFront server is enabled. Default is `true`.", + Computed: true, + }, + }, + } +} diff --git a/internal/daas/storefront_server/storefront_server_resource.go b/internal/daas/storefront_server/storefront_server_resource.go index ff09480..aa5598c 100644 --- a/internal/daas/storefront_server/storefront_server_resource.go +++ b/internal/daas/storefront_server/storefront_server_resource.go @@ -12,10 +12,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" ) // Ensure the implementation satisfies the expected interfaces. @@ -42,36 +38,7 @@ func (r *storeFrontServerResource) Metadata(_ context.Context, req resource.Meta // Schema defines the schema for the resource. func (r *storeFrontServerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "CVAD --- Manages a StoreFront server.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the StoreFront server.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the StoreFront server.", - Required: true, - }, - "description": schema.StringAttribute{ - Description: "Description of the StoreFront server.", - Required: true, - }, - "url": schema.StringAttribute{ - Description: "URL for connecting to the StoreFront server.", - Required: true, - }, - "enabled": schema.BoolAttribute{ - Description: "Indicates if the StoreFront server is enabled. Default is `true`.", - Optional: true, - Computed: true, - Default: booldefault.StaticBool(true), - }, - }, - } + resp.Schema = StoreFrontServerResourceModel{}.GetSchema() } // Configure adds the provider configured client to the resource. diff --git a/internal/daas/storefront_server/storefront_server_resource_model.go b/internal/daas/storefront_server/storefront_server_resource_model.go index b755c78..092823d 100644 --- a/internal/daas/storefront_server/storefront_server_resource_model.go +++ b/internal/daas/storefront_server/storefront_server_resource_model.go @@ -5,6 +5,10 @@ package storefront_server import ( citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -17,6 +21,39 @@ type StoreFrontServerResourceModel struct { Enabled types.Bool `tfsdk:"enabled"` } +func (StoreFrontServerResourceModel) GetSchema() schema.Schema { + return schema.Schema{ + Description: "CVAD --- Manages a StoreFront server.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the StoreFront server.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the StoreFront server.", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the StoreFront server.", + Required: true, + }, + "url": schema.StringAttribute{ + Description: "URL for connecting to the StoreFront server.", + Required: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Indicates if the StoreFront server is enabled. Default is `true`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + }, + } +} + func (r StoreFrontServerResourceModel) RefreshPropertyValues(sfServer *citrixorchestration.StoreFrontServerResponseModel) StoreFrontServerResourceModel { // Overwrite StoreFront server with refreshed state r.Id = types.StringValue(sfServer.GetId()) diff --git a/internal/daas/tags/tag_data_source.go b/internal/daas/tags/tag_data_source.go index 88ecb54..38c88de 100644 --- a/internal/daas/tags/tag_data_source.go +++ b/internal/daas/tags/tag_data_source.go @@ -57,10 +57,9 @@ func (d *TagDataSource) Read(ctx context.Context, req datasource.ReadRequest, re // Read the data from the API var tagNameOrId string - if data.Id.ValueString() != "" { + if !data.Id.IsNull() { tagNameOrId = data.Id.ValueString() - } - if data.Name.ValueString() != "" { + } else { tagNameOrId = data.Name.ValueString() } diff --git a/internal/daas/tags/tag_data_source_model.go b/internal/daas/tags/tag_data_source_model.go index 160850a..191c8ab 100644 --- a/internal/daas/tags/tag_data_source_model.go +++ b/internal/daas/tags/tag_data_source_model.go @@ -35,11 +35,15 @@ func (TagDataSourceModel) GetSchema() schema.Schema { Optional: true, Validators: []validator.String{ stringvalidator.ExactlyOneOf(path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. + stringvalidator.LengthAtLeast(1), }, }, "name": schema.StringAttribute{ Description: "Name of the tag.", Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, }, "description": schema.StringAttribute{ Description: "Description of the tag.", diff --git a/internal/daas/vda/vda_data_source_model.go b/internal/daas/vda/vda_data_source_model.go index ebb1387..c1bca4a 100644 --- a/internal/daas/vda/vda_data_source_model.go +++ b/internal/daas/vda/vda_data_source_model.go @@ -46,6 +46,7 @@ func (VdaDataSourceModel) GetSchema() schema.Schema { // VdaModel defines the single VDA data model implementation. type VdaModel struct { + Id types.String `tfsdk:"id"` MachineName types.String `tfsdk:"machine_name"` HostedMachineId types.String `tfsdk:"hosted_machine_id"` AssociatedMachineCatalog types.String `tfsdk:"associated_machine_catalog"` @@ -55,6 +56,10 @@ type VdaModel struct { func (VdaModel) GetSchema() schema.NestedAttributeObject { return schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Id of the VDA.", + Computed: true, + }, "machine_name": schema.StringAttribute{ Description: "Machine name of the VDA.", Computed: true, @@ -88,6 +93,7 @@ func (r VdaDataSourceModel) RefreshPropertyValues(vdas *citrixorchestration.Mach deliveryGroupId := deliveryGroup.GetId() res = append(res, VdaModel{ + Id: types.StringValue(model.GetId()), MachineName: types.StringValue(machineName), HostedMachineId: types.StringValue(hostedMachineId), AssociatedMachineCatalog: types.StringValue(machineCatalogId), diff --git a/internal/daas/zone/zone_resource_model.go b/internal/daas/zone/zone_resource_model.go index 84a7306..008d7cc 100644 --- a/internal/daas/zone/zone_resource_model.go +++ b/internal/daas/zone/zone_resource_model.go @@ -50,7 +50,7 @@ func (ZoneResourceModel) GetSchema() schema.Schema { Optional: true, Computed: true, Validators: []validator.String{ - stringvalidator.ExactlyOneOf(path.MatchRoot("name"), path.MatchRoot("resource_location_id")), + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), }, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), diff --git a/internal/examples/data-sources/citrix_admin_role/data-source.tf b/internal/examples/data-sources/citrix_admin_role/data-source.tf new file mode 100644 index 0000000..9ff4d42 --- /dev/null +++ b/internal/examples/data-sources/citrix_admin_role/data-source.tf @@ -0,0 +1,9 @@ +# Get Admin Scope resource by name +data "citrix_admin_role" "example_admin_role" { + name = "ExampleAdminRole" +} + +# Get Admin Scope resource by id +data "citrix_admin_role" "example_admin_role" { + id = "00000000-0000-0000-0000-000000000000" +} \ No newline at end of file diff --git a/internal/examples/data-sources/citrix_admin_user/data-source.tf b/internal/examples/data-sources/citrix_admin_user/data-source.tf new file mode 100644 index 0000000..e3774a6 --- /dev/null +++ b/internal/examples/data-sources/citrix_admin_user/data-source.tf @@ -0,0 +1,4 @@ +# Get Admin Scope resource by id +data "citrix_admin_user" "example_admin_user" { + id = "00000000-0000-0000-0000-000000000000" +} \ No newline at end of file diff --git a/internal/examples/data-sources/citrix_delivery_group/data-source.tf b/internal/examples/data-sources/citrix_delivery_group/data-source.tf index 6693d0c..39b6c4b 100644 --- a/internal/examples/data-sources/citrix_delivery_group/data-source.tf +++ b/internal/examples/data-sources/citrix_delivery_group/data-source.tf @@ -1,3 +1,9 @@ +# Get Citrix Delivery Group data source by name data "citrix_delivery_group" "example_delivery_group" { name = "exampleDeliveryGroupName" } + +# Get Citrix Delivery Group data source by ID +data "citrix_delivery_group" "example_delivery_group" { + id = "00000000-0000-0000-0000-000000000000" +} diff --git a/internal/examples/data-sources/citrix_hypervisor/data-source.tf b/internal/examples/data-sources/citrix_hypervisor/data-source.tf index 28048b9..6671978 100644 --- a/internal/examples/data-sources/citrix_hypervisor/data-source.tf +++ b/internal/examples/data-sources/citrix_hypervisor/data-source.tf @@ -1,4 +1,9 @@ # Get Hypervisor resource of any connection type by name data "citrix_hypervisor" "azure-hypervisor" { name = "azure-hyperv" +} + +# Get Hypervisor resource of any connection type by id +data "citrix_hypervisor" "azure-hypervisor" { + id = "00000000-0000-0000-0000-000000000000" } \ No newline at end of file diff --git a/internal/examples/data-sources/citrix_hypervisor_resource_pool/data-source.tf b/internal/examples/data-sources/citrix_hypervisor_resource_pool/data-source.tf index e802a0b..debdc27 100644 --- a/internal/examples/data-sources/citrix_hypervisor_resource_pool/data-source.tf +++ b/internal/examples/data-sources/citrix_hypervisor_resource_pool/data-source.tf @@ -2,4 +2,10 @@ data "citrix_hypervisor_resource_pool" "azure-resource-pool" { name = "azure-rp" hypervisor_name = "azure-hyperv" +} + +# Get Hypervisor Resource Pool of any connection type resource by id and the hypervisor name it belongs to +data "citrix_hypervisor_resource_pool" "azure-resource-pool" { + id = "00000000-0000-0000-0000-000000000000" + hypervisor_name = "azure-hyperv" } \ No newline at end of file diff --git a/internal/examples/data-sources/citrix_machine_catalog/data-source.tf b/internal/examples/data-sources/citrix_machine_catalog/data-source.tf index 83528b8..ed74cfe 100644 --- a/internal/examples/data-sources/citrix_machine_catalog/data-source.tf +++ b/internal/examples/data-sources/citrix_machine_catalog/data-source.tf @@ -1,3 +1,9 @@ +# Get Citrix Machine Catalog data source by name data "citrix_machine_catalog" "example_machine_catalog" { name = "example-catalog" } + +# Get Citrix Machine Catalog data source by ID +data "citrix_machine_catalog" "example_machine_catalog" { + id = "00000000-0000-0000-0000-000000000000" +} diff --git a/internal/examples/data-sources/citrix_policy_set/data-source.tf b/internal/examples/data-sources/citrix_policy_set/data-source.tf new file mode 100644 index 0000000..ec1f8ec --- /dev/null +++ b/internal/examples/data-sources/citrix_policy_set/data-source.tf @@ -0,0 +1,9 @@ +# Get Policy Set data source by id +data "citrix_policy_set" "example_policy_set_data_source_with_id" { + id = "00000000-0000-0000-0000-000000000000" +} + +# Get Policy Set data source by name +data "citrix_policy_set" "example_policy_set_data_source_with_name" { + name = "example-policy-set" +} diff --git a/internal/examples/data-sources/citrix_storefront_server/data-source.tf b/internal/examples/data-sources/citrix_storefront_server/data-source.tf new file mode 100644 index 0000000..b05b0fb --- /dev/null +++ b/internal/examples/data-sources/citrix_storefront_server/data-source.tf @@ -0,0 +1,9 @@ +# Get StoreFront Server data source by name +data "citrix_storefront_server" "example_storefront_server_by_name" { + name = "ExampleStoreFrontServer" +} + +# Get StoreFront Server data source by id +data "citrix_storefront_server" "example_storefront_server_by_id" { + id = "00000000-0000-0000-0000-000000000000" +} \ No newline at end of file diff --git a/internal/examples/resources/citrix_application_icon/import.sh b/internal/examples/resources/citrix_application_icon/import.sh index 449a596..8e1efff 100644 --- a/internal/examples/resources/citrix_application_icon/import.sh +++ b/internal/examples/resources/citrix_application_icon/import.sh @@ -1,2 +1,2 @@ -# Application icon can be imported by specifying the GUID -terraform import citrix_application_icon.example-application-icon 4cec0568-1c91-407f-a32e-cc487822defc \ No newline at end of file +# Application icon can be imported by specifying the ID +terraform import citrix_application_icon.example-application-icon 1 \ No newline at end of file diff --git a/internal/examples/resources/citrix_delivery_group/resource.tf b/internal/examples/resources/citrix_delivery_group/resource.tf index 1f40735..03faef6 100644 --- a/internal/examples/resources/citrix_delivery_group/resource.tf +++ b/internal/examples/resources/citrix_delivery_group/resource.tf @@ -25,6 +25,9 @@ resource "citrix_delivery_group" "example-delivery-group" { ] autoscale_settings = { autoscale_enabled = true + restrict_autoscale_tag = "example-tag" + peak_restrict_min_idle_untagged_percent = 10 + off_peak_restrict_min_idle_untagged_percent = 10 disconnect_peak_idle_session_after_seconds = 3600 log_off_peak_disconnected_session_after_seconds = 3600 peak_log_off_action = "Nothing" diff --git a/internal/examples/resources/citrix_desktop_icon/import.sh b/internal/examples/resources/citrix_desktop_icon/import.sh index 6467654..55f9f72 100644 --- a/internal/examples/resources/citrix_desktop_icon/import.sh +++ b/internal/examples/resources/citrix_desktop_icon/import.sh @@ -1,2 +1,2 @@ -# Desktop icon can be imported by specifying the GUID -terraform import citrix_desktop_icon.example-desktop-icon 4cec0568-1c91-407f-a32e-cc487822d0a0 \ No newline at end of file +# Desktop icon can be imported by specifying the ID +terraform import citrix_desktop_icon.example-desktop-icon 1 \ No newline at end of file diff --git a/internal/examples/resources/citrix_gac_discovery/import.sh b/internal/examples/resources/citrix_gac_discovery/import.sh new file mode 100644 index 0000000..acc5da4 --- /dev/null +++ b/internal/examples/resources/citrix_gac_discovery/import.sh @@ -0,0 +1,2 @@ +# Global App Configuration Discovery can be imported by specifying the domain +terraform import citrix_gac_discovery.example-gac-discovery example-domain.com \ No newline at end of file diff --git a/internal/examples/resources/citrix_gac_discovery/resource.tf b/internal/examples/resources/citrix_gac_discovery/resource.tf new file mode 100644 index 0000000..d3ed240 --- /dev/null +++ b/internal/examples/resources/citrix_gac_discovery/resource.tf @@ -0,0 +1,5 @@ +resource "citrix_gac_discovery" "example-gac-discovery" { + domain = "example-domain.com" + service_urls = ["https://example.com:443", "https://example2.com:80"] + allowed_web_store_urls = ["https://example.com", "https://example2.com"] +} \ No newline at end of file diff --git a/internal/examples/resources/citrix_machine_catalog/resource.tf b/internal/examples/resources/citrix_machine_catalog/resource.tf index 8df49da..d18d63e 100644 --- a/internal/examples/resources/citrix_machine_catalog/resource.tf +++ b/internal/examples/resources/citrix_machine_catalog/resource.tf @@ -22,8 +22,10 @@ resource "citrix_machine_catalog" "example-azure-mtsession" { azure_master_image = { # shared_subscription = var.azure_image_subscription # Uncomment if the image is from a subscription outside of the hypervisor's subscription - # For Azure master image from managed disk or snapshot + # Resource Group is required for any type of Azure master image resource_group = var.azure_resource_group + + # For Azure master image from managed disk or snapshot master_image = var.azure_master_image # For Azure image gallery @@ -303,7 +305,7 @@ resource "citrix_machine_catalog" "example-manual-power-managed-mtsession" { { region = "East US" resource_group_name = "machine-resource-group-name" - machine_account = "DOMAIN\\MachineName" + machine_account = "domain\\machine-name" machine_name = "MachineName" } ] @@ -382,8 +384,10 @@ resource "citrix_machine_catalog" "example-non-domain-joined-azure-mcs" { azure_master_image = { # shared_subscription = var.azure_image_subscription # Uncomment if the image is from a subscription outside of the hypervisor's subscription - # For Azure master image from managed disk or snapshot + # Resource Group is required for any type of Azure master image resource_group = var.azure_resource_group + + # For Azure master image from managed disk or snapshot master_image = var.azure_master_image # For Azure image gallery diff --git a/internal/examples/resources/citrix_machine_properties/import.sh b/internal/examples/resources/citrix_machine_properties/import.sh new file mode 100644 index 0000000..b2c9800 --- /dev/null +++ b/internal/examples/resources/citrix_machine_properties/import.sh @@ -0,0 +1,2 @@ +# citrix_machine_properties resource can be imported with the Machine Name +terraform import citrix_machine_properties.example_machine_properties domain\machine-name \ No newline at end of file diff --git a/internal/examples/resources/citrix_machine_properties/resource.tf b/internal/examples/resources/citrix_machine_properties/resource.tf new file mode 100644 index 0000000..0476891 --- /dev/null +++ b/internal/examples/resources/citrix_machine_properties/resource.tf @@ -0,0 +1,5 @@ +resource "citrix_machine_properties" "example_machine_properties" { + name = "domain\\machine-name" // For workgroup machines, use machine-name only + machine_catalog_id = "00000000-0000-0000-0000-000000000000" // Id of the machine catalog the machine belongs to + tags = [ "11111111-1111-1111-1111-111111111111" ] // Tags to be assigned to the machine +} diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index 3b2e90a..e5a8e01 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -63,3 +63,28 @@ func MiddlewareAuthFunc(authClient *citrixclient.CitrixDaasClient, r *http.Reque "transactionId": transactionId, }) } + +// Middleware Auth for Wem OnPrem Client with SessionId +func MiddlewareAuthWithSessionIdFunc(authClient *citrixclient.CitrixDaasClient, r *http.Request) { + if authClient != nil && r.Header.Get("Authorization") == "" { + token, _, err := authClient.SignInWemOnPrem() + if err != nil { + tflog.Error(r.Context(), "Could not sign into Citrix Wem On-Premise Host, error: "+err.Error()) + } + r.Header["Authorization"] = []string{token} + } + + // TransactionId + transactionId := r.Header.Get("Citrix-TransactionId") + if transactionId == "" { + transactionId = uuid.NewString() + r.Header.Add("Citrix-TransactionId", transactionId) + } + + // Log the request + tflog.Info(r.Context(), "WEM On-Prem Host API request", map[string]interface{}{ + "url": r.URL.String(), + "method": r.Method, + "transactionId": transactionId, + }) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index db81484..7d78aec 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -21,7 +21,7 @@ import ( citrixclient "github.com/citrix/citrix-daas-rest-go/client" cc_admin_user "github.com/citrix/terraform-provider-citrix/internal/citrixcloud/admin_user" - "github.com/citrix/terraform-provider-citrix/internal/citrixcloud/gac_settings" + "github.com/citrix/terraform-provider-citrix/internal/citrixcloud/global_app_configuration" cc_identity_providers "github.com/citrix/terraform-provider-citrix/internal/citrixcloud/identity_providers" "github.com/citrix/terraform-provider-citrix/internal/citrixcloud/resource_locations" "github.com/citrix/terraform-provider-citrix/internal/daas/admin_role" @@ -97,6 +97,7 @@ type citrixProvider struct { type citrixProviderModel struct { CvadConfig *cvadConfig `tfsdk:"cvad_config"` StoreFrontRemoteHost *storefrontConfig `tfsdk:"storefront_remote_host"` + WemOnPremConfig *wemonpremconfig `tfsdk:"wem_on_prem_config"` } type cvadConfig struct { @@ -107,6 +108,7 @@ type cvadConfig struct { ClientSecret types.String `tfsdk:"client_secret"` DisableSslVerification types.Bool `tfsdk:"disable_ssl_verification"` DisableDaaSClient types.Bool `tfsdk:"disable_daas_client"` + WemRegion types.String `tfsdk:"wem_region"` } type storefrontConfig struct { @@ -116,6 +118,13 @@ type storefrontConfig struct { DisableSslVerification types.Bool `tfsdk:"disable_ssl_verification"` } +type wemonpremconfig struct { + Hostname types.String `tfsdk:"hostname"` + AdminUserName types.String `tfsdk:"admin_username"` + AdminPassword types.String `tfsdk:"admin_password"` + DisableSslVerification types.Bool `tfsdk:"disable_ssl_verification"` +} + // Metadata returns the provider type name. func (p *citrixProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { resp.TypeName = "citrix" @@ -192,6 +201,19 @@ func (p *citrixProvider) Schema(_ context.Context, _ provider.SchemaRequest, res "\n\n-> **Note** Can be set via Environment Variable **CITRIX_DISABLE_DAAS_CLIENT**.", Optional: true, }, + "wem_region": schema.StringAttribute{ + Description: "WEM Hosting Region of the Citrix Cloud customer. Available values are `US`, `EU`, and `APS`." + + "\n\n-> **Note** Can be set via Environment Variable **CITRIX_WEM_REGION**." + + "\n\n~> **Please Note** Only applicable for Citrix Workspace Environment Management (WEM) Cloud customers.", + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "US", + "EU", + "APS", + ), + }, + }, }, }, "storefront_remote_host": schema.SingleNestedAttribute{ @@ -218,7 +240,8 @@ func (p *citrixProvider) Schema(_ context.Context, _ provider.SchemaRequest, res "Use this to specify AD admin password" + "
" + "Can be set via Environment Variable **SF_AD_ADMIN_PASSWORD**." + "
" + "This parameter is **required** to be specified in the provider configuration or via environment variable.", - Optional: true, + Optional: true, + Sensitive: true, }, "disable_ssl_verification": schema.BoolAttribute{ Description: "Disable SSL verification against the target storefront server. " + "
" + @@ -229,6 +252,41 @@ func (p *citrixProvider) Schema(_ context.Context, _ provider.SchemaRequest, res }, }, }, + "wem_on_prem_config": schema.SingleNestedAttribute{ + Description: "Configuration for WEM on-premises service.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "hostname": schema.StringAttribute{ + Description: "Name of server hosting Citrix WEM service. " + "
" + + "Use this to specify WEM service hostname. " + "
" + + "Can be set via Environment Variable **WEM_HOSTNAME**." + "
" + + "This parameter is **required** to be specified in the provider configuration or via environment variable.", + Optional: true, + }, + "admin_username": schema.StringAttribute{ + Description: "WEM Admin Username to connect to WEM service " + "
" + + "Use this to specify WEM admin username " + "
" + + "Can be set via Environment Variable **WEM_ADMIN_USERNAME**." + "
" + + "This parameter is **required** to be specified in the provider configuration or via environment variable.", + Optional: true, + }, + "admin_password": schema.StringAttribute{ + Description: "WEM Admin Password to connect to WEM service " + "
" + + "Use this to specify WEM admin password" + "
" + + "Can be set via Environment Variable **WEM_ADMIN_PASSWORD**." + "
" + + "This parameter is **required** to be specified in the provider configuration or via environment variable.", + Optional: true, + Sensitive: true, + }, + "disable_ssl_verification": schema.BoolAttribute{ + Description: "Disable SSL verification against the target WEM service. " + "
" + + "Set to true to skip SSL verification only when the target WEM service does not have a valid SSL certificate issued by a trusted CA. " + "
" + + "When set to true, please make sure that your provider config is set for a known WEM hostname. " + "
" + + "Can be set via Environment Variable **WEM_DISABLE_SSL_VERIFICATION**.", + Optional: true, + }, + }, + }, }, } } @@ -332,6 +390,7 @@ func (p *citrixProvider) Configure(ctx context.Context, req provider.ConfigureRe storeFrontClientInitialized := false daasClientInitialized := false + wemOnPremClientInitialized := false // Initialize storefront client storefront_computer_name := os.Getenv("SF_COMPUTER_NAME") @@ -370,8 +429,16 @@ func (p *citrixProvider) Configure(ctx context.Context, req provider.ConfigureRe customerId := os.Getenv("CITRIX_CUSTOMER_ID") disableSslVerification := strings.EqualFold(os.Getenv("CITRIX_DISABLE_SSL_VERIFICATION"), "true") disableDaasClient := strings.EqualFold(os.Getenv("CITRIX_DISABLE_DAAS_CLIENT"), "true") + wemRegion := os.Getenv("CITRIX_WEM_REGION") + wemHostName := os.Getenv("CITRIX_WEM_HOSTNAME") quick_create_host_name := os.Getenv("CITRIX_QUICK_CREATE_HOST_NAME") + // Initialize WEM on-prem client if WEM on-prem config is provided + wemOnPremHostName := os.Getenv("WEM_HOSTNAME") + wem_admin_username := os.Getenv("WEM_ADMIN_USERNAME") + wem_admin_password := os.Getenv("WEM_ADMIN_PASSWORD") + wem_disable_ssl_verification := strings.EqualFold(os.Getenv("WEM_DISABLE_SSL_VERIFICATION"), "true") + if cvadConfig := config.CvadConfig; cvadConfig != nil || (clientId != "" && clientSecret != "") { if cvadConfig != nil { if !cvadConfig.ClientId.IsNull() { @@ -402,19 +469,49 @@ func (p *citrixProvider) Configure(ctx context.Context, req provider.ConfigureRe disableDaasClient = cvadConfig.DisableDaaSClient.ValueBool() } + if !cvadConfig.WemRegion.IsNull() { + wemRegion = cvadConfig.WemRegion.ValueString() + } + } + + if wemOnPremConfig := config.WemOnPremConfig; wemOnPremConfig != nil || (wemOnPremHostName != "" && wem_admin_username != "" && wem_admin_password != "") { + if wemOnPremConfig != nil { + if !wemOnPremConfig.Hostname.IsNull() { + wemOnPremHostName = wemOnPremConfig.Hostname.ValueString() + } + if !wemOnPremConfig.AdminUserName.IsNull() { + wem_admin_username = wemOnPremConfig.AdminUserName.ValueString() + } + if !wemOnPremConfig.AdminPassword.IsNull() { + wem_admin_password = wemOnPremConfig.AdminPassword.ValueString() + } + if !wemOnPremConfig.DisableSslVerification.IsNull() { + wem_disable_ssl_verification = wemOnPremConfig.DisableSslVerification.ValueBool() + } + } + + validateWemOnPremClient(resp, wemOnPremHostName, wem_admin_username, wem_admin_password) + if resp.Diagnostics.HasError() { + return + } + } - validateAndInitializeDaaSClient(ctx, resp, client, clientId, clientSecret, hostname, environment, customerId, quick_create_host_name, p.version, disableSslVerification, disableDaasClient) + validateAndInitializeDaaSClient(ctx, resp, client, clientId, clientSecret, hostname, environment, wemHostName, wemRegion, customerId, quick_create_host_name, p.version, wemOnPremHostName, wem_admin_username, wem_admin_password, disableSslVerification, disableDaasClient, wem_disable_ssl_verification) if resp.Diagnostics.HasError() { return } daasClientInitialized = true + + if wemOnPremHostName != "" { + wemOnPremClientInitialized = true + } } - if !storeFrontClientInitialized && !daasClientInitialized { + if !storeFrontClientInitialized && !wemOnPremClientInitialized && !daasClientInitialized { resp.Diagnostics.AddError( "Invalid Provider Configuration", - "At least one of `cvad_config` and `storefront_remote_host` attributes must be specified.", + "At least one of `cvad_config`, `storefront_remote_host` or `wem_on_prem_config` attributes must be specified.", ) return } @@ -427,6 +524,36 @@ func (p *citrixProvider) Configure(ctx context.Context, req provider.ConfigureRe tflog.Info(ctx, "Configured Citrix API client", map[string]any{"success": true}) } +func validateWemOnPremClient(resp *provider.ConfigureResponse, wem_hostname, wem_admin_username, wem_admin_password string) { + if wem_hostname == "" { + resp.Diagnostics.AddAttributeError( + path.Root("wem_on_prem_config").AtName("hostname"), + "Unknown WEM Hostname", + "The provider cannot create the Citrix WEM client as there is an unknown configuration value for the WEM Hostname. "+ + "Either set the value in the provider configuration, or use the WEM_HOSTNAME environment variable.", + ) + return + } + if wem_admin_username == "" { + resp.Diagnostics.AddAttributeError( + path.Root("wem_on_prem_config").AtName("admin_username"), + "Unknown WEM Admin Username", + "The provider cannot create the Citrix WEM client as there is an unknown configuration value for the WEM Admin Username. "+ + "Either set the value in the provider configuration, or use the WEM_ADMIN_USERNAME environment variable.", + ) + return + } + if wem_admin_password == "" { + resp.Diagnostics.AddAttributeError( + path.Root("wem_on_prem_config").AtName("admin_password"), + "Unknown WEM Admin Password", + "The provider cannot create the Citrix WEM client as there is an unknown configuration value for the WEM Admin Password. "+ + "Either set the value in the provider configuration, or use the WEM_ADMIN_PASSWORD environment variable.", + ) + return + } +} + func validateAndInitializeStorefrontClient(ctx context.Context, resp *provider.ConfigureResponse, client *citrixclient.CitrixDaasClient, storefront_computer_name, storefront_ad_admin_username, storefront_ad_admin_password string, storefront_disable_ssl_verification bool) { if storefront_computer_name == "" { resp.Diagnostics.AddAttributeError( @@ -458,7 +585,7 @@ func validateAndInitializeStorefrontClient(ctx context.Context, resp *provider.C client.InitializeStoreFrontClient(ctx, storefront_computer_name, storefront_ad_admin_username, storefront_ad_admin_password, storefront_disable_ssl_verification) } -func validateAndInitializeDaaSClient(ctx context.Context, resp *provider.ConfigureResponse, client *citrixclient.CitrixDaasClient, clientId, clientSecret, hostname, environment, customerId, quick_create_host_name, version string, disableSslVerification bool, disableDaasClient bool) { +func validateAndInitializeDaaSClient(ctx context.Context, resp *provider.ConfigureResponse, client *citrixclient.CitrixDaasClient, clientId, clientSecret, hostname, environment, wemHostName, wemRegion, customerId, quick_create_host_name, version, wemOnPremHostName, wem_admin_username, wem_admin_password string, disableSslVerification, disableDaasClient, wem_disable_ssl_verification bool) { if clientId == "" { resp.Diagnostics.AddAttributeError( path.Root("cvad_config").AtName("client_id"), @@ -499,6 +626,11 @@ func validateAndInitializeDaaSClient(ctx context.Context, resp *provider.Configu onPremises = true } + wemAuthUrl := "" + if onPremises && wemOnPremHostName != "" { + wemAuthUrl = fmt.Sprintf("https://%s/services/wem/onPrem/LogIn", wemOnPremHostName) + } + // If any of the expected configurations are missing, return // errors with provider-specific guidance. @@ -613,19 +745,20 @@ func validateAndInitializeDaaSClient(ctx context.Context, resp *provider.Configu cwsHostName = "cws.ctxwsstgapi.us" } - wemHostName := "" - if environment == "Production" { - wemHostName = "api.wem.cloud.com" - } else if environment == "Staging" { - wemHostName = "api.wem.cloudburrito.com" - } else if environment == "Japan" { - wemHostName = "api.wem.citrixcloud.jp" - } else if environment == "JapanStaging" { - wemHostName = "api.wem.citrixcloudstaging.jp" - } else if environment == "Gov" { - wemHostName = "api.wem.citrixworkspacesapi.us" - } else if environment == "GovStaging" { - wemHostName = "api.wem.ctxwsstgapi.us" + if wemHostName == "" && wemAuthUrl == "" { + if environment == "Production" { + if wemRegion == "EU" { + wemHostName = "eu-api.wem.cloud.com" + } else if wemRegion == "APS" { + wemHostName = "aps-api.wem.cloud.com" + } else { + wemHostName = "api.wem.cloud.com" + } + } else if environment == "Japan" { + wemHostName = "jp-api.wem.citrixcloud.jp" + } else if environment == "Staging" { + wemHostName = "api.wem.cloudburrito.com" + } } ctx = tflog.SetField(ctx, "citrix_hostname", hostname) @@ -655,6 +788,11 @@ func validateAndInitializeDaaSClient(ctx context.Context, resp *provider.Configu ctx = tflog.MaskAllFieldValuesStrings(ctx, customerId) } + if wemOnPremHostName != "" { + ctx = tflog.SetField(ctx, "citrix_wem_onprem_host", wemOnPremHostName) + ctx = tflog.SetField(ctx, "citrix_wem_admin_username", wem_admin_username) + ctx = tflog.MaskFieldValuesWithFieldKeys(ctx, "citrix_wem_admin_password", wem_admin_password) + } tflog.Debug(ctx, "Creating Citrix API client") userAgent := "citrix-terraform-provider/" + version + " (https://github.com/citrix/terraform-provider-citrix)" @@ -677,6 +815,12 @@ func validateAndInitializeDaaSClient(ctx context.Context, resp *provider.Configu return } + // Setup the WEM On-Prem Client API + if wemAuthUrl != "" { + tflog.Debug(ctx, "Creating WEM API client") + client.SetupWemOnPremClientContext(ctx, middleware.MiddlewareAuthWithSessionIdFunc, wemAuthUrl, wemOnPremHostName, wem_admin_username, wem_admin_password, wem_disable_ssl_verification) + } + // Initialize the Cloud Clients if not on-premises if !onPremises { client.InitializeCitrixCloudClients(ctx, ccUrl, hostname, middleware.MiddlewareAuthFunc, middleware.MiddlewareAuthWithCustomerIdHeaderFunc) @@ -725,7 +869,7 @@ func validateAndInitializeDaaSClient(ctx context.Context, resp *provider.Configu } // Set WEM Client if wemHostName != "" { - client.InitializeWemClient(ctx, wemHostName, middleware.MiddlewareAuthFunc) + client.InitializeWemClient(ctx, wemHostName, middleware.MiddlewareAuthFunc, false, false) } } @@ -783,11 +927,15 @@ func (p *citrixProvider) DataSources(_ context.Context) []func() datasource.Data vda.NewVdaDataSource, application.NewApplicationDataSourceSource, admin_folder.NewAdminFolderDataSource, + admin_role.NewAdminRoleDataSource, admin_scope.NewAdminScopeDataSource, + admin_user.NewAdminUserDataSource, machine_catalog.NewPvsDataSource, bearer_token.NewBearerTokenDataSource, cvad_site.NewSiteDataSource, tags.NewTagDataSource, + policies.NewPolicySetDataSource, + storefront_server.NewStoreFrontServerDataSource, // StoreFront DataSources stf_roaming.NewSTFRoamingServiceDataSource, // QuickCreate DataSources @@ -826,6 +974,7 @@ func (p *citrixProvider) Resources(_ context.Context) []func() resource.Resource hypervisor_resource_pool.NewNutanixHypervisorResourcePoolResource, hypervisor_resource_pool.NewSCVMMHypervisorResourcePoolResource, machine_catalog.NewMachineCatalogResource, + machine_catalog.NewMachinePropertiesResource, delivery_group.NewDeliveryGroupResource, storefront_server.NewStoreFrontServerResource, application.NewApplicationResource, @@ -837,7 +986,8 @@ func (p *citrixProvider) Resources(_ context.Context) []func() resource.Resource admin_scope.NewAdminScopeResource, policies.NewPolicySetResource, admin_user.NewAdminUserResource, - gac_settings.NewGacSettingsResource, + global_app_configuration.NewGacSettingsResource, + global_app_configuration.NewGacDiscoveryResource, resource_locations.NewResourceLocationResource, cc_admin_user.NewCCAdminUserResource, tags.NewTagResource, diff --git a/internal/quickcreate/qcs_account/aws_workspaces_account_data_source.go b/internal/quickcreate/qcs_account/aws_workspaces_account_data_source.go index 669211c..8fe18bc 100644 --- a/internal/quickcreate/qcs_account/aws_workspaces_account_data_source.go +++ b/internal/quickcreate/qcs_account/aws_workspaces_account_data_source.go @@ -65,9 +65,9 @@ func (r *awsWorkspacesAccountDataSource) Read(ctx context.Context, req datasourc var account *citrixquickcreate.AwsEdcAccount var err error - if data.AccountId.ValueString() != "" { + if !data.AccountId.IsNull() { account, _, err = getAwsWorkspacesAccountUsingId(ctx, r.client, &resp.Diagnostics, data.AccountId.ValueString()) - } else if data.Name.ValueString() != "" { + } else { account, _, err = getAwsWorkspacesAccountUsingName(ctx, r.client, &resp.Diagnostics, data.Name.ValueString()) } if err != nil { diff --git a/internal/quickcreate/qcs_account/aws_workspaces_account_data_source_model.go b/internal/quickcreate/qcs_account/aws_workspaces_account_data_source_model.go index cca157e..43870fb 100644 --- a/internal/quickcreate/qcs_account/aws_workspaces_account_data_source_model.go +++ b/internal/quickcreate/qcs_account/aws_workspaces_account_data_source_model.go @@ -31,12 +31,15 @@ func (AwsWorkspacesAccountDataSourceModel) GetSchema() schema.Schema { Optional: true, Validators: []validator.String{ stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - stringvalidator.ExactlyOneOf(path.MatchRoot("id"), path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. }, }, "name": schema.StringAttribute{ Description: "Name of the account.", Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, }, "aws_account": schema.StringAttribute{ Description: "AWS account number associated with the account.", diff --git a/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_data_source.go b/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_data_source.go index 365905b..fb707cb 100644 --- a/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_data_source.go +++ b/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_data_source.go @@ -32,7 +32,7 @@ func (d *AwsWorkspacesDirectoryConnectionDataSource) Metadata(_ context.Context, } func (d *AwsWorkspacesDirectoryConnectionDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = AwsWorkspacesDirectoryConnectionDataSourceModel{}.GetSchema() + resp.Schema = AwsWorkspacesDirectoryConnectionModel{}.GetDataSourceSchema() } func (d *AwsWorkspacesDirectoryConnectionDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { @@ -45,7 +45,7 @@ func (d *AwsWorkspacesDirectoryConnectionDataSource) Configure(ctx context.Conte } func (d *AwsWorkspacesDirectoryConnectionDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data AwsWorkspacesDirectoryConnectionDataSourceModel + var data AwsWorkspacesDirectoryConnectionModel // Read Terraform configuration data into the model resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) @@ -58,10 +58,10 @@ func (d *AwsWorkspacesDirectoryConnectionDataSource) Read(ctx context.Context, r var directoryConnection *citrixquickcreate.AwsEdcDirectoryConnection var directoryConnectionIdentifier string var err error - if data.DirectoryConnectionId.ValueString() != "" { + if !data.DirectoryConnectionId.IsNull() { directoryConnectionIdentifier = data.DirectoryConnectionId.ValueString() directoryConnection, _, err = getAwsWorkspacesDirectoryConnection(ctx, d.client, &resp.Diagnostics, data.AccountId.ValueString(), data.DirectoryConnectionId.ValueString(), false) - } else if data.Name.ValueString() != "" { + } else { directoryConnectionIdentifier = data.Name.ValueString() directoryConnection, _, err = getAwsWorkspacesDirectoryConnectionWithName(ctx, d.client, &resp.Diagnostics, data.AccountId.ValueString(), data.Name.ValueString()) } @@ -78,7 +78,7 @@ func (d *AwsWorkspacesDirectoryConnectionDataSource) Read(ctx context.Context, r return } // Map response body to schema and populate computed attribute values - data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, directoryConnection) + data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, false, directoryConnection) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) diff --git a/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_data_source_model.go b/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_data_source_model.go index 34b02fa..cede3ba 100644 --- a/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_data_source_model.go +++ b/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_data_source_model.go @@ -2,34 +2,17 @@ package qcs_connection import ( - "context" "regexp" - quickcreateservice "github.com/citrix/citrix-daas-rest-go/citrixquickcreate" "github.com/citrix/terraform-provider-citrix/internal/util" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) -type AwsWorkspacesDirectoryConnectionDataSourceModel struct { - DirectoryConnectionId types.String `tfsdk:"id"` - AccountId types.String `tfsdk:"account"` - Name types.String `tfsdk:"name"` - ZoneId types.String `tfsdk:"zone"` - ResourceLocationId types.String `tfsdk:"resource_location"` - Directory types.String `tfsdk:"directory"` - Subnets types.Set `tfsdk:"subnets"` - Tenancy types.String `tfsdk:"tenancy"` - UserEnabledAsLocalAdministrator types.Bool `tfsdk:"user_enabled_as_local_administrator"` - SecurityGroup types.String `tfsdk:"security_group"` - DefaultOu types.String `tfsdk:"default_ou"` -} - -func (AwsWorkspacesDirectoryConnectionDataSourceModel) GetSchema() schema.Schema { +func (AwsWorkspacesDirectoryConnectionModel) GetDataSourceSchema() schema.Schema { return schema.Schema{ Description: "DaaS Quick Deploy - AWS WorkSpaces Core --- Data Source of an AWS WorkSpaces directory connection.", Attributes: map[string]schema.Attribute{ @@ -38,12 +21,15 @@ func (AwsWorkspacesDirectoryConnectionDataSourceModel) GetSchema() schema.Schema Optional: true, Validators: []validator.String{ stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - stringvalidator.ExactlyOneOf(path.MatchRoot("id"), path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. }, }, "name": schema.StringAttribute{ Description: "Name of the directory connection.", Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, }, "account": schema.StringAttribute{ Description: "ID of the account the directory connection is associated with.", @@ -89,22 +75,6 @@ func (AwsWorkspacesDirectoryConnectionDataSourceModel) GetSchema() schema.Schema } } -func (AwsWorkspacesDirectoryConnectionDataSourceModel) GetAttributes() map[string]schema.Attribute { - return AwsWorkspacesDirectoryConnectionDataSourceModel{}.GetSchema().Attributes -} - -func (r AwsWorkspacesDirectoryConnectionDataSourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, directory *quickcreateservice.AwsEdcDirectoryConnection) AwsWorkspacesDirectoryConnectionDataSourceModel { - r.DirectoryConnectionId = types.StringValue(directory.GetConnectionId()) - r.Name = types.StringValue(directory.GetName()) - r.AccountId = types.StringValue(directory.GetAccountId()) - r.ZoneId = types.StringValue(directory.GetZoneId()) - r.ResourceLocationId = types.StringValue(directory.GetResourceLocationId()) - r.Directory = types.StringValue(directory.GetDirectoryId()) - r.Subnets = util.StringArrayToStringSet(ctx, diagnostics, []string{directory.GetSubnet1Id(), directory.GetSubnet2Id()}) - r.Tenancy = types.StringValue(string(directory.GetTenancy())) - r.UserEnabledAsLocalAdministrator = types.BoolValue(directory.GetUserEnabledAsLocalAdministrator()) - r.SecurityGroup = types.StringValue(directory.GetSecurityGroupId()) - r.DefaultOu = types.StringValue(directory.GetDefaultOU()) - - return r +func (AwsWorkspacesDirectoryConnectionModel) GetDataSourceAttributes() map[string]schema.Attribute { + return AwsWorkspacesDirectoryConnectionModel{}.GetDataSourceSchema().Attributes } diff --git a/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_resource.go b/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_resource.go index 7eb9710..2231598 100644 --- a/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_resource.go +++ b/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_resource.go @@ -40,7 +40,7 @@ func (r *awsWorkspacesDirectoryConnectionResource) Metadata(_ context.Context, r // Schema defines the schema for the resource. func (r *awsWorkspacesDirectoryConnectionResource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = AwsWorkspacesDirectoryConnectionResourceModel{}.GetSchema() + resp.Schema = AwsWorkspacesDirectoryConnectionModel{}.GetSchema() } // Configure adds the proider configured client to the resource. @@ -57,7 +57,7 @@ func (r *awsWorkspacesDirectoryConnectionResource) Create(ctx context.Context, r defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan AwsWorkspacesDirectoryConnectionResourceModel + var plan AwsWorkspacesDirectoryConnectionModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -95,7 +95,7 @@ func (r *awsWorkspacesDirectoryConnectionResource) Create(ctx context.Context, r } // Map response body to schema and populate computed attribute values - plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, directoryConnection) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, true, directoryConnection) // Set state to fully populated data diags = resp.State.Set(ctx, &plan) @@ -110,7 +110,7 @@ func (r *awsWorkspacesDirectoryConnectionResource) Read(ctx context.Context, req defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from state - var state AwsWorkspacesDirectoryConnectionResourceModel + var state AwsWorkspacesDirectoryConnectionModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -133,7 +133,7 @@ func (r *awsWorkspacesDirectoryConnectionResource) Read(ctx context.Context, req } // Map response body to schema and populate computed attribute values - state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, directoryConnection) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, true, directoryConnection) // Set state to fully populated data diags = resp.State.Set(ctx, &state) @@ -148,7 +148,7 @@ func (r *awsWorkspacesDirectoryConnectionResource) Update(ctx context.Context, r defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan AwsWorkspacesDirectoryConnectionResourceModel + var plan AwsWorkspacesDirectoryConnectionModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -190,7 +190,7 @@ func (r *awsWorkspacesDirectoryConnectionResource) Update(ctx context.Context, r } // Update resource state with new connection details - plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, directoryConnection) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, true, directoryConnection) diags = resp.State.Set(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -204,7 +204,7 @@ func (r *awsWorkspacesDirectoryConnectionResource) Delete(ctx context.Context, r defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from state - var state AwsWorkspacesDirectoryConnectionResourceModel + var state AwsWorkspacesDirectoryConnectionModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -248,7 +248,7 @@ func (r *awsWorkspacesDirectoryConnectionResource) ValidateConfig(ctx context.Co defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from config - var data AwsWorkspacesDirectoryConnectionResourceModel + var data AwsWorkspacesDirectoryConnectionModel diags := req.Config.Get(ctx, &data) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_resource_model.go b/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_resource_model.go index 28d7537..24690c7 100644 --- a/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_resource_model.go +++ b/internal/quickcreate/qcs_connection/aws_workspaces_directory_connection_resource_model.go @@ -20,7 +20,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type AwsWorkspacesDirectoryConnectionResourceModel struct { +type AwsWorkspacesDirectoryConnectionModel struct { DirectoryConnectionId types.String `tfsdk:"id"` AccountId types.String `tfsdk:"account"` Name types.String `tfsdk:"name"` @@ -34,7 +34,7 @@ type AwsWorkspacesDirectoryConnectionResourceModel struct { DefaultOu types.String `tfsdk:"default_ou"` } -func (AwsWorkspacesDirectoryConnectionResourceModel) GetSchema() schema.Schema { +func (AwsWorkspacesDirectoryConnectionModel) GetSchema() schema.Schema { return schema.Schema{ Description: "DaaS Quick Deploy - AWS WorkSpaces Core --- Manages an AWS WorkSpaces directory connection.", Attributes: map[string]schema.Attribute{ @@ -148,18 +148,18 @@ func (AwsWorkspacesDirectoryConnectionResourceModel) GetSchema() schema.Schema { } } -func (AwsWorkspacesDirectoryConnectionResourceModel) GetAttributes() map[string]schema.Attribute { - return AwsWorkspacesDirectoryConnectionResourceModel{}.GetSchema().Attributes +func (AwsWorkspacesDirectoryConnectionModel) GetAttributes() map[string]schema.Attribute { + return AwsWorkspacesDirectoryConnectionModel{}.GetSchema().Attributes } -func (r AwsWorkspacesDirectoryConnectionResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, directory *quickcreateservice.AwsEdcDirectoryConnection) AwsWorkspacesDirectoryConnectionResourceModel { +func (r AwsWorkspacesDirectoryConnectionModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, isResource bool, directory *quickcreateservice.AwsEdcDirectoryConnection) AwsWorkspacesDirectoryConnectionModel { r.DirectoryConnectionId = types.StringValue(directory.GetConnectionId()) r.Name = types.StringValue(directory.GetName()) r.AccountId = types.StringValue(directory.GetAccountId()) - if !r.ZoneId.IsNull() { + if !r.ZoneId.IsNull() || !isResource { r.ZoneId = types.StringValue(directory.GetZoneId()) } - if !r.ResourceLocationId.IsNull() { + if !r.ResourceLocationId.IsNull() || !isResource { r.ResourceLocationId = types.StringValue(directory.GetResourceLocationId()) } r.Directory = types.StringValue(directory.GetDirectoryId()) diff --git a/internal/quickcreate/qcs_deployment/aws_workspace_deployment_data_source.go b/internal/quickcreate/qcs_deployment/aws_workspace_deployment_data_source.go index a989201..ee709b3 100644 --- a/internal/quickcreate/qcs_deployment/aws_workspace_deployment_data_source.go +++ b/internal/quickcreate/qcs_deployment/aws_workspace_deployment_data_source.go @@ -68,9 +68,9 @@ func (r *awsWorkspacesDeploymentDataSource) Read(ctx context.Context, req dataso var deployment *citrixquickcreate.AwsEdcDeployment var err error // Try getting the AWS WorkSpaces Deployment - if data.Id.ValueString() != "" { + if !data.Id.IsNull() { deployment, _, err = getAwsWorkspacesDeploymentUsingId(ctx, r.client, &resp.Diagnostics, data.Id.ValueString(), true) - } else if data.Name.ValueString() != "" { + } else { deployment, _, err = getAwsWorkspacesDeploymentByName(ctx, r.client, &resp.Diagnostics, data.Name.ValueString(), data.AccountId.ValueString()) } if err != nil { diff --git a/internal/quickcreate/qcs_deployment/aws_workspace_deployment_data_source_model.go b/internal/quickcreate/qcs_deployment/aws_workspace_deployment_data_source_model.go index f518ef6..f41fe80 100644 --- a/internal/quickcreate/qcs_deployment/aws_workspace_deployment_data_source_model.go +++ b/internal/quickcreate/qcs_deployment/aws_workspace_deployment_data_source_model.go @@ -32,7 +32,7 @@ func (AwsWorkspacesDeploymentDataSourceModel) GetSchema() schema.Schema { Optional: true, Validators: []validator.String{ stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - stringvalidator.ExactlyOneOf(path.MatchRoot("id"), path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. }, }, "name": schema.StringAttribute{ @@ -40,6 +40,7 @@ func (AwsWorkspacesDeploymentDataSourceModel) GetSchema() schema.Schema { Optional: true, Validators: []validator.String{ stringvalidator.AlsoRequires(path.MatchRoot("account_id")), + stringvalidator.LengthAtLeast(1), }, }, "account_id": schema.StringAttribute{ diff --git a/internal/quickcreate/qcs_deployment/aws_workspaces_deployment_resource_model.go b/internal/quickcreate/qcs_deployment/aws_workspaces_deployment_resource_model.go index f3c55aa..bd61258 100644 --- a/internal/quickcreate/qcs_deployment/aws_workspaces_deployment_resource_model.go +++ b/internal/quickcreate/qcs_deployment/aws_workspaces_deployment_resource_model.go @@ -166,7 +166,7 @@ func (AwsWorkspacesDeploymentWorkspaceModel) GetAttributes() map[string]schema.A return AwsWorkspacesDeploymentWorkspaceModel{}.GetSchema().Attributes } -func (workspace AwsWorkspacesDeploymentWorkspaceModel) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, desktop citrixquickcreate.AwsEdcDeploymentMachine) util.ModelWithAttributes { +func (workspace AwsWorkspacesDeploymentWorkspaceModel) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, desktop citrixquickcreate.AwsEdcDeploymentMachine) util.ResourceModelWithAttributes { workspace.Username = types.StringValue(desktop.GetUsername()) workspace.RootVolumeSize = types.Int64Value(int64(desktop.GetRootVolumeSize())) workspace.UserVolumeSize = types.Int64Value(int64(desktop.GetUserVolumeSize())) @@ -375,7 +375,7 @@ func (r AwsWorkspacesDeploymentResourceModel) RefreshPropertyValues(ctx context. scaleSettingsObbject := util.TypedObjectToObjectValue(ctx, diagnostics, scaleSettings) r.ScaleSettings = scaleSettingsObbject } else { - if attributes, err := util.AttributeMapFromObject(AwsWorkspacesScaleSettingsModel{}); err == nil { + if attributes, err := util.ResourceAttributeMapFromObject(AwsWorkspacesScaleSettingsModel{}); err == nil { r.ScaleSettings = types.ObjectNull(attributes) } else { diagnostics.AddError("Error when creating null ScaleSettings", err.Error()) diff --git a/internal/quickcreate/qcs_image/aws_workspaces_image_data_source.go b/internal/quickcreate/qcs_image/aws_workspaces_image_data_source.go index cb7d683..b5532e0 100644 --- a/internal/quickcreate/qcs_image/aws_workspaces_image_data_source.go +++ b/internal/quickcreate/qcs_image/aws_workspaces_image_data_source.go @@ -32,7 +32,7 @@ func (d *AwsWorkspacesImageDataSource) Metadata(_ context.Context, req datasourc } func (d *AwsWorkspacesImageDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = AwsWorkspacesImageDataSourceModel{}.GetSchema() + resp.Schema = AwsWorkspacesImageModel{}.GetDataSourceSchema() } func (d *AwsWorkspacesImageDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { @@ -45,7 +45,7 @@ func (d *AwsWorkspacesImageDataSource) Configure(ctx context.Context, req dataso } func (d *AwsWorkspacesImageDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data AwsWorkspacesImageDataSourceModel + var data AwsWorkspacesImageModel // Read Terraform configuration data into the model resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) @@ -57,9 +57,9 @@ func (d *AwsWorkspacesImageDataSource) Read(ctx context.Context, req datasource. // Try getting the AWS WorkSpaces Image var image *citrixquickcreate.AwsEdcImage var err error - if data.Id.ValueString() != "" { + if !data.Id.IsNull() { image, _, err = getAwsWorkspacesImageWithId(ctx, d.client, &resp.Diagnostics, data.AccountId.ValueString(), data.Id.ValueString(), false) - } else if data.Name.ValueString() != "" { + } else { image, _, err = getAwsWorkspacesImageWithName(ctx, d.client, &resp.Diagnostics, data.AccountId.ValueString(), data.Name.ValueString()) } @@ -68,7 +68,7 @@ func (d *AwsWorkspacesImageDataSource) Read(ctx context.Context, req datasource. } // Map response body to schema and populate computed attribute values - data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, image) + data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, false, image) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) diff --git a/internal/quickcreate/qcs_image/aws_workspaces_image_data_source_model.go b/internal/quickcreate/qcs_image/aws_workspaces_image_data_source_model.go index e973add..09e220e 100644 --- a/internal/quickcreate/qcs_image/aws_workspaces_image_data_source_model.go +++ b/internal/quickcreate/qcs_image/aws_workspaces_image_data_source_model.go @@ -2,33 +2,16 @@ package qcs_image import ( - "context" "regexp" - quickcreateservice "github.com/citrix/citrix-daas-rest-go/citrixquickcreate" "github.com/citrix/terraform-provider-citrix/internal/util" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" ) -type AwsWorkspacesImageDataSourceModel struct { - Id types.String `tfsdk:"id"` - AccountId types.String `tfsdk:"account_id"` - AwsImageId types.String `tfsdk:"aws_image_id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - SessionSupport types.String `tfsdk:"session_support"` - OperatingSystem types.String `tfsdk:"operating_system"` - Tenancy types.String `tfsdk:"tenancy"` - IngestionProcess types.String `tfsdk:"ingestion_process"` - State types.String `tfsdk:"state"` -} - -func (AwsWorkspacesImageDataSourceModel) GetSchema() schema.Schema { +func (AwsWorkspacesImageModel) GetDataSourceSchema() schema.Schema { return schema.Schema{ Description: "DaaS Quick Deploy - AWS WorkSpaces Core --- Data Source of an AWS WorkSpaces image.", Attributes: map[string]schema.Attribute{ @@ -37,7 +20,7 @@ func (AwsWorkspacesImageDataSourceModel) GetSchema() schema.Schema { Optional: true, Validators: []validator.String{ stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - stringvalidator.ExactlyOneOf(path.MatchRoot("id"), path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. }, }, "account_id": schema.StringAttribute{ @@ -50,9 +33,16 @@ func (AwsWorkspacesImageDataSourceModel) GetSchema() schema.Schema { "name": schema.StringAttribute{ Description: "Name of the image.", Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, }, "aws_image_id": schema.StringAttribute{ - Description: "Id of the image on AWS.", + Description: "Id of the image to be imported in AWS.", + Computed: true, + }, + "aws_imported_image_id": schema.StringAttribute{ + Description: "The Id of the image imported in AWS WorkSpaces.", Computed: true, }, "description": schema.StringAttribute{ @@ -83,21 +73,6 @@ func (AwsWorkspacesImageDataSourceModel) GetSchema() schema.Schema { } } -func (AwsWorkspacesImageDataSourceModel) GetAttributes() map[string]schema.Attribute { - return AwsWorkspacesImageDataSourceModel{}.GetSchema().Attributes -} - -func (r AwsWorkspacesImageDataSourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, image *quickcreateservice.AwsEdcImage) AwsWorkspacesImageDataSourceModel { - r.Id = types.StringValue(image.GetImageId()) - r.AccountId = types.StringValue(image.GetAccountId()) - r.AwsImageId = types.StringValue(image.GetAmazonImageId()) - r.Name = types.StringValue(image.GetName()) - r.Description = types.StringValue(image.GetDescription()) - r.SessionSupport = types.StringValue(util.SessionSupportEnumToString(image.GetSessionSupport())) - r.OperatingSystem = types.StringValue(util.OperatingSystemTypeEnumToString(image.GetOperatingSystem())) - r.Tenancy = types.StringValue(util.AwsEdcWorkspaceImageTenancyEnumToString(image.GetWorkspaceImageTenancy())) - r.IngestionProcess = types.StringValue(util.AwsEdcWorkspaceImageIngestionProcessEnumToString(image.GetIngestionProcess())) - r.State = types.StringValue(util.AwsEdcWorkspaceImageStateEnumToString(image.GetWorkspaceImageState())) - - return r +func (AwsWorkspacesImageModel) GetDataSourceAttributes() map[string]schema.Attribute { + return AwsWorkspacesImageModel{}.GetDataSourceSchema().Attributes } diff --git a/internal/quickcreate/qcs_image/aws_workspaces_image_resource.go b/internal/quickcreate/qcs_image/aws_workspaces_image_resource.go index edfd2fb..0ffdd34 100644 --- a/internal/quickcreate/qcs_image/aws_workspaces_image_resource.go +++ b/internal/quickcreate/qcs_image/aws_workspaces_image_resource.go @@ -42,7 +42,7 @@ func (r *awsWorkspacesImageResource) Metadata(_ context.Context, req resource.Me // Schema defines the schema for the resource. func (r *awsWorkspacesImageResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = AwsWorkspacesImageResourceModel{}.GetSchema() + resp.Schema = AwsWorkspacesImageModel{}.GetSchema() } // Configure adds the provider configured client to the resource. @@ -59,7 +59,7 @@ func (r *awsWorkspacesImageResource) Create(ctx context.Context, req resource.Cr defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from plan - var plan AwsWorkspacesImageResourceModel + var plan AwsWorkspacesImageModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -125,7 +125,7 @@ func (r *awsWorkspacesImageResource) Create(ctx context.Context, req resource.Cr } // Map response body to schema and populate computed attribute values - plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, image) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, true, image) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -140,7 +140,7 @@ func (r *awsWorkspacesImageResource) Read(ctx context.Context, req resource.Read defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from state - var state AwsWorkspacesImageResourceModel + var state AwsWorkspacesImageModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -156,7 +156,7 @@ func (r *awsWorkspacesImageResource) Read(ctx context.Context, req resource.Read } // Map response body to schema and populate computed attribute values - state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, image) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, true, image) // Set state to fully populated data diags = resp.State.Set(ctx, state) @@ -181,7 +181,7 @@ func (r *awsWorkspacesImageResource) Delete(ctx context.Context, req resource.De defer util.PanicHandler(&resp.Diagnostics) // Retrieve values from state - var state AwsWorkspacesImageResourceModel + var state AwsWorkspacesImageModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -222,7 +222,7 @@ func (r *awsWorkspacesImageResource) ImportState(ctx context.Context, req resour func (r *awsWorkspacesImageResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { defer util.PanicHandler(&resp.Diagnostics) - var data AwsWorkspacesImageResourceModel + var data AwsWorkspacesImageModel diags := req.Config.Get(ctx, &data) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/internal/quickcreate/qcs_image/aws_workspaces_image_resource_model.go b/internal/quickcreate/qcs_image/aws_workspaces_image_resource_model.go index 35de721..1f1ee9c 100644 --- a/internal/quickcreate/qcs_image/aws_workspaces_image_resource_model.go +++ b/internal/quickcreate/qcs_image/aws_workspaces_image_resource_model.go @@ -16,20 +16,21 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type AwsWorkspacesImageResourceModel struct { - Id types.String `tfsdk:"id"` - AccountId types.String `tfsdk:"account_id"` - AwsImageId types.String `tfsdk:"aws_image_id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - SessionSupport types.String `tfsdk:"session_support"` - OperatingSystem types.String `tfsdk:"operating_system"` - Tenancy types.String `tfsdk:"tenancy"` - IngestionProcess types.String `tfsdk:"ingestion_process"` - State types.String `tfsdk:"state"` +type AwsWorkspacesImageModel struct { + Id types.String `tfsdk:"id"` + AccountId types.String `tfsdk:"account_id"` + AwsImageId types.String `tfsdk:"aws_image_id"` + AwsImportedImageId types.String `tfsdk:"aws_imported_image_id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + SessionSupport types.String `tfsdk:"session_support"` + OperatingSystem types.String `tfsdk:"operating_system"` + Tenancy types.String `tfsdk:"tenancy"` + IngestionProcess types.String `tfsdk:"ingestion_process"` + State types.String `tfsdk:"state"` } -func (AwsWorkspacesImageResourceModel) GetSchema() schema.Schema { +func (AwsWorkspacesImageModel) GetSchema() schema.Schema { return schema.Schema{ Description: "DaaS Quick Deploy - AWS WorkSpaces Core --- Manages an AWS WorkSpaces image.", Attributes: map[string]schema.Attribute{ @@ -48,7 +49,7 @@ func (AwsWorkspacesImageResourceModel) GetSchema() schema.Schema { }, }, "aws_image_id": schema.StringAttribute{ - Description: "Id of the image on AWS.", + Description: "Id of the image to be imported in AWS.", Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), @@ -57,6 +58,10 @@ func (AwsWorkspacesImageResourceModel) GetSchema() schema.Schema { stringvalidator.RegexMatches(regexp.MustCompile(util.AwsAmiAndWsiRegex), "must be in AMI (`ami-{ImageId}`) or WSI (`wsi-{ImageId}`) format"), }, }, + "aws_imported_image_id": schema.StringAttribute{ + Description: "The Id of the image imported in AWS WorkSpaces.", + Computed: true, + }, "name": schema.StringAttribute{ Description: "Name of the image.", Required: true, @@ -128,14 +133,17 @@ func (AwsWorkspacesImageResourceModel) GetSchema() schema.Schema { } } -func (AwsWorkspacesImageResourceModel) GetAttributes() map[string]schema.Attribute { - return AwsWorkspacesImageResourceModel{}.GetSchema().Attributes +func (AwsWorkspacesImageModel) GetAttributes() map[string]schema.Attribute { + return AwsWorkspacesImageModel{}.GetSchema().Attributes } -func (r AwsWorkspacesImageResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, image *quickcreateservice.AwsEdcImage) AwsWorkspacesImageResourceModel { +func (r AwsWorkspacesImageModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, isResource bool, image *quickcreateservice.AwsEdcImage) AwsWorkspacesImageModel { r.Id = types.StringValue(image.GetImageId()) r.AccountId = types.StringValue(image.GetAccountId()) - r.AwsImageId = types.StringValue(image.GetAmazonImageId()) + if !isResource { + r.AwsImageId = types.StringNull() + } + r.AwsImportedImageId = types.StringValue(image.GetAmazonImageId()) r.Name = types.StringValue(image.GetName()) r.Description = types.StringValue(image.GetDescription()) r.SessionSupport = types.StringValue(util.SessionSupportEnumToString(image.GetSessionSupport())) diff --git a/internal/storefront/stf_deployment/stf_deployment_resource_model.go b/internal/storefront/stf_deployment/stf_deployment_resource_model.go index 11de466..91b5f4d 100644 --- a/internal/storefront/stf_deployment/stf_deployment_resource_model.go +++ b/internal/storefront/stf_deployment/stf_deployment_resource_model.go @@ -70,7 +70,7 @@ func (r STFSecureTicketAuthority) GetKey() string { return r.StaUrl.ValueString() } -func (r STFSecureTicketAuthority) RefreshListItem(_ context.Context, _ *diag.Diagnostics, sta citrixstorefront.STFSTAUrlModel) util.ModelWithAttributes { +func (r STFSecureTicketAuthority) RefreshListItem(_ context.Context, _ *diag.Diagnostics, sta citrixstorefront.STFSTAUrlModel) util.ResourceModelWithAttributes { r.AuthorityId = types.StringValue(*sta.AuthorityId.Get()) r.StaUrl = types.StringValue(*sta.StaUrl.Get()) r.StaValidationEnabled = types.BoolValue(*sta.StaValidationEnabled.Get()) @@ -142,7 +142,7 @@ func (r RoamingGateway) GetKey() string { return r.Name.ValueString() } -func (r RoamingGateway) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, roamingGateway citrixstorefront.STFRoamingGatewayResponseModel) util.ModelWithAttributes { +func (r RoamingGateway) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, roamingGateway citrixstorefront.STFRoamingGatewayResponseModel) util.ResourceModelWithAttributes { r.Name = types.StringValue(*roamingGateway.Name.Get()) r.LogonType = types.StringValue(*roamingGateway.LogonType.Get()) if roamingGateway.SmartCardFallbackLogonType.IsSet() { diff --git a/internal/storefront/stf_multi_site/stf_user_farm_mapping_resource_model.go b/internal/storefront/stf_multi_site/stf_user_farm_mapping_resource_model.go index 420b4ee..5e64fcd 100644 --- a/internal/storefront/stf_multi_site/stf_user_farm_mapping_resource_model.go +++ b/internal/storefront/stf_multi_site/stf_user_farm_mapping_resource_model.go @@ -59,7 +59,7 @@ func (r UserFarmMappingGroup) GetKey() string { return r.GroupName.ValueString() } -func (r UserFarmMappingGroup) RefreshListItem(_ context.Context, _ *diag.Diagnostics, item citrixstorefront.STFGroupMemberResponseModel) util.ModelWithAttributes { +func (r UserFarmMappingGroup) RefreshListItem(_ context.Context, _ *diag.Diagnostics, item citrixstorefront.STFGroupMemberResponseModel) util.ResourceModelWithAttributes { // Implement the logic to refresh the list item based on the item groupName := types.StringValue(*item.GroupName.Get()) r.GroupName = groupName @@ -139,7 +139,7 @@ func (r EquivalentFarmSet) GetKey() string { return r.Name.ValueString() } -func (r EquivalentFarmSet) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, item citrixstorefront.STFFarmSetResponseModel) util.ModelWithAttributes { +func (r EquivalentFarmSet) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, item citrixstorefront.STFFarmSetResponseModel) util.ResourceModelWithAttributes { // Implement the logic to refresh the list item based on the item name := types.StringValue(*item.Name.Get()) r.Name = name diff --git a/internal/test/admin_role_data_source_test.go b/internal/test/admin_role_data_source_test.go new file mode 100644 index 0000000..176ebc4 --- /dev/null +++ b/internal/test/admin_role_data_source_test.go @@ -0,0 +1,92 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// TestAdminRoleDataSourcePreCheck validates the necessary env variable exist in the testing environment +func TestAdminRoleDataSourcePreCheck(t *testing.T) { + if v := os.Getenv("TEST_ADMIN_ROLE_DATA_SOURCE_ID"); v == "" { + t.Fatal("TEST_ADMIN_ROLE_DATA_SOURCE_ID must be set for acceptance tests") + } + + if v := os.Getenv("TEST_ADMIN_ROLE_DATA_SOURCE_NAME"); v == "" { + t.Fatal("TEST_ADMIN_ROLE_DATA_SOURCE_NAME must be set for acceptance tests") + } + + if v := os.Getenv("TEST_ADMIN_ROLE_DATA_SOURCE_EXPECTED_DESCRIPTION"); v == "" { + t.Fatal("TEST_ADMIN_ROLE_DATA_SOURCE_EXPECTED_DESCRIPTION must be set for acceptance tests") + } +} + +func TestAdminRoleDataSource(t *testing.T) { + adminRoleDataSourceId := os.Getenv("TEST_ADMIN_ROLE_DATA_SOURCE_ID") + adminRoleDataSourceName := os.Getenv("TEST_ADMIN_ROLE_DATA_SOURCE_NAME") + expectedDescription := os.Getenv("TEST_ADMIN_ROLE_DATA_SOURCE_EXPECTED_DESCRIPTION") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: func() { + TestProviderPreCheck(t) + TestAdminRoleDataSourcePreCheck(t) + }, + Steps: []resource.TestStep{ + // Read testing using ID + { + Config: BuildAdminRoleDataSourceWithId(t), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify the ID of the admin role data source + resource.TestCheckResourceAttr("data.citrix_admin_role.test_admin_role_data_source_with_id", "id", adminRoleDataSourceId), + // Verify the name of the admin role data source + resource.TestCheckResourceAttr("data.citrix_admin_role.test_admin_role_data_source_with_id", "name", adminRoleDataSourceName), + // Verify the description attribute of the admin role data source + resource.TestCheckResourceAttr("data.citrix_admin_role.test_admin_role_data_source_with_id", "description", expectedDescription), + ), + }, + // Read testing using Name + { + Config: BuildAdminRoleDataSourceWithName(t), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify the ID of the admin role data source + resource.TestCheckResourceAttr("data.citrix_admin_role.test_admin_role_data_source_with_name", "id", adminRoleDataSourceId), + // Verify the name of the admin role data source + resource.TestCheckResourceAttr("data.citrix_admin_role.test_admin_role_data_source_with_name", "name", adminRoleDataSourceName), + // Verify the description attribute of the admin role data source + resource.TestCheckResourceAttr("data.citrix_admin_role.test_admin_role_data_source_with_name", "description", expectedDescription), + ), + }, + }, + }) +} + +func BuildAdminRoleDataSourceWithId(t *testing.T) string { + adminRoleId := os.Getenv("TEST_ADMIN_ROLE_DATA_SOURCE_ID") + + return fmt.Sprintf(admin_role_test_data_source_using_id, adminRoleId) +} + +func BuildAdminRoleDataSourceWithName(t *testing.T) string { + adminRoleName := os.Getenv("TEST_ADMIN_ROLE_DATA_SOURCE_NAME") + + return fmt.Sprintf(admin_role_test_data_source_using_name, adminRoleName) +} + +var ( + admin_role_test_data_source_using_id = ` + data "citrix_admin_role" "test_admin_role_data_source_with_id" { + id = "%s" + } + ` + + admin_role_test_data_source_using_name = ` + data "citrix_admin_role" "test_admin_role_data_source_with_name" { + name = "%s" + } + ` +) diff --git a/internal/test/admin_user_data_source_test.go b/internal/test/admin_user_data_source_test.go new file mode 100644 index 0000000..fef38cb --- /dev/null +++ b/internal/test/admin_user_data_source_test.go @@ -0,0 +1,61 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// TestAdminUserDataSourcePreCheck validates the necessary env variable exist in the testing environment +func TestAdminUserDataSourcePreCheck(t *testing.T) { + if v := os.Getenv("TEST_ADMIN_USER_DATA_SOURCE_ID"); v == "" { + t.Fatal("TEST_ADMIN_USER_DATA_SOURCE_ID must be set for acceptance tests") + } + + if v := os.Getenv("TEST_ADMIN_USER_DATA_SOURCE_IS_ENABLED"); v == "" { + t.Fatal("TEST_ADMIN_USER_DATA_SOURCE_IS_ENABLED must be set for acceptance tests") + } +} + +func TestAdminUserDataSource(t *testing.T) { + adminUserDataSourceId := os.Getenv("TEST_ADMIN_USER_DATA_SOURCE_ID") + adminUserDataSourceIsEnabled := os.Getenv("TEST_ADMIN_USER_DATA_SOURCE_IS_ENABLED") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: func() { + TestProviderPreCheck(t) + TestAdminUserDataSourcePreCheck(t) + }, + Steps: []resource.TestStep{ + // Read testing using ID + { + Config: BuildAdminUserDataSourceWithId(t), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify the ID of the admin user data source + resource.TestCheckResourceAttr("data.citrix_admin_user.test_admin_user_data_source_with_id", "id", adminUserDataSourceId), + // Verify the is_enabled attribute of the admin user data source + resource.TestCheckResourceAttr("data.citrix_admin_role.test_admin_user_data_source_with_id", "is_enabled", adminUserDataSourceIsEnabled), + ), + }, + }, + }) +} + +func BuildAdminUserDataSourceWithId(t *testing.T) string { + adminUserId := os.Getenv("TEST_ADMIN_USER_DATA_SOURCE_ID") + + return fmt.Sprintf(admin_user_test_data_source_using_id, adminUserId) +} + +var ( + admin_user_test_data_source_using_id = ` + data "citrix_admin_user" "test_admin_user_data_source_with_id" { + id = "%s" + } + ` +) diff --git a/internal/test/azure_mcs_suite_test.go b/internal/test/azure_mcs_suite_test.go index ee34543..9adfd59 100644 --- a/internal/test/azure_mcs_suite_test.go +++ b/internal/test/azure_mcs_suite_test.go @@ -296,6 +296,7 @@ func TestAzureMcs(t *testing.T) { // Create and Read testing { Config: composeTestResourceTf( + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure, "", "ActiveDirectory"), BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_updated_testResource_azure), @@ -308,6 +309,8 @@ func TestAzureMcs(t *testing.T) { resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "name", deliveryGroupName), // Verify description of delivery group resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "description", "Delivery Group for testing"), + // Verify delivery type of delivery group + resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "delivery_type", "DesktopsOnly"), // Verify number of desktops resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "desktops.#", "2"), // Verify number of reboot schedules @@ -334,6 +337,7 @@ func TestAzureMcs(t *testing.T) { // Delivery Group: Update name, description and add machine testing { Config: composeTestResourceTf( + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources_updated), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_updated, "", "ActiveDirectory"), BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_updated_testResource_azure), @@ -358,10 +362,14 @@ func TestAzureMcs(t *testing.T) { resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "name", fmt.Sprintf("%s-updated", deliveryGroupName)), // Verify description of delivery group resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "description", "Delivery Group for testing updated"), + // Verify delivery type of delivery group + resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "delivery_type", "DesktopsAndApps"), // Verify number of desktops resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "desktops.#", "1"), // Verify number of reboot schedules resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "reboot_schedules.#", "1"), + // Verify number of reboot schedules + resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "reboot_schedules.0.ignore_maintenance_mode", "false"), // Verify total number of machines in delivery group resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "total_machines", "2"), // Verify the policy set id is not assigned to the delivery group @@ -392,6 +400,7 @@ func TestAzureMcs(t *testing.T) { // Machine Catalog: Delete machine test { Config: composeTestResourceTf( + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_delete_machine, "", "ActiveDirectory"), BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_updated_testResource_azure), @@ -417,6 +426,7 @@ func TestAzureMcs(t *testing.T) { { Config: composeTestResourceTf( BuildAdminFolderResource(t, testAdminFolderResource, "ContainsApplications"), + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_delete_machine, "", "ActiveDirectory"), BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_updated_testResource_azure), @@ -449,6 +459,7 @@ func TestAzureMcs(t *testing.T) { { Config: composeTestResourceTf( BuildAdminFolderResource(t, testAdminFolderResource, "ContainsApplicationGroups"), + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_delete_machine, "", "ActiveDirectory"), BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_updated_testResource_azure), @@ -475,6 +486,7 @@ func TestAzureMcs(t *testing.T) { { Config: composeTestResourceTf( BuildAdminFolderResource(t, testAdminFolderResource_nameAndParentPathUpdated1, "ContainsApplications"), + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_delete_machine, "", "ActiveDirectory"), BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_updated_testResource_azure), @@ -501,6 +513,7 @@ func TestAzureMcs(t *testing.T) { { Config: composeTestResourceTf( BuildAdminFolderResource(t, testAdminFolderResource_parentPathRemoved, "ContainsApplications"), + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_delete_machine, "", "ActiveDirectory"), BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_updated_testResource_azure), @@ -525,6 +538,7 @@ func TestAzureMcs(t *testing.T) { { Config: composeTestResourceTf( BuildAdminFolderResourceWithTwoTypes(t, testAdminFolderResource_twoTypes, "ContainsMachineCatalogs", "ContainsApplications"), + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_delete_machine, "", "ActiveDirectory"), BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_updated_testResource_azure), @@ -557,6 +571,7 @@ func TestAzureMcs(t *testing.T) { BuildPolicySetResource(t, policy_set_testResource), BuildApplicationResource(t, testApplicationResource), BuildAdminFolderResourceWithTwoTypes(t, testAdminFolderResource_twoTypes, "ContainsMachineCatalogs", "ContainsApplications"), + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_delete_machine, "", "ActiveDirectory"), BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_updated_testResource_azure), @@ -674,6 +689,7 @@ func TestAzureMcs(t *testing.T) { BuildPolicySetResource(t, policy_set_updated_testResource), BuildApplicationResource(t, testApplicationResource_updated), BuildAdminFolderResourceWithTwoTypes(t, testAdminFolderResource_twoTypes, "ContainsMachineCatalogs", "ContainsApplications"), + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_delete_machine, "", "ActiveDirectory"), BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_updated_testResource_azure), @@ -736,6 +752,7 @@ func TestAzureMcs(t *testing.T) { BuildAdminUserResource(t, adminUserTestResource), BuildAdminRoleResource(t, adminRoleTestResource_updated), BuildAdminScopeResource(t, adminScopeTestResource_updated), + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_delete_machine, "", "ActiveDirectory"), BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_updated_testResource_azure), @@ -772,6 +789,7 @@ func TestAzureMcs(t *testing.T) { BuildAdminUserResource(t, adminUserTestResource_updated), BuildAdminRoleResource(t, adminRoleTestResource_updated), BuildAdminScopeResource(t, adminScopeTestResource_updated), + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_delete_machine, "", "ActiveDirectory"), BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_updated_testResource_azure), diff --git a/internal/test/delivery_group_test.go b/internal/test/delivery_group_test.go index 641915d..851529e 100644 --- a/internal/test/delivery_group_test.go +++ b/internal/test/delivery_group_test.go @@ -58,6 +58,7 @@ func TestDeliveryGroupResourceAzureRM(t *testing.T) { // Create and Read testing { Config: composeTestResourceTf( + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildPolicySetResourceWithoutDeliveryGroup(t), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_updated, "", "ActiveDirectory"), @@ -71,6 +72,8 @@ func TestDeliveryGroupResourceAzureRM(t *testing.T) { resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "name", name), // Verify description of delivery group resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "description", "Delivery Group for testing"), + // Verify delivery type of delivery group + resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "delivery_type", "DesktopsOnly"), // Verify number of desktops resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "desktops.#", "2"), // Verify number of reboot schedules @@ -89,12 +92,13 @@ func TestDeliveryGroupResourceAzureRM(t *testing.T) { ImportStateVerify: true, // The last_updated attribute does not exist in the Orchestration // API, therefore there is no value for it during import. - ImportStateVerifyIgnore: []string{"last_updated", "autoscale_settings", "associated_machine_catalogs", "reboot_schedules"}, + ImportStateVerifyIgnore: []string{"last_updated", "autoscale_settings", "associated_machine_catalogs", "reboot_schedules", "delivery_type"}, }, // Update name, description and add machine testing { Config: composeTestResourceTf( + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources_updated), BuildPolicySetResourceWithoutDeliveryGroup(t), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_updated, "", "ActiveDirectory"), @@ -108,12 +112,14 @@ func TestDeliveryGroupResourceAzureRM(t *testing.T) { resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "name", fmt.Sprintf("%s-updated", name)), // Verify description of delivery group resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "description", "Delivery Group for testing updated"), + // Verify delivery type of delivery group + resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "delivery_type", "DesktopsAndApps"), // Verify number of desktops resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "desktops.#", "1"), // Verify number of reboot schedules resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "reboot_schedules.#", "1"), // Verify number of reboot schedules - resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "reboot_schedules.ignore_maintenance_mode", "false"), + resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "reboot_schedules.0.ignore_maintenance_mode", "false"), // Verify total number of machines in delivery group resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "total_machines", "2"), // Verify the policy set id assigned to the delivery group @@ -124,6 +130,7 @@ func TestDeliveryGroupResourceAzureRM(t *testing.T) { // Remove machine testing { Config: composeTestResourceTf( + BuildDesktopIconResource(t, testDesktopIconResource), BuildDeliveryGroupResource(t, testDeliveryGroupResources), BuildPolicySetResourceWithoutDeliveryGroup(t), BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_updated, "", "ActiveDirectory"), @@ -137,6 +144,8 @@ func TestDeliveryGroupResourceAzureRM(t *testing.T) { resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "total_machines", "1"), // Verify the policy set id assigned to the delivery group resource.TestCheckNoResourceAttr("citrix_delivery_group.testDeliveryGroup", "policy_set_id"), + // Verify delivery type of delivery group + resource.TestCheckResourceAttr("citrix_delivery_group.testDeliveryGroup", "delivery_type", "DesktopsOnly"), ), }, // Update policy set testing @@ -164,6 +173,7 @@ var ( resource "citrix_delivery_group" "testDeliveryGroup" { name = "%s" description = "Delivery Group for testing" + delivery_type = "DesktopsOnly" minimum_functional_level = "L7_9" associated_machine_catalogs = [ { @@ -250,12 +260,14 @@ resource "citrix_delivery_group" "testDeliveryGroup" { } } ] + default_desktop_icon = citrix_desktop_icon.testDesktopIcon.id } ` testDeliveryGroupResources_updated = ` resource "citrix_delivery_group" "testDeliveryGroup" { name = "%s-updated" description = "Delivery Group for testing updated" + delivery_type = "DesktopsAndApps" minimum_functional_level = "L7_20" associated_machine_catalogs = [ { @@ -314,6 +326,7 @@ resource "citrix_delivery_group" "testDeliveryGroup" { natural_reboot_schedule = false } ] + default_desktop_icon = citrix_desktop_icon.testDesktopIcon.id } ` diff --git a/internal/test/desktop_icon_resource_test.go b/internal/test/desktop_icon_resource_test.go new file mode 100644 index 0000000..5ccdfe3 --- /dev/null +++ b/internal/test/desktop_icon_resource_test.go @@ -0,0 +1,58 @@ +package test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// TestDesktopIconPreCheck validates the necessary env variable exist +// in the testing environment +func TestDesktopIconPreCheck(t *testing.T) { + if v := os.Getenv("TEST_DESKTOP_ICON_RAW_DATA"); v == "" { + t.Fatal("TEST_DESKTOP_ICON_RAW_DATA must be set for acceptance tests") + } +} + +func TestDesktopIconResource(t *testing.T) { + desktopIconRawData := os.Getenv("TEST_DESKTOP_ICON_RAW_DATA") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: func() { + TestDesktopIconPreCheck(t) + }, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: BuildDesktopIconResource(t, testDesktopIconResource), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify raw data of desktop icon + resource.TestCheckResourceAttr("citrix_desktop_icon.testDesktopIcon", "raw_data", desktopIconRawData), + ), + }, + // ImportState testing + { + ResourceName: "citrix_desktop_icon.testDesktopIcon", + ImportState: true, + ImportStateVerify: true, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +var ( + testDesktopIconResource = ` +resource "citrix_desktop_icon" "testDesktopIcon" { + raw_data = "%s" +} +` +) + +func BuildDesktopIconResource(t *testing.T, desktopIcon string) string { + desktopIconRawData := os.Getenv("TEST_DESKTOP_ICON_RAW_DATA") + return fmt.Sprintf(desktopIcon, desktopIconRawData) +} diff --git a/internal/test/gac_discovery_resource_test.go b/internal/test/gac_discovery_resource_test.go new file mode 100644 index 0000000..9256fe9 --- /dev/null +++ b/internal/test/gac_discovery_resource_test.go @@ -0,0 +1,92 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestGacDiscoveryPreCheck(t *testing.T) { + if domain := os.Getenv("TEST_GAC_DISCOVERY_DOMAIN"); domain == "" { + t.Fatal("TEST_GAC_DISCOVERY_DOMAIN must be set for acceptance tests") + } + if service_url := os.Getenv("TEST_GAC_DISCOVERY_SERVICE_URL"); service_url == "" { + t.Fatal("TEST_GAC_DISCOVERY_SERVICE_URL must be set for acceptance tests") + } + if service_url_updated := os.Getenv("TEST_GAC_DISCOVERY_SERVICE_URL_UPDATE"); service_url_updated == "" { + t.Fatal("TEST_GAC_DISCOVERY_SERVICE_URL_UPDATE must be set for acceptance tests") + } +} + +func TestGacDiscoveryResource(t *testing.T) { + domain := os.Getenv("TEST_GAC_DISCOVERY_DOMAIN") + service_url := os.Getenv("TEST_GAC_DISCOVERY_SERVICE_URL") + service_url_updated := os.Getenv("TEST_GAC_DISCOVERY_SERVICE_URL_UPDATE") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: func() { + TestProviderPreCheck(t) + TestGacDiscoveryPreCheck(t) + }, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: BuildGacDiscoveryResource(t, gacDiscoveryTestResource, service_url), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify the domain + resource.TestCheckResourceAttr("citrix_gac_discovery.test_gac_discovery", "domain", domain), + // Verify the service urls + resource.TestCheckResourceAttr("citrix_gac_discovery.test_gac_discovery", "service_urls.#", "1"), + // Verify the service urls value + resource.TestCheckResourceAttr("citrix_gac_discovery.test_gac_discovery", "service_urls.0", service_url), + ), + }, + // ImportState testing + { + ResourceName: "citrix_gac_discovery.test_gac_discovery", + ImportState: true, + ImportStateId: domain, + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "domain", + }, + // Update and Read testing + { + Config: BuildGacDiscoveryResource(t, gacDiscoveryTestResource_updated, service_url_updated), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify the domain after update + resource.TestCheckResourceAttr("citrix_gac_discovery.test_gac_discovery", "domain", domain), + // Verify the service urls + resource.TestCheckResourceAttr("citrix_gac_discovery.test_gac_discovery", "service_urls.#", "1"), + // Verify the service urls value + resource.TestCheckResourceAttr("citrix_gac_discovery.test_gac_discovery", "service_urls.0", service_url_updated), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +var ( + gacDiscoveryTestResource = ` + resource "citrix_gac_discovery" "test_gac_discovery" { + domain = "%s" + service_urls = ["%s"] + } + ` + gacDiscoveryTestResource_updated = ` + resource "citrix_gac_discovery" "test_gac_discovery" { + domain = "%s" + service_urls = ["%s"] + } + ` +) + +func BuildGacDiscoveryResource(t *testing.T, settings string, serviceURL string) string { + val := fmt.Sprintf(settings, os.Getenv("TEST_GAC_DISCOVERY_DOMAIN"), serviceURL) + return val +} diff --git a/internal/test/machine_properties_resource_test.go b/internal/test/machine_properties_resource_test.go new file mode 100644 index 0000000..2c1daad --- /dev/null +++ b/internal/test/machine_properties_resource_test.go @@ -0,0 +1,154 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// TestMachinePropertiesResourcePreCheck validates the necessary env variable exist in the testing environment +func TestMachinePropertiesResourcePreCheck(t *testing.T) { + if v := os.Getenv("TEST_MACHINE_PROPERTIES_RESOURCE_NAME"); v == "" { + t.Fatal("TEST_MACHINE_PROPERTIES_RESOURCE_NAME must be set for acceptance tests") + } + + if v := os.Getenv("TEST_MACHINE_PROPERTIES_RESOURCE_NAME_UPDATED"); v == "" { + t.Fatal("TEST_MACHINE_PROPERTIES_RESOURCE_NAME_UPDATED must be set for acceptance tests") + } + + if v := os.Getenv("TEST_MACHINE_PROPERTIES_RESOURCE_MACHINE_CATALOG_ID"); v == "" { + t.Fatal("TEST_MACHINE_PROPERTIES_RESOURCE_MACHINE_CATALOG_ID must be set for acceptance tests") + } + + if v := os.Getenv("TEST_MACHINE_PROPERTIES_RESOURCE_MACHINE_CATALOG_ID_UPDATED"); v == "" { + t.Fatal("TEST_MACHINE_PROPERTIES_RESOURCE_MACHINE_CATALOG_ID_UPDATED must be set for acceptance tests") + } + + if v := os.Getenv("TEST_MACHINE_PROPERTIES_RESOURCE_TAG"); v == "" { + t.Fatal("TEST_MACHINE_PROPERTIES_RESOURCE_TAG must be set for acceptance tests") + } + + if v := os.Getenv("TEST_MACHINE_PROPERTIES_RESOURCE_TAG_UPDATED"); v == "" { + t.Fatal("TEST_MACHINE_PROPERTIES_RESOURCE_TAG_UPDATED must be set for acceptance tests") + } +} + +func TestMachinePropertiesResource(t *testing.T) { + machineName := os.Getenv("TEST_MACHINE_PROPERTIES_RESOURCE_NAME") + machineCatalogId := os.Getenv("TEST_MACHINE_PROPERTIES_RESOURCE_MACHINE_CATALOG_ID") + tagId := os.Getenv("TEST_MACHINE_PROPERTIES_RESOURCE_TAG") + + machineName_updated := os.Getenv("TEST_MACHINE_PROPERTIES_RESOURCE_NAME_UPDATED") + machineCatalogId_updated := os.Getenv("TEST_MACHINE_PROPERTIES_RESOURCE_MACHINE_CATALOG_ID_UPDATED") + tagId_updated := os.Getenv("TEST_MACHINE_PROPERTIES_RESOURCE_TAG_UPDATED") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: func() { + TestProviderPreCheck(t) + TestMachinePropertiesResourcePreCheck(t) + }, + Steps: []resource.TestStep{ + { + Config: BuildMachinePropertiesResourceWithoutTag(t, machineName, machineCatalogId), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify the id of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "name", machineName), + // Verify the machine catalog id of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "machine_catalog_id", machineCatalogId), + // Verify the number of tags of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "tags.#", "0"), + ), + }, + // ImportState testing + { + ResourceName: "citrix_machine_properties.test_machine_properties", + ImportState: true, + ImportStateVerify: true, + }, + // add tag + { + Config: BuildMachinePropertiesResource(t, machineName, machineCatalogId, tagId), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify the id of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "name", machineName), + // Verify the machine catalog id of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "machine_catalog_id", machineCatalogId), + // Verify the number of tags of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "tags.#", "1"), + // Verify the tag id of the machine properties resource + resource.TestCheckTypeSetElemAttr("citrix_machine_properties.test_machine_properties", "tags.*", tagId), + ), + }, + // update machine id + { + Config: BuildMachinePropertiesResource(t, machineName_updated, machineCatalogId_updated, tagId), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify the id of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "name", machineName_updated), + // Verify the machine catalog id of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "machine_catalog_id", machineCatalogId_updated), + // Verify the number of tags of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "tags.#", "1"), + // Verify the tag id of the machine properties resource + resource.TestCheckTypeSetElemAttr("citrix_machine_properties.test_machine_properties", "tags.*", tagId), + ), + }, + // update tag + { + Config: BuildMachinePropertiesResource(t, machineName_updated, machineCatalogId_updated, tagId_updated), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify the id of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "name", machineName_updated), + // Verify the machine catalog id of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "machine_catalog_id", machineCatalogId_updated), + // Verify the number of tags of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "tags.#", "1"), + // Verify the tag id of the machine properties resource + resource.TestCheckTypeSetElemAttr("citrix_machine_properties.test_machine_properties", "tags.*", tagId_updated), + ), + }, + // remove tag + { + Config: BuildMachinePropertiesResourceWithoutTag(t, machineName_updated, machineCatalogId_updated), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify the id of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "name", machineName_updated), + // Verify the machine catalog id of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "machine_catalog_id", machineCatalogId_updated), + // Verify the number of tags of the machine properties resource + resource.TestCheckResourceAttr("citrix_machine_properties.test_machine_properties", "tags.#", "0"), + ), + }, + }, + }) +} + +func BuildMachinePropertiesResourceWithoutTag(t *testing.T, machineName string, machineCatalogId string) string { + return fmt.Sprintf(machine_properties_test_resource_without_tag, machineName, machineCatalogId) +} + +func BuildMachinePropertiesResource(t *testing.T, machineName string, machineCatalogId string, tagId string) string { + return fmt.Sprintf(machine_properties_test_resource, machineName, machineCatalogId, tagId) +} + +var ( + machine_properties_test_resource_without_tag = ` + resource "citrix_machine_properties" "test_machine_properties" { + name = "%s" + machine_catalog_id = "%s" + } + ` + + machine_properties_test_resource = ` + resource "citrix_machine_properties" "test_machine_properties" { + name = "%s" + machine_catalog_id = "%s" + tags = [ "%s" ] + } + ` +) diff --git a/internal/test/policy_set_data_source_test.go b/internal/test/policy_set_data_source_test.go new file mode 100644 index 0000000..3140648 --- /dev/null +++ b/internal/test/policy_set_data_source_test.go @@ -0,0 +1,95 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestPolicySetDataSourcePreCheck(t *testing.T) { + if v := os.Getenv("TEST_POLICY_SET_DATA_SOURCE_ID"); v == "" { + t.Fatal("TEST_POLICY_SET_DATA_SOURCE_ID must be set for acceptance tests") + } + + if v := os.Getenv("TEST_POLICY_SET_DATA_SOURCE_NAME"); v == "" { + t.Fatal("TEST_POLICY_SET_DATA_SOURCE_NAME must be set for acceptance tests") + } + + if v := os.Getenv("TEST_POLICY_SET_DATA_SOURCE_EXPECTED_POLICY_COUNT"); v == "" { + t.Fatal("TEST_POLICY_SET_DATA_SOURCE_EXPECTED_POLICY_COUNT must be set for acceptance tests") + } +} + +func TestPolicySetDataSource(t *testing.T) { + policySetId := os.Getenv("TEST_POLICY_SET_DATA_SOURCE_ID") + policySetName := os.Getenv("TEST_POLICY_SET_DATA_SOURCE_NAME") + policySetExpectedPolicyCount := os.Getenv("TEST_POLICY_SET_DATA_SOURCE_EXPECTED_POLICY_COUNT") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: func() { + TestProviderPreCheck(t) + TestPolicySetDataSourcePreCheck(t) + }, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: composeTestResourceTf( + BuildPolicySetDataSourceById(t), + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify name of the policy set + resource.TestCheckResourceAttr("data.citrix_policy_set.test_policy_set", "id", policySetId), + // Verify description of the policy set + resource.TestCheckResourceAttr("data.citrix_policy_set.test_policy_set", "name", policySetName), + // Verify type of the policy set + resource.TestCheckResourceAttr("data.citrix_policy_set.test_policy_set", "policies.#", policySetExpectedPolicyCount), + ), + }, + // Reorder and Read testing + { + Config: composeTestResourceTf( + BuildPolicySetDataSourceByName(t), + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify name of the policy set + resource.TestCheckResourceAttr("data.citrix_policy_set.test_policy_set", "id", policySetId), + // Verify description of the policy set + resource.TestCheckResourceAttr("data.citrix_policy_set.test_policy_set", "name", policySetName), + // Verify type of the policy set + resource.TestCheckResourceAttr("data.citrix_policy_set.test_policy_set", "policies.#", policySetExpectedPolicyCount), + ), + }, + }, + }) +} + +func BuildPolicySetDataSourceById(t *testing.T) string { + policySetId := os.Getenv("TEST_POLICY_SET_DATA_SOURCE_ID") + + return fmt.Sprintf(policy_set_testResource_by_id, policySetId) +} + +func BuildPolicySetDataSourceByName(t *testing.T) string { + policySetName := os.Getenv("TEST_POLICY_SET_DATA_SOURCE_NAME") + + return fmt.Sprintf(policy_set_testResource_by_name, policySetName) +} + +var ( + policy_set_testResource_by_id = ` +data "citrix_policy_set" "test_policy_set" { + id = "%s" +} +` + + policy_set_testResource_by_name = ` +data "citrix_policy_set" "test_policy_set" { + name = "%s" +} +` +) diff --git a/internal/test/resource_locations_data_resource_test.go b/internal/test/resource_locations_data_source_test.go similarity index 52% rename from internal/test/resource_locations_data_resource_test.go rename to internal/test/resource_locations_data_source_test.go index ad22f8e..9f96fc3 100644 --- a/internal/test/resource_locations_data_resource_test.go +++ b/internal/test/resource_locations_data_source_test.go @@ -10,8 +10,8 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -func TestResourceLocationDataResourcePreCheck(t *testing.T) { - if v := os.Getenv("TEST_RESOURCE_LOCATION_NAME"); v == "" { +func TestResourceLocationDataSourcePreCheck(t *testing.T) { + if v := os.Getenv("TEST_RESOURCE_LOCATION_DATASOURCE_NAME"); v == "" { t.Fatal("TEST_RESOURCE_LOCATION_DATASOURCE_NAME must be set for acceptance tests") } @@ -20,23 +20,23 @@ func TestResourceLocationDataResourcePreCheck(t *testing.T) { } } -func TestResourceLocationDataResource(t *testing.T) { - name := os.Getenv("TEST_RESOURCE_LOCATION_NAME") - id := os.Getenv("TEST_RESOURCE_LOCATION_DATASOURCE_NAME") +func TestResourceLocationDataSource(t *testing.T) { + name := os.Getenv("TEST_RESOURCE_LOCATION_DATASOURCE_NAME") + id := os.Getenv("TEST_RESOURCE_LOCATION_ID") resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, PreCheck: func() { TestProviderPreCheck(t) - TestResourceLocationDataResourcePreCheck(t) + TestResourceLocationDataSourcePreCheck(t) }, Steps: []resource.TestStep{ // Create and Read testing { - Config: BuildResourceLocationDataResource(t, resourceLocationTestDataResource, name), + Config: BuildResourceLocationDataSource(t, resourceLocationTestDataSource, name), Check: resource.ComposeAggregateTestCheckFunc( // Verify the id of the resource location data source - resource.TestCheckResourceAttr("citrix_cloud_resource_location.test_resource_location", "id", id), + resource.TestCheckResourceAttr("data.citrix_cloud_resource_location.test_resource_location", "id", id), ), }, }, @@ -44,14 +44,14 @@ func TestResourceLocationDataResource(t *testing.T) { } var ( - resourceLocationTestDataResource = ` -resource "citrix_cloud_resource_location" "test_resource_location" { + resourceLocationTestDataSource = ` +data "citrix_cloud_resource_location" "test_resource_location" { name = "%s" } ` ) -func BuildResourceLocationDataResource(t *testing.T, resourceLocation string, resourceLocationName string) string { +func BuildResourceLocationDataSource(t *testing.T, resourceLocation string, resourceLocationName string) string { tfbody := fmt.Sprintf(resourceLocation, resourceLocationName) return tfbody } diff --git a/internal/test/storefront_server_data_source_test.go b/internal/test/storefront_server_data_source_test.go new file mode 100644 index 0000000..a859bb2 --- /dev/null +++ b/internal/test/storefront_server_data_source_test.go @@ -0,0 +1,92 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// TestStoreFrontServerDataSourcePreCheck validates the necessary env variable exist in the testing environment +func TestStoreFrontServerDataSourcePreCheck(t *testing.T) { + if v := os.Getenv("TEST_STOREFRONT_SERVER_DATA_SOURCE_ID"); v == "" { + t.Fatal("TEST_STOREFRONT_SERVER_DATA_SOURCE_ID must be set for acceptance tests") + } + + if v := os.Getenv("TEST_STOREFRONT_SERVER_DATA_SOURCE_NAME"); v == "" { + t.Fatal("TEST_STOREFRONT_SERVER_DATA_SOURCE_NAME must be set for acceptance tests") + } + + if v := os.Getenv("TEST_STOREFRONT_SERVER_DATA_SOURCE_EXPECTED_DESCRIPTION"); v == "" { + t.Fatal("TEST_STOREFRONT_SERVER_DATA_SOURCE_EXPECTED_DESCRIPTION must be set for acceptance tests") + } +} + +func TestStoreFrontServerDataSource(t *testing.T) { + storeFrontServerId := os.Getenv("TEST_STOREFRONT_SERVER_DATA_SOURCE_ID") + storeFrontServerName := os.Getenv("TEST_STOREFRONT_SERVER_DATA_SOURCE_NAME") + expectedDescription := os.Getenv("TEST_STOREFRONT_SERVER_DATA_SOURCE_EXPECTED_DESCRIPTION") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: func() { + TestProviderPreCheck(t) + TestAdminRoleDataSourcePreCheck(t) + }, + Steps: []resource.TestStep{ + // Read testing using ID + { + Config: BuildStoreFrontServerDataSourceWithId(t), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify the ID of the admin role data source + resource.TestCheckResourceAttr("data.citrix_storefront_server.test_storefront_server_data_source_with_id", "id", storeFrontServerId), + // Verify the name of the admin role data source + resource.TestCheckResourceAttr("data.citrix_storefront_server.test_storefront_server_data_source_with_id", "name", storeFrontServerName), + // Verify the description attribute of the admin role data source + resource.TestCheckResourceAttr("data.citrix_storefront_server.test_storefront_server_data_source_with_id", "description", expectedDescription), + ), + }, + // Read testing using Name + { + Config: BuildStoreFrontServerDataSourceWithName(t), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify the ID of the admin role data source + resource.TestCheckResourceAttr("data.citrix_storefront_server.test_storefront_server_data_source_with_name", "id", storeFrontServerId), + // Verify the name of the admin role data source + resource.TestCheckResourceAttr("data.citrix_storefront_server.test_storefront_server_data_source_with_name", "name", storeFrontServerName), + // Verify the description attribute of the admin role data source + resource.TestCheckResourceAttr("data.citrix_storefront_server.test_storefront_server_data_source_with_name", "description", expectedDescription), + ), + }, + }, + }) +} + +func BuildStoreFrontServerDataSourceWithId(t *testing.T) string { + adminRoleId := os.Getenv("TEST_STOREFRONT_SERVER_DATA_SOURCE_ID") + + return fmt.Sprintf(storefront_server_test_data_source_using_id, adminRoleId) +} + +func BuildStoreFrontServerDataSourceWithName(t *testing.T) string { + adminRoleName := os.Getenv("TEST_STOREFRONT_SERVER_DATA_SOURCE_NAME") + + return fmt.Sprintf(storefront_server_test_data_source_using_name, adminRoleName) +} + +var ( + storefront_server_test_data_source_using_id = ` + data "citrix_storefront_server" "test_storefront_server_data_source_with_id" { + id = "%s" + } + ` + + storefront_server_test_data_source_using_name = ` + data "citrix_storefront_server" "test_storefront_server_data_source_with_name" { + name = "%s" + } + ` +) diff --git a/internal/util/common.go b/internal/util/common.go index 372cbe8..2a4db60 100644 --- a/internal/util/common.go +++ b/internal/util/common.go @@ -59,6 +59,9 @@ const AwsSubnetIdFormat = `^subnet-[a-zA-Z0-9]+$` // Domain FQDN const DomainFqdnRegex string = `^(([a-zA-Z0-9-_]){1,63}\.)+[a-zA-Z]{2,63}$` +// Lowercase Regex +const LowerCaseRegex string = `^[^A-Z]*$` + // SAM const SamRegex string = `^[a-zA-Z][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9]\\\w[\w\.\- ]+$` @@ -131,7 +134,7 @@ const SamlIdpCertRegex string = `\.[Pp][Ee][Mm]$|\.[Cc][Rr][Tt]$|\.[Cc][Ee][Rr]$ // Admin Folder Path const AdminFolderPathWithBackslashRegex string = `^[^\\].*[^\\]$` -const AdminFolderPathSpecialCharactersRegex string = `^[^/;:#.*?=<>|[\](){}"'\` + "`~]+$" +const AdminFolderPathSpecialCharactersRegex string = `^[^\/;:#.*?=<>|[\](){}"'\` + "`~]+$" // String REGEX without trailing and leading whitespace const StringWithoutTrailingLeadingWhitespaceRegex string = `^\S(.*\S)?$` @@ -591,10 +594,10 @@ type RefreshableListItemWithAttributes[clientType any] interface { GetKey() string // Refreshes the item with the client model and returns the updated item - RefreshListItem(context.Context, *diag.Diagnostics, clientType) ModelWithAttributes + RefreshListItem(context.Context, *diag.Diagnostics, clientType) ResourceModelWithAttributes // Has to implement the ModelWithAttributes interface for conversion back to a Terraform model - ModelWithAttributes + ResourceModelWithAttributes } // These functions are used by RefreshListProperties @@ -699,7 +702,7 @@ func refreshListProperties[tfType RefreshableListItemWithAttributes[clientType], newState[index] = tfItem.RefreshListItem(ctx, diagnostics, clientItem).(tfType) } else { var tfStructItem tfType - if attributeMap, err := AttributeMapFromObject(tfStructItem); err == nil { + if attributeMap, err := ResourceAttributeMapFromObject(tfStructItem); err == nil { // start with the null object to populate all nested lists/objects as null tfStructItem = defaultObjectFromObjectValue[tfType](ctx, types.ObjectNull(attributeMap)) newStateItem := tfStructItem.RefreshListItem(ctx, diagnostics, clientItem).(tfType) @@ -821,7 +824,7 @@ func GetAllowedFunctionalLevelValues() []string { // // Helper function to check the version requirement for DDC. // -func CheckProductVersion(client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, requiredOrchestrationApiVersion int32, requiredProductMajorVersion int, requiredProductMinorVersion int, errorSummary, feature string) bool { +func CheckProductVersion(client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, requiredCloudOrchestrationApiVersion int32, requiredOnPremOrchestrationApiVersion int32, requiredProductMajorVersion int, requiredProductMinorVersion int, errorSummary, feature string) bool { // Validate DDC version if client.AuthConfig.OnPremises { productVersionSplit := strings.Split(client.ClientConfig.ProductVersion, ".") @@ -851,15 +854,24 @@ func CheckProductVersion(client *citrixdaasclient.CitrixDaasClient, diagnostics ) return false } - } - // Validate Orchestration version - if client.ClientConfig.OrchestrationApiVersion < requiredOrchestrationApiVersion { - diagnostics.AddError( - errorSummary, - fmt.Sprintf("%s is not supported for current DDC version %d. Please upgrade your DDC product version to %d or above.", feature, client.ClientConfig.OrchestrationApiVersion, requiredOrchestrationApiVersion), - ) - return false + // Validate Orchestration version + if client.ClientConfig.OrchestrationApiVersion < requiredOnPremOrchestrationApiVersion { + diagnostics.AddError( + errorSummary, + fmt.Sprintf("%s is not supported for current DDC Orchestration Service version %d. Please upgrade your DDC Orchestration Service version to %d or above.", feature, client.ClientConfig.OrchestrationApiVersion, requiredOnPremOrchestrationApiVersion), + ) + return false + } + } else { + // Validate Orchestration version + if client.ClientConfig.OrchestrationApiVersion < requiredCloudOrchestrationApiVersion { + diagnostics.AddError( + errorSummary, + fmt.Sprintf("%s is not supported for current DDC Orchestration Service version %d. Please upgrade your DDC Orchestration Service version to %d or above.", feature, client.ClientConfig.OrchestrationApiVersion, requiredCloudOrchestrationApiVersion), + ) + return false + } } return true @@ -1108,7 +1120,7 @@ func VerifyIdentityUserListCompleteness(inputUserNames []string, remoteUsers []c return nil } -func GetConfigValuesForSchema(ctx context.Context, diags *diag.Diagnostics, m ModelWithAttributes) (string, map[string]interface{}) { +func GetConfigValuesForSchema(ctx context.Context, diags *diag.Diagnostics, m ResourceModelWithAttributes) (string, map[string]interface{}) { sensitiveFields := GetSensitiveFieldsForAttribute(ctx, diags, m.GetAttributes()) dataObj := TypedObjectToObjectValue(ctx, diags, m) return reflect.TypeOf(m).String(), GetConfigValuesForObject(ctx, diags, dataObj, sensitiveFields) diff --git a/internal/util/name-value-string-pair.go b/internal/util/name-value-string-pair.go index d49ba1b..4d8fc0c 100644 --- a/internal/util/name-value-string-pair.go +++ b/internal/util/name-value-string-pair.go @@ -26,7 +26,7 @@ type NameValueStringPairModel struct { } // RefreshListItem implements RefreshableListItemWithAttributes. -func (r NameValueStringPairModel) RefreshListItem(ctx context.Context, diag *diag.Diagnostics, retmote citrixorchestration.NameValueStringPairModel) ModelWithAttributes { +func (r NameValueStringPairModel) RefreshListItem(ctx context.Context, diag *diag.Diagnostics, retmote citrixorchestration.NameValueStringPairModel) ResourceModelWithAttributes { r.Name = types.StringValue(retmote.GetName()) r.Value = types.StringValue(retmote.GetValue()) diff --git a/internal/util/resource.go b/internal/util/resource.go index b16f06d..747dd82 100644 --- a/internal/util/resource.go +++ b/internal/util/resource.go @@ -47,7 +47,7 @@ func GetHypervisorResourcePool(ctx context.Context, client *citrixdaasclient.Cit } func GetMachineCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, machineCatalogId string, addErrorToDiagnostics bool) (*citrixorchestration.MachineCatalogDetailResponseModel, error) { - getMachineCatalogRequest := client.ApiClient.MachineCatalogsAPIsDAAS.MachineCatalogsGetMachineCatalog(ctx, machineCatalogId).Fields("Id,Name,Description,ProvisioningType,Zone,AllocationType,SessionSupport,TotalCount,HypervisorConnection,ProvisioningScheme,RemotePCEnrollmentScopes,IsPowerManaged,MinimumFunctionalLevel,IsRemotePC,Metadata,Scopes,UpgradeInfo,AdminFolder") + getMachineCatalogRequest := client.ApiClient.MachineCatalogsAPIsDAAS.MachineCatalogsGetMachineCatalog(ctx, machineCatalogId).Fields("Id,Name,Description,ProvisioningType,PersistChanges,Zone,AllocationType,SessionSupport,TotalCount,HypervisorConnection,ProvisioningScheme,RemotePCEnrollmentScopes,IsPowerManaged,MinimumFunctionalLevel,IsRemotePC,Metadata,Scopes,UpgradeInfo,AdminFolder") catalog, httpResp, err := citrixdaasclient.ExecuteWithRetry[*citrixorchestration.MachineCatalogDetailResponseModel](getMachineCatalogRequest, client) if err != nil && addErrorToDiagnostics { diagnostics.AddError( @@ -462,3 +462,11 @@ func GetAllScopedObjects(ctx context.Context, client *citrixdaasclient.CitrixDaa return responseModel.GetItems(), nil } } + +func BuildResourcePathForGetRequest(resourcePathInput string, resourceName string) string { + if resourcePathInput != "" { + return strings.ReplaceAll(resourcePathInput, "\\", "|") + "|" + resourceName + } else { + return resourceName + } +} diff --git a/internal/util/types.go b/internal/util/types.go index 862fe08..bb72a34 100644 --- a/internal/util/types.go +++ b/internal/util/types.go @@ -10,14 +10,19 @@ import ( "sync" "github.com/hashicorp/terraform-plugin-framework/attr" + datasourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" + resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) -type ModelWithAttributes interface { - GetAttributes() map[string]schema.Attribute // workaround because NestedAttributeObject and SingleNestedAttribute do not share a base type +type ResourceModelWithAttributes interface { + GetAttributes() map[string]resourceSchema.Attribute // workaround because NestedAttributeObject and SingleNestedAttribute do not share a base type +} + +type DataSourceModelWithAttributes interface { + GetDataSourceAttributes() map[string]datasourceSchema.Attribute // workaround because NestedAttributeObject and SingleNestedAttribute do not share a base type } // Store the attribute map for each model type so we don't have to regenerate it every time @@ -27,18 +32,18 @@ var attributeMapCache sync.Map var defaultObjectCache sync.Map // -// Helper function to convert a model to a map of attribute types. Used when converting back to a types.Object +// Helper function to convert a resource model to a map of attribute types. Used when converting back to a types.Object // -// Model to convert, must implement the ModelWithSchema interface +// Model to convert, must implement the ResourceModelWithSchema interface // Map of attribute types -func AttributeMapFromObject(m ModelWithAttributes) (map[string]attr.Type, error) { +func ResourceAttributeMapFromObject(m ResourceModelWithAttributes) (map[string]attr.Type, error) { keyName := reflect.TypeOf(m).String() if attributes, ok := attributeMapCache.Load(keyName); ok { return attributes.(map[string]attr.Type), nil } // not doing an extra sync/double checked lock because generating the attribute map is pretty quick - attributeMap, err := attributeMapFromSchema(m.GetAttributes()) + attributeMap, err := resourceAttributeMapFromSchema(m.GetAttributes()) if err != nil { return nil, err } @@ -47,14 +52,34 @@ func AttributeMapFromObject(m ModelWithAttributes) (map[string]attr.Type, error) } // -// Helper function to convert a schema map to a map of attribute types. Used when converting back to a types.Object +// Helper function to convert a data source model to a map of attribute types. Used when converting back to a types.Object +// +// Model to convert, must implement the DataSourceModelWithSchema interface +// Map of attribute types +func DataSourceAttributeMapFromObject(m DataSourceModelWithAttributes) (map[string]attr.Type, error) { + keyName := reflect.TypeOf(m).String() + if attributes, ok := attributeMapCache.Load(keyName); ok { + return attributes.(map[string]attr.Type), nil + } + + // not doing an extra sync/double checked lock because generating the attribute map is pretty quick + attributeMap, err := dataSourceAttributeMapFromSchema(m.GetDataSourceAttributes()) + if err != nil { + return nil, err + } + attributeMapCache.Store(keyName, attributeMap) + return attributeMap, nil +} + +// +// Helper function to convert a resource schema map to a map of attribute types. Used when converting back to a types.Object // // Schema map of the object // Map of attribute types -func attributeMapFromSchema(s map[string]schema.Attribute) (map[string]attr.Type, error) { +func resourceAttributeMapFromSchema(s map[string]resourceSchema.Attribute) (map[string]attr.Type, error) { var attributeTypes = map[string]attr.Type{} for attributeName, attribute := range s { - attrib, err := attributeToTerraformType(attribute) + attrib, err := resourceAttributeToTerraformType(attribute) if err != nil { return nil, err } @@ -63,55 +88,129 @@ func attributeMapFromSchema(s map[string]schema.Attribute) (map[string]attr.Type return attributeTypes, nil } -// Converts a schema.Attribute to a terraform attr.Type. Will recurse if the attribute contains a nested object or list of nested objects. -func attributeToTerraformType(attribute schema.Attribute) (attr.Type, error) { +// +// Helper function to convert a data source schema map to a map of attribute types. Used when converting back to a types.Object +// +// Schema map of the object +// Map of attribute types +func dataSourceAttributeMapFromSchema(s map[string]datasourceSchema.Attribute) (map[string]attr.Type, error) { + var attributeTypes = map[string]attr.Type{} + for attributeName, attribute := range s { + attrib, err := dataSourceAttributeToTerraformType(attribute) + if err != nil { + return nil, err + } + attributeTypes[attributeName] = attrib + } + return attributeTypes, nil +} + +// Converts a resource schema.Attribute to a terraform attr.Type. Will recurse if the attribute contains a nested object or list of nested objects. +func resourceAttributeToTerraformType(attribute resourceSchema.Attribute) (attr.Type, error) { switch attrib := attribute.(type) { - case schema.StringAttribute: + case resourceSchema.StringAttribute: return types.StringType, nil - case schema.BoolAttribute: + case resourceSchema.BoolAttribute: return types.BoolType, nil - case schema.NumberAttribute: + case resourceSchema.NumberAttribute: return types.NumberType, nil - case schema.Int64Attribute: + case resourceSchema.Int64Attribute: return types.Int64Type, nil - case schema.Int32Attribute: + case resourceSchema.Int32Attribute: return types.Int32Type, nil - case schema.Float64Attribute: + case resourceSchema.Float64Attribute: return types.Float64Type, nil - case schema.Float32Attribute: + case resourceSchema.Float32Attribute: return types.Float32Type, nil - case schema.ListAttribute: + case resourceSchema.ListAttribute: return types.ListType{ElemType: attrib.ElementType}, nil - case schema.ListNestedAttribute: + case resourceSchema.ListNestedAttribute: // list of object, recurse - nestedAttributes, err := attributeMapFromSchema(attrib.NestedObject.Attributes) + nestedAttributes, err := resourceAttributeMapFromSchema(attrib.NestedObject.Attributes) if err != nil { return nil, err } return types.ListType{ElemType: types.ObjectType{AttrTypes: nestedAttributes}}, nil - case schema.ObjectAttribute: + case resourceSchema.ObjectAttribute: return attrib.GetType(), nil - case schema.SingleNestedAttribute: + case resourceSchema.SingleNestedAttribute: // object, recurse - nestedAttributes, err := attributeMapFromSchema(attrib.Attributes) + nestedAttributes, err := resourceAttributeMapFromSchema(attrib.Attributes) if err != nil { return nil, err } return types.ObjectType{AttrTypes: nestedAttributes}, nil - case schema.SetAttribute: + case resourceSchema.SetAttribute: return types.SetType{ElemType: attrib.ElementType}, nil - case schema.SetNestedAttribute: + case resourceSchema.SetNestedAttribute: // set of object, recurse - nestedAttributes, err := attributeMapFromSchema(attrib.NestedObject.Attributes) + nestedAttributes, err := resourceAttributeMapFromSchema(attrib.NestedObject.Attributes) if err != nil { return nil, err } return types.SetType{ElemType: types.ObjectType{AttrTypes: nestedAttributes}}, nil - case schema.MapAttribute: + case resourceSchema.MapAttribute: return types.MapType{ElemType: attrib.ElementType}, nil - case schema.MapNestedAttribute: + case resourceSchema.MapNestedAttribute: // map of object, recurse - nestedAttributes, err := attributeMapFromSchema(attrib.NestedObject.Attributes) + nestedAttributes, err := resourceAttributeMapFromSchema(attrib.NestedObject.Attributes) + if err != nil { + return nil, err + } + return types.MapType{ElemType: types.ObjectType{AttrTypes: nestedAttributes}}, nil + } + return nil, fmt.Errorf("unsupported attribute type: %s", attribute) +} + +// Converts a data source schema.Attribute to a terraform attr.Type. Will recurse if the attribute contains a nested object or list of nested objects. +func dataSourceAttributeToTerraformType(attribute datasourceSchema.Attribute) (attr.Type, error) { + switch attrib := attribute.(type) { + case datasourceSchema.StringAttribute: + return types.StringType, nil + case datasourceSchema.BoolAttribute: + return types.BoolType, nil + case datasourceSchema.NumberAttribute: + return types.NumberType, nil + case datasourceSchema.Int64Attribute: + return types.Int64Type, nil + case datasourceSchema.Int32Attribute: + return types.Int32Type, nil + case datasourceSchema.Float64Attribute: + return types.Float64Type, nil + case datasourceSchema.Float32Attribute: + return types.Float32Type, nil + case datasourceSchema.ListAttribute: + return types.ListType{ElemType: attrib.ElementType}, nil + case datasourceSchema.ListNestedAttribute: + // list of object, recurse + nestedAttributes, err := dataSourceAttributeMapFromSchema(attrib.NestedObject.Attributes) + if err != nil { + return nil, err + } + return types.ListType{ElemType: types.ObjectType{AttrTypes: nestedAttributes}}, nil + case datasourceSchema.ObjectAttribute: + return attrib.GetType(), nil + case datasourceSchema.SingleNestedAttribute: + // object, recurse + nestedAttributes, err := dataSourceAttributeMapFromSchema(attrib.Attributes) + if err != nil { + return nil, err + } + return types.ObjectType{AttrTypes: nestedAttributes}, nil + case datasourceSchema.SetAttribute: + return types.SetType{ElemType: attrib.ElementType}, nil + case datasourceSchema.SetNestedAttribute: + // set of object, recurse + nestedAttributes, err := dataSourceAttributeMapFromSchema(attrib.NestedObject.Attributes) + if err != nil { + return nil, err + } + return types.SetType{ElemType: types.ObjectType{AttrTypes: nestedAttributes}}, nil + case datasourceSchema.MapAttribute: + return types.MapType{ElemType: attrib.ElementType}, nil + case datasourceSchema.MapNestedAttribute: + // map of object, recurse + nestedAttributes, err := dataSourceAttributeMapFromSchema(attrib.NestedObject.Attributes) if err != nil { return nil, err } @@ -196,8 +295,34 @@ func ObjectValueToTypedObject[objTyp any](ctx context.Context, diagnostics *diag // Object of the specified type // Schema map of the object // Object in the native terraform types.Object wrapper -func TypedObjectToObjectValue(ctx context.Context, diagnostics *diag.Diagnostics, v ModelWithAttributes) types.Object { - attributesMap, err := AttributeMapFromObject(v) +func TypedObjectToObjectValue(ctx context.Context, diagnostics *diag.Diagnostics, v ResourceModelWithAttributes) types.Object { + attributesMap, err := ResourceAttributeMapFromObject(v) + if err != nil { + diagnostics.AddError("Error converting schema to attribute map", err.Error()) + } + if v == nil { + return types.ObjectNull(attributesMap) + } + + obj, diags := types.ObjectValueFrom(ctx, attributesMap, v) + if diags != nil { + diagnostics.Append(diags...) + return types.ObjectUnknown(attributesMap) + } + return obj +} + +// +// Helper function to convert a golang object to a native terraform object. +// Use ObjectValueToTypedObject to go the other way. +// +// "context +// Any issues will be appended to these diagnostics +// Object of the specified type +// Schema map of the object +// Object in the native terraform types.Object wrapper +func DataSourceTypedObjectToObjectValue(ctx context.Context, diagnostics *diag.Diagnostics, v DataSourceModelWithAttributes) types.Object { + attributesMap, err := DataSourceAttributeMapFromObject(v) if err != nil { diagnostics.AddError("Error converting schema to attribute map", err.Error()) } @@ -249,9 +374,9 @@ func ObjectListToTypedArray[objTyp any](ctx context.Context, diagnostics *diag.D // Any issues will be appended to these diagnostics // Slice of objects // types.List -func TypedArrayToObjectList[objTyp ModelWithAttributes](ctx context.Context, diagnostics *diag.Diagnostics, v []objTyp) types.List { +func TypedArrayToObjectList[objTyp ResourceModelWithAttributes](ctx context.Context, diagnostics *diag.Diagnostics, v []objTyp) types.List { var t objTyp - attributesMap, err := AttributeMapFromObject(t) + attributesMap, err := ResourceAttributeMapFromObject(t) if err != nil { diagnostics.AddError("Error converting schema to attribute map", err.Error()) } @@ -272,6 +397,36 @@ func TypedArrayToObjectList[objTyp ModelWithAttributes](ctx context.Context, dia return list } +// +// Helper function to convert a golang slice to a native terraform list of objects. +// Use ObjectListToTypedArray to go the other way. +// +// Any issues will be appended to these diagnostics +// Slice of objects +// types.List +func DataSourceTypedArrayToObjectList[objTyp DataSourceModelWithAttributes](ctx context.Context, diagnostics *diag.Diagnostics, v []objTyp) types.List { + var t objTyp + attributesMap, err := DataSourceAttributeMapFromObject(t) + if err != nil { + diagnostics.AddError("Error converting schema to attribute map", err.Error()) + } + + if v == nil { + return types.ListNull(types.ObjectType{AttrTypes: attributesMap}) + } + + res := make([]types.Object, 0, len(v)) + for _, val := range v { + res = append(res, DataSourceTypedObjectToObjectValue(ctx, diagnostics, val)) + } + list, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: attributesMap}, res) + if diags != nil { + diagnostics.Append(diags...) + return types.ListNull(types.ObjectType{AttrTypes: attributesMap}) + } + return list +} + // // Helper function to convert a native terraform list of objects to a golang slice of the specified type // Use TypedArrayToObjectSet to go the other way. @@ -308,9 +463,9 @@ func ObjectSetToTypedArray[objTyp any](ctx context.Context, diagnostics *diag.Di // Any issues will be appended to these diagnostics // Slice of objects // types.Set -func TypedArrayToObjectSet[objTyp ModelWithAttributes](ctx context.Context, diagnostics *diag.Diagnostics, v []objTyp) types.Set { +func TypedArrayToObjectSet[objTyp ResourceModelWithAttributes](ctx context.Context, diagnostics *diag.Diagnostics, v []objTyp) types.Set { var t objTyp - attributesMap, err := AttributeMapFromObject(t) + attributesMap, err := ResourceAttributeMapFromObject(t) if err != nil { diagnostics.AddError("Error converting schema to attribute map", err.Error()) } @@ -331,6 +486,36 @@ func TypedArrayToObjectSet[objTyp ModelWithAttributes](ctx context.Context, diag return set } +// +// Helper function to convert a golang slice to a native terraform list of objects. +// Use ObjectSetToTypedArray to go the other way. +// +// Any issues will be appended to these diagnostics +// Slice of objects +// types.Set +func DataSourceTypedArrayToObjectSet[objTyp DataSourceModelWithAttributes](ctx context.Context, diagnostics *diag.Diagnostics, v []objTyp) types.Set { + var t objTyp + attributesMap, err := DataSourceAttributeMapFromObject(t) + if err != nil { + diagnostics.AddError("Error converting schema to attribute map", err.Error()) + } + + if v == nil { + return types.SetNull(types.ObjectType{AttrTypes: attributesMap}) + } + + res := make([]types.Object, 0, len(v)) + for _, val := range v { + res = append(res, DataSourceTypedObjectToObjectValue(ctx, diagnostics, val)) + } + set, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: attributesMap}, res) + if diags != nil { + diagnostics.Append(diags...) + return types.SetNull(types.ObjectType{AttrTypes: attributesMap}) + } + return set +} + // // Helper function to convert a terraform list of terraform strings to array of golang primitive strings. // Use StringArrayToStringList to go the other way. diff --git a/internal/wem/wem_site/wem_site_service_data_source.go b/internal/wem/wem_site/wem_site_service_data_source.go index b4834db..a3e8160 100644 --- a/internal/wem/wem_site/wem_site_service_data_source.go +++ b/internal/wem/wem_site/wem_site_service_data_source.go @@ -78,8 +78,7 @@ func (d *WemSiteDataSource) Read(ctx context.Context, req datasource.ReadRequest return } getWemSiteRequest = getWemSiteRequest.Id(idInt64) - } - if !data.Name.IsNull() { + } else { wemSiteNameOrId = data.Name.ValueString() getWemSiteRequest = getWemSiteRequest.Name(data.Name.ValueString()) } diff --git a/internal/wem/wem_site/wem_site_service_data_source_model.go b/internal/wem/wem_site/wem_site_service_data_source_model.go index d7b34d9..2488a67 100644 --- a/internal/wem/wem_site/wem_site_service_data_source_model.go +++ b/internal/wem/wem_site/wem_site_service_data_source_model.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -38,6 +39,10 @@ func GetWemSiteDataSourceSchema() schema.Schema { "id": schema.StringAttribute{ Description: "ID of the WEM configuration set.", Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("name")), // Ensures that only one of either Id or Name is provided. It will also cause a validation error if none are specified. + stringvalidator.LengthAtLeast(1), + }, }, "name": schema.StringAttribute{ Description: "Name of the configuration set.", diff --git a/internal/wem/wem_site/wem_site_service_resource.go b/internal/wem/wem_site/wem_site_service_resource.go index 21486e0..d7e3e77 100644 --- a/internal/wem/wem_site/wem_site_service_resource.go +++ b/internal/wem/wem_site/wem_site_service_resource.go @@ -62,10 +62,6 @@ func (w *wemSiteServiceResource) ModifyPlan(ctx context.Context, req resource.Mo resp.Diagnostics.AddError(util.ProviderInitializationErrorMsg, util.MissingProviderClientIdAndSecretErrorMsg) return } - - if w.client.AuthConfig.OnPremises { - resp.Diagnostics.AddError("Error managing WEM Configuration Sets", "Configuration Sets are only supported for Cloud customers.") - } } // Create implements resource.Resource. diff --git a/internal/wem/wem_site/wem_site_service_resource_model.go b/internal/wem/wem_site/wem_site_service_resource_model.go index 5855101..5838b52 100644 --- a/internal/wem/wem_site/wem_site_service_resource_model.go +++ b/internal/wem/wem_site/wem_site_service_resource_model.go @@ -26,8 +26,7 @@ type WemSiteResourceModel struct { func (WemSiteResourceModel) GetSchema() schema.Schema { return schema.Schema{ - Description: "WEM --- Manages configuration sets within a WEM deployment." + - "\n\n~> **Disclaimer** This feature is supported for Citrix Cloud customers, and will be made available for On-Premises soon.", + Description: "WEM --- Manages configuration sets within a WEM deployment.", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: "Identifier of the configuration set.", diff --git a/scripts/onboarding-helper/.terraform/providers/registry.terraform.io/citrix/citrix/1.0.6/windows_amd64/LICENSE b/scripts/onboarding-helper/.terraform/providers/registry.terraform.io/citrix/citrix/1.0.6/windows_amd64/LICENSE new file mode 100644 index 0000000..2bb9ad2 --- /dev/null +++ b/scripts/onboarding-helper/.terraform/providers/registry.terraform.io/citrix/citrix/1.0.6/windows_amd64/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/scripts/onboarding-helper/.terraform/providers/registry.terraform.io/citrix/citrix/1.0.6/windows_amd64/README.md b/scripts/onboarding-helper/.terraform/providers/registry.terraform.io/citrix/citrix/1.0.6/windows_amd64/README.md new file mode 100644 index 0000000..3f52ee3 --- /dev/null +++ b/scripts/onboarding-helper/.terraform/providers/registry.terraform.io/citrix/citrix/1.0.6/windows_amd64/README.md @@ -0,0 +1,205 @@ +# Plugin for Terraform Provider for Citrix® + +Citrix has developed a custom Terraform provider for automating Citrix product deployments and configurations. Using [Terraform](https://www.terraform.io) with Citrix provider, you can manage your Citrix products via Infrastructure as Code, giving you higher efficiency and consistency on infrastructure management, as well as better reusability on infrastructure configuration. The provider is developed and maintained by Citrix. + +## Table of Contents +- [Plugin for Terraform Provider for Citrix®](#plugin-for-terraform-provider-for-citrix) + - [Table of Contents](#table-of-contents) + - [Contacting the Maintainers](#contacting-the-maintainers) + - [Examples](#examples) + - [Deployment guides](#deployment-guides) + - [Plugin for Terraform Provider for Citrix® Documentation](#plugin-for-terraform-provider-for-citrix-documentation) + - [Navigating the repository](#navigating-the-repository) + - [Provider Configuration](#provider-configuration) + - [Resource Configuration](#resource-configuration) + - [Using the Plugin for Terraform Provider for Citrix DaaS™](#using-the-plugin-for-terraform-provider-for-citrix-daas) + - [Install Terraform](#install-terraform) + - [(On-Premises Only) Enable Web Studio](#on-premises-only-enable-web-studio) + - [Configure your Plugin for Terraform Provider for Citrix DaaS™](#configure-your-plugin-for-terraform-provider-for-citrix-daas) + - [Start writing Terraform for managing your Citrix DaaS site](#start-writing-terraform-for-managing-your-citrix-daas-site) + - [Create a Zone in Citrix DaaS as the first step](#create-a-zone-in-citrix-daas-as-the-first-step) + - [Create a Hypervisor](#create-a-hypervisor) + - [Create a Hypervisor Resource Pool](#create-a-hypervisor-resource-pool) + - [Create a Machine Catalog](#create-a-machine-catalog) + - [Create a Delivery Group](#create-a-delivery-group) + - [Using the Plugin for Terraform Provider for other Citrix resources](#using-the-plugin-for-terraform-provider-for-other-citrix-resources) + - [Configure Global App Configuration (GAC) Settings](#configure-global-app-configuration-gac-settings) + - [Create Citrix Cloud Resource Locations](#create-citrix-cloud-resource-locations) + - [Managing StoreFront resources](#managing-storefront-resources) + - [Managing DaaS Quick Deploy resources](#managing-daas-quick-deploy-resources) + - [Frequently Asked Questions](#frequently-asked-questions) + - [What resource is supported for different connection types?](#what-resource-is-supported-for-different-connection-types) + - [Attributions](#attributions) + - [License](#license) + +## Contacting the Maintainers +This project uses GitHub to track all issues. See [CONTRIBUTING.md](CONTRIBUTING.md) for more information. + +## Examples +Basic example templates for getting started can be found in the repository at [examples/](/examples/README.md) + +### Deployment guides +Complete steps from start to finsh for a variety of senarios from [Citrix Tech Zone](https://community.citrix.com/tech-zone/automation/): +- [Installing and configuring the provider](https://community.citrix.com/tech-zone/automation/terraform-install-and-config/) +- [AWS EC2](https://community.citrix.com/tech-zone/build/deployment-guides/terraform-daas-aws/) via MCS +- [Azure](https://community.citrix.com/tech-zone/build/deployment-guides/citrix-daas-terraform-azure/) via MCS +- [GCP](https://community.citrix.com/tech-zone/build/deployment-guides/terraform-daas-gcp/) via MCS +- [vSphere](https://community.citrix.com/tech-zone/build/deployment-guides/terraform-daas-vsphere8/) via MCS +- [XenServer](https://community.citrix.com/tech-zone/automation/citrix-terraform-xenserver) via MCS +- [Citrix policies](https://community.citrix.com/tech-zone/automation/cvad-terraform-policies/) + +## Plugin for Terraform Provider for Citrix® Documentation + +### Navigating the repository + +1. `internal` folder - Contains the following sub directories: + * `provider` folder - Contains the Citrix provider implementation for Terraform + * `daas` folder - Contains all the Citrix DaaS resources libraries that we support through Terraform. + * `test` folder - Contains the Go tests for both `provider` and all `resources` that we have. + * `util` folder - Contains general utility functions that can be reused. +2. `examples` folder - Contains the examples for users to use various Citrix resources e.g [zone](examples/resources/citrix_zone) folder contains the resources.tf that illustrates how citrix_zone resource can be used to create a DaaS Zone on target Citrix DaaS site. There are also examples for [Citrix provider](examples/provider) configuration for both Citrix Cloud customer and Citrix on-premises customers. Users can use the examples as a starting point to configure their own Citrix Terraform script. +3. `docs` folder - [resources](docs/resources) - contains the documentation for all resource configurations supported through Terraform. Refer this to understand the properties, accepted values, and how various properties work together for each type of resource. + +### Provider Configuration + +`provider.tf` contains the information on target DaaS site where you want to apply configuration. + +Depending on whether its managing a Citrix Cloud site, or a Citrix on-premises site, Citrix provider should be configured differently. + +Example for on-premises site: + +```hcl +provider "citrix" { + cvad_config = { + hostname = "10.71.136.250" # Optionally set with `CITRIX_HOSTNAME` environment variable. + client_id = "${var.domain_admin_id}" # Optionally set with `CITRIX_CLIENT_ID` environment variable. + client_secret = "${var.domain_admin_secret}" # Optionally set with `CITRIX_CLIENT_SECRET` environment variable. + } +} +``` + +Example for Cloud site: + +```hcl +provider "citrix" { + cvad_config = { + customer_id = "${var.customer_id}" # Optionally set with `CITRIX_CUSTOMER_ID` environment variable. + client_id = "${var.api_key_clientId}" # Optionally set with `CITRIX_CLIENT_ID` environment variable. + client_secret = "${var.api_key_clientSecret}" # Optionally set with `CITRIX_CLIENT_SECRET` environment variable. + } +} +``` + +You can use environment variables as stated in the comments above. When running Go tests, always use environment variables so that no credentials or other sensitive information are checked-in to the code. + +Below is a table to show the difference between on-premises and Cloud provider configuration: + +| | Cloud | On-Premises | +|--------------|-----------------------------------|---------------------------------------| +| environment | `Production`, `Japan`, `Gov` | N/A | +| customerId | Cloud Customer Id | N/A | +| hostname | (Optional) Cloud DDC hostname | On-Premises DDC Hostname / IP address | +| clientId | Citrix Cloud API Key clientId | Domain Admin Username | +| clientSecret | Citrix Cloud API Key clientSecret | Domain Admin Password | + +### Resource Configuration + +Resources.tf can be used to configure the desired state of the resources that you want to create and manage in your Citrix Services. The example below shows how you can configure a Citrix DaaS Zone in Citrix DaaS service in resource.tf. + +**`citrix_zone`** + +```hcl +resource "citrix_zone" "example-zone" { + name = "example-zone" + description = "zone example" + metadata = [ + { + name = "key1" + value = "value1" + } + ] +} +``` + +Please refer the Plugin for Terraform Provider for Citrix DaaS™ documentation such as [docs/resources/zone.md](docs/resources/zone.md) to find out the configurable properties of each type of resources, understand what they do, and what option values are supported. + +--------- + +## Using the Plugin for Terraform Provider for Citrix DaaS™ + +### Install Terraform + +Refer the [Hashicorp documentation](https://learn.hashicorp.com/tutorials/terraform/install-cli) for installing Terraform CLI for your own environment. + +### (On-Premises Only) Enable Web Studio + +For on-premises sites with version >= 2311 are supported. Web Studio needs to be [installed and configured](https://docs.citrix.com/en-us/citrix-virtual-apps-desktops/install-configure/install-core/install-web-studio.html#install-web-studio-1) for the provider to work. + +### Configure your Plugin for Terraform Provider for Citrix DaaS™ + +Refer section [Understanding Provider Configuration](#understanding-provider-configuration) or [Provider documentation](docs/index.md) to configure the provider for the Citrix DaaS site you want to manage with Terraform. + +### Start writing Terraform for managing your Citrix DaaS site + +To find all the Citrix DaaS resources manageable via Terraform, understand all the configurable properties for each resource and how they work together, refer documentations for resources in [Citrix Terraform resource documentation](docs/resources). To better understand how the resource is managed via Citrix DaaS Rest API, you can refer the [Citrix DaaS Rest API documentation](https://developer.cloud.com/citrixworkspace/citrix-daas/citrix-daas-rest-apis/docs/overview). + +### Create a Zone in Citrix DaaS as the first step + +Refer the [DaaS Zone documentation](docs/resources/zone.md) to configure a zone via terraform. + +### Create a Hypervisor + +Hypervisor is needed to use your preferred public cloud provider with Citrix DaaS. Refer the [DaaS Hypervisor documentation](docs/resources/azure_hypervisor.md) to configure an Azure hypervisor in a zone via terraform. + +### Create a Hypervisor Resource Pool + +The hypervisor resource pool defines the network configuration for a hypervisor connection. Refer the [DaaS Hypervisor Resource Pool documentation](docs/resources/azure_hypervisor_resource_pool.md) to configure an Azure hypervisr resource pool via terraform. + +### Create a Machine Catalog + +A machine catalog is a collection of machines managed as a single entity. Refer the [DaaS Machine Catalog documentation](docs/resources/machine_catalog.md) to configure a machine catalog via terraform. + +### Create a Delivery Group +A delivery group is a collection of machines selected from one or more machine catalogs. The delivery group can also specify which users can use those machines, plus the applications and desktops available to those users. Refer the [DaaS Delivery Group documentation](docs/resources/delivery_group.md) to configure a delivery group via terraform. + +## Using the Plugin for Terraform Provider for other Citrix resources + +### Configure Global App Configuration (GAC) Settings + +The Global App Configuration service provides a centralized setup for IT admins to easily configure Citrix Workspace app settings on Windows, Mac, Android, iOS, HTML5, Chrome OS platforms. Please refer to [Global App Configuration settings documentation](docs/resources/gac_settings.md) to configure GAC settings via terraform. + +### Create Citrix Cloud Resource Locations + +Resource locations contain the resources (e.g. cloud connectors) required to deliver applications and desktops to users. Resource locations are only supported for Cloud customers. On-premises customers can use the zone resource directly. Please refer to [Citrix Resource Location documentation](docs/resources/resource_location.md) to configure citrix cloud resource locations via terraform. + +### Managing StoreFront resources +Please refer to the [StoreFront.md](StoreFront.md) to configure StoreFront resources via terraform. Note that this feature is in Tech Preview. + +### Managing DaaS Quick Deploy resources +QuickCreate service allows customers to create and manage Amazon WorkSpaces Core instances in Amazon Web Services (AWS). Please refer to the [QuickCreate documentation](https://docs.citrix.com/en-us/citrix-daas/install-configure/amazon-workspaces-core.html) to learn more. Note that this feature is in Tech Preview. + +## Frequently Asked Questions + +#### What resource is supported for different connection types? + +| Connection Type | Hypervisor | Resource Pool | MCS Power Managed | MCS Provisioning | PVS | Manual/Remote PC | +|------------------|--------------------|--------------------|----------------------|----------------------|--------------------------|----------------------| +| AzureRM |:heavy_check_mark: |:heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |:heavy_check_mark: | :heavy_check_mark: | +| AWS EC2 |:heavy_check_mark: |:heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |:heavy_multiplication_x: | :heavy_check_mark: | +| GCP |:heavy_check_mark: |:heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |:heavy_multiplication_x: | :heavy_check_mark: | +| vSphere |:heavy_check_mark: |:heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |:heavy_multiplication_x: | :heavy_check_mark: | +| XenServer |:heavy_check_mark: |:heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |:heavy_multiplication_x: | :heavy_check_mark: | +| Nutanix |:heavy_check_mark: |:heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |:heavy_multiplication_x: | :heavy_check_mark: | +| SCVMM |:heavy_check_mark: |:heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |:heavy_multiplication_x: | :heavy_check_mark: | + +## Attributions +The code in this repository makes use of the following packages: +- Hashicorp Terraform Plugin Framework (https://github.com/hashicorp/terraform-plugin-framework) +- Google Go Uuid (https://github.com/google/uuid) + +## License +This project is Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +Copyright © 2024. Citrix Systems, Inc. \ No newline at end of file diff --git a/scripts/onboarding-helper/.terraform/terraform.tfstate b/scripts/onboarding-helper/.terraform/terraform.tfstate new file mode 100644 index 0000000..57d943a --- /dev/null +++ b/scripts/onboarding-helper/.terraform/terraform.tfstate @@ -0,0 +1,12 @@ +{ + "version": 3, + "terraform_version": "1.9.8", + "backend": { + "type": "local", + "config": { + "path": null, + "workspace_dir": null + }, + "hash": 666019178 + } +} \ No newline at end of file diff --git a/scripts/onboarding-helper/README.md b/scripts/onboarding-helper/README.md index d6f337f..438e640 100644 --- a/scripts/onboarding-helper/README.md +++ b/scripts/onboarding-helper/README.md @@ -5,7 +5,7 @@ This automation script is designed to onboard an existing site to Terraform. It ## Environment Requirements - PowerShell version `5.0` or higher -- Citrix Provider version `1.0.5` +- Citrix Provider version `1.0.7` - For On-Premises Customers: CVAD DDC `version 2311` or newer. ## Workflow: diff --git a/scripts/onboarding-helper/terraform-onboarding.ps1 b/scripts/onboarding-helper/terraform-onboarding.ps1 index cd4c43f..e23d85b 100644 --- a/scripts/onboarding-helper/terraform-onboarding.ps1 +++ b/scripts/onboarding-helper/terraform-onboarding.ps1 @@ -158,6 +158,17 @@ function Get-AuthToken { return $jsonObj.Token } else { + # Return the token if its still valid + if($null -eq $script:Token){ + Write-Verbose "Requesting new token." + } + elseif ((Get-Date) -lt $script:TokenExpiryTime) { + Write-Verbose "Refresh token is still valid. Returning the existing token." + return $script:Token + }else { + Write-Verbose "Refresh token Expired. Requesting new token." + } + if ($script:environment -eq "Production") { $url = "https://api.cloud.com/cctrustoauth2/$script:customerId/tokens/clients" } @@ -173,7 +184,11 @@ function Get-AuthToken { $contentType = 'application/x-www-form-urlencoded' $response = Invoke-WebRequestWithRetry -Uri $url -Method 'POST' -body $body -ContentType $contentType $jsonObj = ConvertFrom-Json $([String]::new($response.Content)) - return $jsonObj.access_token + + # Save the new token and calculate the expiry time of the refresh token + $script:Token = $jsonObj.access_token + $script:TokenExpiryTime = (Get-Date).AddSeconds([int]($jsonObj.expires_in * 0.9)) # Calculate the expiry time of the refresh token with buffer + return $script:Token } } @@ -192,6 +207,7 @@ function Start-GetRequest { $headers = @{ "Authorization" = "CwsAuth Bearer=$token" "Citrix-CustomerId" = $script:customerId + "Accept" = "application/json, text/plain, */*" } if ($null -ne $script:siteId) { $headers["Citrix-InstanceId"] = $script:siteId @@ -264,6 +280,43 @@ provider "citrix" { } +# Function to get the URL for WEM objects +function Get-UrlForWemObjects { + param( + [parameter(Mandatory = $true)] + [string] $requestPath + ) + + if($script:environment -eq "Production") { + $script:wemHostName = "api.wem.cloud.com" + } else { + $script:wemHostName = "api.wem.cloudburrito.com" + } + + if($requestPath -eq "sites") { + return "https://$script:wemHostName/services/wem/sites?includeHidden=true&includeUnboundAgentsSite=true" + } + else { + return "https://$script:wemHostName/services/wem/machines" + } +} + +# Function to find Catalog AD objects +function Find-CatalogADObjects { + param( + [parameter(Mandatory = $true)] + [array] $items + ) + + $catalogItems = @() + foreach ($item in $items) { + if ($item.type -eq "Catalog") { + $catalogItems += $item + } + } + return $catalogItems +} + # Function to get list of resources for a given resource provider function Get-ResourceList { param( @@ -276,8 +329,30 @@ function Get-ResourceList { $url = "$script:urlBase/$requestPath" - $response = Start-GetRequest -url $url + # Update url for WEM Objects + if($resourceProviderName -in "wem_configuration_set", "wem_directory_object") { + $url = Get-UrlForWemObjects -requestPath $requestPath + } + + # Check if the resource provider is supported in the current environment (eg. WEM is not supported for most environments) + try { + $response = Start-GetRequest -url $url + } + catch { + # Ignore 503 errors for WEM objects + if (-not($_.Exception.Response.StatusCode -eq 503)) { + Write-Error "Failed to get $resourceProviderName. Error: $($_.Exception.Message)" -ErrorAction Continue + } + return @() + } + $items = $response.Items + + # WEM supports AD object type 'Catalog'. Filter out other object types + if($resourceProviderName -eq "wem_directory_object" -and $items.Count -gt 0) { + $items = Find-CatalogADObjects -items $items + } + $resourceList = @() $pathMap = @{} foreach ($item in $items) { @@ -432,6 +507,22 @@ function Get-ExistingCVADResources { } } + # Add WEM resources for cloud customer environment + if (-not($script:onPremise)) { + $wemResources = @{ + "wem_configuration_set" = @{ + "resourceApi" = "sites" + "resourceProviderName" = "wem_configuration_set" + } + "wem_directory_object" = @{ + "resourceApi" = "ad_objects" + "resourceProviderName" = "wem_directory_object" + } + } + + $resources += $wemResources + } + $script:cvadResourcesMap = @{} foreach ($resource in $resources.Keys) { @@ -557,7 +648,13 @@ function RemoveComputedProperties { "(\s+)assigned(\s+)= (\S+)", "(\s+)is_built_in(\s+)= (\S+)", "(\s+)built_in_scopes\s*=\s*\[[\s\S]*?\]", - "(\s+)inherited_scopes\s*=\s*\[[\s\S]*?\]" + "(\s+)inherited_scopes\s*=\s*\[[\s\S]*?\]", + "(\s+)total_application_groups(\s+)= (\S+)", + "(\s+)total_applications(\s+)= (\S+)", + "(\s+)total_delivery_groups(\s+)= (\S+)", + "(\s+)total_machine_catalogs(\s+)= (\S+)", + "(\s+)total_machines(\s+)= (\S+)", + "(\s+)is_all_scope(\s+)= (\S+)" ) # Identify the delivery_groups_priority block @@ -600,6 +697,9 @@ function ReplaceDependencyRelationships { Write-Verbose "Creating dependency relationships between resources." # Create dependency relationships between resources with id references foreach ($resource in $script:cvadResourcesMap.Keys) { + if ($resource -like "wem_*") { + continue + } foreach ($id in $script:cvadResourcesMap[$resource].Keys) { $content = $content -replace "`"$id`"", "citrix_$($resource).$($script:cvadResourcesMap[$resource][$id]).id" } @@ -646,7 +746,7 @@ function ExtractAndSaveApplicationIcons { # Check if application icon exists; if not, then exit if ($content -notmatch 'citrix_application_icon') { - return + return $content } Write-Verbose "Extracting and saving application icons into icons folder." @@ -778,6 +878,7 @@ $script:hypervisorResourceMap = @{ } $NUTANIX_PLUGIN_ID = "AcropolisFactory" $script:applicationFolderPathMap = @{} +$script:TokenExpiryTime = (Get-Date).AddMinutes(-1) # Initialize the expiry time of the refresh token to an earlier time # Set environment variables for client secret $env:CITRIX_CLIENT_SECRET = $ClientSecret diff --git a/scripts/onboarding-helper/terraform.tf b/scripts/onboarding-helper/terraform.tf index 007b69e..6363d6f 100644 --- a/scripts/onboarding-helper/terraform.tf +++ b/scripts/onboarding-helper/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { citrix = { source = "citrix/citrix" - version = "=1.0.5" + version = "=1.0.7" } } diff --git a/settings.cloud.example.json b/settings.cloud.example.json index 05fc1ae..145e67a 100644 --- a/settings.cloud.example.json +++ b/settings.cloud.example.json @@ -111,6 +111,14 @@ "TEST_HYPERV_RP_AVAILABILITY_ZONE_AWS_EC2" :"{aws_ec2_availability_zone}", "Test_HYPERV_RP_SUBNETS_AWS_EC2" :"[\"{subnet_mask}\"]", + // Machine Properties Go Tests + "TEST_MACHINE_PROPERTIES_RESOURCE_NAME": "{machine_id}", + "TEST_MACHINE_PROPERTIES_RESOURCE_NAME_UPDATED": "{machine_id_updated}", + "TEST_MACHINE_PROPERTIES_RESOURCE_MACHINE_CATALOG_ID": "{machine_catalog_id}", + "TEST_MACHINE_PROPERTIES_RESOURCE_MACHINE_CATALOG_ID_UPDATED": "{machine_catalog_id_updated}", + "TEST_MACHINE_PROPERTIES_RESOURCE_TAG": "{machine_tag_id}", + "TEST_MACHINE_PROPERTIES_RESOURCE_TAG_UPDATED": "{machine_tag_id_updated}", + // Machine Catalog Go Tests - Azure "TEST_MC_NAME" :"{catalog_name}", "TEST_MC_SERVICE_ACCOUNT" :"{admin_username}", @@ -249,15 +257,29 @@ // Admin role env variable "TEST_ROLE_NAME" :"ctx-test-role", + // Admin Role Data Source env variable + "TEST_ADMIN_ROLE_DATA_SOURCE_ID": "{ADMIN_ROLE_ID}", + "TEST_ADMIN_ROLE_DATA_SOURCE_NAME": "{ADMIN_ROLE_NAME}", + "TEST_ADMIN_ROLE_DATA_SOURCE_EXPECTED_DESCRIPTION": "{ADMIN_ROLE_DESCRIPTION}", + // Admin Scope Go Tests "TEST_ADMIN_SCOPE_NAME" :"ctx-test-scope", // Policy Set env variable + // Policy Set Resource Tests env variables "TEST_POLICY_SET_NAME" :"ctx-test-policy-set", + // Policy Set Data Source Tests env variables + "TEST_POLICY_SET_DATA_SOURCE_ID" : "{Policy Set Data Source ID}", + "TEST_POLICY_SET_DATA_SOURCE_NAME" : "{Policy Set Data Source Name}", + "TEST_POLICY_SET_DATA_SOURCE_EXPECTED_POLICY_COUNT" : "{Policy Set Data Source Policy Count}", - // GAC Go Tests + // GAC Settings Go Tests "TEST_SETTINGS_CONFIG_SERVICE_URL" : "{workspace_url_with_port_number}", "TEST_SETTINGS_CONFIG_NAME" : "test-settings-config", + // GAC Discovery Go Tests + "TEST_GAC_DISCOVERY_DOMAIN" : "{gac_discovery_domain}", + "TEST_GAC_DISCOVERY_SERVICE_URL" : "{gac_discovery_service_url}", + "TEST_GAC_DISCOVERY_SERVICE_URL_UPDATE" : "{gac_discovery_service_url}", // Resource Location Go Tests "TEST_RESOURCE_LOCATION_NAME": "ctx-test-resource-location", @@ -486,7 +508,10 @@ // WEM Datasource go tests "TEST_WEM_SITE_DATA_SOURCE_ID": "{Id of the WEM Configuration Set}", "TEST_WEM_SITE_DATA_SOURCE_NAME": "{Name of the WEM Configuration Set}", - "TEST_WEM_SITE_DATA_SOURCE_DESCRIPTION": "{Description of the WEM Configuration Set}" + "TEST_WEM_SITE_DATA_SOURCE_DESCRIPTION": "{Description of the WEM Configuration Set}", + + // Desktop Icon Go Tests + "TEST_DESKTOP_ICON_RAW_DATA": "{Desktop Icon Raw Data}" }, "go.testTimeout": "120m" } \ No newline at end of file diff --git a/settings.onprem.example.json b/settings.onprem.example.json index 07299c8..71087af 100644 --- a/settings.onprem.example.json +++ b/settings.onprem.example.json @@ -111,6 +111,14 @@ "TEST_HYPERV_RP_VPC_AWS_EC2" :"{aws_ec2_vpc_name}", "TEST_HYPERV_RP_AVAILABILITY_ZONE_AWS_EC2" :"{aws_ec2_availability_zone}", "Test_HYPERV_RP_SUBNETS_AWS_EC2" :"[\"{subnet_mask}\"]", + + // Machine Properties Go Tests + "TEST_MACHINE_PROPERTIES_RESOURCE_NAME": "{machine_id}", + "TEST_MACHINE_PROPERTIES_RESOURCE_NAME_UPDATED": "{machine_id_updated}", + "TEST_MACHINE_PROPERTIES_RESOURCE_MACHINE_CATALOG_ID": "{machine_catalog_id}", + "TEST_MACHINE_PROPERTIES_RESOURCE_MACHINE_CATALOG_ID_UPDATED": "{machine_catalog_id_updated}", + "TEST_MACHINE_PROPERTIES_RESOURCE_TAG": "{machine_tag_id}", + "TEST_MACHINE_PROPERTIES_RESOURCE_TAG_UPDATED": "{machine_tag_id_updated}", // Machine Catalog Go Tests - Azure "TEST_MC_NAME" :"{catalog_name}", @@ -269,14 +277,27 @@ // Admin role env variable "TEST_ROLE_NAME" :"ctx-test-role", + // Admin Role Data Source env variable + "TEST_ADMIN_ROLE_DATA_SOURCE_ID": "{ADMIN_ROLE_ID}", + "TEST_ADMIN_ROLE_DATA_SOURCE_NAME": "{ADMIN_ROLE_NAME}", + "TEST_ADMIN_ROLE_DATA_SOURCE_EXPECTED_DESCRIPTION": "{ADMIN_ROLE_DESCRIPTION}", + // Admin Scope Go Tests "TEST_ADMIN_SCOPE_NAME" :"ctx-test-scope", // Admin User Go Tests "TEST_ADMIN_USER_NAME" :"user0001", "TEST_ADMIN_USER_DOMAIN" :"CTX-AD", + // Admin User Data Source Go Tests + "TEST_ADMIN_USER_DATA_SOURCE_ID" :"{ADMIN_USER_ID}", + "TEST_ADMIN_USER_DATA_SOURCE_IS_ENABLED" :"{IS_ENABLED}", // Policy Set env variable + // Policy Set Resource Tests env variables "TEST_POLICY_SET_NAME" :"ctx-test-policy-set", + // Policy Set Data Source Tests env variables + "TEST_POLICY_SET_DATA_SOURCE_ID" : "{Policy Set Data Source ID}", + "TEST_POLICY_SET_DATA_SOURCE_NAME" : "{Policy Set Data Source Name}", + "TEST_POLICY_SET_DATA_SOURCE_EXPECTED_POLICY_COUNT" : "{Policy Set Data Source Policy Count}", // StoreFront env variable "SF_COMPUTER_NAME" : "{storefront_computer_name}", @@ -361,7 +382,10 @@ "TEST_TAG_DATA_SOURCE_DESCRIPTION": "{Description of the tag}", // Tag Resource Go Tests Env Variables "TEST_TAG_RESOURCE_NAME": "{Name of the tag resource}", - "TEST_TAG_RESOURCE_DESCRIPTION": "{Description of the tag resource}" + "TEST_TAG_RESOURCE_DESCRIPTION": "{Description of the tag resource}", + + // Desktop Icon Go Tests + "TEST_DESKTOP_ICON_RAW_DATA": "{Desktop Icon Raw Data}" }, "go.testTimeout": "30m" } \ No newline at end of file