diff --git a/DEVELOPER.md b/DEVELOPER.md index a18dd69..732eead 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -3,7 +3,6 @@ This documentation will guide you through the process of setting up your dev environment for running Plugin for Terraform Provider for Citrix® server locally on your dev machine. ## Table of Contents - - [Plugin for Terraform Provider for Citrix® Developer Guide](#plugin-for-terraform-provider-for-citrix-developer-guide) - [Table of Contents](#table-of-contents) - [Install Dependencies](#install-dependencies) @@ -13,9 +12,13 @@ This documentation will guide you through the process of setting up your dev env - [Start Debugger](#start-debugger) - [Attach Local Provider to PowerShell](#attach-local-provider-to-powershell) - [Debugging with citrix-daas-rest-go client code in Visual Studio Code](#debugging-with-citrix-daas-rest-go-client-code-in-visual-studio-code) + - [Handling Terraform lists/sets and nested objects](#handling-terraform-listssets-and-nested-objects) + - [Converting to Go native types](#converting-to-go-native-types) + - [Preserving order in lists](#preserving-order-in-lists) - [Running the tests](#running-the-tests) - [Commonly faced errors](#commonly-faced-errors) - [Plugin for Terraform Provider for StoreFront Developer Guide](#plugin-for-terraform-provider-for-storefront-developer-guide) + ## Install Dependencies * Install Go on your local system: https://go.dev/doc/install * `choco install golang` @@ -85,6 +88,35 @@ Run [Debugging Provider code in Visual Studio Code](#debugging-provider-code-in- Set a breakpoint in `terraform-provider-citrix/internal/provider/provider.go::Configure` +## Handling Terraform lists/sets and nested objects +### Converting to Go native types +When the Terraform configuration, state, or plan is being converted into a Go model we must use `types.List` and `types.Object` for lists and nested objects rather than go native slices and structs. This is in order to support Null/Unknown values. Unknown is especially important because any variables in the .tf configuration files can be unknown in `ValidateConfig` and `ModifyPlan`. However, handling these Terraform List and Object types is cumbersome as they are dynamically typed at runtime. See [this doc](https://developer.hashicorp.com/terraform/plugin/framework/handling-data/accessing-values) for more information. + +In order to reduce errors this project has introduced a system to convert between Terraform List/Object and Go native slices/structs. When data needs to be operated on it should be first converted to the Go native representation, then converted back to the Terraform representation. The following helper methods can handle this for you. + +| 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.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 4 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. + +### Preserving order in lists +Often time the order of elements in a list does not matter to the service. In this case one of the following helper functions should be used. These functions will get state list in sync with the remote list while preserving the order in the state when possible. + +| Function | Input | Notes | +|----------|-------|-------| +| `RefreshList` | `[]string` | | +| `RefreshUsersList` | `types.Set` | Will ensure users are not duplicated by UPN or SAMname | +| `RefreshListValues` | `types.List` of `string` | | +| `RefreshListValueProperties` | `types.List` of `types.Object` | Each element will have its `RefreshListItem` method called. The element's type must implement the `RefreshableListItemWithAttributes` interface | + ## Running the tests Before running the tests, you need to provide values for environment variables required by the test files. diff --git a/README.md b/README.md index 0d65fc1..c3c3211 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ 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. Please note that this provider is still in tech preview. -## Table of Content +## Table of Contents - [Plugin for Terraform Provider for Citrix®](#plugin-for-terraform-provider-for-citrix) - - [Table of Content](#table-of-content) + - [Table of Contents](#table-of-contents) - [Contacting the Maintainers](#contacting-the-maintainers) - [Plugin for Terraform Provider for Citrix® Documentation](#plugin-for-terraform-provider-for-citrix-documentation) - [Navigating the repository](#navigating-the-repository) @@ -24,7 +24,7 @@ Citrix has developed a custom Terraform provider for automating Citrix product d - [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) - - [Deployment Guides](#deployment-guides) + - [Examples and Deployment Guides](#examples-and-deployment-guides) - [Frequently Asked Questions](#frequently-asked-questions) - [What resource is supported for different connection types?](#what-resource-is-supported-for-different-connection-types) - [What provisioning types are supported for machine catalog?](#what-provisioning-types-are-supported-for-machine-catalog) @@ -66,24 +66,19 @@ Example for Cloud site: ```hcl provider "citrix" { - region = "US" # Optionally set with `CITRIX_REGION` environment variable. - environment = "Production" # Optionally set with `CITRIX_ENVIRONMENT` environment variable. 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 also set `hostname` for cloud site to force override the Citrix DaaS service URL for a cloud customer. - 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 | |--------------|-----------------------------------|---------------------------------------| -| region | `US` / `EU` / `AP-S` / `JP` | N/A | -| environment | `Production` / `Staging` | N/A | +| 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 | @@ -162,9 +157,10 @@ Resource locations contain the resources (e.g. cloud connectors) required to del ### Managing StoreFront resources Please refer to the [StoreFront.md](StoreFront.md) to configure StoreFront resources via terraform. -## Deployment Guides -Detailed instructions on setting up deployments on different cloud providers. +## Examples and Deployment Guides +Basic example templates for getting started: [/examples](/examples) +Detailed instructions on setting up deployments on different cloud providers from Citrix Tech Zone: - [AWS EC2](https://community.citrix.com/tech-zone/build/deployment-guides/terraform-daas-aws/) - [Azure](https://community.citrix.com/tech-zone/build/deployment-guides/citrix-daas-terraform-azure/) - [GCP](https://community.citrix.com/tech-zone/build/deployment-guides/terraform-daas-gcp/) @@ -211,4 +207,4 @@ This project is Licensed under the Apache License, Version 2.0 (the "License"); 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 © 2023. Citrix Systems, Inc. \ No newline at end of file +Copyright © 2024. Citrix Systems, Inc. \ No newline at end of file diff --git a/StoreFront.md b/StoreFront.md index 88ea32d..b1d1c79 100644 --- a/StoreFront.md +++ b/StoreFront.md @@ -3,7 +3,6 @@ This Terraform module allows you to manage resources in Citrix StoreFront. ## Table of Contents - - [Terraform Module for Citrix StoreFront](#terraform-module-for-citrix-storefront) - [Table of Contents](#table-of-contents) - [Prerequisites](#prerequisites) @@ -26,9 +25,9 @@ This Terraform module allows you to manage resources in Citrix StoreFront. If running the StoreFront provider on a machine other than the machine where StoreFront is installed, please provide the Active Directory Admin credentials in either environment variables or provider configuration - `SF_COMPUTER_NAME`: - The name of the remote computer where the StoreFront server is running. - - `SF_AD_ADMAIN_USERNAME`: + - `SF_AD_ADMIN_USERNAME`: - The Active Directory Admin username to connect to the remote PowerShell of the StoreFront Server machine. - - `SF_AD_ADMAIN_PASSWORD`: + - `SF_AD_ADMIN_PASSWORD`: - The Active Directory Admin password to connect to the remote PowerShell of the StoreFront server machine. diff --git a/docs/data-sources/application_folder_details.md b/docs/data-sources/application_folder_details.md index 57d7964..d675c69 100644 --- a/docs/data-sources/application_folder_details.md +++ b/docs/data-sources/application_folder_details.md @@ -30,7 +30,7 @@ Data source for retrieving details of applications belonging to a specific folde Read-Only: - `application_folder_path` (String) The path of the folder which the application belongs to -- `delivery_groups` (List of String) The delivery groups which the application is associated with. +- `delivery_groups` (Set of String) The delivery groups which the application is associated with. - `description` (String) The description of the application. - `installed_app_properties` (Attributes) The installed application properties of the application. (see [below for nested schema](#nestedatt--applications_list--installed_app_properties)) - `name` (String) The name of the application. diff --git a/docs/index.md b/docs/index.md index c76e92c..50caef8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -33,8 +33,8 @@ provider "citrix" { ### Optional -- `client_id` (String) Client Id for Citrix DaaS service authentication.
For Citrix On-Premises customers: Use this to specify Domain Admin Username.
For Citrix Cloud customers: Use this to specify Cloud API Key Client Id.
Can be set via Environment Variable **CITRIX_CLIENT_ID**. -- `client_secret` (String, Sensitive) Client Secret for Citrix DaaS service authentication.
For Citrix on-premises customers: Use this to specify Domain Admin Password.
For Citrix Cloud customers: Use this to specify Cloud API Key Client Secret.
Can be set via Environment Variable **CITRIX_CLIENT_SECRET**. +- `client_id` (String) Client Id for Citrix DaaS service authentication.
For Citrix On-Premises customers: Use this to specify a DDC administrator username.
For Citrix Cloud customers: Use this to specify Cloud API Key Client Id.
Can be set via Environment Variable **CITRIX_CLIENT_ID**. +- `client_secret` (String, Sensitive) Client Secret for Citrix DaaS service authentication.
For Citrix on-premises customers: Use this to specify a DDC administrator password.
For Citrix Cloud customers: Use this to specify Cloud API Key Client Secret.
Can be set via Environment Variable **CITRIX_CLIENT_SECRET**. - `customer_id` (String) Citrix Cloud customer ID. Only applicable for Citrix Cloud customers.
Can be set via Environment Variable **CITRIX_CUSTOMER_ID**. - `disable_ssl_verification` (Boolean) Disable SSL verification against the target DDC.
Only applicable to on-premises customers. Citrix Cloud customers should omit this option. 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 config is set for a known DDC hostname.
[It is recommended to configure a valid certificate for the target DDC](https://docs.citrix.com/en-us/citrix-virtual-apps-desktops/install-configure/install-core/secure-web-studio-deployment)
Can be set via Environment Variable **CITRIX_DISABLE_SSL_VERIFICATION**. - `environment` (String) Citrix Cloud environment of the customer. Only applicable for Citrix Cloud customers. Available options: `Production`, `Staging`, `Japan`, `JapanStaging`, `Gov`, `GovStaging`.
Can be set via Environment Variable **CITRIX_ENVIRONMENT**. @@ -46,6 +46,6 @@ provider "citrix" { Required: -- `ad_admin_password` (String) Active Directory Admin Password to connect to storefront server
Only applicable for Citrix on-premises customers. Use this to specify AD admin password
Can be set via Environment Variable **SF_AD_ADMAIN_PASSWORD**. -- `ad_admin_username` (String) Active Directory Admin Username to connect to storefront server
Only applicable for Citrix on-premises customers. Use this to specify AD admin username
Can be set via Environment Variable **SF_AD_ADMAIN_USERNAME**. +- `ad_admin_password` (String) Active Directory Admin Password to connect to storefront server
Only applicable for Citrix on-premises customers. Use this to specify AD admin password
Can be set via Environment Variable **SF_AD_ADMIN_PASSWORD**. +- `ad_admin_username` (String) Active Directory Admin Username to connect to storefront server
Only applicable for Citrix on-premises customers. Use this to specify AD admin username
Can be set via Environment Variable **SF_AD_ADMIN_USERNAME**. - `computer_name` (String) StoreFront server computer Name
Only applicable for Citrix on-premises customers. Use this to specify StoreFront server computer name
Can be set via Environment Variable **SF_COMPUTER_NAME**. diff --git a/docs/resources/admin_role.md b/docs/resources/admin_role.md index b37c971..d66194c 100644 --- a/docs/resources/admin_role.md +++ b/docs/resources/admin_role.md @@ -38,7 +38,7 @@ resource "citrix_admin_role" "cloud_example_role" { ### Required - `name` (String) Name of the admin role. -- `permissions` (List of String) List of permissions to be associated with the admin role. 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). +- `permissions` (Set of String) Permissions to be associated with the admin role. 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). ### Optional diff --git a/docs/resources/admin_scope.md b/docs/resources/admin_scope.md index f330e50..71bd3f6 100644 --- a/docs/resources/admin_scope.md +++ b/docs/resources/admin_scope.md @@ -16,16 +16,6 @@ Manages an administrator scope. resource "citrix_admin_scope" "example-admin-scope" { name = "example-admin-scope" description = "Example admin scope for delivery group and machine catalog" - scoped_objects = [ - { - object_type = "DeliveryGroup", - object = "" - }, - { - object_type = "MachineCatalog", - object = "" - } - ] } ``` @@ -39,20 +29,11 @@ resource "citrix_admin_scope" "example-admin-scope" { ### Optional - `description` (String) Description of the admin scope. -- `scoped_objects` (Attributes List) List of scoped objects to be associated with the admin scope. (see [below for nested schema](#nestedatt--scoped_objects)) ### Read-Only - `id` (String) ID of the admin scope. - -### Nested Schema for `scoped_objects` - -Required: - -- `object` (String) Name of an existing object under the object type to be added to the scope. -- `object_type` (String) Type of the scoped object. Allowed values are: `HypervisorConnection`, `MachineCatalog`, `DeliveryGroup`, `ApplicationGroup`, `Tag`, `PolicySet` and `Unknown`. - ## Import Import is supported using the following syntax: diff --git a/docs/resources/admin_user.md b/docs/resources/admin_user.md index 985de67..e09b75e 100644 --- a/docs/resources/admin_user.md +++ b/docs/resources/admin_user.md @@ -33,7 +33,7 @@ resource "citrix_admin_user" "example-admin-user" { - `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. - `name` (String) Name of an existing user in the active directory. -- `rights` (Attributes List) List of rights to be associated with the admin user. (see [below for nested schema](#nestedatt--rights)) +- `rights` (Attributes List) Rights to be associated with the admin user. (see [below for nested schema](#nestedatt--rights)) ### Optional diff --git a/docs/resources/application.md b/docs/resources/application.md index c8b844c..ba400bf 100644 --- a/docs/resources/application.md +++ b/docs/resources/application.md @@ -24,6 +24,8 @@ resource "citrix_application" "example-application" { working_directory = "" } delivery_groups = [citrix_delivery_group.example-delivery-group.id] + icon = citrix_application_icon.example-application-icon.id + limit_visibility_to_users = ["example\\user1"] } ``` @@ -32,7 +34,7 @@ resource "citrix_application" "example-application" { ### Required -- `delivery_groups` (List of String) The delivery group id's to which the application should be added. +- `delivery_groups` (Set of String) The delivery group IDs to which the application should be added. - `installed_app_properties` (Attributes) The install application properties. (see [below for nested schema](#nestedatt--installed_app_properties)) - `name` (String) Name of the application. - `published_name` (String) A display name for the application that is shown to users. @@ -41,6 +43,8 @@ resource "citrix_application" "example-application" { - `application_folder_path` (String) The application folder path in which the application should be created. - `description` (String) Description of the application. +- `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. Must be in `DOMAIN\UserOrGroupName` or `user@domain.com` format ### Read-Only diff --git a/docs/resources/application_group.md b/docs/resources/application_group.md new file mode 100644 index 0000000..7ff8276 --- /dev/null +++ b/docs/resources/application_group.md @@ -0,0 +1,50 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "citrix_application_group Resource - citrix" +subcategory: "" +description: |- + Resource for creating and managing application group. +--- + +# citrix_application_group (Resource) + +Resource for creating and managing application group. + +## Example Usage + +```terraform +resource "citrix_application_group" "example-application-group" { + name = "example-name" + description = "example-description" + included_users = ["user@text.com"] + delivery_groups = [citrix_delivery_group.example-delivery-group.id, citrix_delivery_group.example-delivery-group-2.id] +} +``` + + +## Schema + +### Required + +- `delivery_groups` (Set of String) Delivery groups to associate with the application group. +- `name` (String) Name of the application group to create. + +### Optional + +- `description` (String) Description of the application group. +- `included_users` (Set of String) Users who can use this application group. Must be in `Domain\UserOrGroupName` or `user@domain.com` format +- `restrict_to_tag` (String) The tag to restrict the application group to. +- `scopes` (Set of String) The IDs of the scopes for the application group to be a part of. + +### Read-Only + +- `id` (String) GUID identifier of the application group. + +## Import + +Import is supported using the following syntax: + +```shell +# Application group can be imported by specifying the GUID +terraform import citrix_application_group.example-application-group b620d505-0d0d-43b1-8c94-5cb21c5ab40d +``` diff --git a/docs/resources/application_icon.md b/docs/resources/application_icon.md new file mode 100644 index 0000000..052ded0 --- /dev/null +++ b/docs/resources/application_icon.md @@ -0,0 +1,43 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "citrix_application_icon Resource - citrix" +subcategory: "" +description: |- + Resource for managing application icons. +--- + +# citrix_application_icon (Resource) + +Resource for managing application icons. + +## Example Usage + +```terraform +resource "citrix_application_icon" "example-application-icon" { + raw_data = "example-raw-data" +} + +# You can use the following PowerShell commands to convert an .ico file to base64: +# $pic = Get-Content 'fileName.ico' -Encoding Byte +# $picBase64 = [System.Convert]::ToBase64String($pic) +``` + + +## Schema + +### Required + +- `raw_data` (String) Prepare an icon in ICO format and convert its binary raw data to base64 encoding. Use the base64 encoded string as the value of this attribute. + +### Read-Only + +- `id` (String) GUID identifier of the application icon. + +## Import + +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 +``` diff --git a/docs/resources/aws_hypervisor.md b/docs/resources/aws_hypervisor.md index 7a22fbd..1041732 100644 --- a/docs/resources/aws_hypervisor.md +++ b/docs/resources/aws_hypervisor.md @@ -34,6 +34,10 @@ resource "citrix_aws_hypervisor" "example-aws-hypervisor" { - `secret_key` (String, Sensitive) The secret key used to authenticate with the AWS APIs. - `zone` (String) Id of the zone the hypervisor is associated with. +### Optional + +- `scopes` (Set of String) The IDs of the scopes for the hypervisor to be a part of. + ### Read-Only - `id` (String) GUID identifier of the hypervisor. diff --git a/docs/resources/aws_hypervisor_resource_pool.md b/docs/resources/aws_hypervisor_resource_pool.md index df0d300..5079dd4 100644 --- a/docs/resources/aws_hypervisor_resource_pool.md +++ b/docs/resources/aws_hypervisor_resource_pool.md @@ -32,7 +32,7 @@ resource "citrix_aws_hypervisor_resource_pool" "example-aws-hypervisor-resource- - `availability_zone` (String) The name of the availability zone resource to use for provisioning operations in this resource pool. - `hypervisor` (String) Id of the hypervisor for which the resource pool needs to be created. - `name` (String) Name of the resource pool. Name should be unique across all hypervisors. -- `subnets` (List of String) List of subnets to allocate VDAs within the virtual private cloud. +- `subnets` (List of String) Subnets to allocate VDAs within the virtual private cloud. - `vpc` (String) Name of the virtual private cloud. ### Read-Only diff --git a/docs/resources/azure_hypervisor.md b/docs/resources/azure_hypervisor.md index 3da0102..de34180 100644 --- a/docs/resources/azure_hypervisor.md +++ b/docs/resources/azure_hypervisor.md @@ -40,6 +40,7 @@ resource "citrix_azure_hypervisor" "example-azure-hypervisor" { - `application_secret_expiration_date` (String) The expiration date of the application secret of the service principal used to access the Azure APIs. Format is YYYY-MM-DD. - `enable_azure_ad_device_management` (Boolean) Enable Azure AD device management. Default is false. +- `scopes` (Set of String) The IDs of the scopes for the hypervisor to be a part of. ### Read-Only diff --git a/docs/resources/azure_hypervisor_resource_pool.md b/docs/resources/azure_hypervisor_resource_pool.md index 9e52fe2..2af3719 100644 --- a/docs/resources/azure_hypervisor_resource_pool.md +++ b/docs/resources/azure_hypervisor_resource_pool.md @@ -34,7 +34,7 @@ resource "citrix_azure_hypervisor_resource_pool" "example-azure-hypervisor-resou - `hypervisor` (String) Id of the hypervisor for which the resource pool needs to be created. - `name` (String) Name of the resource pool. Name should be unique across all hypervisors. - `region` (String) Cloud Region where the virtual network sits in. -- `subnets` (List of String) List of subnets to allocate VDAs within the virtual network. +- `subnets` (List of String) Subnets to allocate VDAs within the virtual network. - `virtual_network` (String) Name of the cloud virtual network. - `virtual_network_resource_group` (String) The name of the resource group where the vnet resides. diff --git a/docs/resources/delivery_group.md b/docs/resources/delivery_group.md index 6d51cee..4ae0236 100644 --- a/docs/resources/delivery_group.md +++ b/docs/resources/delivery_group.md @@ -24,7 +24,7 @@ resource "citrix_delivery_group" "example-delivery-group" { desktops = [ { published_name = "Example Desktop" - description = "Desription for example desktop" + description = "Description for example desktop" restricted_access_users = { allow_list = [ "user1@example.com" @@ -113,7 +113,6 @@ resource "citrix_delivery_group" "example-delivery-group" { } } ] - policy_set_id = citrix_policy_set.example-policy-set.id minimum_functional_level = "L7_20" } @@ -133,10 +132,13 @@ resource "citrix_delivery_group" "example-delivery-group" { - `autoscale_settings` (Attributes) The power management settings governing the machine(s) in the delivery group. (see [below for nested schema](#nestedatt--autoscale_settings)) - `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. This setting only impacts Single Session OS Random (pooled) desktops which are power managed. LHC is always enabled for Single Session OS static and Multi Session OS desktops.When set to `true`, machines will remain available and allow new connections and changes to the machine caused by a user might be present in subsequent sessions. When set to `false`, machines in the delivery group will be unavailable for new connections during a Local Host Cache event. - `minimum_functional_level` (String) Specifies the minimum functional level for the VDA machines in the delivery group. Defaults to `L7_20`. - `policy_set_id` (String) GUID identifier of the policy set. - `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. +- `storefront_servers` (Set of String) A list of GUID identifiers of StoreFront Servers to associate with the delivery group. ### Read-Only @@ -186,14 +188,14 @@ Optional: Required: -- `days_of_week` (List of String) The pattern of days of the week that the power time scheme covers. +- `days_of_week` (Set of String) The pattern of days of the week that the power time scheme covers. - `display_name` (String) The name of the power time scheme as displayed in the console. -- `peak_time_ranges` (List of String) List of peak time ranges during the day. e.g. 09:00-17:00 +- `peak_time_ranges` (Set of String) Peak time ranges during the day. e.g. 09:00-17:00 - `pool_using_percentage` (Boolean) Indicates whether the integer values in the pool size array are to be treated as absolute values (if this value is `false`) or as percentages of the number of machines in the delivery group (if this value is `true`). Optional: -- `pool_size_schedules` (Attributes List) List of pool size schedules during the day. Each is specified as a time range and an indicator of the number of machines that should be powered on during that time range. Do not specify schedules when no machines should be powered on. (see [below for nested schema](#nestedatt--autoscale_settings--power_time_schemes--pool_size_schedules)) +- `pool_size_schedules` (Attributes List) Pool size schedules during the day. Each is specified as a time range and an indicator of the number of machines that should be powered on during that time range. Do not specify schedules when no machines should be powered on. (see [below for nested schema](#nestedatt--autoscale_settings--power_time_schemes--pool_size_schedules)) ### Nested Schema for `autoscale_settings.power_time_schemes.pool_size_schedules` @@ -225,8 +227,8 @@ Optional: Optional: -- `allow_list` (List of String) Users who can use this Desktop. Must be in `DOMAIN\UserOrGroupName` or `user@domain.com` format -- `block_list` (List of String) Users who cannot use this Desktop. A block list is meaningful only when used to block users in the allow list. Must be in `Domain\UserOrGroupName` or `user@domain.com` format +- `allow_list` (Set of String) Users who can use this Desktop. Must be in `DOMAIN\UserOrGroupName` or `user@domain.com` format +- `block_list` (Set of String) Users who cannot use this Desktop. A block list is meaningful only when used to block users in the allow list. Must be in `DOMAIN\UserOrGroupName` or `user@domain.com` format @@ -248,7 +250,7 @@ Required: Optional: - `day_in_month` (String) The day in the month on which the reboot schedule runs monthly. Can only be set to `Sunday`, `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, or `Saturday`. -- `days_in_week` (List of String) The days of the week on which the reboot schedule runs weekly. Can only be set to `Sunday`, `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, or `Saturday`. +- `days_in_week` (Set of String) The days of the week on which the reboot schedule runs weekly. Can only be set to `Sunday`, `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, or `Saturday`. - `description` (String) The description of the reboot schedule. - `reboot_notification_to_users` (Attributes) The reboot notification for the reboot schedule. Not available for natural reboot. (see [below for nested schema](#nestedatt--reboot_schedules--reboot_notification_to_users)) - `restrict_to_tag` (String) The tag to which the reboot schedule is restricted. @@ -274,8 +276,8 @@ Optional: Optional: -- `allow_list` (List of String) Users who can use this Delivery Group. Must be in `DOMAIN\UserOrGroupName` or `user@domain.com` format -- `block_list` (List of String) Users who cannot use this Delivery Group. A block list is meaningful only when used to block users in the allow list. Must be in `Domain\UserOrGroupName` or `user@domain.com` format +- `allow_list` (Set of String) Users who can use this Delivery Group. Must be in `DOMAIN\UserOrGroupName` or `user@domain.com` format +- `block_list` (Set of String) Users who cannot use this Delivery Group. A block list is meaningful only when used to block users in the allow list. Must be in `DOMAIN\UserOrGroupName` or `user@domain.com` format ## Import diff --git a/docs/resources/gcp_hypervisor.md b/docs/resources/gcp_hypervisor.md index 808ff20..50ca2c1 100644 --- a/docs/resources/gcp_hypervisor.md +++ b/docs/resources/gcp_hypervisor.md @@ -32,6 +32,10 @@ resource "citrix_gcp_hypervisor" "example-gcp-hypervisor" { - `service_account_id` (String) The service account ID used to access the Google Cloud APIs. - `zone` (String) Id of the zone the hypervisor is associated with. +### Optional + +- `scopes` (Set of String) The IDs of the scopes for the hypervisor to be a part of. + ### Read-Only - `id` (String) GUID identifier of the hypervisor. diff --git a/docs/resources/gcp_hypervisor_resource_pool.md b/docs/resources/gcp_hypervisor_resource_pool.md index c8e451d..ce63412 100644 --- a/docs/resources/gcp_hypervisor_resource_pool.md +++ b/docs/resources/gcp_hypervisor_resource_pool.md @@ -34,7 +34,7 @@ resource "citrix_gcp_hypervisor_resource_pool" "example-gcp-hypervisor-resource- - `name` (String) Name of the resource pool. Name should be unique across all hypervisors. - `project_name` (String) GCP Project name. - `region` (String) Cloud Region where the virtual network sits in. -- `subnets` (List of String) List of subnets to allocate VDAs within the virtual network. +- `subnets` (List of String) Subnets to allocate VDAs within the virtual network. - `vpc` (String) Name of the cloud virtual network. ### Optional diff --git a/docs/resources/machine_catalog.md b/docs/resources/machine_catalog.md index c5e9114..2740996 100644 --- a/docs/resources/machine_catalog.md +++ b/docs/resources/machine_catalog.md @@ -19,10 +19,7 @@ resource "citrix_machine_catalog" "example-azure-mtsession" { zone = "" allocation_type = "Random" session_support = "MultiSession" - is_power_managed = true - is_remote_pc = false provisioning_type = "MCS" - minimum_functional_level = "L7_20" provisioning_scheme = { hypervisor = citrix_azure_hypervisor.example-azure-hypervisor.id hypervisor_resource_pool = citrix_azure_hypervisor_resource_pool.example-azure-hypervisor-resource-pool.id @@ -37,11 +34,19 @@ resource "citrix_machine_catalog" "example-azure-mtsession" { storage_type = "Standard_LRS" use_managed_disks = true service_offering = "Standard_D2_v2" - azure_machine_config = { - resource_group = "" - storage_account = "" - container = "" - master_image = "" + 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 = var.azure_resource_group + master_image = var.azure_master_image + + # For Azure image gallery + # gallery_image = { + # gallery = var.azure_gallery_name + # definition = var.azure_gallery_image_definition + # version = var.azure_gallery_image_version + # } } writeback_cache = { wbc_disk_storage_type = "pd-standard" @@ -53,12 +58,6 @@ resource "citrix_machine_catalog" "example-azure-mtsession" { storage_cost_saving = true } } - network_mapping = [ - { - network_device = "0" - network = "" - } - ] availability_zones = "1,2,..." number_of_total_machines = 1 machine_account_creation_rules ={ @@ -74,8 +73,6 @@ resource "citrix_machine_catalog" "example-aws-mtsession" { zone = "" allocation_type = "Random" session_support = "MultiSession" - is_power_managed = true - is_remote_pc = false provisioning_type = "MCS" provisioning_scheme = { hypervisor = citrix_aws_hypervisor.example-aws-hypervisor.id @@ -96,12 +93,6 @@ resource "citrix_machine_catalog" "example-aws-mtsession" { ] tenancy_type = "Shared" } - network_mapping = [ - { - network_device = "0" - network = "10.0.128.0/20" - } - ] number_of_total_machines = 1 machine_account_creation_rules ={ naming_scheme = "aws-multi-##" @@ -116,8 +107,6 @@ resource "citrix_machine_catalog" "example-gcp-mtsession" { zone = "" allocation_type = "Random" session_support = "MultiSession" - is_power_managed = true - is_remote_pc = false provisioning_type = "MCS" provisioning_scheme = { hypervisor = citrix_gcp_hypervisor.example-gcp-hypervisor.id @@ -130,7 +119,6 @@ resource "citrix_machine_catalog" "example-gcp-mtsession" { service_account_password = "" } gcp_machine_config = { - machine_profile = "" master_image = "" machine_snapshot = "" @@ -155,29 +143,29 @@ resource "citrix_machine_catalog" "example-gcp-mtsession" { resource "citrix_machine_catalog" "example-vsphere-mtsession" { name = "example-vsphere-mtsession" description = "Example multi-session catalog on Vsphere hypervisor" - provisioning_type = "MCS" + zone = "" allocation_type = "Random" session_support = "MultiSession" - zone = "" + provisioning_type = "MCS" provisioning_scheme = { - identity_type = "ActiveDirectory" - number_of_total_machines = 1 - machine_account_creation_rules = { - naming_scheme = "catalog-##" - naming_scheme_type = "Numeric" - } hypervisor = citrix_vsphere_hypervisor.vsphere-hypervisor-1.id hypervisor_resource_pool = citrix_vsphere_hypervisor_resource_pool.vsphere-hypervisor-rp-1.id + identity_type = "ActiveDirectory" + machine_domain_identity = { + domain = "" + service_account = "" + service_account_password = "" + } vsphere_machine_config = { master_image_vm = "" image_snapshot = "///..." cpu_count = 2 memory_mb = 4096 } - machine_domain_identity = { - domain = "" - service_account = "" - service_account_password = "" + number_of_total_machines = 1 + machine_account_creation_rules = { + naming_scheme = "catalog-##" + naming_scheme_type = "Numeric" } } } @@ -185,29 +173,29 @@ resource "citrix_machine_catalog" "example-vsphere-mtsession" { resource "citrix_machine_catalog" "example-xenserver-mtsession" { name = "example-xenserver-mtsession" description = "Example multi-session catalog on XenServer hypervisor" - provisioning_type = "MCS" + zone = "" allocation_type = "Random" session_support = "MultiSession" - zone = "" + provisioning_type = "MCS" provisioning_scheme = { - identity_type = "ActiveDirectory" - number_of_total_machines = 1 - machine_account_creation_rules = { - naming_scheme = "catalog-##" - naming_scheme_type = "Numeric" - } hypervisor = citrix_xenserver_hypervisor.xenserver-hypervisor-1.id hypervisor_resource_pool = citrix_xenserver_hypervisor_resource_pool.xenserver-hypervisor-rp-1.id + identity_type = "ActiveDirectory" + machine_domain_identity = { + domain = "" + service_account = "" + service_account_password = "" + } xenserver_machine_config = { master_image_vm = "" image_snapshot = "///..." cpu_count = 2 memory_mb = 4096 } - machine_domain_identity = { - domain = "" - service_account = "" - service_account_password = "" + number_of_total_machines = 1 + machine_account_creation_rules = { + naming_scheme = "catalog-##" + naming_scheme_type = "Numeric" } } } @@ -215,19 +203,19 @@ resource "citrix_machine_catalog" "example-xenserver-mtsession" { resource "citrix_machine_catalog" "example-nutanix-mtsession" { name = "example-nutanix-mtsession" description = "Example multi-session catalog on Nutanix hypervisor" - provisioning_type = "MCS" + zone = citrix_zone.example-zone.id allocation_type = "Random" session_support = "MultiSession" - zone = citrix_zone.example-zone.id + provisioning_type = "MCS" provisioning_scheme = { - identity_type = "ActiveDirectory" - number_of_total_machines = 1 - machine_account_creation_rules = { - naming_scheme = "catalog-##" - naming_scheme_type = "Numeric" - } hypervisor = citrix_nutanix_hypervisor.example-nutanix-hypervisor.id hypervisor_resource_pool = citrix_nutanix_hypervisor_resource_pool.example-nutanix-rp.id + identity_type = "ActiveDirectory" + machine_domain_identity = { + domain = "" + service_account = "" + service_account_password = "" + } nutanix_machine_config = { container = "" master_image = "" @@ -235,10 +223,10 @@ resource "citrix_machine_catalog" "example-nutanix-mtsession" { memory_mb = 4096 cores_per_cpu_count = 2 } - machine_domain_identity = { - domain = "" - service_account = "" - service_account_password = "" + number_of_total_machines = 1 + machine_account_creation_rules = { + naming_scheme = "catalog-##" + naming_scheme_type = "Numeric" } } } @@ -259,7 +247,8 @@ resource "citrix_machine_catalog" "example-manual-power-managed-mtsession" { { region = "East US" resource_group_name = "machine-resource-group-name" - machine_name = "Domain\\MachineName" + machine_account = "DOMAIN\\MachineName" + machine_name = "MachineName" } ] } @@ -279,10 +268,10 @@ resource "citrix_machine_catalog" "example-manual-non-power-managed-mtsession" { { machines = [ { - machine_name = "Domain\\MachineName1" + machine_account = "DOMAIN\\MachineName1" }, { - machine_name = "Domain\\MachineName2" + machine_account = "DOMAIN\\MachineName2" } ] } @@ -302,10 +291,10 @@ resource "citrix_machine_catalog" "example-remote-pc" { { machines = [ { - machine_name = "Domain\\MachineName1" + machine_account = "DOMAIN\\MachineName1" }, { - machine_name = "Domain\\MachineName2" + machine_account = "DOMAIN\\MachineName2" } ] } @@ -335,10 +324,18 @@ resource "citrix_machine_catalog" "example-non-domain-joined-azure-mcs" { use_managed_disks = true service_offering = "Standard_D2_v2" azure_master_image = { - resource_group = "" - storage_account = "" - container = "" - 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 = var.azure_resource_group + master_image = var.azure_master_image + + # For Azure image gallery + # gallery_image = { + # gallery = var.azure_gallery_name + # definition = var.azure_gallery_image_definition + # version = var.azure_gallery_image_version + # } } writeback_cache = { wbc_disk_storage_type = "pd-standard" @@ -376,10 +373,11 @@ resource "citrix_machine_catalog" "example-non-domain-joined-azure-mcs" { - `description` (String) Description of the machine catalog. - `is_power_managed` (Boolean) Specify if the machines in the machine catalog will be power managed. - `is_remote_pc` (Boolean) Specify if this catalog is for Remote PC access. -- `machine_accounts` (Attributes List) List of machine accounts to add to the catalog. Only to be used when using `provisioning_type = MANUAL` (see [below for nested schema](#nestedatt--machine_accounts)) +- `machine_accounts` (Attributes List) Machine accounts to add to the catalog. Only to be used when using `provisioning_type = MANUAL` (see [below for nested schema](#nestedatt--machine_accounts)) - `minimum_functional_level` (String) Specifies the minimum functional level for the VDA machines in the catalog. Defaults to `L7_20`. - `provisioning_scheme` (Attributes) Machine catalog provisioning scheme. Required when `provisioning_type = MCS` (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. - `vda_upgrade_type` (String) Type of Vda Upgrade. Choose between LTSR and CR. When omitted, Vda Upgrade is disabled. ### Read-Only @@ -391,7 +389,7 @@ resource "citrix_machine_catalog" "example-non-domain-joined-azure-mcs" { Required: -- `machines` (Attributes List) List of machines (see [below for nested schema](#nestedatt--machine_accounts--machines)) +- `machines` (Attributes List) Machines to add to the catalog (see [below for nested schema](#nestedatt--machine_accounts--machines)) Optional: @@ -430,7 +428,7 @@ Required: Optional: -- `availability_zones` (String) The Availability Zones for provisioning virtual machines. Use a comma as a delimiter for multiple availability_zones. +- `availability_zones` (List of String) The Availability Zones for provisioning virtual machines. - `aws_machine_config` (Attributes) Machine Configuration For AWS EC2 MCS catalog. (see [below for nested schema](#nestedatt--provisioning_scheme--aws_machine_config)) - `azure_machine_config` (Attributes) Machine Configuration For Azure MCS catalog. (see [below for nested schema](#nestedatt--provisioning_scheme--azure_machine_config)) - `custom_properties` (Attributes List) **This is an advanced feature. Use with caution.** Custom properties to be set for the machine catalog. For properties that are already supported as a terraform configuration field, please use terraform field instead. (see [below for nested schema](#nestedatt--provisioning_scheme--custom_properties)) @@ -457,10 +455,29 @@ Required: - `image_ami` (String) AMI of the AWS image to be used as the template image for the machine catalog. - `master_image` (String) The name of the virtual machine image that will be used. -- `security_groups` (List of String) List of security groups to associate with the machine. When omitted, the default security group of the VPC will be used by default. +- `security_groups` (List of String) Security groups to associate with the machine. When omitted, the default security group of the VPC will be used by default. - `service_offering` (String) The AWS VM Sku to use when creating machines. - `tenancy_type` (String) Tenancy type of the machine. Choose between `Shared`, `Instance` and `Host`. +Optional: + +- `image_update_reboot_options` (Attributes) The options for how rebooting is performed for image update. When omitted, image update on the VDAs will be performed on next shutdown. (see [below for nested schema](#nestedatt--provisioning_scheme--aws_machine_config--image_update_reboot_options)) +- `master_image_note` (String) The note for the master image. + + +### Nested Schema for `provisioning_scheme.aws_machine_config.image_update_reboot_options` + +Required: + +- `reboot_duration` (Number) Approximate maximum duration over which the reboot cycle runs, in minutes. Set to `-1` to skip reboot, and perform image update on the VDAs on next shutdown. Set to `0` to reboot all machines immediately. + +Optional: + +- `warning_duration` (Number) Time in minutes prior to a machine reboot at which a warning message is displayed in all user sessions on that machine. When omitted, no warning about reboot will be displayed in user session. +- `warning_message` (String) Warning message displayed in user sessions on a machine scheduled for a reboot. The optional pattern '%m%' is replaced by the number of minutes until the reboot. +- `warning_repeat_interval` (Number) Number of minutes to wait before showing the reboot warning message again. + + ### Nested Schema for `provisioning_scheme.azure_machine_config` @@ -475,8 +492,10 @@ Optional: - `disk_encryption_set` (Attributes) The configuration for Disk Encryption Set (DES). The DES must be in the same subscription and region as your resources. If your master image is encrypted with a DES, use the same DES when creating this machine catalog. When using a DES, if you later disable the key with which the corresponding DES is associated in Azure, you can no longer power on the machines in this catalog or add machines to it. (see [below for nested schema](#nestedatt--provisioning_scheme--azure_machine_config--disk_encryption_set)) - `enroll_in_intune` (Boolean) Specify whether to enroll machines in Microsoft Intune. Use this property only when `identity_type` is set to `AzureAD`. +- `image_update_reboot_options` (Attributes) The options for how rebooting is performed for image update. When omitted, image update on the VDAs will be performed on next shutdown. (see [below for nested schema](#nestedatt--provisioning_scheme--azure_machine_config--image_update_reboot_options)) - `license_type` (String) Windows license type used to provision virtual machines in Azure at the base compute rate. License types include: `Windows_Client` and `Windows_Server`. - `machine_profile` (Attributes) The name of the virtual machine or template spec that will be used to identify the default value for the tags, virtual machine size, boot diagnostics, host cache property of OS disk, accelerated networking and availability zone.
Required when identity_type is set to `AzureAD` (see [below for nested schema](#nestedatt--provisioning_scheme--azure_machine_config--machine_profile)) +- `master_image_note` (String) The note for the master image. - `use_azure_compute_gallery` (Attributes) Use this to place prepared image in Azure Compute Gallery. Required when `storage_type = Azure_Ephemeral_OS_Disk`. (see [below for nested schema](#nestedatt--provisioning_scheme--azure_machine_config--use_azure_compute_gallery)) - `use_managed_disks` (Boolean) Indicate whether to use Azure managed disks for the provisioned virtual machine. - `vda_resource_group` (String) Designated resource group where the VDA VMs will be located on Azure. @@ -517,6 +536,20 @@ Required: - `disk_encryption_set_resource_group` (String) The name of the resource group in which the disk encryption set resides. + +### Nested Schema for `provisioning_scheme.azure_machine_config.image_update_reboot_options` + +Required: + +- `reboot_duration` (Number) Approximate maximum duration over which the reboot cycle runs, in minutes. Set to `-1` to skip reboot, and perform image update on the VDAs on next shutdown. Set to `0` to reboot all machines immediately. + +Optional: + +- `warning_duration` (Number) Time in minutes prior to a machine reboot at which a warning message is displayed in all user sessions on that machine. When omitted, no warning about reboot will be displayed in user session. +- `warning_message` (String) Warning message displayed in user sessions on a machine scheduled for a reboot. The optional pattern '%m%' is replaced by the number of minutes until the reboot. +- `warning_repeat_interval` (Number) Number of minutes to wait before showing the reboot warning message again. + + ### Nested Schema for `provisioning_scheme.azure_machine_config.machine_profile` @@ -574,10 +607,26 @@ Required: Optional: +- `image_update_reboot_options` (Attributes) The options for how rebooting is performed for image update. When omitted, image update on the VDAs will be performed on next shutdown. (see [below for nested schema](#nestedatt--provisioning_scheme--gcp_machine_config--image_update_reboot_options)) - `machine_profile` (String) The name of the virtual machine template that will be used to identify the default value for the tags, virtual machine size, boot diagnostics, host cache property of OS disk, accelerated networking and availability zone. If not specified, the VM specified in master_image will be used as template. - `machine_snapshot` (String) The name of the virtual machine snapshot of a GCP VM that will be used as master image. +- `master_image_note` (String) The note for the master image. - `writeback_cache` (Attributes) Write-back Cache config. Leave this empty to disable Write-back Cache. (see [below for nested schema](#nestedatt--provisioning_scheme--gcp_machine_config--writeback_cache)) + +### Nested Schema for `provisioning_scheme.gcp_machine_config.image_update_reboot_options` + +Required: + +- `reboot_duration` (Number) Approximate maximum duration over which the reboot cycle runs, in minutes. Set to `-1` to skip reboot, and perform image update on the VDAs on next shutdown. Set to `0` to reboot all machines immediately. + +Optional: + +- `warning_duration` (Number) Time in minutes prior to a machine reboot at which a warning message is displayed in all user sessions on that machine. When omitted, no warning about reboot will be displayed in user session. +- `warning_message` (String) Warning message displayed in user sessions on a machine scheduled for a reboot. The optional pattern '%m%' is replaced by the number of minutes until the reboot. +- `warning_repeat_interval` (Number) Number of minutes to wait before showing the reboot warning message again. + + ### Nested Schema for `provisioning_scheme.gcp_machine_config.writeback_cache` @@ -625,6 +674,25 @@ Required: - `master_image` (String) The name of the master image that will be the template for all virtual machines in this catalog. - `memory_mb` (Number) The maximum amount of memory that virtual machines created from the provisioning scheme should use. +Optional: + +- `image_update_reboot_options` (Attributes) The options for how rebooting is performed for image update. When omitted, image update on the VDAs will be performed on next shutdown. (see [below for nested schema](#nestedatt--provisioning_scheme--nutanix_machine_config--image_update_reboot_options)) +- `master_image_note` (String) The note for the master image. + + +### Nested Schema for `provisioning_scheme.nutanix_machine_config.image_update_reboot_options` + +Required: + +- `reboot_duration` (Number) Approximate maximum duration over which the reboot cycle runs, in minutes. Set to `-1` to skip reboot, and perform image update on the VDAs on next shutdown. Set to `0` to reboot all machines immediately. + +Optional: + +- `warning_duration` (Number) Time in minutes prior to a machine reboot at which a warning message is displayed in all user sessions on that machine. When omitted, no warning about reboot will be displayed in user session. +- `warning_message` (String) Warning message displayed in user sessions on a machine scheduled for a reboot. The optional pattern '%m%' is replaced by the number of minutes until the reboot. +- `warning_repeat_interval` (Number) Number of minutes to wait before showing the reboot warning message again. + + ### Nested Schema for `provisioning_scheme.vsphere_machine_config` @@ -638,8 +706,24 @@ Required: Optional: - `image_snapshot` (String) The Snapshot of the virtual machine specified in `master_image_vm`. Specify the relative path of the snapshot. Eg: snaphost-1/snapshot-2/snapshot-3. This property is case sensitive. +- `image_update_reboot_options` (Attributes) The options for how rebooting is performed for image update. When omitted, image update on the VDAs will be performed on next shutdown. (see [below for nested schema](#nestedatt--provisioning_scheme--vsphere_machine_config--image_update_reboot_options)) +- `master_image_note` (String) The note for the master image. - `writeback_cache` (Attributes) Write-back Cache config. Leave this empty to disable Write-back Cache. (see [below for nested schema](#nestedatt--provisioning_scheme--vsphere_machine_config--writeback_cache)) + +### Nested Schema for `provisioning_scheme.vsphere_machine_config.image_update_reboot_options` + +Required: + +- `reboot_duration` (Number) Approximate maximum duration over which the reboot cycle runs, in minutes. Set to `-1` to skip reboot, and perform image update on the VDAs on next shutdown. Set to `0` to reboot all machines immediately. + +Optional: + +- `warning_duration` (Number) Time in minutes prior to a machine reboot at which a warning message is displayed in all user sessions on that machine. When omitted, no warning about reboot will be displayed in user session. +- `warning_message` (String) Warning message displayed in user sessions on a machine scheduled for a reboot. The optional pattern '%m%' is replaced by the number of minutes until the reboot. +- `warning_repeat_interval` (Number) Number of minutes to wait before showing the reboot warning message again. + + ### Nested Schema for `provisioning_scheme.vsphere_machine_config.writeback_cache` @@ -666,8 +750,24 @@ Required: Optional: - `image_snapshot` (String) The Snapshot of the virtual machine specified in `master_image_vm`. Specify the relative path of the snapshot. Eg: snaphost-1/snapshot-2/snapshot-3. This property is case sensitive. +- `image_update_reboot_options` (Attributes) The options for how rebooting is performed for image update. When omitted, image update on the VDAs will be performed on next shutdown. (see [below for nested schema](#nestedatt--provisioning_scheme--xenserver_machine_config--image_update_reboot_options)) +- `master_image_note` (String) The note for the master image. - `writeback_cache` (Attributes) Write-back Cache config. Leave this empty to disable Write-back Cache. (see [below for nested schema](#nestedatt--provisioning_scheme--xenserver_machine_config--writeback_cache)) + +### Nested Schema for `provisioning_scheme.xenserver_machine_config.image_update_reboot_options` + +Required: + +- `reboot_duration` (Number) Approximate maximum duration over which the reboot cycle runs, in minutes. Set to `-1` to skip reboot, and perform image update on the VDAs on next shutdown. Set to `0` to reboot all machines immediately. + +Optional: + +- `warning_duration` (Number) Time in minutes prior to a machine reboot at which a warning message is displayed in all user sessions on that machine. When omitted, no warning about reboot will be displayed in user session. +- `warning_message` (String) Warning message displayed in user sessions on a machine scheduled for a reboot. The optional pattern '%m%' is replaced by the number of minutes until the reboot. +- `warning_repeat_interval` (Number) Number of minutes to wait before showing the reboot warning message again. + + ### Nested Schema for `provisioning_scheme.xenserver_machine_config.writeback_cache` diff --git a/docs/resources/nutanix_hypervisor.md b/docs/resources/nutanix_hypervisor.md index ac8a61e..f7d54aa 100644 --- a/docs/resources/nutanix_hypervisor.md +++ b/docs/resources/nutanix_hypervisor.md @@ -42,6 +42,7 @@ resource "citrix_nutanix_hypervisor" "example-nutanix-hypervisor" { - `max_absolute_active_actions` (Number) Maximum number of actions that can execute in parallel on the hypervisor. Default is 100. - `max_absolute_new_actions_per_minute` (Number) Maximum number of actions that can be started on the hypervisor per-minute. Default is 10. - `max_power_actions_percentage_of_machines` (Number) Maximum percentage of machines on the hypervisor which can have their power state changed simultaneously. Default is 20. +- `scopes` (Set of String) The IDs of the scopes for the hypervisor to be a part of. ### Read-Only diff --git a/docs/resources/nutanix_hypervisor_resource_pool.md b/docs/resources/nutanix_hypervisor_resource_pool.md index 86ab7c9..596929b 100644 --- a/docs/resources/nutanix_hypervisor_resource_pool.md +++ b/docs/resources/nutanix_hypervisor_resource_pool.md @@ -30,7 +30,7 @@ resource "citrix_nutanix_hypervisor_resource_pool" "example-nutanix-hypervisor-r - `hypervisor` (String) Id of the hypervisor for which the resource pool needs to be created. - `name` (String) Name of the resource pool. Name should be unique across all hypervisors. -- `networks` (List of String) List of networks for allocating resources. +- `networks` (List of String) Networks for allocating resources. ### Read-Only diff --git a/docs/resources/policy_set.md b/docs/resources/policy_set.md index 5e28676..51d9ab0 100644 --- a/docs/resources/policy_set.md +++ b/docs/resources/policy_set.md @@ -17,7 +17,7 @@ resource "citrix_policy_set" "example-policy-set" { name = "example-policy-set" description = "This is an example policy set description" type = "DeliveryGroupPolicies" - scopes = [ "All", citrix_admin_scope.example-admin-scope.name ] + scopes = [ citrix_admin_scope.example-admin-scope.id ] policies = [ { name = "test-policy-with-priority-0" @@ -30,13 +30,64 @@ resource "citrix_policy_set" "example-policy-set" { use_default = false }, ] - policy_filters = [ + access_control_filters = [ { - type = "DesktopGroup" - data = { - server = "0.0.0.0" - uuid = citrix_delivery_group.example-delivery-group.id - } + connection = "WithAccessGateway" + condition = "*" + gateway = "*" + enabled = true + allowed = true + }, + ] + branch_repeater_filter = { + enabled = true + allowed = true + }, + client_ip_filters = [ + { + ip_address = "10.0.0.1" + enabled = true + allowed = true + } + ] + client_name_filters = [ + { + client_name = "Example Client Name" + enabled = true + allowed = true + } + ] + delivery_group_filters = [ + { + delivery_group_id = citrix_delivery_group.example-delivery-group.id + enabled = true + allowed = true + }, + ] + delivery_group_type_filters = [ + { + delivery_group_type = "Private" + enabled = true + allowed = true + }, + ] + ou_filters = [ + { + ou = "{Path of the oranizational unit to be filtered}" + enabled = true + allowed = true + }, + ] + user_filters = [ + { + sid = "{SID of the user or user group to be filtered}" + enabled = true + allowed = true + }, + ] + tag_filters = [ + { + tag = "{ID of the tag to be filtered}" enabled = true allowed = true }, @@ -47,7 +98,6 @@ resource "citrix_policy_set" "example-policy-set" { description = "Test policy in the example policy set with priority 1" enabled = false policy_settings = [] - policy_filters = [] } ] } @@ -60,17 +110,17 @@ resource "citrix_policy_set" "example-policy-set" { - `name` (String) Name of the policy set. - `policies` (Attributes List) Ordered list of policies. The order of policies in the list determines the priority of the policies. (see [below for nested schema](#nestedatt--policies)) -- `type` (String) Type of the policy set. Type can be one of `SitePolicies`, `DeliveryGroupPolicies`, `SiteTemplates`, or `CustomTemplates`. ### Optional - `description` (String) Description of the policy set. -- `scopes` (List of String) The names of the scopes for the policy set to apply on. +- `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`. ### Read-Only +- `assigned` (Boolean) Indicate whether the policy set is being assigned to delivery groups. - `id` (String) GUID identifier of the policy set. -- `is_assigned` (Boolean) Indicate whether the policy set is being assigned to delivery groups. ### Nested Schema for `policies` @@ -79,52 +129,124 @@ Required: - `enabled` (Boolean) Indicate whether the policy is being enabled. - `name` (String) Name of the policy. -- `policy_filters` (Attributes List) Set of policy filters. (see [below for nested schema](#nestedatt--policies--policy_filters)) - `policy_settings` (Attributes List) Set of policy settings. (see [below for nested schema](#nestedatt--policies--policy_settings)) Optional: +- `access_control_filters` (Attributes List) Access control policy filters. (see [below for nested schema](#nestedatt--policies--access_control_filters)) +- `branch_repeater_filter` (Attributes) Set of policy filters. (see [below for nested schema](#nestedatt--policies--branch_repeater_filter)) +- `client_ip_filters` (Attributes List) Client ip policy filters. (see [below for nested schema](#nestedatt--policies--client_ip_filters)) +- `client_name_filters` (Attributes List) Client name policy filters. (see [below for nested schema](#nestedatt--policies--client_name_filters)) +- `delivery_group_filters` (Attributes List) Delivery group policy filters. (see [below for nested schema](#nestedatt--policies--delivery_group_filters)) +- `delivery_group_type_filters` (Attributes List) Delivery group type policy filters. (see [below for nested schema](#nestedatt--policies--delivery_group_type_filters)) - `description` (String) Description of the policy. +- `ou_filters` (Attributes List) Organizational unit policy filters. (see [below for nested schema](#nestedatt--policies--ou_filters)) +- `tag_filters` (Attributes List) Tag policy filters. (see [below for nested schema](#nestedatt--policies--tag_filters)) +- `user_filters` (Attributes List) User policy filters. (see [below for nested schema](#nestedatt--policies--user_filters)) - -### Nested Schema for `policies.policy_filters` + +### Nested Schema for `policies.policy_settings` Required: -- `allowed` (Boolean) Indicate the filtered policy is allowed or denied if the filter condition is met. -- `enabled` (Boolean) Indicate whether the policy is being enabled. -- `type` (String) Type of the policy filter. Type can be one of `AccessControl`, `BranchRepeater`, `ClientIP`, `ClientName`, `DesktopGroup`, `DesktopKind`, `OU`, `User`, and `DesktopTag` +- `name` (String) Name of the policy setting name. +- `use_default` (Boolean) Indicate whether using default value for the policy setting. Optional: -- `data` (Attributes) Data of the policy filter. (see [below for nested schema](#nestedatt--policies--policy_filters--data)) +- `enabled` (Boolean) Whether of the policy setting has enabled or allowed value. +- `value` (String) Value of the policy setting. - -### Nested Schema for `policies.policy_filters.data` -Optional: + +### Nested Schema for `policies.access_control_filters` -- `condition` (String) Gateway condition for the policy filter data. -- `connection` (String) Gateway connection for the policy filter data. -- `gateway` (String) Gateway for the policy filter data. -- `server` (String) Server address for the policy filter data. -- `uuid` (String) Resource UUID for the policy filter data. -- `value` (String) Va;ie for the policy filter data. +Required: +- `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. - -### Nested Schema for `policies.policy_settings` + +### Nested Schema for `policies.branch_repeater_filter` Required: -- `name` (String) Name of the policy setting name. -- `use_default` (Boolean) Indicate whether using default value for the policy setting. +- `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. -Optional: -- `enabled` (Boolean) Whether of the policy setting has enabled or allowed value. -- `value` (String) Value of the policy setting. + +### Nested Schema for `policies.client_ip_filters` + +Required: + +- `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. +- `ip_address` (String) IP Address of the client to be filtered. + + + +### Nested Schema for `policies.client_name_filters` + +Required: + +- `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. + + + +### Nested Schema for `policies.delivery_group_filters` + +Required: + +- `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. + + + +### Nested Schema for `policies.delivery_group_type_filters` + +Required: + +- `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. + + + +### Nested Schema for `policies.ou_filters` + +Required: + +- `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. +- `ou` (String) Organizational Unit to be filtered. + + + +### Nested Schema for `policies.tag_filters` + +Required: + +- `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. +- `tag` (String) Tag to be filtered. + + + +### Nested Schema for `policies.user_filters` + +Required: + +- `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. +- `sid` (String) SID of the user or user group to be filtered. ## Import diff --git a/docs/resources/stf_authentication_service.md b/docs/resources/stf_authentication_service.md index ebb03bb..25002db 100644 --- a/docs/resources/stf_authentication_service.md +++ b/docs/resources/stf_authentication_service.md @@ -3,12 +3,12 @@ page_title: "citrix_stf_authentication_service Resource - citrix" subcategory: "" description: |- - Storefront Authentication Service. + StoreFront Authentication Service. --- # citrix_stf_authentication_service (Resource) -Storefront Authentication Service. +StoreFront Authentication Service. ## Example Usage diff --git a/docs/resources/stf_deployment.md b/docs/resources/stf_deployment.md index 48e5211..f10755d 100644 --- a/docs/resources/stf_deployment.md +++ b/docs/resources/stf_deployment.md @@ -3,12 +3,12 @@ page_title: "citrix_stf_deployment Resource - citrix" subcategory: "" description: |- - Storefront Deployment. + StoreFront Deployment. --- # citrix_stf_deployment (Resource) -Storefront Deployment. +StoreFront Deployment. ## Example Usage @@ -28,7 +28,7 @@ resource "citrix_stf_deployment" "example-stf-deployment" { ### Optional -- `site_id` (String) The IIS site id of the Storefront deployment. Defaults to 1. +- `site_id` (String) The IIS site id of the StoreFront deployment. Defaults to 1. ## Import diff --git a/docs/resources/stf_store_service.md b/docs/resources/stf_store_service.md index b3817a9..c78ea45 100644 --- a/docs/resources/stf_store_service.md +++ b/docs/resources/stf_store_service.md @@ -3,12 +3,12 @@ page_title: "citrix_stf_store_service Resource - citrix" subcategory: "" description: |- - Storefront StoreService. + StoreFront StoreService. --- # citrix_stf_store_service (Resource) -Storefront StoreService. +StoreFront StoreService. ## Example Usage @@ -48,7 +48,7 @@ resource "citrix_stf_store_service" "example-stf-store-service" { - `farm_config` (Attributes) Farm configuration for the Store. (see [below for nested schema](#nestedatt--farm_config)) - `friendly_name` (String) The friendly name of the Store - `load_balance` (Boolean) Whether the Store is load balanced. -- `site_id` (String) The IIS site id of the Storefront storeservice. Defaults to 1. +- `site_id` (String) The IIS site id of the StoreFront storeservice. Defaults to 1. ### Nested Schema for `farm_config` diff --git a/docs/resources/stf_user_farm_mapping.md b/docs/resources/stf_user_farm_mapping.md new file mode 100644 index 0000000..5aeb509 --- /dev/null +++ b/docs/resources/stf_user_farm_mapping.md @@ -0,0 +1,93 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "citrix_stf_user_farm_mapping Resource - citrix" +subcategory: "" +description: |- + StoreFront User Farm Mapping Resource +--- + +# citrix_stf_user_farm_mapping (Resource) + +StoreFront User Farm Mapping Resource + +## Example Usage + +```terraform +resource "citrix_stf_user_farm_mapping" "example-stf-user-farm-mapping" { + name = "Example STFUserFarmMapping" + store_virtual_path = citrix_stf_storeservice.example-stf-store-service.virtual_path + group_members = [ + { + group_name = "TestGroup1" + account_sid = "{First Account Sid}" + }, + { + group_name = "TestGroup2" + account_sid = "{Second Account Sid}" + } + ] + equivalent_farm_sets = [ + { + name = "EU1", + aggregation_group_name = "EU1Users" + primary_farms = ["Primary"] + backup_farms = ["Backup"] + load_balance_mode = "LoadBalanced" + farms_are_identical = true + }, + { + name = "EU2", + aggregation_group_name = "EU2Users" + primary_farms = ["Secondary"] + load_balance_mode = "Failover" + farms_are_identical = false + } + ] +} +``` + + +## Schema + +### Required + +- `equivalent_farm_sets` (Attributes List) Configurations of the EquivalentFarmSets that will be assigned to the UserFarmMapping. (see [below for nested schema](#nestedatt--equivalent_farm_sets)) +- `name` (String) The unique name used to identify the UserFarmMapping. +- `store_virtual_path` (String) The IIS VirtualPath at which the Store is configured to be accessed by Receivers. + +### Optional + +- `group_members` (Attributes List) The Windows groups to which the UserFarmMapping will apply. Not specifying this field will assign all users to the UserFarmMapping. (see [below for nested schema](#nestedatt--group_members)) + + +### Nested Schema for `equivalent_farm_sets` + +Required: + +- `aggregation_group_name` (String) The AggregationGroupName used to de-duplicate applications and desktops that are available on multiple EquivalentFarmSets. Where multiple EquivalentFarmSets are defined the AggregationGroup will prevent the user seeing the application multiple times if it exists in both places. +- `load_balance_mode` (String) The load balance mode, either `Failover` or `LoadBalanced`. +- `name` (String) The unique Name used to identify the EquivalentFarmSet. +- `primary_farms` (List of String) The PrimaryFarms. The farm names should match those defined in the Store service. + +Optional: + +- `backup_farms` (List of String) The BackupFarms. The farm names should match those defined in the Store Service. +- `farms_are_identical` (Boolean) Whether the PrimaryFarms in the EquivalentFarmSet all publish identical resources. Set to true if all resources are identical on all primary farms. Set to false if the deployment has some unique resources per farm. Default to `false`. + + + +### Nested Schema for `group_members` + +Required: + +- `account_sid` (String) Sid of the account. +- `group_name` (String) A display only group name. + +## Import + +Import is supported using the following syntax: + +```shell +# StoreFront UserFarmMapping can be imported with the Store Virtual Path and UserFarmMapping Name +terraform import citrix_stf_store_service.example-stf-store-service "/Citrix/Store","Example UserFarmMapping" +``` diff --git a/docs/resources/stf_webreceiver_service.md b/docs/resources/stf_webreceiver_service.md index c299357..233ca6a 100644 --- a/docs/resources/stf_webreceiver_service.md +++ b/docs/resources/stf_webreceiver_service.md @@ -3,12 +3,12 @@ page_title: "citrix_stf_webreceiver_service Resource - citrix" subcategory: "" description: |- - Storefront WebReceiver. + StoreFront WebReceiver. --- # citrix_stf_webreceiver_service (Resource) -Storefront WebReceiver. +StoreFront WebReceiver. ## Example Usage @@ -40,10 +40,10 @@ resource "citrix_stf_webreceiver_service" "example-stf-webreceiver-service"{ ### Optional -- `authentication_methods` (List of String) The authentication methods supported by the WebReceiver. +- `authentication_methods` (Set of String) The authentication methods supported by the WebReceiver. - `friendly_name` (String) The friendly name of the WebReceiver - `plugin_assistant` (Attributes) Pluin Assistant configuration for the WebReceiver. (see [below for nested schema](#nestedatt--plugin_assistant)) -- `site_id` (String) The IIS site id of the Storefront webreceiver. Defaults to 1. +- `site_id` (String) The IIS site id of the StoreFront webreceiver. Defaults to 1. - `store_service` (String) The StoreFront Store Service linked to the WebReceiver. diff --git a/docs/resources/vsphere_hypervisor.md b/docs/resources/vsphere_hypervisor.md index 5ce9181..e4c191d 100644 --- a/docs/resources/vsphere_hypervisor.md +++ b/docs/resources/vsphere_hypervisor.md @@ -42,6 +42,7 @@ resource "citrix_vsphere_hypervisor" "example-vsphere-hypervisor" { - `max_absolute_active_actions` (Number) Maximum number of actions that can execute in parallel on the hypervisor. Default is 40. - `max_absolute_new_actions_per_minute` (Number) Maximum number of actions that can be started on the hypervisor per-minute. Default is 10. - `max_power_actions_percentage_of_machines` (Number) Maximum percentage of machines on the hypervisor which can have their power state changed simultaneously. Default is 20. +- `scopes` (Set of String) The IDs of the scopes for the hypervisor to be a part of. - `ssl_thumbprints` (List of String) SSL certificate thumbprints to consider acceptable for this connection. If not specified, and the hypervisor uses SSL for its connection, the SSL certificate's root certification authority and any intermediate certificates must be trusted. ### Read-Only diff --git a/docs/resources/vsphere_hypervisor_resource_pool.md b/docs/resources/vsphere_hypervisor_resource_pool.md index 0c0bfe9..8f02343 100644 --- a/docs/resources/vsphere_hypervisor_resource_pool.md +++ b/docs/resources/vsphere_hypervisor_resource_pool.md @@ -49,9 +49,9 @@ resource "citrix_vsphere_hypervisor_resource_pool" "example-vsphere-hypervisor-r - `cluster` (Attributes) Details of the cluster where resources reside and new resources will be created. (see [below for nested schema](#nestedatt--cluster)) - `hypervisor` (String) Id of the hypervisor for which the resource pool needs to be created. - `name` (String) Name of the resource pool. Name should be unique across all hypervisors. -- `networks` (List of String) List of networks for allocating resources. -- `storage` (Attributes List) List of hypervisor storage to use for OS data. (see [below for nested schema](#nestedatt--storage)) -- `temporary_storage` (Attributes List) List of hypervisor storage to use for temporary data. (see [below for nested schema](#nestedatt--temporary_storage)) +- `networks` (List of String) Networks for allocating resources. +- `storage` (Attributes List) Storage resources to use for OS data. (see [below for nested schema](#nestedatt--storage)) +- `temporary_storage` (Attributes List) Storage resources to use for temporary data. (see [below for nested schema](#nestedatt--temporary_storage)) ### Optional diff --git a/docs/resources/xenserver_hypervisor.md b/docs/resources/xenserver_hypervisor.md index 55aa2d0..e4a4e60 100644 --- a/docs/resources/xenserver_hypervisor.md +++ b/docs/resources/xenserver_hypervisor.md @@ -46,6 +46,7 @@ resource "citrix_xenserver_hypervisor" "example-xenserver-hypervisor" { - `max_absolute_active_actions` (Number) Maximum number of actions that can execute in parallel on the hypervisor. Default is 40. - `max_absolute_new_actions_per_minute` (Number) Maximum number of actions that can be started on the hypervisor per-minute. Default is 10. - `max_power_actions_percentage_of_machines` (Number) Maximum percentage of machines on the hypervisor which can have their power state changed simultaneously. Default is 20. +- `scopes` (Set of String) The IDs of the scopes for the hypervisor to be a part of. - `ssl_thumbprints` (List of String) SSL certificate thumbprints to consider acceptable for this connection. If not specified, and the hypervisor uses SSL for its connection, the SSL certificate's root certification authority and any intermediate certificates must be trusted. ### Read-Only diff --git a/docs/resources/xenserver_hypervisor_resource_pool.md b/docs/resources/xenserver_hypervisor_resource_pool.md index 6049aa1..16e2e78 100644 --- a/docs/resources/xenserver_hypervisor_resource_pool.md +++ b/docs/resources/xenserver_hypervisor_resource_pool.md @@ -43,7 +43,7 @@ resource "citrix_xenserver_hypervisor_resource_pool" "example-xenserver-hypervis - `hypervisor` (String) Id of the hypervisor for which the resource pool needs to be created. - `name` (String) Name of the resource pool. Name should be unique across all hypervisors. -- `networks` (List of String) List of networks for allocating resources. +- `networks` (List of String) Networks for allocating resources. - `storage` (Attributes List) List of hypervisor storage to use for OS data. (see [below for nested schema](#nestedatt--storage)) - `temporary_storage` (Attributes List) List of hypervisor storage to use for temporary data. (see [below for nested schema](#nestedatt--temporary_storage)) diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..fd09f1d --- /dev/null +++ b/examples/README.md @@ -0,0 +1,67 @@ +# Plugin for Terraform Provider for Citrix® Examples + +This folder contains examples of how to configure Citrix environments in various ways using this provider. + +## Table of Contents +- [Plugin for Terraform Provider for Citrix® Examples](#plugin-for-terraform-provider-for-citrix-examples) + - [Table of Contents](#table-of-contents) + - [How to use the examples](#how-to-use-the-examples) + - [Specifying variables](#specifying-variables) + - [Provider settings](#provider-settings) + - [Examples](#examples) + - [Basic MCS](#basic-mcs) + - [Non-domain joined MCS](#non-domain-joined-mcs) + - [Detailed instructions for cloud providers:](#detailed-instructions-for-cloud-providers) + +## How to use the examples +Clone this repository and then navigate to the given example directory, [specify the variables](#specifying-variables), and run terraform there: +```shell +> git clone https://github.com/citrix/terraform-provider-citrix.git +> cd terraform-provider-citrix/examples/basic_azure_mcs_vda +> cp terraform.template.tfvars terraform.tfvars +> vscode terraform.tfvars # open the file and specify variables +> terraform init +> terraform plan +> terraform apply +``` + +### Specifying variables +Each example contains a [variables.tf](basic_azure_mcs_vda/variables.tf) file which needs to be specified. There are also some default values and configuration options in the other `.tf` files in the directory. Review these options and adjust depending on your use case. + +The variables can be specified by copying the [terraform.template.tfvars](basic_azure_mcs_vda/terraform.template.tfvars) file to `terraform.tfvars` and then filling it out with your values. + +Another option is to pass them into the terraform command one by one: +```shell +terraform apply -var="customer_id=" -var="client_id=" -var=... +``` + +This method has the benefit of being able to fetch secrets from secure locations and pass them via the commandline in the form of shell or environment variables. + +### Provider settings +Each example contains a `citrix.tf` file with the Citrix provider configuration. In this file select between on-premises and Cloud and fill in the required credentials, then delete the other one so there is only one Citrix provider configuration. + + +## Examples + +### Basic MCS +Each of the following examples creates a single multi-session domain joined VDA in the given hypervisor using machine creation services. The VDA is power managed and uses autoscale to stay powered on between 9am and 5pm. [machine_catalogs.tf](basic_azure_mcs_vda/machine_catalogs.tf) can be modified depending how the master image is stored. *Note for Cloud customers*, the zone specified by `var.zone_name` needs to already exist and have [Cloud Connectors](https://docs.citrix.com/en-us/citrix-cloud/citrix-cloud-resource-locations/citrix-cloud-connector.html) configured. +* [AWS EC2](basic_aws_mcs_vda/) +* [Microsoft Azure](basic_azure_mcs_vda/) + * If using an Azure image gallery, uncomment the `gallery_image` in [machine_catalogs.tf](basic_azure_mcs_vda/machine_catalogs.tf) and remove the VHD parameters. +* [Google Cloud Compute](basic_gcp_mcs_vda/) +* [Nutanix](basic_nutanix_mcs_vda/) +* [VMware vSphere](basic_vsphere_mcs_vda/) +* [XenServer](basic_xenserver_mcs_vda/) + +### Non-domain joined MCS +A single example of how to create a non-domain joined VDA, based on the [basic_azure_mcs_vda](basic_aws_mcs_vda/) example. +* [Microsoft Azure](non_domain_joined_azure_mcs_vda/) + * If using an Azure image gallery, uncomment the `gallery_image` in [machine_catalogs.tf](basic_azure_mcs_vda/machine_catalogs.tf) and remove the VHD parameters. + +The difference is in [machine_catalogs.tf](non_domain_joined_azure_mcs_vda/machine_catalogs.tf), `provisioning_scheme.identity_type = "Workgroup"` and the addition of a Citrix Group Policy in [policy_sets.tf](non_domain_joined_azure_mcs_vda/policy_sets.tf). + +### Detailed instructions for cloud providers: +Detailed instructions on setting up deployments on different cloud providers from Citrix Tech Zone: +- [AWS EC2](https://community.citrix.com/tech-zone/build/deployment-guides/terraform-daas-aws/) +- [Azure](https://community.citrix.com/tech-zone/build/deployment-guides/citrix-daas-terraform-azure/) +- [GCP](https://community.citrix.com/tech-zone/build/deployment-guides/terraform-daas-gcp/) diff --git a/examples/basic_aws_mcs_vda/citrix.tf b/examples/basic_aws_mcs_vda/citrix.tf index 5ac9b01..f32f7f4 100644 --- a/examples/basic_aws_mcs_vda/citrix.tf +++ b/examples/basic_aws_mcs_vda/citrix.tf @@ -1,17 +1,17 @@ -// On-Premises customer provider settings -// Please comment out / remove this provider settings block if you are a Citrix Cloud customer +# On-Premises customer provider settings +# Please comment out / remove this provider settings block if you are a Citrix Cloud customer provider "citrix" { - hostname = "" - client_id = "\\" - client_secret = "" - disable_ssl_verification = true # omit this field if DDC has valid SSL certificate configured + hostname = var.provider_hostname + client_id = "${var.provider_domain_fqdn}\\${var.provider_client_id}" + client_secret = "${var.provider_client_secret}" + disable_ssl_verification = var.provider_disable_ssl_verification } -// Citrix Cloud customer provider settings -// Please comment out / remove this provider settings block if you are an On-Premises customer +# Citrix Cloud customer provider settings +# Please comment out / remove this provider settings block if you are an On-Premises customer provider "citrix" { - customer_id = "" # set your customer id - client_id = "" - client_secret = "" # API key client id and secret are needed to interact with Citrix Cloud APIs. These can be created/found under Identity and Access Management > API Access - environment = "Production" # use "Japan" for Citrix Cloud customers in Japan region + customer_id = var.provider_customer_id + client_id = var.provider_client_id + client_secret = var.provider_client_secret + environment = var.provider_environment } diff --git a/examples/basic_aws_mcs_vda/delivery_group.tf b/examples/basic_aws_mcs_vda/delivery_groups.tf similarity index 73% rename from examples/basic_aws_mcs_vda/delivery_group.tf rename to examples/basic_aws_mcs_vda/delivery_groups.tf index 272dd56..bdc8d19 100644 --- a/examples/basic_aws_mcs_vda/delivery_group.tf +++ b/examples/basic_aws_mcs_vda/delivery_groups.tf @@ -1,6 +1,5 @@ resource "citrix_delivery_group" "example-delivery-group" { - name = "example-delivery-group" - minimum_functional_level = "L7_20" + name = var.delivery_group_name associated_machine_catalogs = [ { machine_catalog = citrix_machine_catalog.example-aws-catalog.id @@ -10,19 +9,13 @@ resource "citrix_delivery_group" "example-delivery-group" { desktops = [ { published_name = "Example Desktop" - description = "Desription for example desktop" + description = "Description for example desktop" restricted_access_users = { - allow_list = [ - "example\\user1" - ] - block_list = [ - "example\\user2", - ] + allow_list = var.allow_list } enabled = true enable_session_roaming = false } - ] autoscale_settings = { autoscale_enabled = true @@ -51,11 +44,6 @@ resource "citrix_delivery_group" "example-delivery-group" { ] } restricted_access_users = { - allow_list = [ - "example\\user1" - ] - block_list = [ - "example\\user2", - ] + allow_list = var.allow_list } } diff --git a/examples/basic_aws_mcs_vda/hypervisors.tf b/examples/basic_aws_mcs_vda/hypervisors.tf index a0826d5..a3cf08d 100644 --- a/examples/basic_aws_mcs_vda/hypervisors.tf +++ b/examples/basic_aws_mcs_vda/hypervisors.tf @@ -1,8 +1,8 @@ # AWS Hypervisor resource "citrix_aws_hypervisor" "example-aws-hypervisor" { - name = "example-aws-hyperv" + name = var.hypervisor_name zone = citrix_zone.example-zone.id - api_key = "{AWS API access key}" - secret_key = "{AWS API secret key}" - region = "us-east-1" + api_key = var.aws_api_key + secret_key = var.aws_secret_key + region = var.aws_region } diff --git a/examples/basic_aws_mcs_vda/machine_catalogs.tf b/examples/basic_aws_mcs_vda/machine_catalogs.tf index 2b8420c..ea5bb1a 100644 --- a/examples/basic_aws_mcs_vda/machine_catalogs.tf +++ b/examples/basic_aws_mcs_vda/machine_catalogs.tf @@ -1,27 +1,24 @@ resource "citrix_machine_catalog" "example-aws-catalog" { - name = "example-aws-catalog" + name = var.machine_catalog_name description = "Example multi-session catalog on AWS hypervisor" allocation_type = "Random" session_support = "MultiSession" - is_power_managed = true - is_remote_pc = false provisioning_type = "MCS" zone = citrix_zone.example-zone.id - minimum_functional_level = "L7_20" provisioning_scheme = { hypervisor = citrix_aws_hypervisor.example-aws-hypervisor.id hypervisor_resource_pool = citrix_aws_hypervisor_resource_pool.example-aws-rp.id identity_type = "ActiveDirectory" machine_domain_identity = { - domain = "" - domain_ou = "" - service_account = "" - service_account_password = "" + domain = var.domain_fqdn + domain_ou = var.domain_ou + service_account = var.domain_service_account + service_account_password = var.domain_service_account_password } aws_machine_config = { - image_ami = "" - master_image = "" - service_offering = "t2.small" + image_ami = var.aws_ami_id + master_image = var.aws_ami_name + service_offering = var.aws_service_offering security_groups = [ "default" ] @@ -29,7 +26,7 @@ resource "citrix_machine_catalog" "example-aws-catalog" { } number_of_total_machines = 1 machine_account_creation_rules = { - naming_scheme = "ctx-aws-###" + naming_scheme = var.machine_catalog_naming_scheme naming_scheme_type = "Numeric" } } diff --git a/examples/basic_aws_mcs_vda/resource_pools.tf b/examples/basic_aws_mcs_vda/resource_pools.tf index ddb810b..874e92f 100644 --- a/examples/basic_aws_mcs_vda/resource_pools.tf +++ b/examples/basic_aws_mcs_vda/resource_pools.tf @@ -1,9 +1,7 @@ resource "citrix_aws_hypervisor_resource_pool" "example-aws-rp" { - name = "example-aws-rp" + name = var.resource_pool_name hypervisor = citrix_aws_hypervisor.example-aws-hypervisor.id - subnets = [ - "", - ] - vpc = "" - availability_zone = "us-east-1a" + subnets = var.aws_subnets + vpc = var.aws_vpc + availability_zone = var.aws_availability_zone } diff --git a/examples/basic_aws_mcs_vda/terraform.template.tfvars b/examples/basic_aws_mcs_vda/terraform.template.tfvars new file mode 100644 index 0000000..a9f8833 --- /dev/null +++ b/examples/basic_aws_mcs_vda/terraform.template.tfvars @@ -0,0 +1,37 @@ +# citrix.tf variables, uncomment the ones you need for on-premises or cloud +# provider_hostname = "" # on-premises only +# provider_domain_fqdn = "" # on-premises only +provider_client_id = "" # or Citrx Cloud secure client ID for cloud +provider_client_secret = "" # or Citrix Cloud secure client secret for cloud +# provider_customer_id = "" # cloud only + +# delivery_groups.tf variables +delivery_group_name = "example-delivery-group" +allow_list = ["DOMAIN\\user1", "DOMAIN\\user2"] +block_list = ["DOMAIN\\user3", "DOMAIN\\user4"] + +# hypervisors.tf variables +hypervisor_name = "example-aws-hyperv" +aws_api_key = "" +aws_secret_key = "" +aws_region = "us-east-1" + +# machine_catalogs.tf variables +machine_catalog_name = "example-aws-catalog" +domain_fqdn = "" +domain_ou = "" +domain_service_account = "" +domain_service_account_password = "" +aws_ami_id = "" +aws_ami_name = "" +aws_service_offering = "t2.small" +machine_catalog_naming_scheme = "ctx-aws-##" + +# resource_pools.tf variables +resource_pool_name = "example-aws-resource-pool" +aws_subnets = [""] +aws_vpc = "" +aws_availability_zone = "us-east-1a" + +# zones.tf variables +zone_name = "example-aws-zone" \ No newline at end of file diff --git a/examples/basic_aws_mcs_vda/terraform.tf b/examples/basic_aws_mcs_vda/terraform.tf index f2f10f4..6484546 100644 --- a/examples/basic_aws_mcs_vda/terraform.tf +++ b/examples/basic_aws_mcs_vda/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { citrix = { source = "citrix/citrix" - version = ">=0.5.1" + version = ">=0.6.1" } } diff --git a/examples/basic_aws_mcs_vda/variables.tf b/examples/basic_aws_mcs_vda/variables.tf new file mode 100644 index 0000000..0ab2df3 --- /dev/null +++ b/examples/basic_aws_mcs_vda/variables.tf @@ -0,0 +1,170 @@ +# citrix.tf variables +## On-Premises customer provider settings +variable provider_hostname { + description = "The hostname of the Citrix Virtual Apps and Desktops Delivery Controller." + type = string + default = "" # Leave this variable empty for Citrix Cloud customer. +} + +variable provider_domain_fqdn { + description = "The domain FQDN of the on-premises Active Directory." + type = string + default = null # Leave this variable empty for Citrix Cloud customer. +} + +variable provider_disable_ssl_verification { + description = "Disable SSL verification for the Citrix Virtual Apps and Desktops Delivery Controller." + type = bool + default = false # Set this field to true if DDC does not have a valid SSL certificate configured. Omit this variable for Citrix Cloud customer. +} + +## Citrix Cloud customer provider settings +variable provider_customer_id { + description = "The customer id of the Citrix Cloud customer." + type = string + default = "" # Set your customer id for Citrix Cloud customer. Omit this variable for On-Premises customer. +} + +variable provider_environment { + description = "The environment of the Citrix Cloud customer." + type = string + default = "Production" # Use "Japan" for Citrix Cloud customers in Japan region. Omit this variable for On-Premises customer. +} + +# Common provider settings +# For On-Premises customers: Domain Admin username and password are needed to interact with the Citrix Virtual Apps and Desktops Delivery Controller. +# For Citrix Cloud customers: API key client id and secret are needed to interact with Citrix DaaS APIs. These can be created/found under Identity and Access Management > API Access +variable provider_client_id { + description = "The Domain Admin username of the on-premises Active Directory / The API key client id for Citrix Cloud customer." + type = string + default = "" +} + +variable provider_client_secret { + description = "The Domain Admin password of the on-premises Active Directory / The API key client secret for Citrix Cloud customer." + type = string + default = "" +} + + +# delivery_groups.tf variables +variable "delivery_group_name" { + description = "Name of the Delivery Group to create" + type = string + default = "example-delivery-group" +} + +variable "allow_list" { + description = "List of users to allow for the Delivery Group in DOMAIN\\username format" + type = list(string) + default = [] +} + + +# hypervisors.tf variables +variable "hypervisor_name" { + description = "Name of the Hypervisor to create" + type = string + default = "example-aws-hyperv" +} + +variable "aws_api_key" { + description = "AWS API Key" + type = string + sensitive = true +} + +variable "aws_secret_key" { + description = "AWS Secret Key" + type = string + sensitive = true +} + +variable "aws_region" { + description = "AWS Region" + type = string + default = "us-east-1" +} + + +# machine_catalogs.tf variables +variable "machine_catalog_name" { + description = "Name of the Machine Catalog to create" + type = string + default = "example-aws-catalog" +} + +variable "domain_fqdn" { + description = "Domain FQDN" + type = string +} + +variable "domain_ou" { + description = "Domain organizational unit" + type = string + default = null +} + +variable "domain_service_account" { + description = "Domain service account with permissions to create machine accounts" + type = string +} + +variable "domain_service_account_password" { + description = "Domain service account password" + type = string + sensitive = true +} + +variable "aws_ami_id" { + description = "AWS AMI ID" + type = string +} + +variable "aws_ami_name" { + description = "AWS AMI Name" + type = string +} + +variable "aws_service_offering" { + description = "AWS Service Offering" + type = string + default = "t2.small" +} + +variable "machine_catalog_naming_scheme" { + description = "Machine Catalog naming scheme" + type = string + default = "ctx-aws-##" +} + + +# resource_pools.tf variables +variable "resource_pool_name" { + description = "Name of the Resource Pool to create" + type = string + default = "example-aws-rp" +} + +variable "aws_subnets" { + description = "List of AWS subnets" + type = list(string) +} + +variable "aws_vpc" { + description = "AWS VPC name" + type = string +} + +variable "aws_availability_zone" { + description = "AWS Availability Zone" + type = string + default = "us-east-1a" +} + + +# zones.tf variables +variable "zone_name" { + description = "Name of the Zone to create. For Citrix Cloud customers the zone should already exist." + type = string +} diff --git a/examples/basic_aws_mcs_vda/zones.tf b/examples/basic_aws_mcs_vda/zones.tf index b3f0728..f73a488 100644 --- a/examples/basic_aws_mcs_vda/zones.tf +++ b/examples/basic_aws_mcs_vda/zones.tf @@ -1,4 +1,3 @@ resource "citrix_zone" "example-zone" { - name = "example zone" - description = "description for example zone" + name = var.zone_name } diff --git a/examples/basic_azure_mcs_vda/citrix.tf b/examples/basic_azure_mcs_vda/citrix.tf index 5ac9b01..f32f7f4 100644 --- a/examples/basic_azure_mcs_vda/citrix.tf +++ b/examples/basic_azure_mcs_vda/citrix.tf @@ -1,17 +1,17 @@ -// On-Premises customer provider settings -// Please comment out / remove this provider settings block if you are a Citrix Cloud customer +# On-Premises customer provider settings +# Please comment out / remove this provider settings block if you are a Citrix Cloud customer provider "citrix" { - hostname = "" - client_id = "\\" - client_secret = "" - disable_ssl_verification = true # omit this field if DDC has valid SSL certificate configured + hostname = var.provider_hostname + client_id = "${var.provider_domain_fqdn}\\${var.provider_client_id}" + client_secret = "${var.provider_client_secret}" + disable_ssl_verification = var.provider_disable_ssl_verification } -// Citrix Cloud customer provider settings -// Please comment out / remove this provider settings block if you are an On-Premises customer +# Citrix Cloud customer provider settings +# Please comment out / remove this provider settings block if you are an On-Premises customer provider "citrix" { - customer_id = "" # set your customer id - client_id = "" - client_secret = "" # API key client id and secret are needed to interact with Citrix Cloud APIs. These can be created/found under Identity and Access Management > API Access - environment = "Production" # use "Japan" for Citrix Cloud customers in Japan region + customer_id = var.provider_customer_id + client_id = var.provider_client_id + client_secret = var.provider_client_secret + environment = var.provider_environment } diff --git a/examples/basic_azure_mcs_vda/delivery_group.tf b/examples/basic_azure_mcs_vda/delivery_group.tf deleted file mode 100644 index 0efc666..0000000 --- a/examples/basic_azure_mcs_vda/delivery_group.tf +++ /dev/null @@ -1,61 +0,0 @@ -resource "citrix_delivery_group" "example-delivery-group" { - name = "example-delivery-group" - minimum_functional_level = "L7_20" - associated_machine_catalogs = [ - { - machine_catalog = citrix_machine_catalog.example-catalog.id - machine_count = 1 - } - ] - desktops = [ - { - published_name = "Example Desktop" - description = "Desription for example desktop" - restricted_access_users = { - allow_list = [ - "example\\user1" - ] - block_list = [ - "example\\user2", - ] - } - enabled = true - enable_session_roaming = false - } - - ] - autoscale_settings = { - autoscale_enabled = true - power_time_schemes = [ - { - days_of_week = [ - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday" - ] - name = "weekdays test" - display_name = "weekdays schedule" - peak_time_ranges = [ - "09:00-17:00" - ] - pool_size_schedules = [ - { - time_range = "00:00-00:00", - pool_size = 1 - } - ] - pool_using_percentage = false - }, - ] - } - restricted_access_users = { - allow_list = [ - "example\\user1" - ] - block_list = [ - "example\\user2", - ] - } -} \ No newline at end of file diff --git a/examples/basic_azure_mcs_vda/delivery_groups.tf b/examples/basic_azure_mcs_vda/delivery_groups.tf new file mode 100644 index 0000000..4918a56 --- /dev/null +++ b/examples/basic_azure_mcs_vda/delivery_groups.tf @@ -0,0 +1,49 @@ +resource "citrix_delivery_group" "example-delivery-group" { + name = var.delivery_group_name + associated_machine_catalogs = [ + { + machine_catalog = citrix_machine_catalog.example-catalog.id + machine_count = 1 + } + ] + desktops = [ + { + published_name = "Example Desktop" + description = "Description for example desktop" + restricted_access_users = { + allow_list = var.allow_list + } + enabled = true + enable_session_roaming = false + } + ] + autoscale_settings = { + autoscale_enabled = true + power_time_schemes = [ + { + days_of_week = [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday" + ] + name = "weekdays test" + display_name = "weekdays schedule" + peak_time_ranges = [ + "09:00-17:00" + ] + pool_size_schedules = [ + { + time_range = "00:00-00:00", + pool_size = 1 + } + ] + pool_using_percentage = false + }, + ] + } + restricted_access_users = { + allow_list = var.allow_list + } +} \ No newline at end of file diff --git a/examples/basic_azure_mcs_vda/hypervisors.tf b/examples/basic_azure_mcs_vda/hypervisors.tf index 175b8db..556d0a8 100644 --- a/examples/basic_azure_mcs_vda/hypervisors.tf +++ b/examples/basic_azure_mcs_vda/hypervisors.tf @@ -1,8 +1,8 @@ resource "citrix_azure_hypervisor" "example-azure-hypervisor" { - name = "example-azure-hyperv" + name = var.hypervisor_name zone = citrix_zone.example-zone.id - application_id = "" - application_secret = "" - subscription_id = "" - active_directory_id = "" + application_id = var.azure_application_id + application_secret = var.azure_application_secret + subscription_id = var.azure_subscription_id + active_directory_id = var.azure_tenant_id } \ No newline at end of file diff --git a/examples/basic_azure_mcs_vda/machine_catalogs.tf b/examples/basic_azure_mcs_vda/machine_catalogs.tf index 46a6539..d6d2b7a 100644 --- a/examples/basic_azure_mcs_vda/machine_catalogs.tf +++ b/examples/basic_azure_mcs_vda/machine_catalogs.tf @@ -1,36 +1,42 @@ resource "citrix_machine_catalog" "example-catalog" { - name = "example-catalog" + name = var.machine_catalog_name description = "description for example catalog" allocation_type = "Random" session_support = "MultiSession" - is_power_managed = true - is_remote_pc = false provisioning_type = "MCS" zone = citrix_zone.example-zone.id - minimum_functional_level = "L7_20" provisioning_scheme = { hypervisor = citrix_azure_hypervisor.example-azure-hypervisor.id hypervisor_resource_pool = citrix_azure_hypervisor_resource_pool.example-azure-rp.id identity_type = "ActiveDirectory" - machine_domain_identity = { - domain = "" - service_account = "" - service_account_password = "" - } + machine_domain_identity = { + domain = var.domain_fqdn + domain_ou = var.domain_ou + service_account = var.domain_service_account + service_account_password = var.domain_service_account_password + } azure_machine_config = { - service_offering = "Standard_D2_v2" - azure_master_image = { - resource_group = "" - storage_account = "" - container = "" - master_image = "" + service_offering = var.azure_service_offering + 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 = var.azure_resource_group + master_image = var.azure_master_image + + # For Azure image gallery + # gallery_image = { + # gallery = var.azure_gallery_name + # definition = var.azure_gallery_image_definition + # version = var.azure_gallery_image_version + # } } - storage_type = "Standard_LRS" - use_managed_disks = true + storage_type = var.azure_storage_type + use_managed_disks = true } number_of_total_machines = 1 machine_account_creation_rules = { - naming_scheme = "ctx-pvdr-##" + naming_scheme = var.machine_catalog_naming_scheme naming_scheme_type = "Numeric" } } diff --git a/examples/basic_azure_mcs_vda/resource_pools.tf b/examples/basic_azure_mcs_vda/resource_pools.tf index ff58937..96fad0d 100644 --- a/examples/basic_azure_mcs_vda/resource_pools.tf +++ b/examples/basic_azure_mcs_vda/resource_pools.tf @@ -1,10 +1,8 @@ resource "citrix_azure_hypervisor_resource_pool" "example-azure-rp" { - name = "example-azure-rp" + name = var.resource_pool_name hypervisor = citrix_azure_hypervisor.example-azure-hypervisor.id - region = "East US" - virtual_network_resource_group = "" - virtual_network = "" - subnets = [ - "" - ] + region = var.azure_region + virtual_network_resource_group = var.azure_vnet_resource_group + virtual_network = var.azure_vnet + subnets = var.azure_subnets } \ No newline at end of file diff --git a/examples/basic_azure_mcs_vda/terraform.template.tfvars b/examples/basic_azure_mcs_vda/terraform.template.tfvars new file mode 100644 index 0000000..24d694e --- /dev/null +++ b/examples/basic_azure_mcs_vda/terraform.template.tfvars @@ -0,0 +1,44 @@ +# citrix.tf variables, uncomment the ones you need for on-premises or cloud +# provider_hostname = "" # on-premises only +# provider_domain_fqdn = "" # on-premises only +provider_client_id = "" # or Citrx Cloud secure client ID for cloud +provider_client_secret = "" # or Citrix Cloud secure client secret for cloud +# provider_customer_id = "" # cloud only + +# delivery_groups.tf variables +delivery_group_name = "example-delivery-group" +allow_list = ["DOMAIN\\user1", "DOMAIN\\user2"] +block_list = ["DOMAIN\\user3", "DOMAIN\\user4"] + +# hypervisors.tf variables +hypervisor_name = "example-azure-hyperv" +azure_application_id = "" +azure_application_secret = "" +azure_subscription_id = "" +azure_tenant_id = "" + +# machine_catalogs.tf variables +machine_catalog_name = "example-azure-catalog" +domain_fqdn = "" +domain_ou = "" +domain_service_account = "" +domain_service_account_password = "" +azure_service_offering = "Standard_D2_v2" +azure_storage_type = "Standard_LRS" +#azure_image_subscription = "" +azure_resource_group = "" +azure_master_image = "" +#azure_gallery_name = "" +#azure_gallery_image_definition = "" +#azure_gallery_image_version = "" +machine_catalog_naming_scheme = "ctx-azure-##" + +# resource_pools.tf variables +resource_pool_name = "example-azure-resource-pool" +azure_region = "East US" +azure_vnet_resource_group = "" +azure_vnet = "" +azure_subnets = [""] + +# zones.tf variables +zone_name = "example-azure-zone" \ No newline at end of file diff --git a/examples/basic_azure_mcs_vda/terraform.tf b/examples/basic_azure_mcs_vda/terraform.tf index f2f10f4..6484546 100644 --- a/examples/basic_azure_mcs_vda/terraform.tf +++ b/examples/basic_azure_mcs_vda/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { citrix = { source = "citrix/citrix" - version = ">=0.5.1" + version = ">=0.6.1" } } diff --git a/examples/basic_azure_mcs_vda/variables.tf b/examples/basic_azure_mcs_vda/variables.tf new file mode 100644 index 0000000..f7edf14 --- /dev/null +++ b/examples/basic_azure_mcs_vda/variables.tf @@ -0,0 +1,206 @@ +# citrix.tf variables +## On-Premises customer provider settings +variable provider_hostname { + description = "The hostname of the Citrix Virtual Apps and Desktops Delivery Controller." + type = string + default = "" # Leave this variable empty for Citrix Cloud customer. +} + +variable provider_domain_fqdn { + description = "The domain FQDN of the on-premises Active Directory." + type = string + default = null # Leave this variable empty for Citrix Cloud customer. +} + +variable provider_disable_ssl_verification { + description = "Disable SSL verification for the Citrix Virtual Apps and Desktops Delivery Controller." + type = bool + default = false # Set this field to true if DDC does not have a valid SSL certificate configured. Omit this variable for Citrix Cloud customer. +} + +## Citrix Cloud customer provider settings +variable provider_customer_id { + description = "The customer id of the Citrix Cloud customer." + type = string + default = "" # Set your customer id for Citrix Cloud customer. Omit this variable for On-Premises customer. +} + +variable provider_environment { + description = "The environment of the Citrix Cloud customer." + type = string + default = "Production" # Use "Japan" for Citrix Cloud customers in Japan region. Omit this variable for On-Premises customer. +} + +# Common provider settings +# For On-Premises customers: Domain Admin username and password are needed to interact with the Citrix Virtual Apps and Desktops Delivery Controller. +# For Citrix Cloud customers: API key client id and secret are needed to interact with Citrix DaaS APIs. These can be created/found under Identity and Access Management > API Access +variable provider_client_id { + description = "The Domain Admin username of the on-premises Active Directory / The API key client id for Citrix Cloud customer." + type = string + default = "" +} + +variable provider_client_secret { + description = "The Domain Admin password of the on-premises Active Directory / The API key client secret for Citrix Cloud customer." + type = string + default = "" +} + + +# delivery_groups.tf variables +variable "delivery_group_name" { + description = "Name of the Delivery Group to create" + type = string + default = "example-delivery-group" +} + +variable "allow_list" { + description = "List of users to allow for the Delivery Group in DOMAIN\\username format" + type = list(string) +} + + +# hypervisors.tf variables +variable "hypervisor_name" { + description = "Name of the Hypervisor to create" + type = string + default = "example-azure-hyperv" +} + +variable "azure_application_id" { + description = "Azure SPN client ID" + type = string +} + +variable "azure_application_secret" { + description = "Azure SPN client secret" + type = string + sensitive = true +} + +variable "azure_subscription_id" { + description = "Azure subscription ID" + type = string +} + +variable "azure_tenant_id" { + description = "Azure tenant ID" + type = string +} + + +# machine_catalogs.tf variables +variable "machine_catalog_name" { + description = "Name of the Machine Catalog to create" + type = string + default = "example-azure-catalog" +} + +variable "domain_fqdn" { + description = "Domain FQDN" + type = string +} + +variable "domain_ou" { + description = "Domain organizational unit" + type = string + default = null +} + +variable "domain_service_account" { + description = "Domain service account with permissions to create machine accounts" + type = string +} + +variable "domain_service_account_password" { + description = "Domain service account password" + type = string + sensitive = true +} + +variable "azure_service_offering" { + description = "Azure VM service offering SKU" + type = string + default = "Standard_D2_v2" +} + +# variable "azure_image_subscription" { +# description = "Azure subscription ID for the image, not needed if image is in the same subscription as the hypervisor" +# type = string +# } + +# For Azure master image from managed disk or snapshot +variable "azure_resource_group" { + description = "Azure resource group containing the master image" + type = string +} + +variable "azure_master_image" { + description = "Name of the master image managed disk or snapshot" + type = string +} + +# For Azure image gallery +# variable "azure_gallery_name" { +# description = "Azure gallery image name" +# type = string +# } + +# variable "azure_gallery_image_definition" { +# description = "Azure gallery image definition" +# type = string +# } + +# variable "azure_gallery_image_version" { +# description = "Azure gallery image version" +# type = string +# default = "1.0.0" +# } + +variable "azure_storage_type" { + description = "Azure storage type" + type = string + default = "Standard_LRS" +} + +variable "machine_catalog_naming_scheme" { + description = "Machine Catalog naming scheme" + type = string + default = "ctx-azure-##" +} + + +# resource_pools.tf variables +variable "resource_pool_name" { + description = "Name of the Resource Pool to create" + type = string + default = "example-azure-rp" +} + +variable "azure_region" { + description = "Azure region for the Resource Pool" + type = string + default = "East US" +} + +variable "azure_vnet_resource_group" { + description = "Name of the Azure virtual network resource group" + type = string +} + +variable "azure_vnet" { + description = "Name of the Azure virtual network" + type = string +} + +variable "azure_subnets" { + description = "List of Azure subnets" + type = list(string) +} + + +# zones.tf variables +variable "zone_name" { + description = "Name of the Zone to create. For Citrix Cloud customers the zone should already exist." + type = string +} diff --git a/examples/basic_azure_mcs_vda/zones.tf b/examples/basic_azure_mcs_vda/zones.tf index b3f0728..eeafc84 100644 --- a/examples/basic_azure_mcs_vda/zones.tf +++ b/examples/basic_azure_mcs_vda/zones.tf @@ -1,4 +1,4 @@ resource "citrix_zone" "example-zone" { - name = "example zone" + name = var.zone_name description = "description for example zone" } diff --git a/examples/basic_gcp_mcs_vda/citrix.tf b/examples/basic_gcp_mcs_vda/citrix.tf index 5ac9b01..f32f7f4 100644 --- a/examples/basic_gcp_mcs_vda/citrix.tf +++ b/examples/basic_gcp_mcs_vda/citrix.tf @@ -1,17 +1,17 @@ -// On-Premises customer provider settings -// Please comment out / remove this provider settings block if you are a Citrix Cloud customer +# On-Premises customer provider settings +# Please comment out / remove this provider settings block if you are a Citrix Cloud customer provider "citrix" { - hostname = "" - client_id = "\\" - client_secret = "" - disable_ssl_verification = true # omit this field if DDC has valid SSL certificate configured + hostname = var.provider_hostname + client_id = "${var.provider_domain_fqdn}\\${var.provider_client_id}" + client_secret = "${var.provider_client_secret}" + disable_ssl_verification = var.provider_disable_ssl_verification } -// Citrix Cloud customer provider settings -// Please comment out / remove this provider settings block if you are an On-Premises customer +# Citrix Cloud customer provider settings +# Please comment out / remove this provider settings block if you are an On-Premises customer provider "citrix" { - customer_id = "" # set your customer id - client_id = "" - client_secret = "" # API key client id and secret are needed to interact with Citrix Cloud APIs. These can be created/found under Identity and Access Management > API Access - environment = "Production" # use "Japan" for Citrix Cloud customers in Japan region + customer_id = var.provider_customer_id + client_id = var.provider_client_id + client_secret = var.provider_client_secret + environment = var.provider_environment } diff --git a/examples/basic_vsphere_mcs_vda/delivery_group.tf b/examples/basic_gcp_mcs_vda/delivery_groups.tf similarity index 75% rename from examples/basic_vsphere_mcs_vda/delivery_group.tf rename to examples/basic_gcp_mcs_vda/delivery_groups.tf index 836f752..fb92a67 100644 --- a/examples/basic_vsphere_mcs_vda/delivery_group.tf +++ b/examples/basic_gcp_mcs_vda/delivery_groups.tf @@ -1,5 +1,5 @@ resource "citrix_delivery_group" "example-delivery-group" { - name = "example-delivery-group" + name = var.delivery_group_name associated_machine_catalogs = [ { machine_catalog = citrix_machine_catalog.example-catalog.id @@ -9,19 +9,13 @@ resource "citrix_delivery_group" "example-delivery-group" { desktops = [ { published_name = "Example Desktop" - description = "Desription for example desktop" + description = "Description for example desktop" restricted_access_users = { - allow_list = [ - "example\\user1" - ] - block_list = [ - "example\\user2", - ] + allow_list = var.allow_list } enabled = true enable_session_roaming = false } - ] autoscale_settings = { autoscale_enabled = true @@ -50,11 +44,6 @@ resource "citrix_delivery_group" "example-delivery-group" { ] } restricted_access_users = { - allow_list = [ - "example\\user1" - ] - block_list = [ - "example\\user2", - ] + allow_list = var.allow_list } } \ No newline at end of file diff --git a/examples/basic_gcp_mcs_vda/hypervisors.tf b/examples/basic_gcp_mcs_vda/hypervisors.tf index c90741f..7f86373 100644 --- a/examples/basic_gcp_mcs_vda/hypervisors.tf +++ b/examples/basic_gcp_mcs_vda/hypervisors.tf @@ -1,7 +1,7 @@ resource "citrix_gcp_hypervisor" "example-gcp-hypervisor" { - name = "example-gcp-hyperv" + name = var.hypervisor_name zone = citrix_zone.example-zone.id - service_account_id = "{GCP service account Id}" - service_account_credentials = "{GCP service account private key}" + service_account_id = var.gcp_service_account_id + service_account_credentials = var.gcp_service_account_credentials } diff --git a/examples/basic_gcp_mcs_vda/machine_catalogs.tf b/examples/basic_gcp_mcs_vda/machine_catalogs.tf index 3638513..e0ec2a9 100644 --- a/examples/basic_gcp_mcs_vda/machine_catalogs.tf +++ b/examples/basic_gcp_mcs_vda/machine_catalogs.tf @@ -1,36 +1,32 @@ resource "citrix_machine_catalog" "example-catalog" { - name = "example-gcp-catalog" + name = var.machine_catalog_name description = "description for example catalog" allocation_type = "Random" session_support = "MultiSession" - is_power_managed = true - is_remote_pc = false provisioning_type = "MCS" zone = citrix_zone.example-zone.id - minimum_functional_level = "L7_20" provisioning_scheme = { hypervisor = citrix_gcp_hypervisor.example-gcp-hypervisor.id hypervisor_resource_pool = citrix_gcp_hypervisor_resource_pool.example-gcp-rp.id identity_type = "ActiveDirectory" - machine_domain_identity = { - domain = "" - service_account = "" - service_account_password = "" + machine_domain_identity = { + domain = var.domain_fqdn + domain_ou = var.domain_ou + service_account = var.domain_service_account + service_account_password = var.domain_service_account_password } gcp_machine_config = { - storage_type = "pd-standard" - machine_profile = "" - master_image = "" - machine_snapshot = "" + storage_type = var.gcp_storage_type + master_image = var.gcp_master_image } - availability_zones = "::,::,..." + availability_zones = var.gcp_availability_zones number_of_total_machines = 1 machine_account_creation_rules = { - naming_scheme = "ctx-pvdr-##" + naming_scheme = var.machine_catalog_naming_scheme naming_scheme_type = "Numeric" } writeback_cache = { - wbc_disk_storage_type = "pd-standard" + wbc_disk_storage_type = var.gcp_storage_type persist_wbc = true persist_os_disk = true writeback_cache_disk_size_gb = 127 diff --git a/examples/basic_gcp_mcs_vda/resource_pools.tf b/examples/basic_gcp_mcs_vda/resource_pools.tf index e8f3df6..1d1dda0 100644 --- a/examples/basic_gcp_mcs_vda/resource_pools.tf +++ b/examples/basic_gcp_mcs_vda/resource_pools.tf @@ -1,12 +1,10 @@ resource "citrix_gcp_hypervisor_resource_pool" "example-gcp-rp" { - name = "example-gcp-rp" + name = var.resource_pool_name hypervisor = citrix_gcp_hypervisor.example-gcp-hypervisor.id - project_name = "" - region = "" - subnets = [ - "" - ] - vpc = "{VPC name}" + project_name = var.gcp_project_name + region = var.gcp_vpc_region + subnets = var.gcp_subnets + vpc = var.gcp_vpc } diff --git a/examples/basic_gcp_mcs_vda/terraform.template.tfvars b/examples/basic_gcp_mcs_vda/terraform.template.tfvars new file mode 100644 index 0000000..d0f497f --- /dev/null +++ b/examples/basic_gcp_mcs_vda/terraform.template.tfvars @@ -0,0 +1,37 @@ +# citrix.tf variables, uncomment the ones you need for on-premises or cloud +# provider_hostname = "" # on-premises only +# provider_domain_fqdn = "" # on-premises only +provider_client_id = "" # or Citrx Cloud secure client ID for cloud +provider_client_secret = "" # or Citrix Cloud secure client secret for cloud +# provider_customer_id = "" # cloud only + +# delivery_groups.tf variables +delivery_group_name = "example-delivery-group" +allow_list = ["DOMAIN\\user1", "DOMAIN\\user2"] +block_list = ["DOMAIN\\user3", "DOMAIN\\user4"] + +# hypervisors.tf variables +hypervisor_name = "example-gcp-hyperv" +gcp_service_account_id = "" +gcp_service_account_credentials = "" + +# machine_catalogs.tf variables +machine_catalog_name = "example-gcp-catalog" +domain_fqdn = "" +domain_ou = "" +domain_service_account = "" +domain_service_account_password = "" +gcp_storage_type = "pd-standard" +gcp_master_image = "" +gcp_availability_zones = "::,::,..." +machine_catalog_naming_scheme = "ctx-gcp-##" + +# resource_pools.tf variables +resource_pool_name = "example-gcp-resource-pool" +gcp_project_name = "" +gcp_vpc_region = "" +gcp_vpc = [""] +gcp_subnets = "" + +# zones.tf variables +zone_name = "example-gcp-zone" \ No newline at end of file diff --git a/examples/basic_gcp_mcs_vda/terraform.tf b/examples/basic_gcp_mcs_vda/terraform.tf index f2f10f4..6484546 100644 --- a/examples/basic_gcp_mcs_vda/terraform.tf +++ b/examples/basic_gcp_mcs_vda/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { citrix = { source = "citrix/citrix" - version = ">=0.5.1" + version = ">=0.6.1" } } diff --git a/examples/basic_gcp_mcs_vda/variables.tf b/examples/basic_gcp_mcs_vda/variables.tf new file mode 100644 index 0000000..e397242 --- /dev/null +++ b/examples/basic_gcp_mcs_vda/variables.tf @@ -0,0 +1,167 @@ +# citrix.tf variables +## On-Premises customer provider settings +variable provider_hostname { + description = "The hostname of the Citrix Virtual Apps and Desktops Delivery Controller." + type = string + default = "" # Leave this variable empty for Citrix Cloud customer. +} + +variable provider_domain_fqdn { + description = "The domain FQDN of the on-premises Active Directory." + type = string + default = null # Leave this variable empty for Citrix Cloud customer. +} + +variable provider_disable_ssl_verification { + description = "Disable SSL verification for the Citrix Virtual Apps and Desktops Delivery Controller." + type = bool + default = false # Set this field to true if DDC does not have a valid SSL certificate configured. Omit this variable for Citrix Cloud customer. +} + +## Citrix Cloud customer provider settings +variable provider_customer_id { + description = "The customer id of the Citrix Cloud customer." + type = string + default = "" # Set your customer id for Citrix Cloud customer. Omit this variable for On-Premises customer. +} + +variable provider_environment { + description = "The environment of the Citrix Cloud customer." + type = string + default = "Production" # Use "Japan" for Citrix Cloud customers in Japan region. Omit this variable for On-Premises customer. +} + +# Common provider settings +# For On-Premises customers: Domain Admin username and password are needed to interact with the Citrix Virtual Apps and Desktops Delivery Controller. +# For Citrix Cloud customers: API key client id and secret are needed to interact with Citrix DaaS APIs. These can be created/found under Identity and Access Management > API Access +variable provider_client_id { + description = "The Domain Admin username of the on-premises Active Directory / The API key client id for Citrix Cloud customer." + type = string + default = "" +} + +variable provider_client_secret { + description = "The Domain Admin password of the on-premises Active Directory / The API key client secret for Citrix Cloud customer." + type = string + default = "" +} + + +# delivery_groups.tf variables +variable "delivery_group_name" { + description = "Name of the Delivery Group to create" + type = string + default = "example-delivery-group" +} + +variable "allow_list" { + description = "List of users to allow for the Delivery Group in DOMAIN\\username format" + type = list(string) +} + + +# hypervisors.tf variables +variable "hypervisor_name" { + description = "Name of the Hypervisor to create" + type = string + default = "example-gcp-hyperv" +} + +variable "gcp_service_account_id" { + description = "GCP service account ID" + type = string +} + +variable "gcp_service_account_credentials" { + description = "GCP service account private key, base64 encoded" + type = string + sensitive = true +} + + +# machine_catalogs.tf variables +variable "machine_catalog_name" { + description = "Name of the Machine Catalog to create" + type = string + default = "example-gcp-catalog" +} + +variable "domain_fqdn" { + description = "Domain FQDN" + type = string +} + +variable "domain_ou" { + description = "Domain organizational unit" + type = string + default = null +} + +variable "domain_service_account" { + description = "Domain service account with permissions to create machine accounts" + type = string +} + +variable "domain_service_account_password" { + description = "Domain service account password" + type = string + sensitive = true +} + +variable "gcp_storage_type" { + description = "Storage type of the provisioned VM disks on GCP" + type = string + default = "pd-standard" +} + +variable "gcp_master_image" { + description = "Name of the master image VM in GCP" + type = string +} + +variable "gcp_availability_zones" { + description = "Comma seperate list of GCP availability zones in the format of \"::,::,...\"" + type = string +} + +variable "machine_catalog_naming_scheme" { + description = "Machine Catalog naming scheme" + type = string + default = "ctx-gcp-##" +} + + +# resource_pools.tf variables +variable "resource_pool_name" { + description = "Name of the Resource Pool to create" + type = string + default = "example-gcp-rp" +} + +variable "gcp_project_name" { + description = "Project to create the Resource Pool in" + type = string +} + +variable "gcp_vpc_region" { + description = "Region to create the Resource Pool in" + type = string + default = "us-east1" +} + +variable "gcp_vpc" { + description = "Name of the GCP VPC" + type = string +} + +variable "gcp_subnets" { + description = "List of GCP subnets in the VPC" + type = list(string) +} + + +# zones.tf variables +variable "zone_name" { + description = "Name of the Zone to create. For Citrix Cloud customers the zone should already exist." + type = string +} diff --git a/examples/basic_gcp_mcs_vda/zones.tf b/examples/basic_gcp_mcs_vda/zones.tf index b3f0728..eeafc84 100644 --- a/examples/basic_gcp_mcs_vda/zones.tf +++ b/examples/basic_gcp_mcs_vda/zones.tf @@ -1,4 +1,4 @@ resource "citrix_zone" "example-zone" { - name = "example zone" + name = var.zone_name description = "description for example zone" } diff --git a/examples/basic_nutanix_mcs_vda/citrix.tf b/examples/basic_nutanix_mcs_vda/citrix.tf index 5ac9b01..f32f7f4 100644 --- a/examples/basic_nutanix_mcs_vda/citrix.tf +++ b/examples/basic_nutanix_mcs_vda/citrix.tf @@ -1,17 +1,17 @@ -// On-Premises customer provider settings -// Please comment out / remove this provider settings block if you are a Citrix Cloud customer +# On-Premises customer provider settings +# Please comment out / remove this provider settings block if you are a Citrix Cloud customer provider "citrix" { - hostname = "" - client_id = "\\" - client_secret = "" - disable_ssl_verification = true # omit this field if DDC has valid SSL certificate configured + hostname = var.provider_hostname + client_id = "${var.provider_domain_fqdn}\\${var.provider_client_id}" + client_secret = "${var.provider_client_secret}" + disable_ssl_verification = var.provider_disable_ssl_verification } -// Citrix Cloud customer provider settings -// Please comment out / remove this provider settings block if you are an On-Premises customer +# Citrix Cloud customer provider settings +# Please comment out / remove this provider settings block if you are an On-Premises customer provider "citrix" { - customer_id = "" # set your customer id - client_id = "" - client_secret = "" # API key client id and secret are needed to interact with Citrix Cloud APIs. These can be created/found under Identity and Access Management > API Access - environment = "Production" # use "Japan" for Citrix Cloud customers in Japan region + customer_id = var.provider_customer_id + client_id = var.provider_client_id + client_secret = var.provider_client_secret + environment = var.provider_environment } diff --git a/examples/basic_xenserver_mcs_vda/delivery_group.tf b/examples/basic_nutanix_mcs_vda/delivery_groups.tf similarity index 75% rename from examples/basic_xenserver_mcs_vda/delivery_group.tf rename to examples/basic_nutanix_mcs_vda/delivery_groups.tf index 836f752..fb92a67 100644 --- a/examples/basic_xenserver_mcs_vda/delivery_group.tf +++ b/examples/basic_nutanix_mcs_vda/delivery_groups.tf @@ -1,5 +1,5 @@ resource "citrix_delivery_group" "example-delivery-group" { - name = "example-delivery-group" + name = var.delivery_group_name associated_machine_catalogs = [ { machine_catalog = citrix_machine_catalog.example-catalog.id @@ -9,19 +9,13 @@ resource "citrix_delivery_group" "example-delivery-group" { desktops = [ { published_name = "Example Desktop" - description = "Desription for example desktop" + description = "Description for example desktop" restricted_access_users = { - allow_list = [ - "example\\user1" - ] - block_list = [ - "example\\user2", - ] + allow_list = var.allow_list } enabled = true enable_session_roaming = false } - ] autoscale_settings = { autoscale_enabled = true @@ -50,11 +44,6 @@ resource "citrix_delivery_group" "example-delivery-group" { ] } restricted_access_users = { - allow_list = [ - "example\\user1" - ] - block_list = [ - "example\\user2", - ] + allow_list = var.allow_list } } \ No newline at end of file diff --git a/examples/basic_nutanix_mcs_vda/hypervisors.tf b/examples/basic_nutanix_mcs_vda/hypervisors.tf index 4aa6b91..e897ed1 100644 --- a/examples/basic_nutanix_mcs_vda/hypervisors.tf +++ b/examples/basic_nutanix_mcs_vda/hypervisors.tf @@ -1,10 +1,8 @@ resource "citrix_nutanix_hypervisor" "example-nutanix-hypervisor" { - name = "example-nutanix-hyperv" + name = var.hypervisor_name zone = citrix_zone.example-zone.id - username = "" - password = "" - password_format = "PlainText" - addresses = [ - "http://" - ] + username = var.nutanix_username + password = var.nutanix_password + password_format = var.nutanix_password_format + addresses = var.nutanix_addresses } \ No newline at end of file diff --git a/examples/basic_nutanix_mcs_vda/machine_catalogs.tf b/examples/basic_nutanix_mcs_vda/machine_catalogs.tf index 2445885..7055500 100644 --- a/examples/basic_nutanix_mcs_vda/machine_catalogs.tf +++ b/examples/basic_nutanix_mcs_vda/machine_catalogs.tf @@ -1,30 +1,31 @@ resource "citrix_machine_catalog" "example-catalog" { - name = "example-catalog" + name = var.machine_catalog_name description = "description for example catalog" - provisioning_type = "MCS" allocation_type = "Random" session_support = "MultiSession" + provisioning_type = "MCS" zone = citrix_zone.example-zone.id provisioning_scheme = { - identity_type = "ActiveDirectory" - number_of_total_machines = 1 - machine_account_creation_rules = { - naming_scheme = "catalog-##" - naming_scheme_type = "Numeric" - } hypervisor = citrix_nutanix_hypervisor.example-nutanix-hypervisor.id hypervisor_resource_pool = citrix_nutanix_hypervisor_resource_pool.example-nutanix-rp.id + identity_type = "ActiveDirectory" + machine_domain_identity = { + domain = var.domain_fqdn + domain_ou = var.domain_ou + service_account = var.domain_service_account + service_account_password = var.domain_service_account_password + } nutanix_machine_config = { - container = "" - master_image = "" - cpu_count = 2 - memory_mb = 4096 - cores_per_cpu_count = 2 + container = var.nutanix_container + master_image = var.nutanix_master_image + cpu_count = var.nutanix_cpu_count + cores_per_cpu_count = var.nutanix_core_per_cpu_count + memory_mb = var.nutanix_memory_size } - machine_domain_identity = { - domain = "" - service_account = "" - service_account_password = "" + number_of_total_machines = 1 + machine_account_creation_rules = { + naming_scheme = var.machine_catalog_naming_scheme + naming_scheme_type = "Numeric" } } } \ No newline at end of file diff --git a/examples/basic_nutanix_mcs_vda/resource_pools.tf b/examples/basic_nutanix_mcs_vda/resource_pools.tf index 3f9cb0c..25b734f 100644 --- a/examples/basic_nutanix_mcs_vda/resource_pools.tf +++ b/examples/basic_nutanix_mcs_vda/resource_pools.tf @@ -1,8 +1,5 @@ resource "citrix_nutanix_hypervisor_resource_pool" "example-nutanix-rp" { - name = "example-nutanix-rp" + name = var.resource_pool_name hypervisor = citrix_nutanix_hypervisor.example-nutanix-hypervisor.id - networks = [ - "", - "" - ] + networks = var.nutanix_networks } \ No newline at end of file diff --git a/examples/basic_nutanix_mcs_vda/terraform.template.tfvars b/examples/basic_nutanix_mcs_vda/terraform.template.tfvars new file mode 100644 index 0000000..d42b243 --- /dev/null +++ b/examples/basic_nutanix_mcs_vda/terraform.template.tfvars @@ -0,0 +1,37 @@ +# citrix.tf variables, uncomment the ones you need for on-premises or cloud +# provider_hostname = "" # on-premises only +# provider_domain_fqdn = "" # on-premises only +provider_client_id = "" # or Citrx Cloud secure client ID for cloud +provider_client_secret = "" # or Citrix Cloud secure client secret for cloud +# provider_customer_id = "" # cloud only + +# delivery_groups.tf variables +delivery_group_name = "example-delivery-group" +allow_list = ["DOMAIN\\user1", "DOMAIN\\user2"] +block_list = ["DOMAIN\\user3", "DOMAIN\\user4"] + +# hypervisors.tf variables +hypervisor_name = "example-nutanix-hyperv" +nutanix_username = "" +nutanix_password = "" +nutanix_addresses = ["http://"] + +# machine_catalogs.tf variables +machine_catalog_name = "example-nutanix-catalog" +domain_fqdn = "" +domain_ou = "" +domain_service_account = "" +domain_service_account_password = "" +nutanix_container = "" +nutanix_master_image = "" +nutanix_cpu_count = 2 +nutanix_core_per_cpu_count = 2 +nutanix_memory_size = 4096 +machine_catalog_naming_scheme = "ctx-nutanix-##" + +# resource_pools.tf variables +resource_pool_name = "example-nutanix-resource-pool" +nutanix_networks = ["", ""] + +# zones.tf variables +zone_name = "example-nutanix-zone" \ No newline at end of file diff --git a/examples/basic_nutanix_mcs_vda/terraform.tf b/examples/basic_nutanix_mcs_vda/terraform.tf index 234252a..6484546 100644 --- a/examples/basic_nutanix_mcs_vda/terraform.tf +++ b/examples/basic_nutanix_mcs_vda/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { citrix = { source = "citrix/citrix" - version = ">=0.5.4" + version = ">=0.6.1" } } diff --git a/examples/basic_nutanix_mcs_vda/variables.tf b/examples/basic_nutanix_mcs_vda/variables.tf new file mode 100644 index 0000000..e5e8753 --- /dev/null +++ b/examples/basic_nutanix_mcs_vda/variables.tf @@ -0,0 +1,174 @@ +# citrix.tf variables +## On-Premises customer provider settings +variable provider_hostname { + description = "The hostname of the Citrix Virtual Apps and Desktops Delivery Controller." + type = string + default = "" # Leave this variable empty for Citrix Cloud customer. +} + +variable provider_domain_fqdn { + description = "The domain FQDN of the on-premises Active Directory." + type = string + default = null # Leave this variable empty for Citrix Cloud customer. +} + +variable provider_disable_ssl_verification { + description = "Disable SSL verification for the Citrix Virtual Apps and Desktops Delivery Controller." + type = bool + default = false # Set this field to true if DDC does not have a valid SSL certificate configured. Omit this variable for Citrix Cloud customer. +} + +## Citrix Cloud customer provider settings +variable provider_customer_id { + description = "The customer id of the Citrix Cloud customer." + type = string + default = "" # Set your customer id for Citrix Cloud customer. Omit this variable for On-Premises customer. +} + +variable provider_environment { + description = "The environment of the Citrix Cloud customer." + type = string + default = "Production" # Use "Japan" for Citrix Cloud customers in Japan region. Omit this variable for On-Premises customer. +} + +# Common provider settings +# For On-Premises customers: Domain Admin username and password are needed to interact with the Citrix Virtual Apps and Desktops Delivery Controller. +# For Citrix Cloud customers: API key client id and secret are needed to interact with Citrix DaaS APIs. These can be created/found under Identity and Access Management > API Access +variable provider_client_id { + description = "The Domain Admin username of the on-premises Active Directory / The API key client id for Citrix Cloud customer." + type = string + default = "" +} + +variable provider_client_secret { + description = "The Domain Admin password of the on-premises Active Directory / The API key client secret for Citrix Cloud customer." + type = string + default = "" +} + + +# delivery_groups.tf variables +variable "delivery_group_name" { + description = "Name of the Delivery Group to create" + type = string + default = "example-delivery-group" +} + +variable "allow_list" { + description = "List of users to allow for the Delivery Group in DOMAIN\\username format" + type = list(string) +} + + +# hypervisors.tf variables +variable "hypervisor_name" { + description = "Name of the Hypervisor to create" + type = string + default = "example-nutanix-hyperv" +} + +variable "nutanix_username" { + description = "Username to the Nutanix hypervisor" + type = string +} + +variable "nutanix_password" { + description = "Password to the Nutanix hypervisor" + type = string + sensitive = true +} + +variable "nutanix_password_format" { + description = "Nutanix password format" + type = string + default = "PlainText" +} + +variable "nutanix_addresses" { + description = "List of addresses to the Nutanix hypervisor in the format of \"http://\"" + type = list(string) +} + + +# machine_catalogs.tf variables +variable "machine_catalog_name" { + description = "Name of the Machine Catalog to create" + type = string + default = "example-nutanix-catalog" +} + +variable "domain_fqdn" { + description = "Domain FQDN" + type = string +} + +variable "domain_ou" { + description = "Domain organizational unit" + type = string + default = null +} + +variable "domain_service_account" { + description = "Domain service account with permissions to create machine accounts" + type = string +} + +variable "domain_service_account_password" { + description = "Domain service account password" + type = string + sensitive = true +} + +variable "nutanix_container" { + description = "Name of the container to place the identity disks in Nutanix" + type = string +} + +variable "nutanix_master_image" { + description = "Name of the master image VM in Nutanix" + type = string +} + +variable "nutanix_cpu_count" { + description = "Number of CPUs per VM created" + type = number + default = 2 +} + +variable "nutanix_core_per_cpu_count" { + description = "Number of cores per CPUs per VM created" + type = number + default = 2 +} + +variable "nutanix_memory_size" { + description = "Amount of memory in MB per VM created" + type = number + default = 4096 +} + +variable "machine_catalog_naming_scheme" { + description = "Machine Catalog naming scheme" + type = string + default = "ctx-nutanix-##" +} + + +# resource_pools.tf variables +variable "resource_pool_name" { + description = "Name of the Resource Pool to create" + type = string + default = "example-nutanix-rp" +} + +variable "nutanix_networks" { + description = "List of network names for the Resource Pool to use" + type = list(string) +} + + +# zones.tf variables +variable "zone_name" { + description = "Name of the Zone to create. For Citrix Cloud customers the zone should already exist." + type = string +} diff --git a/examples/basic_nutanix_mcs_vda/zones.tf b/examples/basic_nutanix_mcs_vda/zones.tf index b3f0728..eeafc84 100644 --- a/examples/basic_nutanix_mcs_vda/zones.tf +++ b/examples/basic_nutanix_mcs_vda/zones.tf @@ -1,4 +1,4 @@ resource "citrix_zone" "example-zone" { - name = "example zone" + name = var.zone_name description = "description for example zone" } diff --git a/examples/basic_vsphere_mcs_vda/citrix.tf b/examples/basic_vsphere_mcs_vda/citrix.tf index 5ac9b01..f32f7f4 100644 --- a/examples/basic_vsphere_mcs_vda/citrix.tf +++ b/examples/basic_vsphere_mcs_vda/citrix.tf @@ -1,17 +1,17 @@ -// On-Premises customer provider settings -// Please comment out / remove this provider settings block if you are a Citrix Cloud customer +# On-Premises customer provider settings +# Please comment out / remove this provider settings block if you are a Citrix Cloud customer provider "citrix" { - hostname = "" - client_id = "\\" - client_secret = "" - disable_ssl_verification = true # omit this field if DDC has valid SSL certificate configured + hostname = var.provider_hostname + client_id = "${var.provider_domain_fqdn}\\${var.provider_client_id}" + client_secret = "${var.provider_client_secret}" + disable_ssl_verification = var.provider_disable_ssl_verification } -// Citrix Cloud customer provider settings -// Please comment out / remove this provider settings block if you are an On-Premises customer +# Citrix Cloud customer provider settings +# Please comment out / remove this provider settings block if you are an On-Premises customer provider "citrix" { - customer_id = "" # set your customer id - client_id = "" - client_secret = "" # API key client id and secret are needed to interact with Citrix Cloud APIs. These can be created/found under Identity and Access Management > API Access - environment = "Production" # use "Japan" for Citrix Cloud customers in Japan region + customer_id = var.provider_customer_id + client_id = var.provider_client_id + client_secret = var.provider_client_secret + environment = var.provider_environment } diff --git a/examples/basic_nutanix_mcs_vda/delivery_group.tf b/examples/basic_vsphere_mcs_vda/delivery_groups.tf similarity index 75% rename from examples/basic_nutanix_mcs_vda/delivery_group.tf rename to examples/basic_vsphere_mcs_vda/delivery_groups.tf index 836f752..fb92a67 100644 --- a/examples/basic_nutanix_mcs_vda/delivery_group.tf +++ b/examples/basic_vsphere_mcs_vda/delivery_groups.tf @@ -1,5 +1,5 @@ resource "citrix_delivery_group" "example-delivery-group" { - name = "example-delivery-group" + name = var.delivery_group_name associated_machine_catalogs = [ { machine_catalog = citrix_machine_catalog.example-catalog.id @@ -9,19 +9,13 @@ resource "citrix_delivery_group" "example-delivery-group" { desktops = [ { published_name = "Example Desktop" - description = "Desription for example desktop" + description = "Description for example desktop" restricted_access_users = { - allow_list = [ - "example\\user1" - ] - block_list = [ - "example\\user2", - ] + allow_list = var.allow_list } enabled = true enable_session_roaming = false } - ] autoscale_settings = { autoscale_enabled = true @@ -50,11 +44,6 @@ resource "citrix_delivery_group" "example-delivery-group" { ] } restricted_access_users = { - allow_list = [ - "example\\user1" - ] - block_list = [ - "example\\user2", - ] + allow_list = var.allow_list } } \ No newline at end of file diff --git a/examples/basic_vsphere_mcs_vda/hypervisors.tf b/examples/basic_vsphere_mcs_vda/hypervisors.tf index 92a9972..93a699f 100644 --- a/examples/basic_vsphere_mcs_vda/hypervisors.tf +++ b/examples/basic_vsphere_mcs_vda/hypervisors.tf @@ -1,11 +1,9 @@ resource "citrix_vsphere_hypervisor" "example-vsphere-hypervisor" { - name = "example-vsphere-hyperv" + name = var.hypervisor_name zone = citrix_zone.example-zone.id - username = "" - password = "" - password_format = "PlainText" - addresses = [ - "http://" - ] + username = var.vsphere_username + password = var.vsphere_password + password_format = var.vsphere_password_format + addresses = var.vsphere_addresses max_absolute_active_actions = 20 } \ No newline at end of file diff --git a/examples/basic_vsphere_mcs_vda/machine_catalogs.tf b/examples/basic_vsphere_mcs_vda/machine_catalogs.tf index 5a8ce24..a440dfb 100644 --- a/examples/basic_vsphere_mcs_vda/machine_catalogs.tf +++ b/examples/basic_vsphere_mcs_vda/machine_catalogs.tf @@ -1,28 +1,29 @@ resource "citrix_machine_catalog" "example-catalog" { - name = "example-catalog" + name = var.machine_catalog_name description = "description for example catalog" - provisioning_type = "MCS" allocation_type = "Random" session_support = "MultiSession" - zone = "" + provisioning_type = "MCS" + zone = citrix_zone.example-zone.id provisioning_scheme = { - identity_type = "ActiveDirectory" - number_of_total_machines = 1 - machine_account_creation_rules = { - naming_scheme = "catalog-##" - naming_scheme_type = "Numeric" - } hypervisor = citrix_vsphere_hypervisor.example-vsphere-hypervisor.id hypervisor_resource_pool = citrix_vsphere_hypervisor_resource_pool.example-vsphere-rp.id + identity_type = "ActiveDirectory" + machine_domain_identity = { + domain = var.domain_fqdn + domain_ou = var.domain_ou + service_account = var.domain_service_account + service_account_password = var.domain_service_account_password + } vsphere_machine_config = { - master_image_vm = "" - cpu_count = 2 - memory_mb = 4096 + master_image_vm = var.vsphere_master_image_vm + cpu_count = var.vsphere_cpu_count + memory_mb = var.vsphere_memory_size } - machine_domain_identity = { - domain = "" - service_account = "" - service_account_password = "" + number_of_total_machines = 1 + machine_account_creation_rules = { + naming_scheme = var.machine_catalog_naming_scheme + naming_scheme_type = "Numeric" } } } \ No newline at end of file diff --git a/examples/basic_vsphere_mcs_vda/resource_pools.tf b/examples/basic_vsphere_mcs_vda/resource_pools.tf index f208401..e392b4e 100644 --- a/examples/basic_vsphere_mcs_vda/resource_pools.tf +++ b/examples/basic_vsphere_mcs_vda/resource_pools.tf @@ -1,23 +1,20 @@ resource "citrix_vsphere_hypervisor_resource_pool" "example-vsphere-rp" { - name = "example-vsphere-rp" + name = var.resource_pool_name hypervisor = citrix_vsphere_hypervisor.example-vsphere-hypervisor.id cluster = { - datacenter = "" - cluster_name = "" - host = "" + datacenter = var.vsphere_cluster_datacenter + cluster_name = var.vsphere_cluster_name + host = var.vsphere_cluster_host } - networks = [ - "", - "" - ] + networks = var.vsphere_networks storage = [ { - storage_name = "" + storage_name = var.vsphere_storage_name } ] temporary_storage = [ { - storage_name = "" + storage_name = var.vsphere_temporary_storage_name } ] use_local_storage_caching = false diff --git a/examples/basic_vsphere_mcs_vda/terraform.template.tfvars b/examples/basic_vsphere_mcs_vda/terraform.template.tfvars new file mode 100644 index 0000000..dd5bb45 --- /dev/null +++ b/examples/basic_vsphere_mcs_vda/terraform.template.tfvars @@ -0,0 +1,41 @@ +# citrix.tf variables, uncomment the ones you need for on-premises or cloud +# provider_hostname = "" # on-premises only +# provider_domain_fqdn = "" # on-premises only +provider_client_id = "" # or Citrx Cloud secure client ID for cloud +provider_client_secret = "" # or Citrix Cloud secure client secret for cloud +# provider_customer_id = "" # cloud only + +# delivery_groups.tf variables +delivery_group_name = "example-delivery-group" +allow_list = ["DOMAIN\\user1", "DOMAIN\\user2"] +block_list = ["DOMAIN\\user3", "DOMAIN\\user4"] + +# hypervisors.tf variables +hypervisor_name = "example-vsphere-hyperv" +vsphere_username = "" +vsphere_password = "" +vsphere_addresses = ["http://"] + +# machine_catalogs.tf variables +machine_catalog_name = "example-vsphere-catalog" +domain_fqdn = "" +domain_ou = "" +domain_service_account = "" +domain_service_account_password = "" +vsphere_master_image_vm = "" +vsphere_cpu_count = 2 +nutanix_core_per_cpu_count = 2 +vsphere_memory_size = 4096 +machine_catalog_naming_scheme = "ctx-vsphere-##" + +# resource_pools.tf variables +resource_pool_name = "example-vsphere-resource-pool" +vsphere_networks = ["", ""] +vsphere_cluster_datacenter = "" +vsphere_cluster_name = "" +vsphere_cluster_host = "" +vsphere_storage_name = "" +vsphere_temporary_storage_name = "" + +# zones.tf variables +zone_name = "example-vsphere-zone" \ No newline at end of file diff --git a/examples/basic_vsphere_mcs_vda/terraform.tf b/examples/basic_vsphere_mcs_vda/terraform.tf index 3d89e96..6484546 100644 --- a/examples/basic_vsphere_mcs_vda/terraform.tf +++ b/examples/basic_vsphere_mcs_vda/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { citrix = { source = "citrix/citrix" - version = ">=0.5.3" + version = ">=0.6.1" } } diff --git a/examples/basic_vsphere_mcs_vda/variables.tf b/examples/basic_vsphere_mcs_vda/variables.tf new file mode 100644 index 0000000..10cddaa --- /dev/null +++ b/examples/basic_vsphere_mcs_vda/variables.tf @@ -0,0 +1,187 @@ +# citrix.tf variables +## On-Premises customer provider settings +variable provider_hostname { + description = "The hostname of the Citrix Virtual Apps and Desktops Delivery Controller." + type = string + default = "" # Leave this variable empty for Citrix Cloud customer. +} + +variable provider_domain_fqdn { + description = "The domain FQDN of the on-premises Active Directory." + type = string + default = null # Leave this variable empty for Citrix Cloud customer. +} + +variable provider_disable_ssl_verification { + description = "Disable SSL verification for the Citrix Virtual Apps and Desktops Delivery Controller." + type = bool + default = false # Set this field to true if DDC does not have a valid SSL certificate configured. Omit this variable for Citrix Cloud customer. +} + +## Citrix Cloud customer provider settings +variable provider_customer_id { + description = "The customer id of the Citrix Cloud customer." + type = string + default = "" # Set your customer id for Citrix Cloud customer. Omit this variable for On-Premises customer. +} + +variable provider_environment { + description = "The environment of the Citrix Cloud customer." + type = string + default = "Production" # Use "Japan" for Citrix Cloud customers in Japan region. Omit this variable for On-Premises customer. +} + +# Common provider settings +# For On-Premises customers: Domain Admin username and password are needed to interact with the Citrix Virtual Apps and Desktops Delivery Controller. +# For Citrix Cloud customers: API key client id and secret are needed to interact with Citrix DaaS APIs. These can be created/found under Identity and Access Management > API Access +variable provider_client_id { + description = "The Domain Admin username of the on-premises Active Directory / The API key client id for Citrix Cloud customer." + type = string + default = "" +} + +variable provider_client_secret { + description = "The Domain Admin password of the on-premises Active Directory / The API key client secret for Citrix Cloud customer." + type = string + default = "" +} + + +# delivery_groups.tf variables +variable "delivery_group_name" { + description = "Name of the Delivery Group to create" + type = string + default = "example-delivery-group" +} + +variable "allow_list" { + description = "List of users to allow for the Delivery Group in DOMAIN\\username format" + type = list(string) +} + + +# hypervisors.tf variables +variable "hypervisor_name" { + description = "Name of the Hypervisor to create" + type = string + default = "example-vsphere-hyperv" +} + +variable "vsphere_username" { + description = "Username to the vSphere hypervisor" + type = string +} + +variable "vsphere_password" { + description = "Password to the vSphere hypervisor" + type = string + sensitive = true +} + +variable "vsphere_password_format" { + description = "vSphere password format" + type = string + default = "PlainText" +} + +variable "vsphere_addresses" { + description = "List of addresses to the vSphere hypervisor in the format of \"http://\"" + type = list(string) +} + + +# machine_catalogs.tf variables +variable "machine_catalog_name" { + description = "Name of the Machine Catalog to create" + type = string + default = "example-vsphere-catalog" +} + +variable "domain_fqdn" { + description = "Domain FQDN" + type = string +} + +variable "domain_ou" { + description = "Domain organizational unit" + type = string + default = null +} + +variable "domain_service_account" { + description = "Domain service account with permissions to create machine accounts" + type = string +} + +variable "domain_service_account_password" { + description = "Domain service account password" + type = string + sensitive = true +} + +variable "vsphere_master_image_vm" { + description = "Name of the VM to be used as a master image" + type = string +} + +variable "vsphere_cpu_count" { + description = "Number of CPUs per VM created" + type = number + default = 2 +} + +variable "vsphere_memory_size" { + description = "Amount of memory in MB per VM created" + type = number + default = 4096 +} + +variable "machine_catalog_naming_scheme" { + description = "Machine Catalog naming scheme" + type = string + default = "ctx-vsphere-##" +} + + +# resource_pools.tf variables +variable "resource_pool_name" { + description = "Name of the Resource Pool to create" + type = string + default = "example-vsphere-rp" +} + +variable "vsphere_networks" { + description = "List of network names for the Resource Pool to use" + type = list(string) +} + +variable "vsphere_cluster_datacenter" { + description = "Name of the vSphere Datacenter" + type = string +} + +variable "vsphere_cluster_name" { + description = "Name of the cluster" + type = string +} + +variable "vsphere_cluster_host" { + description = "FQDN or IP address of the host" + type = string +} + +variable "vsphere_storage_name" { + description = "Name of the storage" + type = string +} + +variable "vsphere_temporary_storage_name" { + description = "Name of the temporary storage" + type = string +} + +# zones.tf variables +variable "zone_name" { + description = "Name of the Zone to create. For Citrix Cloud customers the zone should already exist." + type = string +} diff --git a/examples/basic_vsphere_mcs_vda/zones.tf b/examples/basic_vsphere_mcs_vda/zones.tf index b3f0728..eeafc84 100644 --- a/examples/basic_vsphere_mcs_vda/zones.tf +++ b/examples/basic_vsphere_mcs_vda/zones.tf @@ -1,4 +1,4 @@ resource "citrix_zone" "example-zone" { - name = "example zone" + name = var.zone_name description = "description for example zone" } diff --git a/examples/basic_xenserver_mcs_vda/citrix.tf b/examples/basic_xenserver_mcs_vda/citrix.tf index 5ac9b01..f32f7f4 100644 --- a/examples/basic_xenserver_mcs_vda/citrix.tf +++ b/examples/basic_xenserver_mcs_vda/citrix.tf @@ -1,17 +1,17 @@ -// On-Premises customer provider settings -// Please comment out / remove this provider settings block if you are a Citrix Cloud customer +# On-Premises customer provider settings +# Please comment out / remove this provider settings block if you are a Citrix Cloud customer provider "citrix" { - hostname = "" - client_id = "\\" - client_secret = "" - disable_ssl_verification = true # omit this field if DDC has valid SSL certificate configured + hostname = var.provider_hostname + client_id = "${var.provider_domain_fqdn}\\${var.provider_client_id}" + client_secret = "${var.provider_client_secret}" + disable_ssl_verification = var.provider_disable_ssl_verification } -// Citrix Cloud customer provider settings -// Please comment out / remove this provider settings block if you are an On-Premises customer +# Citrix Cloud customer provider settings +# Please comment out / remove this provider settings block if you are an On-Premises customer provider "citrix" { - customer_id = "" # set your customer id - client_id = "" - client_secret = "" # API key client id and secret are needed to interact with Citrix Cloud APIs. These can be created/found under Identity and Access Management > API Access - environment = "Production" # use "Japan" for Citrix Cloud customers in Japan region + customer_id = var.provider_customer_id + client_id = var.provider_client_id + client_secret = var.provider_client_secret + environment = var.provider_environment } diff --git a/examples/basic_gcp_mcs_vda/delivery_group.tf b/examples/basic_xenserver_mcs_vda/delivery_groups.tf similarity index 73% rename from examples/basic_gcp_mcs_vda/delivery_group.tf rename to examples/basic_xenserver_mcs_vda/delivery_groups.tf index 0efc666..fb92a67 100644 --- a/examples/basic_gcp_mcs_vda/delivery_group.tf +++ b/examples/basic_xenserver_mcs_vda/delivery_groups.tf @@ -1,6 +1,5 @@ resource "citrix_delivery_group" "example-delivery-group" { - name = "example-delivery-group" - minimum_functional_level = "L7_20" + name = var.delivery_group_name associated_machine_catalogs = [ { machine_catalog = citrix_machine_catalog.example-catalog.id @@ -10,19 +9,13 @@ resource "citrix_delivery_group" "example-delivery-group" { desktops = [ { published_name = "Example Desktop" - description = "Desription for example desktop" + description = "Description for example desktop" restricted_access_users = { - allow_list = [ - "example\\user1" - ] - block_list = [ - "example\\user2", - ] + allow_list = var.allow_list } enabled = true enable_session_roaming = false } - ] autoscale_settings = { autoscale_enabled = true @@ -51,11 +44,6 @@ resource "citrix_delivery_group" "example-delivery-group" { ] } restricted_access_users = { - allow_list = [ - "example\\user1" - ] - block_list = [ - "example\\user2", - ] + allow_list = var.allow_list } } \ No newline at end of file diff --git a/examples/basic_xenserver_mcs_vda/hypervisors.tf b/examples/basic_xenserver_mcs_vda/hypervisors.tf index c0a6826..6e01596 100644 --- a/examples/basic_xenserver_mcs_vda/hypervisors.tf +++ b/examples/basic_xenserver_mcs_vda/hypervisors.tf @@ -1,13 +1,8 @@ resource "citrix_xenserver_hypervisor" "example-xenserver-hypervisor" { - name = "example-xenserver-hyperv" + name = var.hypervisor_name zone = citrix_zone.example-zone.id - username = "" - password = "" - password_format = "PlainText" - addresses = [ - "http://" - ] - ssl_thumbprints = [ - "" - ] + username = var.xenserver_username + password = var.xenserver_password + password_format = var.xenserver_password_format + addresses = var.xenserver_addresses } \ No newline at end of file diff --git a/examples/basic_xenserver_mcs_vda/machine_catalogs.tf b/examples/basic_xenserver_mcs_vda/machine_catalogs.tf index 6082b2c..d8af1f2 100644 --- a/examples/basic_xenserver_mcs_vda/machine_catalogs.tf +++ b/examples/basic_xenserver_mcs_vda/machine_catalogs.tf @@ -1,28 +1,29 @@ resource "citrix_machine_catalog" "example-catalog" { - name = "example-catalog" + name = var.machine_catalog_name description = "description for example catalog" provisioning_type = "MCS" allocation_type = "Random" session_support = "MultiSession" zone = "" provisioning_scheme = { - identity_type = "ActiveDirectory" - number_of_total_machines = 1 - machine_account_creation_rules = { - naming_scheme = "catalog-##" - naming_scheme_type = "Numeric" - } hypervisor = citrix_xenserver_hypervisor.example-xenserver-hypervisor.id hypervisor_resource_pool = citrix_xenserver_hypervisor_resource_pool.example-xenserver-rp.id + identity_type = "ActiveDirectory" + machine_domain_identity = { + domain = var.domain_fqdn + domain_ou = var.domain_ou + service_account = var.domain_service_account + service_account_password = var.domain_service_account_password + } xenserver_machine_config = { - master_image_vm = "" - cpu_count = 2 - memory_mb = 4096 + master_image_vm = var.xenserver_master_image_vm + cpu_count = var.xenserver_cpu_count + memory_mb = var.xenserver_memory_size } - machine_domain_identity = { - domain = "" - service_account = "" - service_account_password = "" + number_of_total_machines = 1 + machine_account_creation_rules = { + naming_scheme = var.machine_catalog_naming_scheme + naming_scheme_type = "Numeric" } } } \ No newline at end of file diff --git a/examples/basic_xenserver_mcs_vda/resource_pools.tf b/examples/basic_xenserver_mcs_vda/resource_pools.tf index b3ef3f2..41ce297 100644 --- a/examples/basic_xenserver_mcs_vda/resource_pools.tf +++ b/examples/basic_xenserver_mcs_vda/resource_pools.tf @@ -1,18 +1,15 @@ resource "citrix_xenserver_hypervisor_resource_pool" "example-xenserver-rp" { - name = "example-xenserver-rp" - hypervisor = citrix_xenserver_hypervisor.example-xenserver-hypervisor.id - networks = [ - "", - "" - ] + name = var.resource_pool_name + hypervisor = citrix_xenserver_hypervisor.example-xenserver-hypervisor.id + networks = var.xenserver_networks storage = [ { - storage_name = "" + storage_name = var.xenserver_storage_name } ] temporary_storage = [ { - storage_name = "" + storage_name = var.xenserver_temporary_storage_name } ] use_local_storage_caching = false diff --git a/examples/basic_xenserver_mcs_vda/terraform.template.tfvars b/examples/basic_xenserver_mcs_vda/terraform.template.tfvars new file mode 100644 index 0000000..098051b --- /dev/null +++ b/examples/basic_xenserver_mcs_vda/terraform.template.tfvars @@ -0,0 +1,36 @@ +# citrix.tf variables, uncomment the ones you need for on-premises or cloud +# provider_hostname = "" # on-premises only +# provider_domain_fqdn = "" # on-premises only +provider_client_id = "" # or Citrx Cloud secure client ID for cloud +provider_client_secret = "" # or Citrix Cloud secure client secret for cloud +# provider_customer_id = "" # cloud only + +# delivery_groups.tf variables +delivery_group_name = "example-delivery-group" +allow_list = ["DOMAIN\\user1", "DOMAIN\\user2"] + +# hypervisors.tf variables +hypervisor_name = "example-xen-hyperv" +xenserver_username = "" +xenserver_password = "" +xenserver_addresses = ["http://"] + +# machine_catalogs.tf variables +machine_catalog_name = "example-xen-catalog" +domain_fqdn = "" +domain_ou = "" +domain_service_account = "" +domain_service_account_password = "" +xenserver_master_image_vm = "" +xenserver_cpu_count = 2 +xenserver_memory_size = 4096 +machine_catalog_naming_scheme = "ctx-xen-##" + +# resource_pools.tf variables +resource_pool_name = "example-xen-resource-pool" +xenserver_networks = ["", ""] +xenserver_storage_name = "" +xenserver_temporary_storage_name = "" + +# zones.tf variables +zone_name = "example-xen-zone" \ No newline at end of file diff --git a/examples/basic_xenserver_mcs_vda/terraform.tf b/examples/basic_xenserver_mcs_vda/terraform.tf index 3d89e96..6484546 100644 --- a/examples/basic_xenserver_mcs_vda/terraform.tf +++ b/examples/basic_xenserver_mcs_vda/terraform.tf @@ -4,7 +4,7 @@ terraform { required_providers { citrix = { source = "citrix/citrix" - version = ">=0.5.3" + version = ">=0.6.1" } } diff --git a/examples/basic_xenserver_mcs_vda/variables.tf b/examples/basic_xenserver_mcs_vda/variables.tf new file mode 100644 index 0000000..8ec315a --- /dev/null +++ b/examples/basic_xenserver_mcs_vda/variables.tf @@ -0,0 +1,172 @@ +# citrix.tf variables +## On-Premises customer provider settings +variable provider_hostname { + description = "The hostname of the Citrix Virtual Apps and Desktops Delivery Controller." + type = string + default = "" # Leave this variable empty for Citrix Cloud customer. +} + +variable provider_domain_fqdn { + description = "The domain FQDN of the on-premises Active Directory." + type = string + default = null # Leave this variable empty for Citrix Cloud customer. +} + +variable provider_disable_ssl_verification { + description = "Disable SSL verification for the Citrix Virtual Apps and Desktops Delivery Controller." + type = bool + default = false # Set this field to true if DDC does not have a valid SSL certificate configured. Omit this variable for Citrix Cloud customer. +} + +## Citrix Cloud customer provider settings +variable provider_customer_id { + description = "The customer id of the Citrix Cloud customer." + type = string + default = "" # Set your customer id for Citrix Cloud customer. Omit this variable for On-Premises customer. +} + +variable provider_environment { + description = "The environment of the Citrix Cloud customer." + type = string + default = "Production" # Use "Japan" for Citrix Cloud customers in Japan region. Omit this variable for On-Premises customer. +} + +# Common provider settings +# For On-Premises customers: Domain Admin username and password are needed to interact with the Citrix Virtual Apps and Desktops Delivery Controller. +# For Citrix Cloud customers: API key client id and secret are needed to interact with Citrix DaaS APIs. These can be created/found under Identity and Access Management > API Access +variable provider_client_id { + description = "The Domain Admin username of the on-premises Active Directory / The API key client id for Citrix Cloud customer." + type = string + default = "" +} + +variable provider_client_secret { + description = "The Domain Admin password of the on-premises Active Directory / The API key client secret for Citrix Cloud customer." + type = string + default = "" +} + + +# delivery_groups.tf variables +variable "delivery_group_name" { + description = "Name of the Delivery Group to create" + type = string + default = "example-delivery-group" +} + +variable "allow_list" { + description = "List of users to allow for the Delivery Group in DOMAIN\\username format" + type = list(string) +} + + +# hypervisors.tf variables +variable "hypervisor_name" { + description = "Name of the Hypervisor to create" + type = string + default = "example-xenserver-hyperv" +} + +variable "xenserver_username" { + description = "Username to the xenserver hypervisor" + type = string +} + +variable "xenserver_password" { + description = "Password to the xenserver hypervisor" + type = string + sensitive = true +} + +variable "xenserver_password_format" { + description = "xenserver password format" + type = string + default = "PlainText" +} + +variable "xenserver_addresses" { + description = "List of addresses to the xenserver hypervisor in the format of \"http://\"" + type = list(string) +} + + +# machine_catalogs.tf variables +variable "machine_catalog_name" { + description = "Name of the Machine Catalog to create" + type = string + default = "example-xenserver-catalog" +} + +variable "domain_fqdn" { + description = "Domain FQDN" + type = string +} + +variable "domain_ou" { + description = "Domain organizational unit" + type = string + default = null +} + +variable "domain_service_account" { + description = "Domain service account with permissions to create machine accounts" + type = string +} + +variable "domain_service_account_password" { + description = "Domain service account password" + type = string + sensitive = true +} + +variable "xenserver_master_image_vm" { + description = "Name of the VM to be used as a master image" + type = string +} + +variable "xenserver_cpu_count" { + description = "Number of CPUs per VM created" + type = number + default = 2 +} + +variable "xenserver_memory_size" { + description = "Amount of memory in MB per VM created" + type = number + default = 4096 +} + +variable "machine_catalog_naming_scheme" { + description = "Machine Catalog naming scheme" + type = string + default = "ctx-xen-###" +} + + +# resource_pools.tf variables +variable "resource_pool_name" { + description = "Name of the Resource Pool to create" + type = string + default = "example-xenserver-rp" +} + +variable "xenserver_networks" { + description = "List of network names for the Resource Pool to use" + type = list(string) +} + +variable "xenserver_storage_name" { + description = "Name of the storage" + type = string +} + +variable "xenserver_temporary_storage_name" { + description = "Name of the temporary storage" + type = string +} + +# zones.tf variables +variable "zone_name" { + description = "Name of the Zone to create. For Citrix Cloud customers the zone should already exist." + type = string +} diff --git a/examples/basic_xenserver_mcs_vda/zones.tf b/examples/basic_xenserver_mcs_vda/zones.tf index b3f0728..eeafc84 100644 --- a/examples/basic_xenserver_mcs_vda/zones.tf +++ b/examples/basic_xenserver_mcs_vda/zones.tf @@ -1,4 +1,4 @@ resource "citrix_zone" "example-zone" { - name = "example zone" + name = var.zone_name description = "description for example zone" } diff --git a/examples/non_domain_joined_azure_mcs_vda/citrix.tf b/examples/non_domain_joined_azure_mcs_vda/citrix.tf new file mode 100644 index 0000000..038eb79 --- /dev/null +++ b/examples/non_domain_joined_azure_mcs_vda/citrix.tf @@ -0,0 +1,9 @@ +# Citrix Cloud customer provider settings +# Please comment out / remove this provider settings block if you are an On-Premises customer +provider "citrix" { + customer_id = var.provider_customer_id + client_id = var.provider_client_id + client_secret = var.provider_client_secret + environment = "Staging" + hostname = "api.dev.cloud.com" +} diff --git a/examples/non_domain_joined_azure_mcs_vda/delivery_groups.tf b/examples/non_domain_joined_azure_mcs_vda/delivery_groups.tf new file mode 100644 index 0000000..fb92a67 --- /dev/null +++ b/examples/non_domain_joined_azure_mcs_vda/delivery_groups.tf @@ -0,0 +1,49 @@ +resource "citrix_delivery_group" "example-delivery-group" { + name = var.delivery_group_name + associated_machine_catalogs = [ + { + machine_catalog = citrix_machine_catalog.example-catalog.id + machine_count = 1 + } + ] + desktops = [ + { + published_name = "Example Desktop" + description = "Description for example desktop" + restricted_access_users = { + allow_list = var.allow_list + } + enabled = true + enable_session_roaming = false + } + ] + autoscale_settings = { + autoscale_enabled = true + power_time_schemes = [ + { + days_of_week = [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday" + ] + name = "weekdays test" + display_name = "weekdays schedule" + peak_time_ranges = [ + "09:00-17:00" + ] + pool_size_schedules = [ + { + time_range = "00:00-00:00", + pool_size = 1 + } + ] + pool_using_percentage = false + }, + ] + } + restricted_access_users = { + allow_list = var.allow_list + } +} \ No newline at end of file diff --git a/examples/non_domain_joined_azure_mcs_vda/hypervisors.tf b/examples/non_domain_joined_azure_mcs_vda/hypervisors.tf new file mode 100644 index 0000000..556d0a8 --- /dev/null +++ b/examples/non_domain_joined_azure_mcs_vda/hypervisors.tf @@ -0,0 +1,8 @@ +resource "citrix_azure_hypervisor" "example-azure-hypervisor" { + name = var.hypervisor_name + zone = citrix_zone.example-zone.id + application_id = var.azure_application_id + application_secret = var.azure_application_secret + subscription_id = var.azure_subscription_id + active_directory_id = var.azure_tenant_id +} \ No newline at end of file diff --git a/examples/non_domain_joined_azure_mcs_vda/machine_catalogs.tf b/examples/non_domain_joined_azure_mcs_vda/machine_catalogs.tf new file mode 100644 index 0000000..0dbf9cc --- /dev/null +++ b/examples/non_domain_joined_azure_mcs_vda/machine_catalogs.tf @@ -0,0 +1,37 @@ +resource "citrix_machine_catalog" "example-catalog" { + name = var.machine_catalog_name + description = "description for example catalog" + allocation_type = "Random" + session_support = "MultiSession" + provisioning_type = "MCS" + zone = citrix_zone.example-zone.id + provisioning_scheme = { + hypervisor = citrix_azure_hypervisor.example-azure-hypervisor.id + hypervisor_resource_pool = citrix_azure_hypervisor_resource_pool.example-azure-rp.id + identity_type = "Workgroup" # Workgroup specifies that the machines are not domain-joined + azure_machine_config = { + service_offering = var.azure_service_offering + storage_type = var.azure_storage_type + 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 = var.azure_resource_group + master_image = var.azure_master_image + + # For Azure image gallery + # gallery_image = { + # gallery = var.azure_gallery_name + # definition = var.azure_gallery_image_definition + # version = var.azure_gallery_image_version + # } + } + use_managed_disks = true + } + number_of_total_machines = 1 + machine_account_creation_rules = { + naming_scheme = var.machine_catalog_naming_scheme + naming_scheme_type = "Numeric" + } + } +} \ No newline at end of file diff --git a/examples/non_domain_joined_azure_mcs_vda/policy_sets.tf b/examples/non_domain_joined_azure_mcs_vda/policy_sets.tf new file mode 100644 index 0000000..b6236b9 --- /dev/null +++ b/examples/non_domain_joined_azure_mcs_vda/policy_sets.tf @@ -0,0 +1,25 @@ +resource "citrix_policy_set" "rv2-policy-set" { + name = "rv2-policy-set" + description = "This is an policy set to enable rendezvous v2 for Connectorless VDAs" + type = "DeliveryGroupPolicies" + policies = [ + { + name = "rv2-policy-with-priority-0" + enabled = true + policy_settings = [ + { + name = "RendezvousProtocol" + enabled = true + use_default = false + }, + ] + delivery_group_filters = [ + { + delivery_group_id = citrix_delivery_group.example-delivery-group.id + enabled = true + allowed = true + }, + ] + } + ] +} \ No newline at end of file diff --git a/examples/non_domain_joined_azure_mcs_vda/resource_pools.tf b/examples/non_domain_joined_azure_mcs_vda/resource_pools.tf new file mode 100644 index 0000000..96fad0d --- /dev/null +++ b/examples/non_domain_joined_azure_mcs_vda/resource_pools.tf @@ -0,0 +1,8 @@ +resource "citrix_azure_hypervisor_resource_pool" "example-azure-rp" { + name = var.resource_pool_name + hypervisor = citrix_azure_hypervisor.example-azure-hypervisor.id + region = var.azure_region + virtual_network_resource_group = var.azure_vnet_resource_group + virtual_network = var.azure_vnet + subnets = var.azure_subnets +} \ No newline at end of file diff --git a/examples/non_domain_joined_azure_mcs_vda/terraform.template.tfvars b/examples/non_domain_joined_azure_mcs_vda/terraform.template.tfvars new file mode 100644 index 0000000..ba82b01 --- /dev/null +++ b/examples/non_domain_joined_azure_mcs_vda/terraform.template.tfvars @@ -0,0 +1,38 @@ +# citrix.tf variables +# Non-domain joined is only available for Cloud customers +provider_client_id = "" +provider_client_secret = "" +provider_customer_id = "" + +# delivery_groups.tf variables +delivery_group_name = "example-delivery-group" +allow_list = ["DOMAIN\\user1", "DOMAIN\\user2"] + +# hypervisors.tf variables +hypervisor_name = "example-azure-hyperv" +azure_application_id = "" +azure_application_secret = "" +azure_subscription_id = "" +azure_tenant_id = "" + +# machine_catalogs.tf variables +machine_catalog_name = "example-azure-catalog" +azure_service_offering = "Standard_D2_v2" +#azure_image_subscription = "" +azure_resource_group = "" +azure_master_image = "" +#azure_gallery_name = "" +#azure_gallery_image_definition = "" +#azure_gallery_image_version = "" +azure_storage_type = "Standard_LRS" +machine_catalog_naming_scheme = "ctx-azure-##" + +# resource_pools.tf variables +resource_pool_name = "example-azure-resource-pool" +azure_region = "East US" +azure_vnet_resource_group = "" +azure_vnet = "" +azure_subnets = [""] + +# zones.tf variables +zone_name = "example-azure-zone" \ No newline at end of file diff --git a/examples/non_domain_joined_azure_mcs_vda/terraform.tf b/examples/non_domain_joined_azure_mcs_vda/terraform.tf new file mode 100644 index 0000000..6484546 --- /dev/null +++ b/examples/non_domain_joined_azure_mcs_vda/terraform.tf @@ -0,0 +1,12 @@ +terraform { + required_version = ">= 1.4.0" + + required_providers { + citrix = { + source = "citrix/citrix" + version = ">=0.6.1" + } + } + + backend "local" {} +} diff --git a/examples/non_domain_joined_azure_mcs_vda/variables.tf b/examples/non_domain_joined_azure_mcs_vda/variables.tf new file mode 100644 index 0000000..42b6091 --- /dev/null +++ b/examples/non_domain_joined_azure_mcs_vda/variables.tf @@ -0,0 +1,167 @@ +# citrix.tf variables +## Citrix Cloud customer provider settings +variable provider_customer_id { + description = "The customer id of the Citrix Cloud customer." + type = string + default = "" # Set your customer id for Citrix Cloud customer. Omit this variable for On-Premises customer. +} + +variable provider_environment { + description = "The environment of the Citrix Cloud customer." + type = string + default = "Production" # Use "Japan" for Citrix Cloud customers in Japan region. Omit this variable for On-Premises customer. +} + +# For Citrix Cloud customers: API key client id and secret are needed to interact with Citrix DaaS APIs. These can be created/found under Identity and Access Management > API Access +variable provider_client_id { + description = "The API key client id for Citrix Cloud customer." + type = string + default = "" +} + +variable provider_client_secret { + description = "The API key client secret for Citrix Cloud customer." + type = string + default = "" +} + + +# delivery_groups.tf variables +variable "delivery_group_name" { + description = "Name of the Delivery Group to create" + type = string + default = "example-delivery-group" +} + +variable "allow_list" { + description = "List of users to allow for the Delivery Group in DOMAIN\\username format" + type = list(string) + default = [] +} + + +# hypervisors.tf variables +variable "hypervisor_name" { + description = "Name of the Hypervisor to create" + type = string + default = "example-azure-hyperv" +} + +variable "azure_application_id" { + description = "Azure SPN client ID" + type = string +} + +variable "azure_application_secret" { + description = "Azure SPN client secret" + type = string + sensitive = true +} + +variable "azure_subscription_id" { + description = "Azure subscription ID" + type = string +} + +variable "azure_tenant_id" { + description = "Azure tenant ID" + type = string +} + + +# machine_catalogs.tf variables +variable "machine_catalog_name" { + description = "Name of the Machine Catalog to create" + type = string + default = "example-azure-catalog" +} + +variable "azure_service_offering" { + description = "Azure VM service offering SKU" + type = string + default = "Standard_D2_v2" +} + +variable "azure_storage_type" { + description = "Azure storage type" + type = string + default = "Standard_LRS" +} + +# variable "azure_image_subscription" { +# description = "Azure subscription ID for the image, not needed if image is in the same subscription as the hypervisor" +# type = string +# } + +# For Azure master image from managed disk or snapshot +variable "azure_resource_group" { + description = "Azure resource group containing the master image" + type = string +} + +variable "azure_master_image" { + description = "Name of the master image managed disk or snapshot" + type = string +} + +# For Azure image gallery +# variable "azure_gallery_name" { +# description = "Azure gallery image name" +# type = string +# } + +# variable "azure_gallery_image_definition" { +# description = "Azure gallery image definition" +# type = string +# } + +# variable "azure_gallery_image_version" { +# description = "Azure gallery image version" +# type = string +# default = "1.0.0" +# } + +variable "machine_catalog_naming_scheme" { + description = "Machine Catalog naming scheme" + type = string + default = "ctx-non-dj-##" +} + + +# policy_sets.tf has no variables + + +# resource_pools.tf variables +variable "resource_pool_name" { + description = "Name of the Resource Pool to create" + type = string + default = "example-azure-rp" +} + +variable "azure_region" { + description = "Azure region for the Resource Pool" + type = string + default = "East US" +} + +variable "azure_vnet_resource_group" { + description = "Name of the Azure virtual network resource group" + type = string +} + +variable "azure_vnet" { + description = "Name of the Azure virtual network" + type = string +} + +variable "azure_subnets" { + description = "List of Azure subnets" + type = list(string) +} + + +# zones.tf variables +variable "zone_name" { + description = "Name of the Zone to create. For Citrix Cloud customers the zone should already exist." + type = string +} diff --git a/examples/non_domain_joined_azure_mcs_vda/zones.tf b/examples/non_domain_joined_azure_mcs_vda/zones.tf new file mode 100644 index 0000000..f73a488 --- /dev/null +++ b/examples/non_domain_joined_azure_mcs_vda/zones.tf @@ -0,0 +1,3 @@ +resource "citrix_zone" "example-zone" { + name = var.zone_name +} diff --git a/go.mod b/go.mod index 9d786bb..8282b92 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,17 @@ go 1.21 toolchain go1.21.4 require ( - github.com/citrix/citrix-daas-rest-go v0.4.1 + github.com/citrix/citrix-daas-rest-go v0.4.2 github.com/google/uuid v1.6.0 github.com/hashicorp/go-azure-helpers v0.69.0 github.com/hashicorp/terraform-plugin-docs v0.14.1 - github.com/hashicorp/terraform-plugin-framework v1.8.0 + github.com/hashicorp/terraform-plugin-framework v1.9.0 github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 github.com/hashicorp/terraform-plugin-go v0.23.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-20240506185415-9bf2ced13842 - golang.org/x/mod v0.17.0 + golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 + golang.org/x/mod v0.18.0 ) require ( @@ -68,16 +68,16 @@ 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.14.4 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/net v0.25.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.21.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.22.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 // indirect google.golang.org/grpc v1.64.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect + google.golang.org/protobuf v1.34.2 // indirect ) // replace github.com/citrix/citrix-daas-rest-go => ../citrix-daas-rest-go diff --git a/go.sum b/go.sum index f51b990..4d51bea 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 v0.4.1 h1:5xxSzA2VpbanH7GV6bcOZ1qx60/fkeGO4veLtFRe3Co= -github.com/citrix/citrix-daas-rest-go v0.4.1/go.mod h1:wObnH2H4QP/nwoKR589SzQZ5dGTu3AoVi927NN/e77s= +github.com/citrix/citrix-daas-rest-go v0.4.2 h1:pZT1+U6hElHL3+1XmdAg0J8Y/okkaAS5Let6U6zC94g= +github.com/citrix/citrix-daas-rest-go v0.4.2/go.mod h1:UU9KYEGolBovkKtX1Kzec8bDTVfTym4njYgMjr3rDZI= 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= @@ -100,8 +100,8 @@ github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7 github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= 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.8.0 h1:P07qy8RKLcoBkCrY2RHJer5AEvJnDuXomBgou6fD8kI= -github.com/hashicorp/terraform-plugin-framework v1.8.0/go.mod h1:/CpTukO88PcL/62noU7cuyaSJ4Rsim+A/pa+3rUVufY= +github.com/hashicorp/terraform-plugin-framework v1.9.0 h1:caLcDoxiRucNi2hk8+j3kJwkKfvHznubyFsJMWfZqKU= +github.com/hashicorp/terraform-plugin-framework v1.9.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM= github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc= github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg= github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= @@ -213,20 +213,20 @@ 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.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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= @@ -246,8 +246,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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.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,26 +257,26 @@ 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.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 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.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= -golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 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-20240521202816-d264139d666e h1:Elxv5MwEkCI9f5SkoL6afed6NTdxaGoAo39eANBwHL8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 h1:9Xyg6I9IWQZhRVfCWjKK+l6kI0jHcPesVlMnT//aHNo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= 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.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/daas/admin_role/admin_role_resource.go b/internal/daas/admin_role/admin_role_resource.go index 06d4476..e743178 100644 --- a/internal/daas/admin_role/admin_role_resource.go +++ b/internal/daas/admin_role/admin_role_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package admin_role @@ -10,16 +10,9 @@ import ( citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" "github.com/citrix/terraform-provider-citrix/internal/util" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "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/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/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" ) // Ensure the implementation satisfies the expected interfaces. @@ -48,52 +41,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 = schema.Schema{ - // This description is used by the documentation generator and the language server. - Description: "Manages an administrator role.", - - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "ID of the admin role.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the admin role.", - Required: true, - }, - "description": schema.StringAttribute{ - Description: "Description of the admin role.", - Optional: 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. This field is only applicable for cloud admins. For on-premise admins, the only acceptable value is `true`. Defaults to `true`.", - Optional: true, - Computed: true, - Default: booldefault.StaticBool(true), // Default value gets set for an attribute after Validation and before applying configuration changes - }, - "can_launch_monitor": schema.BoolAttribute{ - Description: "Flag to determine if the user will have access to the Monitor tab on the console. This field is only applicable for cloud admins. For on-premise admins, the only acceptable value is `true`. Defaults to `true`.", - Optional: true, - Computed: true, - Default: booldefault.StaticBool(true), - }, - "permissions": schema.ListAttribute{ - ElementType: types.StringType, - Description: "List of permissions to be associated with the admin role. 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).", - Required: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - }, - } + resp.Schema = GetAdminRoleSchema() } // Configure adds the provider configured client to the resource. @@ -123,7 +71,7 @@ func (r *adminRoleResource) Create(ctx context.Context, req resource.CreateReque body.SetDescription(plan.Description.ValueString()) body.SetCanLaunchManage(plan.CanLaunchManage.ValueBool()) body.SetCanLaunchMonitor(plan.CanLaunchMonitor.ValueBool()) - body.SetPermissions(util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Permissions)) + body.SetPermissions(util.StringSetToStringArray(ctx, &diags, plan.Permissions)) createAdminRoleRequest := r.client.ApiClient.AdminAPIsDAAS.AdminCreateAdminRole(ctx) createAdminRoleRequest = createAdminRoleRequest.CreateAdminRoleRequestModel(body) @@ -156,7 +104,7 @@ func (r *adminRoleResource) Create(ctx context.Context, req resource.CreateReque } // Map response body to schema and populate computed attribute values - plan = plan.RefreshPropertyValues(adminRole) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, adminRole) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -184,7 +132,7 @@ func (r *adminRoleResource) Read(ctx context.Context, req resource.ReadRequest, return } - state = state.RefreshPropertyValues(adminRole) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, adminRole) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -215,7 +163,7 @@ func (r *adminRoleResource) Update(ctx context.Context, req resource.UpdateReque body.SetDescription(plan.Description.ValueString()) body.SetCanLaunchManage(plan.CanLaunchManage.ValueBool()) body.SetCanLaunchMonitor(plan.CanLaunchMonitor.ValueBool()) - body.SetPermissions(util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Permissions)) + body.SetPermissions(util.StringSetToStringArray(ctx, &diags, plan.Permissions)) // Update admin role using orchestration call updateAdminRoleRequest := r.client.ApiClient.AdminAPIsDAAS.AdminUpdateAdminRole(ctx, adminRoleId) @@ -237,7 +185,7 @@ func (r *adminRoleResource) Update(ctx context.Context, req resource.UpdateReque } // Update resource state with updated property values - plan = plan.RefreshPropertyValues(updatedAdminRole) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, updatedAdminRole) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) diff --git a/internal/daas/admin_role/admin_role_resource_model.go b/internal/daas/admin_role/admin_role_resource_model.go index 1b19a16..38ffbe5 100644 --- a/internal/daas/admin_role/admin_role_resource_model.go +++ b/internal/daas/admin_role/admin_role_resource_model.go @@ -1,33 +1,41 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package admin_role import ( + "context" + citrixorchestration "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/diag" + "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" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) // AdminRoleResourceModel maps the resource schema data. type AdminRoleResourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - IsBuiltIn types.Bool `tfsdk:"is_built_in"` - Description types.String `tfsdk:"description"` - CanLaunchManage types.Bool `tfsdk:"can_launch_manage"` - CanLaunchMonitor types.Bool `tfsdk:"can_launch_monitor"` - Permissions []types.String `tfsdk:"permissions"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + IsBuiltIn types.Bool `tfsdk:"is_built_in"` + Description types.String `tfsdk:"description"` + CanLaunchManage types.Bool `tfsdk:"can_launch_manage"` + CanLaunchMonitor types.Bool `tfsdk:"can_launch_monitor"` + Permissions types.Set `tfsdk:"permissions"` //Set[string] } -func (r AdminRoleResourceModel) RefreshPropertyValues(adminRole *citrixorchestration.RoleResponseModel) AdminRoleResourceModel { +func (r AdminRoleResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, adminRole *citrixorchestration.RoleResponseModel) AdminRoleResourceModel { // Overwrite admin role with refreshed state r.Id = types.StringValue(adminRole.GetId()) r.Name = types.StringValue(adminRole.GetName()) - if adminRole.GetDescription() != "" || !r.Description.IsNull() { - r.Description = types.StringValue(adminRole.GetDescription()) - } + r.Description = types.StringValue(adminRole.GetDescription()) r.IsBuiltIn = types.BoolValue(adminRole.GetIsBuiltIn()) r.CanLaunchManage = types.BoolValue(adminRole.GetCanLaunchManage()) r.CanLaunchMonitor = types.BoolValue(adminRole.GetCanLaunchMonitor()) @@ -36,7 +44,58 @@ func (r AdminRoleResourceModel) RefreshPropertyValues(adminRole *citrixorchestra for _, permission := range adminRole.GetPermissions() { permissionListFromRemote = append(permissionListFromRemote, permission.GetId()) } - r.Permissions = util.RefreshList(r.Permissions, permissionListFromRemote) + r.Permissions = util.StringArrayToStringSet(ctx, diagnostics, permissionListFromRemote) return r } + +func GetAdminRoleSchema() schema.Schema { + return schema.Schema{ + // This description is used by the documentation generator and the language server. + Description: "Manages an administrator role.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "ID of the admin role.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the admin role.", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the admin role.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "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. This field is only applicable for cloud admins. For on-premise admins, the only acceptable value is `true`. Defaults to `true`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), // Default value gets set for an attribute after Validation and before applying configuration changes + }, + "can_launch_monitor": schema.BoolAttribute{ + Description: "Flag to determine if the user will have access to the Monitor tab on the console. This field is only applicable for cloud admins. For on-premise admins, the only acceptable value is `true`. Defaults to `true`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "permissions": schema.SetAttribute{ + ElementType: types.StringType, + Description: "Permissions to be associated with the admin role. 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).", + Required: true, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + }, + }, + }, + } +} diff --git a/internal/daas/admin_scope/admin_scope_data_source.go b/internal/daas/admin_scope/admin_scope_data_source.go index b309308..964f8ae 100644 --- a/internal/daas/admin_scope/admin_scope_data_source.go +++ b/internal/daas/admin_scope/admin_scope_data_source.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package admin_scope @@ -8,11 +8,7 @@ import ( 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/path" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) var ( @@ -32,48 +28,7 @@ func (d *AdminScopeDataSource) Metadata(_ context.Context, req datasource.Metada } func (d *AdminScopeDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = schema.Schema{ - // This description is used by the documentation generator and the language server. - MarkdownDescription: "Data source to get details regarding a specific Administrator scope.", - - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - MarkdownDescription: "ID of the Admin Scope.", - Optional: true, - Validators: []validator.String{ - 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. - }, - }, - "name": schema.StringAttribute{ - MarkdownDescription: "Name of the Admin Scope.", - Optional: true, - }, - "description": schema.StringAttribute{ - MarkdownDescription: "Description of the Admin Scope.", - Computed: true, - }, - "is_built_in": schema.BoolAttribute{ - MarkdownDescription: "Indicates whether the Admin Scope is built-in or not.", - Computed: true, - }, - "is_all_scope": schema.BoolAttribute{ - MarkdownDescription: "Indicates whether the Admin Scope is all scope or not.", - Computed: true, - }, - "is_tenant_scope": schema.BoolAttribute{ - MarkdownDescription: "Indicates whether the Admin Scope is tenant scope or not.", - Computed: true, - }, - "tenant_id": schema.StringAttribute{ - MarkdownDescription: "ID of the tenant to which the Admin Scope belongs.", - Computed: true, - }, - "tenant_name": schema.StringAttribute{ - MarkdownDescription: "Name of the tenant to which the Admin Scope belongs.", - Computed: true, - }, - }, - } + resp.Schema = GetAdminScopeDataSourceSchema() } func (d *AdminScopeDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { @@ -118,7 +73,7 @@ func (d *AdminScopeDataSource) Read(ctx context.Context, req datasource.ReadRequ ) } - data = data.RefreshPropertyValues(adminScope) + data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, adminScope) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) 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 0c23b5a..7faa652 100644 --- a/internal/daas/admin_scope/admin_scope_data_source_model.go +++ b/internal/daas/admin_scope/admin_scope_data_source_model.go @@ -1,9 +1,16 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. 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" ) @@ -19,7 +26,7 @@ type AdminScopeDataSourceModel struct { TenantName types.String `tfsdk:"tenant_name"` } -func (r AdminScopeDataSourceModel) RefreshPropertyValues(adminScope *citrixorchestration.ScopeResponseModel) AdminScopeDataSourceModel { +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()) @@ -32,3 +39,49 @@ func (r AdminScopeDataSourceModel) RefreshPropertyValues(adminScope *citrixorche return r } + +func GetAdminScopeDataSourceSchema() schema.Schema { + return schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "Data source to get details regarding a specific Administrator scope.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "ID of the Admin Scope.", + Optional: true, + Validators: []validator.String{ + 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. + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the Admin Scope.", + Optional: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: "Description of the Admin Scope.", + Computed: true, + }, + "is_built_in": schema.BoolAttribute{ + MarkdownDescription: "Indicates whether the Admin Scope is built-in or not.", + Computed: true, + }, + "is_all_scope": schema.BoolAttribute{ + MarkdownDescription: "Indicates whether the Admin Scope is all scope or not.", + Computed: true, + }, + "is_tenant_scope": schema.BoolAttribute{ + MarkdownDescription: "Indicates whether the Admin Scope is tenant scope or not.", + Computed: true, + }, + "tenant_id": schema.StringAttribute{ + MarkdownDescription: "ID of the tenant to which the Admin Scope belongs.", + Computed: true, + }, + "tenant_name": schema.StringAttribute{ + MarkdownDescription: "Name of the tenant to which the Admin Scope belongs.", + Computed: true, + }, + }, + } + +} diff --git a/internal/daas/admin_scope/admin_scope_resource.go b/internal/daas/admin_scope/admin_scope_resource.go index 2cadcf7..91b9af7 100644 --- a/internal/daas/admin_scope/admin_scope_resource.go +++ b/internal/daas/admin_scope/admin_scope_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package admin_scope @@ -10,15 +10,9 @@ import ( citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" "github.com/citrix/terraform-provider-citrix/internal/util" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/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" ) // Ensure the implementation satisfies the expected interfaces. @@ -45,56 +39,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 = schema.Schema{ - // This description is used by the documentation generator and the language server. - Description: "Manages an administrator scope.", - - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "ID of the admin scope.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the admin scope.", - Required: true, - }, - "description": schema.StringAttribute{ - Description: "Description of the admin scope.", - Optional: true, - }, - "scoped_objects": schema.ListNestedAttribute{ - Description: "List of scoped objects to be associated with the admin scope.", - Optional: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "object_type": schema.StringAttribute{ - Description: "Type of the scoped object. Allowed values are: `HypervisorConnection`, `MachineCatalog`, `DeliveryGroup`, `ApplicationGroup`, `Tag`, `PolicySet` and `Unknown`.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf("Unknown", - "HypervisorConnection", - "MachineCatalog", - "DeliveryGroup", - "ApplicationGroup", - "Tag", - "PolicySet"), - }, - }, - "object": schema.StringAttribute{ - Description: "Name of an existing object under the object type to be added to the scope.", - Required: true, - }, - }, - }, - }, - }, - } + resp.Schema = GetAdminScopeSchema() } // Configure adds the provider configured client to the resource. @@ -118,20 +63,10 @@ func (r *adminScopeResource) Create(ctx context.Context, req resource.CreateRequ return } - var scopedObjectsRequestModel, errorMsg = getScopedObjectsRequestModel(plan.ScopedObjects) - if errorMsg != "" { - resp.Diagnostics.AddError( - "Error creating Admin Scope", - errorMsg, - ) - return - } - // Generate API request body from plan var body citrixorchestration.CreateAdminScopeRequestModel body.SetName(plan.Name.ValueString()) body.SetDescription(plan.Description.ValueString()) - body.SetScopedObjects(scopedObjectsRequestModel) createAdminScopeRequest := r.client.ApiClient.AdminAPIsDAAS.AdminCreateAdminScope(ctx) createAdminScopeRequest = createAdminScopeRequest.CreateAdminScopeRequestModel(body) @@ -153,14 +88,8 @@ func (r *adminScopeResource) Create(ctx context.Context, req resource.CreateRequ return } - // Get the scoped objects for the admin scope - scopedObjects, err := getScopedObjects(ctx, r.client, &resp.Diagnostics, plan.Name.ValueString()) - if err != nil { - return - } - // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(adminScope, scopedObjects) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, adminScope) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -188,13 +117,7 @@ func (r *adminScopeResource) Read(ctx context.Context, req resource.ReadRequest, return } - // Get the scoped objects for the admin scope - scopedObjects, err := getScopedObjects(ctx, r.client, &resp.Diagnostics, state.Id.ValueString()) - if err != nil { - return - } - - state = state.RefreshPropertyValues(adminScope, scopedObjects) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, adminScope) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -216,15 +139,6 @@ func (r *adminScopeResource) Update(ctx context.Context, req resource.UpdateRequ return } - var scopedObjectsRequestModel, errorMsg = getScopedObjectsRequestModel(plan.ScopedObjects) - if errorMsg != "" { - resp.Diagnostics.AddError( - "Error creating Admin Scope", - errorMsg, - ) - return - } - var adminScopeId = plan.Id.ValueString() var adminScopeName = plan.Name.ValueString() @@ -232,7 +146,6 @@ func (r *adminScopeResource) Update(ctx context.Context, req resource.UpdateRequ var body citrixorchestration.EditAdminScopeRequestModel body.SetName(plan.Name.ValueString()) body.SetDescription(plan.Description.ValueString()) - body.SetScopedObjects(scopedObjectsRequestModel) // Update admin scope using orchestration call updateAdminScopeRequest := r.client.ApiClient.AdminAPIsDAAS.AdminUpdateAdminScope(ctx, adminScopeId) @@ -253,14 +166,8 @@ func (r *adminScopeResource) Update(ctx context.Context, req resource.UpdateRequ return } - // Get the scoped objects for the admin scope - updatedScopedObjects, err := getScopedObjects(ctx, r.client, &resp.Diagnostics, adminScopeId) - if err != nil { - return - } - // Update resource state with updated property values - plan = plan.RefreshPropertyValues(updatedAdminScope, updatedScopedObjects) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, updatedAdminScope) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) @@ -301,23 +208,6 @@ func (r *adminScopeResource) ImportState(ctx context.Context, req resource.Impor resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } -func getScopedObjectsRequestModel(scopedObjects []ScopedObjectsModel) ([]citrixorchestration.ScopedObjectRequestModel, string) { - scopedObjectsRequestModel := []citrixorchestration.ScopedObjectRequestModel{} - - // No nil check required. For-range will perform 0 iterations - for _, scopedObject := range scopedObjects { - var scopedObjectType, err = citrixorchestration.NewScopedObjectTypeFromValue(scopedObject.ObjectType.ValueString()) - if err != nil { - return nil, "Unsupported object type for scoped object." - } - scopedObjectsRequestModel = append(scopedObjectsRequestModel, citrixorchestration.ScopedObjectRequestModel{ - ObjectType: *scopedObjectType, - Object: scopedObject.Object.ValueString(), - }) - } - return scopedObjectsRequestModel, "" -} - func getAdminScope(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, adminScopeName string) (*citrixorchestration.ScopeResponseModel, error) { getAdminScopeRequest := client.ApiClient.AdminAPIsDAAS.AdminGetAdminScope(ctx, adminScopeName) adminScope, httpResp, err := citrixdaasclient.ExecuteWithRetry[*citrixorchestration.ScopeResponseModel](getAdminScopeRequest, client) @@ -337,35 +227,3 @@ func readAdminScope(ctx context.Context, client *citrixdaasclient.CitrixDaasClie adminScope, _, err := util.ReadResource[*citrixorchestration.ScopeResponseModel](getAdminScopeRequest, ctx, client, resp, "Admin Scope", adminScopeName) return adminScope, err } - -func getScopedObjects(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, adminScopeName string) ([]citrixorchestration.ScopedObjectResponseModel, error) { - getScopedObjectRequest := client.ApiClient.AdminAPIsDAAS.AdminGetAdminScopedObjects(ctx, adminScopeName) - - var scopedObjects []citrixorchestration.ScopedObjectResponseModel - scopedObjectsResponse, httpResp, err := citrixdaasclient.AddRequestData(getScopedObjectRequest, client).Execute() - - if err != nil { - diagnostics.AddError( - "Error reading Admin Scope: "+adminScopeName, - "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ - "\nError message: "+util.ReadClientError(err), - ) - } - - scopedObjects = scopedObjectsResponse.GetItems() - - for scopedObjectsResponse.GetContinuationToken() != "" { - getScopedObjectRequest = getScopedObjectRequest.ContinuationToken(scopedObjectsResponse.GetContinuationToken()) - scopedObjectsResponse, httpResp, err = citrixdaasclient.AddRequestData(getScopedObjectRequest, client).Execute() - if err != nil { - diagnostics.AddError( - "Error reading Admin Scope: "+adminScopeName, - "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ - "\nError message: "+util.ReadClientError(err), - ) - } - scopedObjects = append(scopedObjects, scopedObjectsResponse.GetItems()...) - } - - return scopedObjects, err -} diff --git a/internal/daas/admin_scope/admin_scope_resource_model.go b/internal/daas/admin_scope/admin_scope_resource_model.go index a6f52e1..d1360f0 100644 --- a/internal/daas/admin_scope/admin_scope_resource_model.go +++ b/internal/daas/admin_scope/admin_scope_resource_model.go @@ -1,84 +1,59 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package admin_scope import ( - "reflect" + "context" citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" + "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/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" ) // AdminScopeResourceModel maps the resource schema data. type AdminScopeResourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - ScopedObjects []ScopedObjectsModel `tfsdk:"scoped_objects"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` } -type ScopedObjectsModel struct { - ObjectType types.String `tfsdk:"object_type"` - Object types.String `tfsdk:"object"` -} - -func (r AdminScopeResourceModel) RefreshPropertyValues(adminScope *citrixorchestration.ScopeResponseModel, scopedObjects []citrixorchestration.ScopedObjectResponseModel) AdminScopeResourceModel { +func (r AdminScopeResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, adminScope *citrixorchestration.ScopeResponseModel) AdminScopeResourceModel { // Overwrite admin scope with refreshed state r.Id = types.StringValue(adminScope.GetId()) r.Name = types.StringValue(adminScope.GetName()) - r.ScopedObjects = r.refreshScopedObjects(scopedObjects) - - if adminScope.GetDescription() != "" || !r.Description.IsNull() { - r.Description = types.StringValue(adminScope.GetDescription()) - } + r.Description = types.StringValue(adminScope.GetDescription()) return r } -func (r AdminScopeResourceModel) refreshScopedObjects(scopedObjectsFromRemote []citrixorchestration.ScopedObjectResponseModel) []ScopedObjectsModel { - - type RemoteScopedObjectTracker struct { - ObjectType types.String - IsVisited bool - } - - // Create a map of ObjectName -> RemoteScopedObjectTracker from the scoped objects returned from remote - scopedObjectMapFromRemote := map[string]*RemoteScopedObjectTracker{} - for _, scopedObjectFromRemote := range scopedObjectsFromRemote { - scopedObjectMapFromRemote[scopedObjectFromRemote.Object.GetName()] = &RemoteScopedObjectTracker{ - ObjectType: types.StringValue(reflect.ValueOf(scopedObjectFromRemote.GetObjectType()).String()), - IsVisited: false, - } +func GetAdminScopeSchema() schema.Schema { + return schema.Schema{ + // This description is used by the documentation generator and the language server. + Description: "Manages an administrator scope.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "ID of the admin scope.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the admin scope.", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the admin scope.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + }, } - - // Prepare the scoped objects list to be stored in the state - var scopedObjectsForState []ScopedObjectsModel - for _, scopedObject := range r.ScopedObjects { - scopedObjectFromRemote, exists := scopedObjectMapFromRemote[scopedObject.Object.ValueString()] - if !exists { - // If scoped object is not present in the remote, then don't add it to the state - continue - } - - scopedObjectsForState = append(scopedObjectsForState, ScopedObjectsModel{ - ObjectType: scopedObjectFromRemote.ObjectType, - Object: scopedObject.Object, - }) - - scopedObjectMapFromRemote[scopedObject.Object.ValueString()].IsVisited = true - } - - // Add all the scoped objects from remote which are not present in the state - for scopedObjectName, scopedObjectType := range scopedObjectMapFromRemote { - if !scopedObjectType.IsVisited { - scopedObjectsForState = append(scopedObjectsForState, ScopedObjectsModel{ - ObjectType: scopedObjectType.ObjectType, - Object: types.StringValue(scopedObjectName), - }) - } - } - - return scopedObjectsForState } diff --git a/internal/daas/admin_user/admin_user_resource.go b/internal/daas/admin_user/admin_user_resource.go index 47fd962..5a4ece2 100644 --- a/internal/daas/admin_user/admin_user_resource.go +++ b/internal/daas/admin_user/admin_user_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package admin_user @@ -66,7 +66,7 @@ func (r *adminUserResource) Schema(_ context.Context, _ resource.SchemaRequest, Required: true, }, "rights": schema.ListNestedAttribute{ - Description: "List of rights to be associated with the admin user.", + Description: "Rights to be associated with the admin user.", Required: true, Validators: []validator.List{ listvalidator.SizeAtLeast(1), @@ -114,7 +114,7 @@ func (r *adminUserResource) Create(ctx context.Context, req resource.CreateReque } var adminRights []citrixorchestration.AdminRightRequestModel - for _, right := range plan.Rights { + for _, right := range util.ObjectListToTypedArray[RightsModel](ctx, &resp.Diagnostics, plan.Rights) { adminRights = append(adminRights, citrixorchestration.AdminRightRequestModel{ Role: right.Role.ValueString(), Scope: right.Scope.ValueString(), @@ -149,7 +149,7 @@ func (r *adminUserResource) Create(ctx context.Context, req resource.CreateReque } // Map response body to schema and populate computed attribute values - plan = plan.RefreshPropertyValues(adminUser) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, adminUser) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -177,7 +177,7 @@ func (r *adminUserResource) Read(ctx context.Context, req resource.ReadRequest, return } - state = state.RefreshPropertyValues(adminUser) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, adminUser) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -203,7 +203,7 @@ func (r *adminUserResource) Update(ctx context.Context, req resource.UpdateReque var adminUserName = plan.Name.ValueString() var adminRights []citrixorchestration.AdminRightRequestModel - for _, right := range plan.Rights { + for _, right := range util.ObjectListToTypedArray[RightsModel](ctx, &resp.Diagnostics, plan.Rights) { adminRights = append(adminRights, citrixorchestration.AdminRightRequestModel{ Role: right.Role.ValueString(), Scope: right.Scope.ValueString(), @@ -236,7 +236,7 @@ func (r *adminUserResource) Update(ctx context.Context, req resource.UpdateReque } // Update resource state with updated property values - plan = plan.RefreshPropertyValues(updatedAdminUser) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, 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 005391e..f520a6a 100644 --- a/internal/daas/admin_user/admin_user_resource_model.go +++ b/internal/daas/admin_user/admin_user_resource_model.go @@ -1,22 +1,30 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package admin_user import ( + "context" "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/listvalidator" + "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" ) // AdminUserResourceModel maps the resource schema data. type AdminUserResourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - DomainName types.String `tfsdk:"domain_name"` - Rights []RightsModel `tfsdk:"rights"` - IsEnabled types.Bool `tfsdk:"is_enabled"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + DomainName types.String `tfsdk:"domain_name"` + Rights types.List `tfsdk:"rights"` //List[RightsModel] + IsEnabled types.Bool `tfsdk:"is_enabled"` } type RightsModel struct { @@ -24,7 +32,7 @@ type RightsModel struct { Scope types.String `tfsdk:"scope"` } -func (r AdminUserResourceModel) RefreshPropertyValues(adminUser *citrixorchestration.AdministratorResponseModel) AdminUserResourceModel { +func (r AdminUserResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, adminUser *citrixorchestration.AdministratorResponseModel) AdminUserResourceModel { // Overwrite admin user data with refreshed state userDetails := adminUser.GetUser() userFQDN := strings.Split(userDetails.GetSamName(), "\\") @@ -32,13 +40,32 @@ func (r AdminUserResourceModel) RefreshPropertyValues(adminUser *citrixorchestra r.Id = types.StringValue(userDetails.GetSid()) r.Name = types.StringValue(userFQDN[len(userFQDN)-1]) r.DomainName = types.StringValue(userDetails.GetDomain()) - r.Rights = r.refreshRights(adminUser.GetScopesAndRoles()) + r.Rights = util.TypedArrayToObjectList[RightsModel](ctx, diagnostics, r.refreshRights(ctx, diagnostics, adminUser.GetScopesAndRoles())) r.IsEnabled = types.BoolValue(adminUser.GetEnabled()) return r } -func (r AdminUserResourceModel) refreshRights(rightsFromRemote []citrixorchestration.AdministratorRightResponseModel) []RightsModel { +func (RightsModel) GetSchema() 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.", + Required: true, + }, + "scope": schema.StringAttribute{ + Description: "Name of the scope to be associated with the admin user.", + Required: true, + }, + }, + } +} + +func (RightsModel) GetAttributes() map[string]schema.Attribute { + return RightsModel{}.GetSchema().Attributes +} + +func (r AdminUserResourceModel) refreshRights(ctx context.Context, diagnostics *diag.Diagnostics, rightsFromRemote []citrixorchestration.AdministratorRightResponseModel) []RightsModel { type RemoteRightsTracker struct { RoleName types.String @@ -61,7 +88,7 @@ func (r AdminUserResourceModel) refreshRights(rightsFromRemote []citrixorchestra // Prepare the rights list to be stored in the state var rightsForState []RightsModel - for _, right := range r.Rights { + for _, right := range util.ObjectListToTypedArray[RightsModel](ctx, diagnostics, r.Rights) { rightFromRemote, exists := rightsMapFromRemote[strings.ToLower(right.Role.ValueString()+right.Scope.ValueString())] if !exists { // If right is not present in the remote, then don't add it to the state @@ -87,3 +114,40 @@ func (r AdminUserResourceModel) refreshRights(rightsFromRemote []citrixorchestra return rightsForState } + +func GetAdminUserSchema() schema.Schema { + return schema.Schema{ + // This description is used by the documentation generator and the language server. + Description: "Manages an administrator user for on-premise environment.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "ID of the admin user.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of an existing user in the active directory.", + Required: 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.", + Required: true, + }, + "rights": schema.ListNestedAttribute{ + Description: "Rights to be associated with the admin user.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + NestedObject: RightsModel{}.GetSchema(), + }, + "is_enabled": schema.BoolAttribute{ + Description: "Flag to determine if the administrator is to be enabled or not.", + Optional: true, + }, + }, + } +} diff --git a/internal/daas/application/application_folder_details_data_source.go b/internal/daas/application/application_folder_details_data_source.go index 7a5696b..6843341 100644 --- a/internal/daas/application/application_folder_details_data_source.go +++ b/internal/daas/application/application_folder_details_data_source.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package application @@ -77,7 +77,7 @@ func (d *ApplicationDataSource) Schema(ctx context.Context, req datasource.Schem }, }, }, - "delivery_groups": schema.ListAttribute{ + "delivery_groups": schema.SetAttribute{ ElementType: types.StringType, MarkdownDescription: "The delivery groups which the application is associated with.", Computed: true, @@ -128,7 +128,7 @@ func (d *ApplicationDataSource) Read(ctx context.Context, req datasource.ReadReq ) return // Stop processing } - data = data.RefreshPropertyValues(apps) + data = data.RefreshPropertyValues(ctx, &resp.Diagnostics, apps) } // Save data into Terraform state diff --git a/internal/daas/application/application_folder_details_data_source_model.go b/internal/daas/application/application_folder_details_data_source_model.go index a5ecfa1..31c4915 100644 --- a/internal/daas/application/application_folder_details_data_source_model.go +++ b/internal/daas/application/application_folder_details_data_source_model.go @@ -1,9 +1,13 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package application import ( + "context" + "github.com/citrix/citrix-daas-rest-go/citrixorchestration" + "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -13,7 +17,7 @@ type ApplicationFolderDetailsDataSourceModel struct { ApplicationsList []ApplicationResourceModel `tfsdk:"applications_list"` } -func (r ApplicationFolderDetailsDataSourceModel) RefreshPropertyValues(apps *citrixorchestration.ApplicationResponseModelCollection) ApplicationFolderDetailsDataSourceModel { +func (r ApplicationFolderDetailsDataSourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, apps *citrixorchestration.ApplicationResponseModelCollection) ApplicationFolderDetailsDataSourceModel { var res []ApplicationResourceModel for _, app := range apps.GetItems() { @@ -22,8 +26,8 @@ func (r ApplicationFolderDetailsDataSourceModel) RefreshPropertyValues(apps *cit PublishedName: types.StringValue(app.GetPublishedName()), Description: types.StringValue(app.GetDescription()), ApplicationFolderPath: types.StringValue(*app.GetApplicationFolder().Name.Get()), - InstalledAppProperties: r.getInstalledAppProperties(app), // Fix: Change the type to *InstalledAppResponseModel - DeliveryGroups: r.getDeliveryGroups(app), + InstalledAppProperties: r.getInstalledAppProperties(ctx, diagnostics, app), + DeliveryGroups: r.getDeliveryGroups(ctx, diagnostics, app), }) } @@ -32,18 +36,15 @@ func (r ApplicationFolderDetailsDataSourceModel) RefreshPropertyValues(apps *cit return r } -func (r ApplicationFolderDetailsDataSourceModel) getInstalledAppProperties(app citrixorchestration.ApplicationResponseModel) *InstalledAppResponseModel { - return &InstalledAppResponseModel{ +func (r ApplicationFolderDetailsDataSourceModel) getInstalledAppProperties(ctx context.Context, diagnostics *diag.Diagnostics, app citrixorchestration.ApplicationResponseModel) types.Object { + var installedAppResponse = InstalledAppResponseModel{ CommandLineArguments: types.StringValue(app.GetInstalledAppProperties().CommandLineArguments), CommandLineExecutable: types.StringValue(app.GetInstalledAppProperties().CommandLineExecutable), WorkingDirectory: types.StringValue(app.GetInstalledAppProperties().WorkingDirectory), } + return util.TypedObjectToObjectValue(ctx, diagnostics, installedAppResponse) } -func (r ApplicationFolderDetailsDataSourceModel) getDeliveryGroups(app citrixorchestration.ApplicationResponseModel) []types.String { - var res []types.String - for _, dg := range app.AssociatedDeliveryGroupUuids { - res = append(res, types.StringValue(dg)) - } - return res +func (r ApplicationFolderDetailsDataSourceModel) getDeliveryGroups(ctx context.Context, diagnostics *diag.Diagnostics, app citrixorchestration.ApplicationResponseModel) types.Set { + return util.StringArrayToStringSet(ctx, diagnostics, app.AssociatedDeliveryGroupUuids) } diff --git a/internal/daas/application/application_folder_resource.go b/internal/daas/application/application_folder_resource.go index e9e34f0..4c845e1 100644 --- a/internal/daas/application/application_folder_resource.go +++ b/internal/daas/application/application_folder_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package application diff --git a/internal/daas/application/application_folder_resource_model.go b/internal/daas/application/application_folder_resource_model.go index 7313fd5..1b49d51 100644 --- a/internal/daas/application/application_folder_resource_model.go +++ b/internal/daas/application/application_folder_resource_model.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package application diff --git a/internal/daas/application/application_group_resource.go b/internal/daas/application/application_group_resource.go new file mode 100644 index 0000000..6f6864d --- /dev/null +++ b/internal/daas/application/application_group_resource.go @@ -0,0 +1,318 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package application + +import ( + "context" + "net/http" + + 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/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &applicationGroupResource{} + _ resource.ResourceWithConfigure = &applicationGroupResource{} + _ resource.ResourceWithImportState = &applicationGroupResource{} +) + +// NewApplicationGroupResource is a helper function to simplify the provider implementation. +func NewApplicationGroupResource() resource.Resource { + return &applicationGroupResource{} +} + +// applicationResource is the resource implementation. +type applicationGroupResource struct { + client *citrixdaasclient.CitrixDaasClient +} + +// Metadata returns the data source type name. +func (r *applicationGroupResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_application_group" +} + +// Configure adds the provider configured client to the data source. +func (r *applicationGroupResource) 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 *applicationGroupResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = GetApplicationGroupSchema() +} + +// Create creates the resource and sets the initial Terraform state. +func (r *applicationGroupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + // Retrieve values from plan + var plan ApplicationGroupResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var createApplicationGroupRequest citrixorchestration.CreateApplicationGroupRequestModel + createApplicationGroupRequest.SetName(plan.Name.ValueString()) + createApplicationGroupRequest.SetDescription(plan.Description.ValueString()) + createApplicationGroupRequest.SetRestrictToTag(plan.RestrictToTag.ValueString()) + + if !plan.IncludedUsers.IsNull() { + createApplicationGroupRequest.SetIncludedUserFilterEnabled(true) + includedUsers := util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.IncludedUsers) + includedUserIds, httpResp, err := util.GetUserIdsUsingIdentity(ctx, r.client, includedUsers) + if err != nil { + diags.AddError( + "Error fetching user details for application group", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return + } + createApplicationGroupRequest.SetIncludedUsers(includedUserIds) + } else { + createApplicationGroupRequest.SetIncludedUserFilterEnabled(false) + } + + if !plan.Scopes.IsNull() { + createApplicationGroupRequest.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } + + var deliveryGroups []citrixorchestration.PriorityRefRequestModel + for _, value := range util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.DeliveryGroups) { + var deliveryGroupRequestModel citrixorchestration.PriorityRefRequestModel + deliveryGroupRequestModel.SetItem(value) + deliveryGroups = append(deliveryGroups, deliveryGroupRequestModel) + } + + createApplicationGroupRequest.SetDeliveryGroups(deliveryGroups) + + addApplicationsGroupRequest := r.client.ApiClient.ApplicationGroupsAPIsDAAS.ApplicationGroupsCreateApplicationGroup(ctx) + addApplicationsGroupRequest = addApplicationsGroupRequest.CreateApplicationGroupRequestModel(createApplicationGroupRequest) + + // Create new application group + addAppGroupResp, httpResp, err := citrixdaasclient.AddRequestData(addApplicationsGroupRequest, r.client).Execute() + if err != nil { + resp.Diagnostics.AddError( + "Error creating Application", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return + } + // Map response body to schema and populate Computed attribute values + + //Create AppGroup response does not return delivery groups so we are making another call to fetch delivery groups + dgs, err := getDeliveryGroups(ctx, r.client, &resp.Diagnostics, addAppGroupResp.GetId()) + if err != nil { + return + } + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, addAppGroupResp, dgs) + // 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 *applicationGroupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + // Get current state + var state ApplicationGroupResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Get refreshed application properties from Orchestration + applicationGroup, err := readApplicationGroup(ctx, r.client, resp, state.Id.ValueString()) + if err != nil { + return + } + + //AppGroup response does not return delivery groups so we are making another call to fetch delivery groups + dgs, err := getDeliveryGroups(ctx, r.client, &resp.Diagnostics, applicationGroup.GetId()) + if err != nil { + return + } + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, applicationGroup, dgs) + + // 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 *applicationGroupResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + // Retrieve values from plan + var plan ApplicationGroupResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Get refreshed application properties from Orchestration + applicationGroupId := plan.Id.ValueString() + applicationGroupName := plan.Name.ValueString() + + _, err := getApplicationGroup(ctx, r.client, &resp.Diagnostics, applicationGroupId) + if err != nil { + return + } + + // Construct the update model + var editApplicationGroupRequestBody = &citrixorchestration.EditApplicationGroupRequestModel{} + editApplicationGroupRequestBody.SetName(plan.Name.ValueString()) + editApplicationGroupRequestBody.SetDescription(plan.Description.ValueString()) + + editApplicationGroupRequestBody.SetRestrictToTag(plan.RestrictToTag.ValueString()) + if !plan.IncludedUsers.IsNull() { + editApplicationGroupRequestBody.SetIncludedUserFilterEnabled(true) + includedUsers := util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.IncludedUsers) + includedUserIds, httpResp, err := util.GetUserIdsUsingIdentity(ctx, r.client, includedUsers) + if err != nil { + diags.AddError( + "Error fetching user details for application group", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return + } + editApplicationGroupRequestBody.SetIncludedUsers(includedUserIds) + } else { + editApplicationGroupRequestBody.SetIncludedUserFilterEnabled(false) + editApplicationGroupRequestBody.SetIncludedUsers([]string{}) + } + + if !plan.Scopes.IsNull() { + editApplicationGroupRequestBody.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } + + var deliveryGroups []citrixorchestration.PriorityRefRequestModel + for _, value := range util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.DeliveryGroups) { + var deliveryGroupRequestModel citrixorchestration.PriorityRefRequestModel + deliveryGroupRequestModel.SetItem(value) + deliveryGroups = append(deliveryGroups, deliveryGroupRequestModel) + } + + editApplicationGroupRequestBody.SetDeliveryGroups(deliveryGroups) + + // Update Application + editApplicationRequest := r.client.ApiClient.ApplicationGroupsAPIsDAAS.ApplicationGroupsUpdateApplicationGroup(ctx, applicationGroupId) + editApplicationRequest = editApplicationRequest.EditApplicationGroupRequestModel(*editApplicationGroupRequestBody) + httpResp, err := citrixdaasclient.AddRequestData(editApplicationRequest, r.client).Execute() + if err != nil { + resp.Diagnostics.AddError( + "Error updating Application "+applicationGroupName, + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + } + + // Get updated applicationGroup from GetApplication + applicationGroup, err := getApplicationGroup(ctx, r.client, &resp.Diagnostics, applicationGroupId) + if err != nil { + return + } + + //Create AppGroup response does not return delivery groups so we are making another call to fetch delivery groups + dgs, err := getDeliveryGroups(ctx, r.client, &resp.Diagnostics, applicationGroup.GetId()) + if err != nil { + return + } + // Update resource state with updated property values + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, applicationGroup, dgs) + + 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 *applicationGroupResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + // Retrieve values from state + var state ApplicationGroupResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Delete existing delivery group + applicationGroupId := state.Id.ValueString() + applicationGroupName := state.Name.ValueString() + deleteApplicationRequest := r.client.ApiClient.ApplicationGroupsAPIsDAAS.ApplicationGroupsDeleteApplicationGroup(ctx, applicationGroupId) + httpResp, err := citrixdaasclient.AddRequestData(deleteApplicationRequest, r.client).Execute() + if err != nil && httpResp.StatusCode != http.StatusNotFound { + resp.Diagnostics.AddError( + "Error deleting Application "+applicationGroupName, + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return + } +} + +func (r *applicationGroupResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // Retrieve import ID and save to id attribute + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func readApplicationGroup(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, resp *resource.ReadResponse, applicationGroupId string) (*citrixorchestration.ApplicationGroupDetailResponseModel, error) { + getApplicationGroupRequest := client.ApiClient.ApplicationGroupsAPIsDAAS.ApplicationGroupsGetApplicationGroup(ctx, applicationGroupId) + applicationGroupResource, _, err := util.ReadResource[*citrixorchestration.ApplicationGroupDetailResponseModel](getApplicationGroupRequest, ctx, client, resp, "ApplicationGroup", applicationGroupId) + return applicationGroupResource, err +} + +func getApplicationGroup(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, applicationGroupId string) (*citrixorchestration.ApplicationGroupDetailResponseModel, error) { + getApplicationRequest := client.ApiClient.ApplicationGroupsAPIsDAAS.ApplicationGroupsGetApplicationGroup(ctx, applicationGroupId) + applicationGroup, httpResp, err := citrixdaasclient.ExecuteWithRetry[*citrixorchestration.ApplicationGroupDetailResponseModel](getApplicationRequest, client) + if err != nil { + diagnostics.AddError( + "Error Reading Application "+applicationGroupId, + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + } + + return applicationGroup, err +} + +func getDeliveryGroups(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, applicationGroupId string) (*citrixorchestration.ApplicationGroupDeliveryGroupResponseModelCollection, error) { + getDeliveryGroupsRequest := client.ApiClient.ApplicationGroupsAPIsDAAS.ApplicationGroupsGetApplicationGroupDeliveryGroups(ctx, applicationGroupId) + deliveryGroups, httpResp, err := citrixdaasclient.ExecuteWithRetry[*citrixorchestration.ApplicationGroupDeliveryGroupResponseModelCollection](getDeliveryGroupsRequest, client) + if err != nil { + diagnostics.AddError( + "Error Reading Delivery Groups", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + } + + return deliveryGroups, err +} diff --git a/internal/daas/application/application_group_resource_model.go b/internal/daas/application/application_group_resource_model.go new file mode 100644 index 0000000..a6b955c --- /dev/null +++ b/internal/daas/application/application_group_resource_model.go @@ -0,0 +1,144 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package application + +import ( + "context" + "regexp" + + citrixorchestration "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/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "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/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" +) + +// ApplicationGroupResource maps the resource schema data. +type ApplicationGroupResourceModel struct { + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + RestrictToTag types.String `tfsdk:"restrict_to_tag"` + IncludedUsers types.Set `tfsdk:"included_users"` // Set[string] + DeliveryGroups types.Set `tfsdk:"delivery_groups"` // Set[string] + Scopes types.Set `tfsdk:"scopes"` // Set[string] +} + +func GetApplicationGroupSchema() schema.Schema { + return schema.Schema{ + Description: "Resource for creating and managing application group.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the application group.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the application group to create.", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the application group.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "restrict_to_tag": schema.StringAttribute{ + Description: "The tag to restrict the application group to.", + Optional: true, + }, + "included_users": schema.SetAttribute{ + ElementType: types.StringType, + Description: "Users who can use this application group. Must be in `Domain\\UserOrGroupName` or `user@domain.com` format", + Optional: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + validator.String( + stringvalidator.RegexMatches(regexp.MustCompile(util.SamAndUpnRegex), "must be in `Domain\\UserOrGroupName` or `user@domain.com` format"), + ), + ), + }, + }, + "delivery_groups": schema.SetAttribute{ + ElementType: types.StringType, + Description: "Delivery groups to associate with the application group.", + Required: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + validator.String( + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + ), + ), + }, + }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The IDs of the scopes for the application group to be a part of.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), + 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 (appGroup ApplicationGroupResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, application *citrixorchestration.ApplicationGroupDetailResponseModel, dgs *citrixorchestration.ApplicationGroupDeliveryGroupResponseModelCollection) ApplicationGroupResourceModel { + // Overwrite application with refreshed state + appGroup.Id = types.StringValue(application.GetId()) + appGroup.Name = types.StringValue(application.GetName()) + + // Set optional values + if application.GetDescription() != "" { + appGroup.Description = types.StringValue(application.GetDescription()) + } else { + appGroup.Description = types.StringNull() + } + + restrictToTag := application.GetRestrictToTag() + retrictToTagName := restrictToTag.GetName() + if retrictToTagName != "" { + appGroup.RestrictToTag = types.StringValue(retrictToTagName) + } else { + appGroup.RestrictToTag = types.StringNull() + } + + if application.GetScopes() != nil { + scopeIds := util.GetIdsForScopeObjects(application.GetScopes()) + appGroup.Scopes = util.StringArrayToStringSet(ctx, diagnostics, scopeIds) + } + + // Set included users + includedUsers := application.GetIncludedUsers() //only set IncludedUsers to null when it is null in the configuration + if len(includedUsers) == 0 && appGroup.IncludedUsers.IsNull() { + appGroup.IncludedUsers = types.SetNull(types.StringType) + } else { // If included users is not null or empty list, we need to update the list + appGroup.IncludedUsers = util.RefreshUsersList(ctx, diagnostics, appGroup.IncludedUsers, includedUsers) + } + + resultDeliveryGroupIds := []string{} + for _, deliveryGroup := range dgs.Items { + resultDeliveryGroupIds = append(resultDeliveryGroupIds, deliveryGroup.GetId()) + } + appGroup.DeliveryGroups = util.StringArrayToStringSet(ctx, diagnostics, resultDeliveryGroupIds) + + return appGroup +} diff --git a/internal/daas/application/application_icon_resource.go b/internal/daas/application/application_icon_resource.go new file mode 100644 index 0000000..1d4005a --- /dev/null +++ b/internal/daas/application/application_icon_resource.go @@ -0,0 +1,168 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package application + +import ( + "context" + "net/http" + "strconv" + + "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/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &applicationIconResource{} + _ resource.ResourceWithConfigure = &applicationIconResource{} + _ resource.ResourceWithImportState = &applicationIconResource{} +) + +// NewApplicationIconResource is a helper function to simplify the provider implementation. +func NewApplicationIconResource() resource.Resource { + return &applicationIconResource{} +} + +// applicationIconResource is the resource implementation. +type applicationIconResource struct { + client *citrixdaasclient.CitrixDaasClient +} + +// Metadata returns the data source type name. +func (r *applicationIconResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_application_icon" +} + +// Configure adds the provider configured client to the data source. +func (r *applicationIconResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*citrixdaasclient.CitrixDaasClient) +} + +// Schema returns the resource schema. +func (r *applicationIconResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = GetApplicationIconSchema() +} + +func (r *applicationIconResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + // Retrieve values from plan + var plan ApplicationIconResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Generate API request body from plan + var createApplicationIconRequest citrixorchestration.AddIconRequestModel + createApplicationIconRequest.SetRawData(plan.RawData.ValueString()) + // Set default icon format to 32x32x24 png format + createApplicationIconRequest.SetIconFormat("image/png;32x32x24") + + // Create new application icon + addApplicationIconRequest := r.client.ApiClient.IconsAPIsDAAS.IconsAddIcon(ctx) + addApplicationIconRequest = addApplicationIconRequest.AddIconRequestModel(createApplicationIconRequest) + + applicationIcon, httpResp, err := citrixdaasclient.AddRequestData(addApplicationIconRequest, r.client).Execute() + if err != nil { + resp.Diagnostics.AddError( + "Error creating Application Icon", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return + } + + // Map response body to schema and populate Computed attribute values + plan = plan.RefreshPropertyValues(applicationIcon) + + // Set state to fully populated data + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + +} + +func (r *applicationIconResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + // Retrieve values from state + var state ApplicationIconResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + applicationIconId := state.Id.ValueString() + // Get refreshed application properties from Orchestration + applicationIcon, err := readApplicationIcon(ctx, r.client, resp, applicationIconId) + if err != nil { + return + } + + state = state.RefreshPropertyValues(applicationIcon) + + // Set refreshed state + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + +} + +func (r *applicationIconResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.Diagnostics.AddError("Unsupported Operation", "Update is not supported for this resource") + return +} + +func (r *applicationIconResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + // Retrieve values from state + var state ApplicationIconResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + applicationIconId, err := strconv.ParseInt(state.Id.ValueString(), 10, 32) + if err != nil { + resp.Diagnostics.AddError("Error deleting Icon", "Invalid Icon Id") + return + } + + deleteApplicationIconRequest := r.client.ApiClient.IconsAPIsDAAS.IconsRemoveIcon(ctx, int32(applicationIconId)) + httpResp, err := citrixdaasclient.AddRequestData(deleteApplicationIconRequest, r.client).Execute() + if err != nil && httpResp.StatusCode != http.StatusNotFound { + resp.Diagnostics.AddError( + "Error deleting Application Icon "+state.Id.ValueString(), + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return + } +} + +func (r *applicationIconResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // Retrieve import ID and save to id attribute + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func readApplicationIcon(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, resp *resource.ReadResponse, applicationIconId string) (*citrixorchestration.IconResponseModel, error) { + getApplicationIconRequest := client.ApiClient.IconsAPIsDAAS.IconsGetIcon(ctx, applicationIconId) + applicationIcon, _, err := util.ReadResource[*citrixorchestration.IconResponseModel](getApplicationIconRequest, ctx, client, resp, "Application Icon", applicationIconId) + return applicationIcon, err +} diff --git a/internal/daas/application/application_icon_resource_model.go b/internal/daas/application/application_icon_resource_model.go new file mode 100644 index 0000000..d97e80d --- /dev/null +++ b/internal/daas/application/application_icon_resource_model.go @@ -0,0 +1,43 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package application + +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/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// ApplicationIconResourceModel maps the resource schema data. +type ApplicationIconResourceModel struct { + Id types.String `tfsdk:"id"` + RawData types.String `tfsdk:"raw_data"` +} + +func GetApplicationIconSchema() schema.Schema { + return schema.Schema{ + Description: "Resource for managing application icons.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the application icon.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "raw_data": schema.StringAttribute{ + Description: "Prepare an icon in ICO format and convert its binary raw data to base64 encoding. Use the base64 encoded string as the value of this attribute.", + Required: true, + }, + }, + } +} + +func (r ApplicationIconResourceModel) RefreshPropertyValues(application *citrixorchestration.IconResponseModel) ApplicationIconResourceModel { + // Overwrite application folder with refreshed state + r.Id = types.StringValue(application.GetId()) + r.RawData = types.StringValue(application.GetRawData()) + return r +} diff --git a/internal/daas/application/application_resource.go b/internal/daas/application/application_resource.go index 548e7a0..50e08f0 100644 --- a/internal/daas/application/application_resource.go +++ b/internal/daas/application/application_resource.go @@ -1,27 +1,19 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package application import ( "context" "net/http" - "regexp" "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-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/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" ) // Ensure the implementation satisfies the expected interfaces. @@ -57,74 +49,7 @@ func (r *applicationResource) Configure(_ context.Context, req resource.Configur // Schema defines the schema for the data source. func (r *applicationResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Resource for creating and managing applications.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the application.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the application.", - Required: true, - }, - "published_name": schema.StringAttribute{ - Description: "A display name for the application that is shown to users.", - Required: true, - }, - "description": schema.StringAttribute{ - Description: "Description of the application.", - Optional: true, - }, - "installed_app_properties": schema.SingleNestedAttribute{ - Description: "The install application properties.", - Required: true, - Attributes: map[string]schema.Attribute{ - "command_line_arguments": schema.StringAttribute{ - Description: "The command-line arguments to use when launching the executable.", - Optional: true, - Validators: []validator.String{ - validator.String( - stringvalidator.LengthAtLeast(1), - ), - }, - }, - "command_line_executable": schema.StringAttribute{ - Description: "The path of the executable file to launch.", - Required: true, - }, - "working_directory": schema.StringAttribute{ - Description: "The working directory which the executable is launched from.", - Optional: true, - Validators: []validator.String{ - validator.String( - stringvalidator.LengthAtLeast(1), - ), - }, - }, - }, - }, - "delivery_groups": schema.ListAttribute{ - ElementType: types.StringType, - Description: "The delivery group id's to which the application should be added.", - Required: true, - Validators: []validator.List{ - listvalidator.ValueStringsAre( - validator.String( - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - ), - ), - }, - }, - "application_folder_path": schema.StringAttribute{ - Description: "The application folder path in which the application should be created.", - Optional: true, - }, - }, - } + resp.Schema = GetSchema() } // Create creates the resource and sets the initial Terraform state. @@ -141,9 +66,10 @@ func (r *applicationResource) Create(ctx context.Context, req resource.CreateReq // Generate API request body from plan var createInstalledAppRequest citrixorchestration.CreateInstalledAppRequestModel - createInstalledAppRequest.SetCommandLineArguments(plan.InstalledAppProperties.CommandLineArguments.ValueString()) - createInstalledAppRequest.SetCommandLineExecutable(plan.InstalledAppProperties.CommandLineExecutable.ValueString()) - createInstalledAppRequest.SetWorkingDirectory(plan.InstalledAppProperties.WorkingDirectory.ValueString()) + var installedAppProperties = util.ObjectValueToTypedObject[InstalledAppResponseModel](ctx, &resp.Diagnostics, plan.InstalledAppProperties) + createInstalledAppRequest.SetCommandLineArguments(installedAppProperties.CommandLineArguments.ValueString()) + createInstalledAppRequest.SetCommandLineExecutable(installedAppProperties.CommandLineExecutable.ValueString()) + createInstalledAppRequest.SetWorkingDirectory(installedAppProperties.WorkingDirectory.ValueString()) var createApplicationRequest citrixorchestration.CreateApplicationRequestModel createApplicationRequest.SetName(plan.Name.ValueString()) @@ -151,14 +77,33 @@ func (r *applicationResource) Create(ctx context.Context, req resource.CreateReq createApplicationRequest.SetPublishedName(plan.PublishedName.ValueString()) createApplicationRequest.SetInstalledAppProperties(createInstalledAppRequest) createApplicationRequest.SetApplicationFolder(plan.ApplicationFolderPath.ValueString()) + createApplicationRequest.SetIcon(plan.Icon.ValueString()) + + if plan.LimitVisibilityToUsers.IsNull() { + createApplicationRequest.SetIncludedUserFilterEnabled(false) + createApplicationRequest.SetIncludedUsers([]string{}) + } else { + limitVisibilityToUsers := util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.LimitVisibilityToUsers) + limitVisibilityToUserIds, httpResponse, err := util.GetUserIdsUsingIdentity(ctx, r.client, limitVisibilityToUsers) + if err != nil { + diags.AddError( + "Error fetching user details for application resource", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResponse)+ + "\nError message: "+util.ReadClientError(err), + ) + return + } + createApplicationRequest.SetIncludedUsers(limitVisibilityToUserIds) + createApplicationRequest.SetIncludedUserFilterEnabled(true) + } var newApplicationRequest []citrixorchestration.CreateApplicationRequestModel newApplicationRequest = append(newApplicationRequest, createApplicationRequest) var deliveryGroups []citrixorchestration.PriorityRefRequestModel - for _, value := range plan.DeliveryGroups { + for _, value := range util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.DeliveryGroups) { var deliveryGroupRequestModel citrixorchestration.PriorityRefRequestModel - deliveryGroupRequestModel.SetItem(value.ValueString()) + deliveryGroupRequestModel.SetItem(value) deliveryGroups = append(deliveryGroups, deliveryGroupRequestModel) } @@ -200,7 +145,7 @@ func (r *applicationResource) Create(ctx context.Context, req resource.CreateReq } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(application) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, application) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -228,7 +173,7 @@ func (r *applicationResource) Read(ctx context.Context, req resource.ReadRequest return } - state = state.RefreshPropertyValues(application) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, application) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -265,18 +210,37 @@ func (r *applicationResource) Update(ctx context.Context, req resource.UpdateReq editApplicationRequestBody.SetDescription(plan.Description.ValueString()) editApplicationRequestBody.SetPublishedName(plan.PublishedName.ValueString()) editApplicationRequestBody.SetApplicationFolder(plan.ApplicationFolderPath.ValueString()) - + editApplicationRequestBody.SetIcon(plan.Icon.ValueString()) + + if plan.LimitVisibilityToUsers.IsNull() { + editApplicationRequestBody.SetIncludedUserFilterEnabled(false) + editApplicationRequestBody.SetIncludedUsers([]string{}) + } else { + limitVisibilityToUsers := util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.LimitVisibilityToUsers) + limitVisibilityToUserIds, httpResponse, err := util.GetUserIdsUsingIdentity(ctx, r.client, limitVisibilityToUsers) + if err != nil { + diags.AddError( + "Error fetching user details for application resource", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResponse)+ + "\nError message: "+util.ReadClientError(err), + ) + return + } + editApplicationRequestBody.SetIncludedUsers(limitVisibilityToUserIds) + editApplicationRequestBody.SetIncludedUserFilterEnabled(true) + } var editInstalledAppRequest citrixorchestration.EditInstalledAppRequestModel - editInstalledAppRequest.SetCommandLineArguments(plan.InstalledAppProperties.CommandLineArguments.ValueString()) - editInstalledAppRequest.SetCommandLineExecutable(plan.InstalledAppProperties.CommandLineExecutable.ValueString()) - editInstalledAppRequest.SetWorkingDirectory(plan.InstalledAppProperties.WorkingDirectory.ValueString()) + var installedAppProperties = util.ObjectValueToTypedObject[InstalledAppResponseModel](ctx, &resp.Diagnostics, plan.InstalledAppProperties) + editInstalledAppRequest.SetCommandLineArguments(installedAppProperties.CommandLineArguments.ValueString()) + editInstalledAppRequest.SetCommandLineExecutable(installedAppProperties.CommandLineExecutable.ValueString()) + editInstalledAppRequest.SetWorkingDirectory(installedAppProperties.WorkingDirectory.ValueString()) editApplicationRequestBody.SetInstalledAppProperties(editInstalledAppRequest) var deliveryGroups []citrixorchestration.PriorityRefRequestModel - for _, value := range plan.DeliveryGroups { + for _, value := range util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.DeliveryGroups) { var deliveryGroupRequestModel citrixorchestration.PriorityRefRequestModel - deliveryGroupRequestModel.SetItem(value.ValueString()) + deliveryGroupRequestModel.SetItem(value) deliveryGroups = append(deliveryGroups, deliveryGroupRequestModel) } @@ -306,7 +270,7 @@ func (r *applicationResource) Update(ctx context.Context, req resource.UpdateReq } // Update resource state with updated property values - plan = plan.RefreshPropertyValues(application) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, application) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) diff --git a/internal/daas/application/application_resource_model.go b/internal/daas/application/application_resource_model.go index 56586ce..5b5b48a 100644 --- a/internal/daas/application/application_resource_model.go +++ b/internal/daas/application/application_resource_model.go @@ -1,9 +1,21 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package application import ( + "context" + "regexp" + citrixorchestration "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/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -17,88 +29,159 @@ type InstalledAppResponseModel struct { WorkingDirectory types.String `tfsdk:"working_directory"` } +func (InstalledAppResponseModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "The install application properties.", + Required: true, + Attributes: map[string]schema.Attribute{ + "command_line_arguments": schema.StringAttribute{ + Description: "The command-line arguments to use when launching the executable.", + Optional: true, + Validators: []validator.String{ + validator.String( + stringvalidator.LengthAtLeast(1), + ), + }, + }, + "command_line_executable": schema.StringAttribute{ + Description: "The path of the executable file to launch.", + Required: true, + }, + "working_directory": schema.StringAttribute{ + Description: "The working directory which the executable is launched from.", + Optional: true, + Validators: []validator.String{ + validator.String( + stringvalidator.LengthAtLeast(1), + ), + }, + }, + }, + } +} + +func (InstalledAppResponseModel) GetAttributes() map[string]schema.Attribute { + return InstalledAppResponseModel{}.GetSchema().Attributes +} + // ApplicationResourceModel maps the resource schema data. type ApplicationResourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - PublishedName types.String `tfsdk:"published_name"` - Description types.String `tfsdk:"description"` - InstalledAppProperties *InstalledAppResponseModel `tfsdk:"installed_app_properties"` - DeliveryGroups []types.String `tfsdk:"delivery_groups"` - ApplicationFolderPath types.String `tfsdk:"application_folder_path"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + PublishedName types.String `tfsdk:"published_name"` + Description types.String `tfsdk:"description"` + InstalledAppProperties types.Object `tfsdk:"installed_app_properties"` // InstalledAppResponseModel + DeliveryGroups types.Set `tfsdk:"delivery_groups"` //Set[string] + ApplicationFolderPath types.String `tfsdk:"application_folder_path"` + Icon types.String `tfsdk:"icon"` + LimitVisibilityToUsers types.Set `tfsdk:"limit_visibility_to_users"` //Set[string] } -func (r ApplicationResourceModel) RefreshPropertyValues(application *citrixorchestration.ApplicationDetailResponseModel) ApplicationResourceModel { +// Schema defines the schema for the data source. +func GetSchema() schema.Schema { + return schema.Schema{ + Description: "Resource for creating and managing applications.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the application.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the application.", + Required: true, + }, + "published_name": schema.StringAttribute{ + Description: "A display name for the application that is shown to users.", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the application.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "installed_app_properties": InstalledAppResponseModel{}.GetSchema(), + "delivery_groups": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The delivery group IDs to which the application should be added.", + Required: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + validator.String( + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + ), + ), + }, + }, + "application_folder_path": schema.StringAttribute{ + Description: "The application folder path in which the application should be created.", + Optional: true, + }, + "icon": schema.StringAttribute{ + Description: "The Id of the icon to be associated with the application.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("0"), + }, + "limit_visibility_to_users": schema.SetAttribute{ + ElementType: types.StringType, + Description: "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. Must be in `DOMAIN\\UserOrGroupName` or `user@domain.com` format", + Optional: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + validator.String( + stringvalidator.RegexMatches(regexp.MustCompile(util.SamAndUpnRegex), "must be in `Domain\\UserOrGroupName` or `user@domain.com` format"), + ), + ), + }, + }, + }, + } +} + +func (r ApplicationResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, application *citrixorchestration.ApplicationDetailResponseModel) ApplicationResourceModel { // Overwrite application with refreshed state r.Id = types.StringValue(application.GetId()) r.Name = types.StringValue(application.GetName()) r.PublishedName = types.StringValue(application.GetPublishedName()) + r.Description = types.StringValue(application.GetDescription()) + r.Icon = types.StringValue(application.GetIconId()) // Set optional values - if application.GetDescription() != "" { - r.Description = types.StringValue(application.GetDescription()) - } else { - r.Description = types.StringNull() - } - if *application.GetApplicationFolder().Name.Get() != "" { r.ApplicationFolderPath = types.StringValue(*application.GetApplicationFolder().Name.Get()) } else { r.ApplicationFolderPath = types.StringNull() } - r = r.updatePlanWithInsalledAppProperties(application) - r = r.updatePlanWithDeliveryGroups(application) - + includedUsers := application.GetIncludedUsers() + if application.GetIncludedUserFilterEnabled() { + r.LimitVisibilityToUsers = util.RefreshUsersList(ctx, diagnostics, r.LimitVisibilityToUsers, includedUsers) + } else { + r.LimitVisibilityToUsers = types.SetNull(types.StringType) + } + r.DeliveryGroups = util.StringArrayToStringSet(ctx, diagnostics, application.GetAssociatedDeliveryGroupUuids()) + r.InstalledAppProperties = r.updatePlanWithInstalledAppProperties(ctx, diagnostics, application) return r } -func (r ApplicationResourceModel) updatePlanWithInsalledAppProperties(application *citrixorchestration.ApplicationDetailResponseModel) ApplicationResourceModel { +func (r ApplicationResourceModel) updatePlanWithInstalledAppProperties(ctx context.Context, diagnostics *diag.Diagnostics, application *citrixorchestration.ApplicationDetailResponseModel) types.Object { - r.InstalledAppProperties = &InstalledAppResponseModel{} + var installedAppProperties = InstalledAppResponseModel{} - r.InstalledAppProperties.CommandLineExecutable = types.StringValue(application.InstalledAppProperties.GetCommandLineExecutable()) + installedAppProperties.CommandLineExecutable = types.StringValue(application.InstalledAppProperties.GetCommandLineExecutable()) // Set optional values if application.InstalledAppProperties.GetWorkingDirectory() != "" { - r.InstalledAppProperties.WorkingDirectory = types.StringValue(application.InstalledAppProperties.GetWorkingDirectory()) + installedAppProperties.WorkingDirectory = types.StringValue(application.InstalledAppProperties.GetWorkingDirectory()) } if application.InstalledAppProperties.GetCommandLineArguments() != "" { - r.InstalledAppProperties.CommandLineArguments = types.StringValue(application.InstalledAppProperties.GetCommandLineArguments()) + installedAppProperties.CommandLineArguments = types.StringValue(application.InstalledAppProperties.GetCommandLineArguments()) } - return r -} - -func (r ApplicationResourceModel) updatePlanWithDeliveryGroups(application *citrixorchestration.ApplicationDetailResponseModel) ApplicationResourceModel { - - // Add the server delivery group ids to a map - serverDeliveryGroupIdsMap := map[string]bool{} - for _, id := range application.GetAssociatedDeliveryGroupUuids() { - serverDeliveryGroupIdsMap[id] = false - } - - // Create a result list of delivery group ids - resultDeliveryGroupIds := []types.String{} - - for _, existingDeliveryGroupId := range r.DeliveryGroups { - _, exists := serverDeliveryGroupIdsMap[existingDeliveryGroupId.ValueString()] - if exists { - // Add the existing delivery group ids which matches with server data to the result list - resultDeliveryGroupIds = append(resultDeliveryGroupIds, existingDeliveryGroupId) - // Mark the server delivery group ids as visited - serverDeliveryGroupIdsMap[existingDeliveryGroupId.ValueString()] = true // Mark as visited - } - } - - for serverDeliveryGroupId, visited := range serverDeliveryGroupIdsMap { - // Add only unvisited delivery groups ids - if !visited { - resultDeliveryGroupIds = append(resultDeliveryGroupIds, types.StringValue(serverDeliveryGroupId)) - } - } - - r.DeliveryGroups = resultDeliveryGroupIds - return r + return util.TypedObjectToObjectValue(ctx, diagnostics, installedAppProperties) } diff --git a/internal/daas/delivery_group/delivery_group_resource.go b/internal/daas/delivery_group/delivery_group_resource.go index 5670881..aac71db 100644 --- a/internal/daas/delivery_group/delivery_group_resource.go +++ b/internal/daas/delivery_group/delivery_group_resource.go @@ -1,27 +1,17 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package delivery_group import ( "context" "net/http" - "regexp" 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-validators/int64validator" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/int64default" - "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" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -60,454 +50,7 @@ func (r *deliveryGroupResource) Configure(_ context.Context, req resource.Config // Schema defines the schema for the resource. func (r *deliveryGroupResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages a delivery group.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the delivery group.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the delivery group.", - Required: true, - }, - "description": schema.StringAttribute{ - Description: "Description of the delivery group.", - Optional: true, - }, - "restricted_access_users": getSchemaForRestrictedAccessUsers(true), - "allow_anonymous_access": schema.BoolAttribute{ - Description: "Give access to unauthenticated (anonymous) users; no credentials are required to access StoreFront. This feature requires a StoreFront store for unauthenticated users.", - Optional: true, - }, - "desktops": schema.ListNestedAttribute{ - Description: "A list of Desktop resources to publish on the delivery group. Only 1 desktop can be added to a Remote PC Delivery Group.", - Optional: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "published_name": schema.StringAttribute{ - Description: "A display name for the desktop.", - Required: true, - }, - "description": schema.StringAttribute{ - Description: "A description for the published desktop. The name and description are shown in Citrix Workspace app.", - Optional: true, - }, - "enabled": schema.BoolAttribute{ - Description: "Specify whether to enable the delivery of this desktop.", - Required: true, - }, - "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. Should be set to false for Remote PC Delivery Group.", - Required: true, - }, - "restricted_access_users": getSchemaForRestrictedAccessUsers(false), - }, - }, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "associated_machine_catalogs": schema.ListNestedAttribute{ - Description: "Machine catalogs from which to assign machines to the newly created delivery group.", - Required: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "machine_catalog": schema.StringAttribute{ - Description: "Id of the machine catalog from which to add machines.", - Required: true, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "machine_count": schema.Int64Attribute{ - Description: "The number of machines to assign from the machine catalog to the delivery group.", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - }, - }, - }, - "autoscale_settings": schema.SingleNestedAttribute{ - Description: "The power management settings governing the machine(s) in the delivery group.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "autoscale_enabled": schema.BoolAttribute{ - Description: "Whether auto-scale is enabled for the delivery group.", - Required: true, - }, - "timezone": schema.StringAttribute{ - Description: "The time zone in which this delivery group's machines reside.", - Optional: true, - }, - "peak_disconnect_timeout_minutes": schema.Int64Attribute{ - Description: "The number of minutes before the configured action should be performed after a user session disconnects in peak hours.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - }, - "peak_log_off_action": schema.StringAttribute{ - Description: "The action to be performed after a configurable period of a user session ending in peak hours.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString(string(citrixorchestration.SESSIONCHANGEHOSTINGACTION_NOTHING)), - Validators: []validator.String{ - sessionHostingActionEnumValidator(), - }, - }, - "peak_disconnect_action": schema.StringAttribute{ - Description: "The action to be performed after a configurable period of a user session disconnecting in peak hours.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString(string(citrixorchestration.SESSIONCHANGEHOSTINGACTION_NOTHING)), - Validators: []validator.String{ - sessionHostingActionEnumValidator(), - }, - }, - "peak_extended_disconnect_action": schema.StringAttribute{ - Description: "The action to be performed after a second configurable period of a user session disconnecting in peak hours.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString(string(citrixorchestration.SESSIONCHANGEHOSTINGACTION_NOTHING)), - Validators: []validator.String{ - sessionHostingActionEnumValidator(), - }, - }, - "peak_extended_disconnect_timeout_minutes": schema.Int64Attribute{ - Description: "The number of minutes before the second configured action should be performed after a user session disconnects in peak hours.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - }, - "off_peak_disconnect_timeout_minutes": schema.Int64Attribute{ - Description: "The number of minutes before the configured action should be performed after a user session disconnectts outside peak hours.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - }, - "off_peak_log_off_action": schema.StringAttribute{ - Description: "The action to be performed after a configurable period of a user session ending outside peak hours.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString(string(citrixorchestration.SESSIONCHANGEHOSTINGACTION_NOTHING)), - Validators: []validator.String{ - sessionHostingActionEnumValidator(), - }, - }, - "off_peak_disconnect_action": schema.StringAttribute{ - Description: "The action to be performed after a configurable period of a user session disconnecting outside peak hours.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString(string(citrixorchestration.SESSIONCHANGEHOSTINGACTION_NOTHING)), - Validators: []validator.String{ - sessionHostingActionEnumValidator(), - }, - }, - "off_peak_extended_disconnect_action": schema.StringAttribute{ - Description: "The action to be performed after a second configurable period of a user session disconnecting outside peak hours.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString(string(citrixorchestration.SESSIONCHANGEHOSTINGACTION_NOTHING)), - Validators: []validator.String{ - sessionHostingActionEnumValidator(), - }, - }, - "off_peak_extended_disconnect_timeout_minutes": schema.Int64Attribute{ - Description: "The number of minutes before the second configured action should be performed after a user session disconnects outside peak hours.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - }, - "peak_buffer_size_percent": schema.Int64Attribute{ - Description: "The percentage of machines in the delivery group that should be kept available in an idle state in peak hours.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - }, - "off_peak_buffer_size_percent": schema.Int64Attribute{ - Description: "The percentage of machines in the delivery group that should be kept available in an idle state outside peak hours.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - }, - "power_off_delay_minutes": schema.Int64Attribute{ - Description: "Delay before machines are powered off, when scaling down. Specified in minutes. By default, the power-off delay is 30 minutes. You can set it in a range of 0 to 60 minutes. Applies only to multi-session machines.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(30), - Validators: []validator.Int64{ - int64validator.Between(0, 60), - }, - }, - "disconnect_peak_idle_session_after_seconds": schema.Int64Attribute{ - Description: "Specifies the time in seconds after which an idle session belonging to the delivery group is disconnected during peak time.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - }, - "disconnect_off_peak_idle_session_after_seconds": schema.Int64Attribute{ - Description: "Specifies the time in seconds after which an idle session belonging to the delivery group is disconnected during off-peak time.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - }, - "log_off_peak_disconnected_session_after_seconds": schema.Int64Attribute{ - Description: "Specifies the time in seconds after which a disconnected session belonging to the delivery group is terminated during peak time.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - }, - "log_off_off_peak_disconnected_session_after_seconds": schema.Int64Attribute{ - Description: "Specifies the time in seconds after which a disconnected session belonging to the delivery group is terminated during off peak time.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(0), - }, - "power_time_schemes": schema.ListNestedAttribute{ - Description: "Power management time schemes. No two schemes for the same delivery group may cover the same day of the week.", - Required: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "days_of_week": schema.ListAttribute{ - ElementType: types.StringType, - Description: "The pattern of days of the week that the power time scheme covers.", - Required: true, - Validators: []validator.List{ - listvalidator.ValueStringsAre( - stringvalidator.OneOf( - "Unknown", - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - "Weekdays", - "Weekend", - ), - ), - }, - }, - "display_name": schema.StringAttribute{ - Description: "The name of the power time scheme as displayed in the console.", - Required: true, - }, - "peak_time_ranges": schema.ListAttribute{ - ElementType: types.StringType, - Description: "List of peak time ranges during the day. e.g. 09:00-17:00", - Required: true, - }, - "pool_size_schedules": schema.ListNestedAttribute{ - Description: "List of pool size schedules during the day. Each is specified as a time range and an indicator of the number of machines that should be powered on during that time range. Do not specify schedules when no machines should be powered on.", - Optional: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "time_range": schema.StringAttribute{ - Description: "Time range during which the pool size applies. Format is HH:mm-HH:mm. e.g. 09:00-17:00", - Required: true, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(`^([0-1][0-9]|2[0-3]):[0|3]0-([0-1][0-9]|2[0-3]):[0|3]0$`), "must be specified in format HH:mm-HH:mm and range between 00:00-00:00 with minutes being 00 or 30."), - }, - }, - "pool_size": schema.Int64Attribute{ - Description: "The number of machines (either as an absolute number or a percentage of the machines in the delivery group, depending on the value of PoolUsingPercentage) that are to be maintained in a running state, whether they are in use or not.", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - }, - }, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "pool_using_percentage": schema.BoolAttribute{ - Description: "Indicates whether the integer values in the pool size array are to be treated as absolute values (if this value is `false`) or as percentages of the number of machines in the delivery group (if this value is `true`).", - Required: true, - }, - }, - }, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - }, - }, - "reboot_schedules": schema.ListNestedAttribute{ - Description: "The reboot schedule for the delivery group.", - Optional: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "name": schema.StringAttribute{ - Description: "The name of the reboot schedule.", - Required: true, - }, - "description": schema.StringAttribute{ - Description: "The description of the reboot schedule.", - Optional: true, - }, - "reboot_schedule_enabled": schema.BoolAttribute{ - Description: "Whether the reboot schedule is enabled.", - Required: true, - }, - "restrict_to_tag": schema.StringAttribute{ - Description: "The tag to which the reboot schedule is restricted.", - Optional: true, - }, - "ignore_maintenance_mode": schema.BoolAttribute{ - Description: "Whether the reboot schedule ignores machines in the maintenance mode.", - Required: true, - }, - "frequency": schema.StringAttribute{ - Description: "The frequency of the reboot schedule. Can only be set to `Daily`, `Weekly`, `Monthly`, or `Once`.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - "Daily", - "Weekly", - "Monthly", - "Once", - ), - }, - }, - "frequency_factor": schema.Int64Attribute{ - Description: "Repeats every X days/weeks/months. Minimum value is 1.", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - "start_date": schema.StringAttribute{ - Description: "The date on which the reboot schedule starts. The date format is `YYYY-MM-DD`.", - Required: true, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.DateRegex), "Date must be in the format YYYY-MM-DD"), - }, - }, - "start_time": schema.StringAttribute{ - Description: "The time at which the reboot schedule starts. The time format is `HH:MM`.", - Required: true, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.TimeRegex), "Time must be in the format HH:MM"), - }, - }, - "reboot_duration_minutes": schema.Int64Attribute{ - Description: "Restart all machines within x minutes. 0 means restarting all machines at the same time. To restart machines after draining sessions, set natural_reboot_schedule to true instead. ", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(0), - }, - }, - "natural_reboot_schedule": schema.BoolAttribute{ - Description: "Indicates whether the reboot will be a natural reboot, where the machines will be rebooted when they have no sessions. This should set to false for reboot_duration_minutes to work. Once UseNaturalReboot is set to true, RebootDurationMinutes won't have any effect.", - Required: true, - }, - "days_in_week": schema.ListAttribute{ - ElementType: types.StringType, - Description: "The days of the week on which the reboot schedule runs weekly. Can only be set to `Sunday`, `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, or `Saturday`.", - Optional: true, - Validators: []validator.List{ - listvalidator.ValueStringsAre( - stringvalidator.OneOf( - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ), - ), - }, - }, - "week_in_month": schema.StringAttribute{ - Description: "The week in the month on which the reboot schedule runs monthly. Can only be set to `First`, `Second`, `Third`, `Fourth`, or `Last`.", - Optional: true, - Validators: []validator.String{ - stringvalidator.OneOf( - "First", - "Second", - "Third", - "Fourth", - "Last", - ), - }, - }, - "day_in_month": schema.StringAttribute{ - Description: "The day in the month on which the reboot schedule runs monthly. Can only be set to `Sunday`, `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, or `Saturday`.", - Optional: true, - Validators: []validator.String{ - stringvalidator.OneOf( - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ), - }, - }, - "reboot_notification_to_users": schema.SingleNestedAttribute{ - Description: "The reboot notification for the reboot schedule. Not available for natural reboot.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "notification_duration_minutes": schema.Int64Attribute{ - Description: "Send notification to users X minutes before user is logged off. Can only be 0, 1, 5 or 15. 0 means no notification.", - Required: true, - Validators: []validator.Int64{ - int64validator.OneOf(0, 1, 5, 15), - }, - }, - "notification_title": schema.StringAttribute{ - Description: "The title to be displayed to users before they are logged off.", - Required: true, - }, - "notification_message": schema.StringAttribute{ - Description: "The message to be displayed to users before they are logged off.", - Required: true, - }, - "notification_repeat_every_5_minutes": schema.BoolAttribute{ - Description: "Repeat notification every 5 minutes, only available for 15 minutes notification duration. ", - Optional: true, - }, - }, - }, - }, - }, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "total_machines": schema.Int64Attribute{ - Description: "The total number of machines in the delivery group.", - Computed: true, - }, - "policy_set_id": schema.StringAttribute{ - Description: "GUID identifier of the policy set.", - Optional: true, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "minimum_functional_level": schema.StringAttribute{ - Description: "Specifies the minimum functional level for the VDA machines in the delivery group. Defaults to `L7_20`.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString("L7_20"), - Validators: []validator.String{ - stringvalidator.OneOfCaseInsensitive(util.GetAllowedFunctionalLevelValues()...), - }, - }, - }, - } + resp.Schema = GetSchema() } func (r *deliveryGroupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -521,13 +64,14 @@ func (r *deliveryGroupResource) Create(ctx context.Context, req resource.CreateR } // Get machine catalogs and verify all of them have the same session support - catalogSessionSupport, areCatalogsPowerManaged, isRemotePcCatalog, identityType, err := validateAndReturnMachineCatalogSessionSupport(ctx, *r.client, &resp.Diagnostics, plan.AssociatedMachineCatalogs, true) + associatedMachineCatalogs := util.ObjectListToTypedArray[DeliveryGroupMachineCatalogModel](ctx, &resp.Diagnostics, plan.AssociatedMachineCatalogs) + associatedMachineCatalogProperties, err := validateAndReturnMachineCatalogSessionSupport(ctx, *r.client, &resp.Diagnostics, associatedMachineCatalogs, true) if err != nil { return } - if plan.AutoscaleSettings != nil && !areCatalogsPowerManaged { + if !plan.AutoscaleSettings.IsNull() && !associatedMachineCatalogProperties.IsPowerManaged { resp.Diagnostics.AddError( "Error creating Delivery Group "+plan.Name.ValueString(), "Autoscale settings can only be configured if associated machine catalogs are power managed.", @@ -535,7 +79,7 @@ func (r *deliveryGroupResource) Create(ctx context.Context, req resource.CreateR return } - if isRemotePcCatalog && plan.Desktops != nil && len(plan.Desktops) > 1 { + if associatedMachineCatalogProperties.IsRemotePcCatalog && !plan.Desktops.IsNull() && len(plan.Desktops.Elements()) > 1 { resp.Diagnostics.AddError( "Error creating Delivery Group "+plan.Name.ValueString(), "Only one assignment policy rule can be added to a Remote PC Delivery Group.", @@ -543,8 +87,8 @@ func (r *deliveryGroupResource) Create(ctx context.Context, req resource.CreateR return } - if isRemotePcCatalog && plan.Desktops != nil && len(plan.Desktops) > 0 { - desktops := plan.Desktops + if associatedMachineCatalogProperties.IsRemotePcCatalog && !plan.Desktops.IsNull() && len(plan.Desktops.Elements()) > 0 { + desktops := util.ObjectListToTypedArray[DeliveryGroupDesktop](ctx, &resp.Diagnostics, plan.Desktops) if desktops[0].EnableSessionRoaming.ValueBool() { resp.Diagnostics.AddError( "Error creating Delivery Group "+plan.Name.ValueString(), @@ -553,7 +97,7 @@ func (r *deliveryGroupResource) Create(ctx context.Context, req resource.CreateR return } - if desktops[0].RestrictedAccessUsers != nil { + if !desktops[0].RestrictedAccessUsers.IsNull() { resp.Diagnostics.AddError( "Error creating Delivery Group "+plan.Name.ValueString(), "restricted_access_users needs to be set for Remote PC Delivery Group.", @@ -562,7 +106,7 @@ func (r *deliveryGroupResource) Create(ctx context.Context, req resource.CreateR } } - body, err := getRequestModelForDeliveryGroupCreate(&resp.Diagnostics, plan, catalogSessionSupport, identityType) + body, err := getRequestModelForDeliveryGroupCreate(ctx, &resp.Diagnostics, r.client, plan, associatedMachineCatalogProperties) if err != nil { return } @@ -629,12 +173,13 @@ func (r *deliveryGroupResource) Create(ctx context.Context, req resource.CreateR deliveryGroup.SetPolicySetGuid(types.StringNull().ValueString()) } - deliveryGroup, deliveryGroupDesktops, err = updateDeliveryGroupUserAccessDetails(ctx, r.client, &resp.Diagnostics, deliveryGroup, deliveryGroupDesktops) - if err != nil { - return + if r.client.AuthConfig.OnPremises { + // DDC 2402 LTSR has a bug where UPN is not returned for AD users. Call Identity API to fetch details for users + deliveryGroup, deliveryGroupDesktops, _ = updateDeliveryGroupAndDesktopUsers(ctx, r.client, &resp.Diagnostics, deliveryGroup, deliveryGroupDesktops) + // Do not return if there is an error. We need to set the resource in the state so that tf knows about the resource and marks it tainted (diagnostics already has the error) } - plan = plan.RefreshPropertyValues(deliveryGroup, deliveryGroupDesktops, deliveryGroupPowerTimeSchemes, deliveryGroupMachines, deliveryGroupRebootSchedule) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, deliveryGroup, deliveryGroupDesktops, deliveryGroupPowerTimeSchemes, deliveryGroupMachines, deliveryGroupRebootSchedule) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -688,12 +233,15 @@ func (r *deliveryGroupResource) Read(ctx context.Context, req resource.ReadReque deliveryGroup.SetPolicySetGuid("") } - deliveryGroup, deliveryGroupDesktops, err = updateDeliveryGroupUserAccessDetails(ctx, r.client, &resp.Diagnostics, deliveryGroup, deliveryGroupDesktops) - if err != nil { - return + if r.client.AuthConfig.OnPremises { + // DDC 2402 LTSR has a bug where UPN is not returned for AD users. Call Identity API to fetch details for users and update dg and dg desktops + deliveryGroup, deliveryGroupDesktops, err = updateDeliveryGroupAndDesktopUsers(ctx, r.client, &resp.Diagnostics, deliveryGroup, deliveryGroupDesktops) + if err != nil { + return + } } - state = state.RefreshPropertyValues(deliveryGroup, deliveryGroupDesktops, deliveryGroupPowerTimeSchemes, deliveryGroupMachines, deliveryGroupRebootSchedule) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, deliveryGroup, deliveryGroupDesktops, deliveryGroupPowerTimeSchemes, deliveryGroupMachines, deliveryGroupRebootSchedule) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -721,7 +269,7 @@ func (r *deliveryGroupResource) Update(ctx context.Context, req resource.UpdateR return } - editDeliveryGroupRequestBody, err := getRequestModelForDeliveryGroupUpdate(&resp.Diagnostics, plan, currentDeliveryGroup) + editDeliveryGroupRequestBody, err := getRequestModelForDeliveryGroupUpdate(ctx, &resp.Diagnostics, r.client, plan, currentDeliveryGroup) if err != nil { return } @@ -785,12 +333,13 @@ func (r *deliveryGroupResource) Update(ctx context.Context, req resource.UpdateR updatedDeliveryGroup.SetPolicySetGuid(types.StringNull().ValueString()) } - updatedDeliveryGroup, deliveryGroupDesktops, err = updateDeliveryGroupUserAccessDetails(ctx, r.client, &resp.Diagnostics, updatedDeliveryGroup, deliveryGroupDesktops) - if err != nil { - return + if r.client.AuthConfig.OnPremises { + // DDC 2402 LTSR has a bug where UPN is not returned for AD users. Call Identity API to fetch details for users + updatedDeliveryGroup, deliveryGroupDesktops, _ = updateDeliveryGroupAndDesktopUsers(ctx, r.client, &resp.Diagnostics, updatedDeliveryGroup, deliveryGroupDesktops) + // Do not return if there is an error. We need to set the resource in the state so that tf knows about the resource and marks it tainted (diagnostics already has the error) } - plan = plan.RefreshPropertyValues(updatedDeliveryGroup, deliveryGroupDesktops, deliveryGroupPowerTimeSchemes, deliveryGroupMachines, deliveryGroupRebootSchedule) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, updatedDeliveryGroup, deliveryGroupDesktops, deliveryGroupPowerTimeSchemes, deliveryGroupMachines, deliveryGroupRebootSchedule) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) @@ -841,17 +390,18 @@ func (r *deliveryGroupResource) ValidateConfig(ctx context.Context, req resource return } - if data.AutoscaleSettings == nil { + if data.AutoscaleSettings.IsNull() { return } + autoscale := util.ObjectValueToTypedObject[DeliveryGroupPowerManagementSettings](ctx, &resp.Diagnostics, data.AutoscaleSettings) - validatePowerTimeSchemes(&resp.Diagnostics, data.AutoscaleSettings.PowerTimeSchemes) + validatePowerTimeSchemes(ctx, &resp.Diagnostics, util.ObjectListToTypedArray[DeliveryGroupPowerTimeScheme](ctx, &resp.Diagnostics, autoscale.PowerTimeSchemes)) - if data.RebootSchedules == nil { + if data.RebootSchedules.IsNull() { return } - validateRebootSchedules(&resp.Diagnostics, data.RebootSchedules) + validateRebootSchedules(ctx, &resp.Diagnostics, util.ObjectListToTypedArray[DeliveryGroupRebootSchedule](ctx, &resp.Diagnostics, data.RebootSchedules)) } func (r *deliveryGroupResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { @@ -870,12 +420,13 @@ func (r *deliveryGroupResource) ModifyPlan(ctx context.Context, req resource.Mod return } - sessionSupport, areCatalogsPowerManaged, isRemotePcCatalog, _, err := validateAndReturnMachineCatalogSessionSupport(ctx, *r.client, &resp.Diagnostics, plan.AssociatedMachineCatalogs, !create) - if err != nil || sessionSupport == nil { + associatedMachineCatalogs := util.ObjectListToTypedArray[DeliveryGroupMachineCatalogModel](ctx, &resp.Diagnostics, plan.AssociatedMachineCatalogs) + associatedMachineCatalogProperties, err := validateAndReturnMachineCatalogSessionSupport(ctx, *r.client, &resp.Diagnostics, associatedMachineCatalogs, !create) + if err != nil || associatedMachineCatalogProperties.SessionSupport == "" { return } - isValid, errMsg := validatePowerManagementSettings(plan, *sessionSupport) + isValid, errMsg := validatePowerManagementSettings(ctx, &resp.Diagnostics, plan, associatedMachineCatalogProperties.SessionSupport) operation := "updating" if create { operation = "creating" @@ -889,7 +440,7 @@ func (r *deliveryGroupResource) ModifyPlan(ctx context.Context, req resource.Mod return } - if plan.AutoscaleSettings != nil && !areCatalogsPowerManaged { + if !plan.AutoscaleSettings.IsNull() && !associatedMachineCatalogProperties.IsPowerManaged { resp.Diagnostics.AddError( "Error "+operation+" Delivery Group "+plan.Name.ValueString(), "Autoscale settings can only be configured if associated machine catalogs are power managed.", @@ -898,7 +449,7 @@ func (r *deliveryGroupResource) ModifyPlan(ctx context.Context, req resource.Mod return } - if isRemotePcCatalog && plan.Desktops != nil && len(plan.Desktops) > 1 { + if associatedMachineCatalogProperties.IsRemotePcCatalog && plan.Desktops.IsNull() && len(plan.Desktops.Elements()) > 1 { resp.Diagnostics.AddError( "Error "+operation+" Delivery Group "+plan.Name.ValueString(), "Only one assignment policy rule can be added to a Remote PC Delivery Group", @@ -906,8 +457,8 @@ func (r *deliveryGroupResource) ModifyPlan(ctx context.Context, req resource.Mod return } - if isRemotePcCatalog && plan.Desktops != nil && len(plan.Desktops) > 0 { - desktops := plan.Desktops + if associatedMachineCatalogProperties.IsRemotePcCatalog && plan.Desktops.IsNull() && len(plan.Desktops.Elements()) > 0 { + desktops := util.ObjectListToTypedArray[DeliveryGroupDesktop](ctx, &resp.Diagnostics, plan.Desktops) if desktops[0].EnableSessionRoaming.ValueBool() { resp.Diagnostics.AddError( "Error "+operation+" Delivery Group "+plan.Name.ValueString(), @@ -916,7 +467,7 @@ func (r *deliveryGroupResource) ModifyPlan(ctx context.Context, req resource.Mod return } - if desktops[0].RestrictedAccessUsers != nil { + if !desktops[0].RestrictedAccessUsers.IsNull() { resp.Diagnostics.AddError( "Error "+operation+" Delivery Group "+plan.Name.ValueString(), "restricted_access_users needs to be set for Remote PC Delivery Group.", @@ -924,4 +475,15 @@ func (r *deliveryGroupResource) ModifyPlan(ctx context.Context, req resource.Mod return } } + + if (associatedMachineCatalogProperties.SessionSupport == citrixorchestration.SESSIONSUPPORT_MULTI_SESSION || + associatedMachineCatalogProperties.AllocationType == citrixorchestration.ALLOCATIONTYPE_STATIC || + !associatedMachineCatalogProperties.IsPowerManaged) && + !plan.MakeResourcesAvailableInLHC.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("make_resources_available_in_lhc"), + "Incorrect Attribute Configuration", + "make_resources_available_in_lhc can only be set for power managed Single Session OS Random (pooled) VDAs.", + ) + } } diff --git a/internal/daas/delivery_group/delivery_group_resource_model.go b/internal/daas/delivery_group/delivery_group_resource_model.go index 5c5b262..b2222d5 100644 --- a/internal/daas/delivery_group/delivery_group_resource_model.go +++ b/internal/daas/delivery_group/delivery_group_resource_model.go @@ -1,9 +1,27 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package delivery_group import ( + "context" + "fmt" + "regexp" + citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" + "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -12,17 +30,120 @@ type DeliveryGroupMachineCatalogModel struct { MachineCount types.Int64 `tfsdk:"machine_count"` } +func (DeliveryGroupMachineCatalogModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "machine_catalog": schema.StringAttribute{ + Description: "Id of the machine catalog from which to add machines.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "machine_count": schema.Int64Attribute{ + Description: "The number of machines to assign from the machine catalog to the delivery group.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + }, + } +} + +func (DeliveryGroupMachineCatalogModel) GetAttributes() map[string]schema.Attribute { + return DeliveryGroupMachineCatalogModel{}.GetSchema().Attributes +} + type PowerTimeSchemePoolSizeScheduleRequestModel struct { TimeRange types.String `tfsdk:"time_range"` PoolSize types.Int64 `tfsdk:"pool_size"` } +func (PowerTimeSchemePoolSizeScheduleRequestModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "time_range": schema.StringAttribute{ + Description: "Time range during which the pool size applies. Format is HH:mm-HH:mm. e.g. 09:00-17:00", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^([0-1][0-9]|2[0-3]):[0|3]0-([0-1][0-9]|2[0-3]):[0|3]0$`), "must be specified in format HH:mm-HH:mm and range between 00:00-00:00 with minutes being 00 or 30."), + }, + }, + "pool_size": schema.Int64Attribute{ + Description: "The number of machines (either as an absolute number or a percentage of the machines in the delivery group, depending on the value of PoolUsingPercentage) that are to be maintained in a running state, whether they are in use or not.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + }, + } +} + +func (PowerTimeSchemePoolSizeScheduleRequestModel) GetAttributes() map[string]schema.Attribute { + return PowerTimeSchemePoolSizeScheduleRequestModel{}.GetSchema().Attributes +} + type DeliveryGroupPowerTimeScheme struct { - DaysOfWeek []types.String `tfsdk:"days_of_week"` - DisplayName types.String `tfsdk:"display_name"` - PeakTimeRanges []types.String `tfsdk:"peak_time_ranges"` - PoolSizeSchedule []PowerTimeSchemePoolSizeScheduleRequestModel `tfsdk:"pool_size_schedules"` - PoolUsingPercentage types.Bool `tfsdk:"pool_using_percentage"` + DaysOfWeek types.Set `tfsdk:"days_of_week"` //Set[string] + DisplayName types.String `tfsdk:"display_name"` + PeakTimeRanges types.Set `tfsdk:"peak_time_ranges"` //Set[string] + PoolSizeSchedule types.List `tfsdk:"pool_size_schedules"` //List[PowerTimeSchemePoolSizeScheduleRequestModel] + PoolUsingPercentage types.Bool `tfsdk:"pool_using_percentage"` +} + +func (DeliveryGroupPowerTimeScheme) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "days_of_week": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The pattern of days of the week that the power time scheme covers.", + Required: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + stringvalidator.OneOf( + "Unknown", + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Weekdays", + "Weekend", + ), + ), + }, + }, + "display_name": schema.StringAttribute{ + Description: "The name of the power time scheme as displayed in the console.", + Required: true, + }, + "peak_time_ranges": schema.SetAttribute{ + ElementType: types.StringType, + Description: "Peak time ranges during the day. e.g. 09:00-17:00", + Required: true, + }, + "pool_size_schedules": schema.ListNestedAttribute{ + Description: "Pool size schedules during the day. Each is specified as a time range and an indicator of the number of machines that should be powered on during that time range. Do not specify schedules when no machines should be powered on.", + Optional: true, + NestedObject: PowerTimeSchemePoolSizeScheduleRequestModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "pool_using_percentage": schema.BoolAttribute{ + Description: "Indicates whether the integer values in the pool size array are to be treated as absolute values (if this value is `false`) or as percentages of the number of machines in the delivery group (if this value is `true`).", + Required: true, + }, + }, + } +} + +func (DeliveryGroupPowerTimeScheme) GetAttributes() map[string]schema.Attribute { + return DeliveryGroupPowerTimeScheme{}.GetSchema().Attributes } type DeliveryGroupRebootNotificationToUsers struct { @@ -32,90 +153,602 @@ type DeliveryGroupRebootNotificationToUsers struct { NotificationTitle types.String `tfsdk:"notification_title"` } +func (DeliveryGroupRebootNotificationToUsers) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "The reboot notification for the reboot schedule. Not available for natural reboot.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "notification_duration_minutes": schema.Int64Attribute{ + Description: "Send notification to users X minutes before user is logged off. Can only be 0, 1, 5 or 15. 0 means no notification.", + Required: true, + Validators: []validator.Int64{ + int64validator.OneOf(0, 1, 5, 15), + }, + }, + "notification_title": schema.StringAttribute{ + Description: "The title to be displayed to users before they are logged off.", + Required: true, + }, + "notification_message": schema.StringAttribute{ + Description: "The message to be displayed to users before they are logged off.", + Required: true, + }, + "notification_repeat_every_5_minutes": schema.BoolAttribute{ + Description: "Repeat notification every 5 minutes, only available for 15 minutes notification duration. ", + Optional: true, + }, + }, + } +} + +func (DeliveryGroupRebootNotificationToUsers) GetAttributes() map[string]schema.Attribute { + return DeliveryGroupRebootNotificationToUsers{}.GetSchema().Attributes +} + +// ensure DeliveryGroupRebootSchedule implements RefreshableListItemWithAttributes +var _ util.RefreshableListItemWithAttributes[citrixorchestration.RebootScheduleResponseModel] = DeliveryGroupRebootSchedule{} + type DeliveryGroupRebootSchedule struct { - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - RebootScheduleEnabled types.Bool `tfsdk:"reboot_schedule_enabled"` - RestrictToTag types.String `tfsdk:"restrict_to_tag"` - IgnoreMaintenanceMode types.Bool `tfsdk:"ignore_maintenance_mode"` - Frequency types.String `tfsdk:"frequency"` - FrequencyFactor types.Int64 `tfsdk:"frequency_factor"` - StartDate types.String `tfsdk:"start_date"` - StartTime types.String `tfsdk:"start_time"` - RebootDurationMinutes types.Int64 `tfsdk:"reboot_duration_minutes"` - UseNaturalRebootSchedule types.Bool `tfsdk:"natural_reboot_schedule"` - DaysInWeek []types.String `tfsdk:"days_in_week"` - WeekInMonth types.String `tfsdk:"week_in_month"` - DayInMonth types.String `tfsdk:"day_in_month"` - DeliveryGroupRebootNotificationToUsers *DeliveryGroupRebootNotificationToUsers `tfsdk:"reboot_notification_to_users"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + RebootScheduleEnabled types.Bool `tfsdk:"reboot_schedule_enabled"` + RestrictToTag types.String `tfsdk:"restrict_to_tag"` + IgnoreMaintenanceMode types.Bool `tfsdk:"ignore_maintenance_mode"` + Frequency types.String `tfsdk:"frequency"` + FrequencyFactor types.Int64 `tfsdk:"frequency_factor"` + StartDate types.String `tfsdk:"start_date"` + StartTime types.String `tfsdk:"start_time"` + RebootDurationMinutes types.Int64 `tfsdk:"reboot_duration_minutes"` + UseNaturalRebootSchedule types.Bool `tfsdk:"natural_reboot_schedule"` + DaysInWeek types.Set `tfsdk:"days_in_week"` //Set[string] + WeekInMonth types.String `tfsdk:"week_in_month"` + DayInMonth types.String `tfsdk:"day_in_month"` + DeliveryGroupRebootNotificationToUsers types.Object `tfsdk:"reboot_notification_to_users"` //DeliveryGroupRebootNotificationToUsers +} + +func (r DeliveryGroupRebootSchedule) GetKey() string { + return r.Name.ValueString() +} + +func (DeliveryGroupRebootSchedule) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "The name of the reboot schedule.", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "The description of the reboot schedule.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "reboot_schedule_enabled": schema.BoolAttribute{ + Description: "Whether the reboot schedule is enabled.", + Required: true, + }, + "restrict_to_tag": schema.StringAttribute{ + Description: "The tag to which the reboot schedule is restricted.", + Optional: true, + }, + "ignore_maintenance_mode": schema.BoolAttribute{ + Description: "Whether the reboot schedule ignores machines in the maintenance mode.", + Required: true, + }, + "frequency": schema.StringAttribute{ + Description: "The frequency of the reboot schedule. Can only be set to `Daily`, `Weekly`, `Monthly`, or `Once`.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "Daily", + "Weekly", + "Monthly", + "Once", + ), + }, + }, + "frequency_factor": schema.Int64Attribute{ + Description: "Repeats every X days/weeks/months. Minimum value is 1.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + "start_date": schema.StringAttribute{ + Description: "The date on which the reboot schedule starts. The date format is `YYYY-MM-DD`.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.DateRegex), "Date must be in the format YYYY-MM-DD"), + }, + }, + "start_time": schema.StringAttribute{ + Description: "The time at which the reboot schedule starts. The time format is `HH:MM`.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.TimeRegex), "Time must be in the format HH:MM"), + }, + }, + "reboot_duration_minutes": schema.Int64Attribute{ + Description: "Restart all machines within x minutes. 0 means restarting all machines at the same time. To restart machines after draining sessions, set natural_reboot_schedule to true instead. ", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + }, + "natural_reboot_schedule": schema.BoolAttribute{ + Description: "Indicates whether the reboot will be a natural reboot, where the machines will be rebooted when they have no sessions. This should set to false for reboot_duration_minutes to work. Once UseNaturalReboot is set to true, RebootDurationMinutes won't have any effect.", + Required: true, + }, + "days_in_week": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The days of the week on which the reboot schedule runs weekly. Can only be set to `Sunday`, `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, or `Saturday`.", + Optional: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + stringvalidator.OneOf( + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ), + ), + }, + }, + "week_in_month": schema.StringAttribute{ + Description: "The week in the month on which the reboot schedule runs monthly. Can only be set to `First`, `Second`, `Third`, `Fourth`, or `Last`.", + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "First", + "Second", + "Third", + "Fourth", + "Last", + ), + }, + }, + "day_in_month": schema.StringAttribute{ + Description: "The day in the month on which the reboot schedule runs monthly. Can only be set to `Sunday`, `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, or `Saturday`.", + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ), + }, + }, + "reboot_notification_to_users": DeliveryGroupRebootNotificationToUsers{}.GetSchema(), + }, + } +} + +func (DeliveryGroupRebootSchedule) GetAttributes() map[string]schema.Attribute { + return DeliveryGroupRebootSchedule{}.GetSchema().Attributes } 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"` - 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"` - 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"` - 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 []DeliveryGroupPowerTimeScheme `tfsdk:"power_time_schemes"` + 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"` + 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"` + 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"` + 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 { + return schema.SingleNestedAttribute{ + Description: "The power management settings governing the machine(s) in the delivery group.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "autoscale_enabled": schema.BoolAttribute{ + Description: "Whether auto-scale is enabled for the delivery group.", + Required: true, + }, + "timezone": schema.StringAttribute{ + Description: "The time zone in which this delivery group's machines reside.", + Optional: true, + }, + "peak_disconnect_timeout_minutes": schema.Int64Attribute{ + Description: "The number of minutes before the configured action should be performed after a user session disconnects in peak hours.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "peak_log_off_action": schema.StringAttribute{ + Description: "The action to be performed after a configurable period of a user session ending in peak hours.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(string(citrixorchestration.SESSIONCHANGEHOSTINGACTION_NOTHING)), + Validators: []validator.String{ + sessionHostingActionEnumValidator(), + }, + }, + "peak_disconnect_action": schema.StringAttribute{ + Description: "The action to be performed after a configurable period of a user session disconnecting in peak hours.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(string(citrixorchestration.SESSIONCHANGEHOSTINGACTION_NOTHING)), + Validators: []validator.String{ + sessionHostingActionEnumValidator(), + }, + }, + "peak_extended_disconnect_action": schema.StringAttribute{ + Description: "The action to be performed after a second configurable period of a user session disconnecting in peak hours.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(string(citrixorchestration.SESSIONCHANGEHOSTINGACTION_NOTHING)), + Validators: []validator.String{ + sessionHostingActionEnumValidator(), + }, + }, + "peak_extended_disconnect_timeout_minutes": schema.Int64Attribute{ + Description: "The number of minutes before the second configured action should be performed after a user session disconnects in peak hours.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "off_peak_disconnect_timeout_minutes": schema.Int64Attribute{ + Description: "The number of minutes before the configured action should be performed after a user session disconnectts outside peak hours.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "off_peak_log_off_action": schema.StringAttribute{ + Description: "The action to be performed after a configurable period of a user session ending outside peak hours.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(string(citrixorchestration.SESSIONCHANGEHOSTINGACTION_NOTHING)), + Validators: []validator.String{ + sessionHostingActionEnumValidator(), + }, + }, + "off_peak_disconnect_action": schema.StringAttribute{ + Description: "The action to be performed after a configurable period of a user session disconnecting outside peak hours.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(string(citrixorchestration.SESSIONCHANGEHOSTINGACTION_NOTHING)), + Validators: []validator.String{ + sessionHostingActionEnumValidator(), + }, + }, + "off_peak_extended_disconnect_action": schema.StringAttribute{ + Description: "The action to be performed after a second configurable period of a user session disconnecting outside peak hours.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(string(citrixorchestration.SESSIONCHANGEHOSTINGACTION_NOTHING)), + Validators: []validator.String{ + sessionHostingActionEnumValidator(), + }, + }, + "off_peak_extended_disconnect_timeout_minutes": schema.Int64Attribute{ + Description: "The number of minutes before the second configured action should be performed after a user session disconnects outside peak hours.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "peak_buffer_size_percent": schema.Int64Attribute{ + Description: "The percentage of machines in the delivery group that should be kept available in an idle state in peak hours.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "off_peak_buffer_size_percent": schema.Int64Attribute{ + Description: "The percentage of machines in the delivery group that should be kept available in an idle state outside peak hours.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "power_off_delay_minutes": schema.Int64Attribute{ + Description: "Delay before machines are powered off, when scaling down. Specified in minutes. By default, the power-off delay is 30 minutes. You can set it in a range of 0 to 60 minutes. Applies only to multi-session machines.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(30), + Validators: []validator.Int64{ + int64validator.Between(0, 60), + }, + }, + "disconnect_peak_idle_session_after_seconds": schema.Int64Attribute{ + Description: "Specifies the time in seconds after which an idle session belonging to the delivery group is disconnected during peak time.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "disconnect_off_peak_idle_session_after_seconds": schema.Int64Attribute{ + Description: "Specifies the time in seconds after which an idle session belonging to the delivery group is disconnected during off-peak time.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "log_off_peak_disconnected_session_after_seconds": schema.Int64Attribute{ + Description: "Specifies the time in seconds after which a disconnected session belonging to the delivery group is terminated during peak time.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "log_off_off_peak_disconnected_session_after_seconds": schema.Int64Attribute{ + Description: "Specifies the time in seconds after which a disconnected session belonging to the delivery group is terminated during off peak time.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "power_time_schemes": schema.ListNestedAttribute{ + Description: "Power management time schemes. No two schemes for the same delivery group may cover the same day of the week.", + Required: true, + NestedObject: DeliveryGroupPowerTimeScheme{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + }, + } +} + +func (DeliveryGroupPowerManagementSettings) GetAttributes() map[string]schema.Attribute { + return DeliveryGroupPowerManagementSettings{}.GetSchema().Attributes } type RestrictedAccessUsers struct { - AllowList []types.String `tfsdk:"allow_list"` - BlockList []types.String `tfsdk:"block_list"` + AllowList types.Set `tfsdk:"allow_list"` //Set[string] + BlockList types.Set `tfsdk:"block_list"` //Set[string] } +func (RestrictedAccessUsers) GetSchema() schema.SingleNestedAttribute { + return RestrictedAccessUsers{}.getSchemaInternal(false) +} + +func (RestrictedAccessUsers) GetSchemaForDeliveryGroup() schema.SingleNestedAttribute { + return RestrictedAccessUsers{}.getSchemaInternal(true) +} + +func (RestrictedAccessUsers) getSchemaInternal(forDeliveryGroup bool) schema.SingleNestedAttribute { + resource := "Delivery Group" + description := "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." + if !forDeliveryGroup { + resource = "Desktop" + description = "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. Required for Remote PC Delivery Groups." + } + + return schema.SingleNestedAttribute{ + Description: description, + Optional: true, + Attributes: map[string]schema.Attribute{ + "allow_list": schema.SetAttribute{ + ElementType: types.StringType, + Description: fmt.Sprintf("Users who can use this %s. Must be in `DOMAIN\\UserOrGroupName` or `user@domain.com` format", resource), + Optional: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + validator.String( + stringvalidator.RegexMatches(regexp.MustCompile(util.SamAndUpnRegex), "must be in `DOMAIN\\UserOrGroupName` or `user@domain.com` format"), + ), + ), + setvalidator.SizeAtLeast(1), + }, + }, + "block_list": schema.SetAttribute{ + ElementType: types.StringType, + Description: fmt.Sprintf("Users who cannot use this %s. A block list is meaningful only when used to block users in the allow list. Must be in `DOMAIN\\UserOrGroupName` or `user@domain.com` format", resource), + Optional: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + validator.String( + stringvalidator.RegexMatches(regexp.MustCompile(util.SamAndUpnRegex), "must be in `DOMAIN\\UserOrGroupName` or `user@domain.com` format"), + ), + ), + setvalidator.SizeAtLeast(1), + }, + }, + }, + } +} + +func (RestrictedAccessUsers) GetAttributes() map[string]schema.Attribute { + return RestrictedAccessUsers{}.GetSchema().Attributes +} + +// ensure DeliveryGroupDesktop implements RefreshableListItemWithAttributes +var _ util.RefreshableListItemWithAttributes[citrixorchestration.DesktopResponseModel] = DeliveryGroupDesktop{} + type DeliveryGroupDesktop struct { - PublishedName types.String `tfsdk:"published_name"` - DesktopDescription types.String `tfsdk:"description"` - Enabled types.Bool `tfsdk:"enabled"` - EnableSessionRoaming types.Bool `tfsdk:"enable_session_roaming"` - RestrictedAccessUsers *RestrictedAccessUsers `tfsdk:"restricted_access_users"` + PublishedName types.String `tfsdk:"published_name"` + DesktopDescription types.String `tfsdk:"description"` + Enabled types.Bool `tfsdk:"enabled"` + EnableSessionRoaming types.Bool `tfsdk:"enable_session_roaming"` + RestrictedAccessUsers types.Object `tfsdk:"restricted_access_users"` //RestrictedAccessUsers +} + +func (r DeliveryGroupDesktop) GetKey() string { + return r.PublishedName.ValueString() +} + +func (DeliveryGroupDesktop) GetSchema() schema.NestedAttributeObject { + var restrictedAccessUsers RestrictedAccessUsers + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "published_name": schema.StringAttribute{ + Description: "A display name for the desktop.", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "A description for the published desktop. The name and description are shown in Citrix Workspace app.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "enabled": schema.BoolAttribute{ + Description: "Specify whether to enable the delivery of this desktop.", + Required: true, + }, + "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. Should be set to false for Remote PC Delivery Group.", + Required: true, + }, + "restricted_access_users": restrictedAccessUsers.GetSchema(), + }, + } +} + +func (DeliveryGroupDesktop) GetAttributes() map[string]schema.Attribute { + return DeliveryGroupDesktop{}.GetSchema().Attributes } // DeliveryGroupResourceModel maps the resource schema data. type DeliveryGroupResourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - RestrictedAccessUsers *RestrictedAccessUsers `tfsdk:"restricted_access_users"` - AllowAnonymousAccess types.Bool `tfsdk:"allow_anonymous_access"` - Desktops []DeliveryGroupDesktop `tfsdk:"desktops"` - AssociatedMachineCatalogs []DeliveryGroupMachineCatalogModel `tfsdk:"associated_machine_catalogs"` - AutoscaleSettings *DeliveryGroupPowerManagementSettings `tfsdk:"autoscale_settings"` - RebootSchedules []DeliveryGroupRebootSchedule `tfsdk:"reboot_schedules"` - TotalMachines types.Int64 `tfsdk:"total_machines"` - PolicySetId types.String `tfsdk:"policy_set_id"` - MinimumFunctionalLevel types.String `tfsdk:"minimum_functional_level"` -} - -func (r DeliveryGroupResourceModel) RefreshPropertyValues(deliveryGroup *citrixorchestration.DeliveryGroupDetailResponseModel, dgDesktops *citrixorchestration.DesktopResponseModelCollection, dgPowerTimeSchemes *citrixorchestration.PowerTimeSchemeResponseModelCollection, dgMachines *citrixorchestration.MachineResponseModelCollection, dgRebootSchedule *citrixorchestration.RebootScheduleResponseModelCollection) DeliveryGroupResourceModel { + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + RestrictedAccessUsers types.Object `tfsdk:"restricted_access_users"` + AllowAnonymousAccess types.Bool `tfsdk:"allow_anonymous_access"` + Desktops types.List `tfsdk:"desktops"` //List[DeliveryGroupDesktop] + AssociatedMachineCatalogs types.List `tfsdk:"associated_machine_catalogs"` //List[DeliveryGroupMachineCatalogModel] + AutoscaleSettings types.Object `tfsdk:"autoscale_settings"` //DeliveryGroupPowerManagementSettings + RebootSchedules types.List `tfsdk:"reboot_schedules"` //List[DeliveryGroupRebootSchedule] + TotalMachines types.Int64 `tfsdk:"total_machines"` + PolicySetId types.String `tfsdk:"policy_set_id"` + MinimumFunctionalLevel types.String `tfsdk:"minimum_functional_level"` + StoreFrontServers types.Set `tfsdk:"storefront_servers"` //Set[string] + Scopes types.Set `tfsdk:"scopes"` //Set[String] + MakeResourcesAvailableInLHC types.Bool `tfsdk:"make_resources_available_in_lhc"` +} + +func GetSchema() schema.Schema { + return schema.Schema{ + Description: "Manages a delivery group.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the delivery group.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the delivery group.", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the delivery group.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "restricted_access_users": RestrictedAccessUsers{}.GetSchemaForDeliveryGroup(), + "allow_anonymous_access": schema.BoolAttribute{ + Description: "Give access to unauthenticated (anonymous) users; no credentials are required to access StoreFront. This feature requires a StoreFront store for unauthenticated users.", + Optional: true, + }, + "desktops": schema.ListNestedAttribute{ + Description: "A list of Desktop resources to publish on the delivery group. Only 1 desktop can be added to a Remote PC Delivery Group.", + Optional: true, + NestedObject: DeliveryGroupDesktop{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "associated_machine_catalogs": schema.ListNestedAttribute{ + Description: "Machine catalogs from which to assign machines to the newly created delivery group.", + Required: true, + NestedObject: DeliveryGroupMachineCatalogModel{}.GetSchema(), + }, + "autoscale_settings": DeliveryGroupPowerManagementSettings{}.GetSchema(), + "reboot_schedules": schema.ListNestedAttribute{ + Description: "The reboot schedule for the delivery group.", + Optional: true, + NestedObject: DeliveryGroupRebootSchedule{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "total_machines": schema.Int64Attribute{ + Description: "The total number of machines in the delivery group.", + Computed: true, + }, + "policy_set_id": schema.StringAttribute{ + Description: "GUID identifier of the policy set.", + Optional: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "minimum_functional_level": schema.StringAttribute{ + Description: "Specifies the minimum functional level for the VDA machines in the delivery group. Defaults to `L7_20`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("L7_20"), + Validators: []validator.String{ + stringvalidator.OneOfCaseInsensitive(util.GetAllowedFunctionalLevelValues()...), + }, + }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The IDs of the scopes for the delivery group to be a part of.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + validator.String( + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + ), + ), + }, + }, + "storefront_servers": schema.SetAttribute{ + ElementType: types.StringType, + Description: "A list of GUID identifiers of StoreFront Servers to associate with the delivery group.", + Optional: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + validator.String( + stringvalidator.RegexMatches(regexp.MustCompile(util.StoreFrontServerIdRegex), "must be specified with StoreFront Server ID format"), + ), + ), + setvalidator.SizeAtLeast(1), + }, + }, + "make_resources_available_in_lhc": schema.BoolAttribute{ + Description: "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. " + + "This setting only impacts Single Session OS Random (pooled) desktops which are power managed. LHC is always enabled for Single Session OS static and Multi Session OS desktops." + + "When set to `true`, machines will remain available and allow new connections and changes to the machine caused by a user might be present in subsequent sessions. " + + "When set to `false`, machines in the delivery group will be unavailable for new connections during a Local Host Cache event. ", + Optional: true, + }, + }, + } +} + +func (r DeliveryGroupResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, deliveryGroup *citrixorchestration.DeliveryGroupDetailResponseModel, dgDesktops *citrixorchestration.DesktopResponseModelCollection, dgPowerTimeSchemes *citrixorchestration.PowerTimeSchemeResponseModelCollection, dgMachines *citrixorchestration.MachineResponseModelCollection, dgRebootSchedule *citrixorchestration.RebootScheduleResponseModelCollection) DeliveryGroupResourceModel { // Set required values r.Id = types.StringValue(deliveryGroup.GetId()) r.Name = types.StringValue(deliveryGroup.GetName()) r.TotalMachines = types.Int64Value(int64(deliveryGroup.GetTotalMachines())) + r.Description = types.StringValue(deliveryGroup.GetDescription()) // Set optional values - if deliveryGroup.GetDescription() != "" { - r.Description = types.StringValue(deliveryGroup.GetDescription()) - } else { - r.Description = types.StringNull() - } - if deliveryGroup.GetPolicySetGuid() != "" { r.PolicySetId = types.StringValue(deliveryGroup.GetPolicySetGuid()) } else { @@ -124,11 +757,28 @@ func (r DeliveryGroupResourceModel) RefreshPropertyValues(deliveryGroup *citrixo minimumFunctionalLevel := deliveryGroup.GetMinimumFunctionalLevel() r.MinimumFunctionalLevel = types.StringValue(string(minimumFunctionalLevel)) + scopeIds := util.GetIdsForScopeObjects(deliveryGroup.GetScopes()) + r.Scopes = util.StringArrayToStringSet(ctx, diagnostics, scopeIds) + + if !r.MakeResourcesAvailableInLHC.IsNull() { + r.MakeResourcesAvailableInLHC = types.BoolValue(deliveryGroup.GetReuseMachinesWithoutShutdownInOutage()) + } + + r = r.updatePlanWithRestrictedAccessUsers(ctx, diagnostics, deliveryGroup) + r = r.updatePlanWithDesktops(ctx, diagnostics, dgDesktops) + r = r.updatePlanWithAssociatedCatalogs(ctx, diagnostics, dgMachines) + r = r.updatePlanWithAutoscaleSettings(ctx, diagnostics, deliveryGroup, dgPowerTimeSchemes) + r = r.updatePlanWithRebootSchedule(ctx, diagnostics, dgRebootSchedule) + + if len(deliveryGroup.GetStoreFrontServersForHostedReceiver()) > 0 || !r.StoreFrontServers.IsNull() { + var remoteAssociatedStoreFrontServers []string + for _, server := range deliveryGroup.GetStoreFrontServersForHostedReceiver() { + remoteAssociatedStoreFrontServers = append(remoteAssociatedStoreFrontServers, server.GetId()) + } + r.StoreFrontServers = util.StringArrayToStringSet(ctx, diagnostics, remoteAssociatedStoreFrontServers) + } else { + r.StoreFrontServers = types.SetNull(types.StringType) + } - r = r.updatePlanWithRestrictedAccessUsers(deliveryGroup) - r = r.updatePlanWithDesktops(dgDesktops) - r = r.updatePlanWithAssociatedCatalogs(dgMachines) - r = r.updatePlanWithAutoscaleSettings(deliveryGroup, dgPowerTimeSchemes) - r = r.updatePlanWithRebootSchedule(dgRebootSchedule) return r } diff --git a/internal/daas/delivery_group/delivery_group_utils.go b/internal/daas/delivery_group/delivery_group_utils.go index bb8c99d..b71733d 100644 --- a/internal/daas/delivery_group/delivery_group_utils.go +++ b/internal/daas/delivery_group/delivery_group_utils.go @@ -1,30 +1,32 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package delivery_group import ( "context" "fmt" - "reflect" - "regexp" - "slices" + "net/http" "strconv" "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-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/resource/schema" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) +type AssociatedMachineCatalogProperties struct { + SessionSupport citrixorchestration.SessionSupport + IsPowerManaged bool + IsRemotePcCatalog bool + IdentityType citrixorchestration.IdentityType + AllocationType citrixorchestration.AllocationType +} + func getDeliveryGroup(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, deliveryGroupId string) (*citrixorchestration.DeliveryGroupDetailResponseModel, error) { getDeliveryGroupRequest := client.ApiClient.DeliveryGroupsAPIsDAAS.DeliveryGroupsGetDeliveryGroup(ctx, deliveryGroupId) deliveryGroup, httpResp, err := citrixdaasclient.ExecuteWithRetry[*citrixorchestration.DeliveryGroupDetailResponseModel](getDeliveryGroupRequest, client) @@ -115,60 +117,63 @@ func sessionHostingActionEnumValidator() validator.String { return util.GetValidatorFromEnum(citrixorchestration.AllowedSessionChangeHostingActionEnumValues) } -func validatePowerManagementSettings(plan DeliveryGroupResourceModel, sessionSupport citrixorchestration.SessionSupport) (bool, string) { - if plan.AutoscaleSettings == nil || sessionSupport == citrixorchestration.SESSIONSUPPORT_SINGLE_SESSION { +func validatePowerManagementSettings(ctx context.Context, diags *diag.Diagnostics, plan DeliveryGroupResourceModel, sessionSupport citrixorchestration.SessionSupport) (bool, string) { + if plan.AutoscaleSettings.IsNull() || sessionSupport == citrixorchestration.SESSIONSUPPORT_SINGLE_SESSION { return true, "" } + autoscale := util.ObjectValueToTypedObject[DeliveryGroupPowerManagementSettings](ctx, diags, plan.AutoscaleSettings) errStringSuffix := "cannot be set for a Multisession catalog" - if plan.AutoscaleSettings.PeakLogOffAction.ValueString() != "Nothing" { + if autoscale.PeakLogOffAction.ValueString() != "Nothing" { return false, "PeakLogOffAction " + errStringSuffix } - if plan.AutoscaleSettings.OffPeakLogOffAction.ValueString() != "Nothing" { + if autoscale.OffPeakLogOffAction.ValueString() != "Nothing" { return false, "OffPeakLogOffAction " + errStringSuffix } - if plan.AutoscaleSettings.PeakDisconnectAction.ValueString() != "Nothing" { + if autoscale.PeakDisconnectAction.ValueString() != "Nothing" { return false, "PeakDisconnectAction " + errStringSuffix } - if plan.AutoscaleSettings.PeakExtendedDisconnectAction.ValueString() != "Nothing" { + if autoscale.PeakExtendedDisconnectAction.ValueString() != "Nothing" { return false, "PeakDisconnectTimeoutMinutes " + errStringSuffix } - if plan.AutoscaleSettings.OffPeakDisconnectAction.ValueString() != "Nothing" { + if autoscale.OffPeakDisconnectAction.ValueString() != "Nothing" { return false, "OffPeakDisconnectAction " + errStringSuffix } - if plan.AutoscaleSettings.OffPeakExtendedDisconnectAction.ValueString() != "Nothing" { + if autoscale.OffPeakExtendedDisconnectAction.ValueString() != "Nothing" { return false, "OffPeakDisconnectTimeoutMinutes " + errStringSuffix } - if plan.AutoscaleSettings.PeakDisconnectTimeoutMinutes.ValueInt64() != 0 { + if autoscale.PeakDisconnectTimeoutMinutes.ValueInt64() != 0 { return false, "PeakDisconnectTimeoutMinutes " + errStringSuffix } - if plan.AutoscaleSettings.PeakExtendedDisconnectTimeoutMinutes.ValueInt64() != 0 { + if autoscale.PeakExtendedDisconnectTimeoutMinutes.ValueInt64() != 0 { return false, "PeakExtendedDisconnectTimeoutMinutes " + errStringSuffix } - if plan.AutoscaleSettings.OffPeakDisconnectTimeoutMinutes.ValueInt64() != 0 { + if autoscale.OffPeakDisconnectTimeoutMinutes.ValueInt64() != 0 { return false, "OffPeakDisconnectTimeoutMinutes " + errStringSuffix } - if plan.AutoscaleSettings.OffPeakExtendedDisconnectTimeoutMinutes.ValueInt64() != 0 { + if autoscale.OffPeakExtendedDisconnectTimeoutMinutes.ValueInt64() != 0 { return false, "OffPeakExtendedDisconnectTimeoutMinutes " + errStringSuffix } return true, "" } -func validateAndReturnMachineCatalogSessionSupport(ctx context.Context, client citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, dgMachineCatalogs []DeliveryGroupMachineCatalogModel, addErrorIfCatalogNotFound bool) (catalogSessionSupport *citrixorchestration.SessionSupport, isPowerManagedCatalog bool, isRemotePcCatalog bool, catalogIdentityType citrixorchestration.IdentityType, err error) { - var sessionSupport *citrixorchestration.SessionSupport +func validateAndReturnMachineCatalogSessionSupport(ctx context.Context, client citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, dgMachineCatalogs []DeliveryGroupMachineCatalogModel, addErrorIfCatalogNotFound bool) (machineCatalogProperties AssociatedMachineCatalogProperties, err error) { var provisioningType *citrixorchestration.ProvisioningType + var sessionSupport citrixorchestration.SessionSupport + var allocationType citrixorchestration.AllocationType var identityType citrixorchestration.IdentityType + var associatedMachineCatalogProperties AssociatedMachineCatalogProperties isPowerManaged := false isRemotePc := false for _, dgMachineCatalog := range dgMachineCatalogs { @@ -180,7 +185,7 @@ func validateAndReturnMachineCatalogSessionSupport(ctx context.Context, client c catalog, err := util.GetMachineCatalog(ctx, &client, diagnostics, catalogId, addErrorIfCatalogNotFound) if err != nil { - return sessionSupport, false, false, citrixorchestration.IDENTITYTYPE_UNKNOWN, err + return associatedMachineCatalogProperties, err } if provisioningType == nil { @@ -189,6 +194,8 @@ func validateAndReturnMachineCatalogSessionSupport(ctx context.Context, client c isRemotePc = catalog.GetIsRemotePC() provScheme := catalog.GetProvisioningScheme() identityType = provScheme.GetIdentityType() + sessionSupport = catalog.GetSessionSupport() + allocationType = catalog.GetAllocationType() } if *provisioningType != catalog.GetProvisioningType() { @@ -196,7 +203,7 @@ func validateAndReturnMachineCatalogSessionSupport(ctx context.Context, client c diagnostics.AddError("Error validating associated Machine Catalogs", "Ensure all associated Machine Catalogs have the same provisioning type.", ) - return sessionSupport, false, false, citrixorchestration.IDENTITYTYPE_UNKNOWN, err + return associatedMachineCatalogProperties, err } provScheme := catalog.GetProvisioningScheme() @@ -206,7 +213,7 @@ func validateAndReturnMachineCatalogSessionSupport(ctx context.Context, client c diagnostics.AddError("Error validating associated Machine Catalogs", "Ensure all associated Machine Catalogs have the same identity type in provisioning scheme.", ) - return sessionSupport, false, false, citrixorchestration.IDENTITYTYPE_UNKNOWN, err + return associatedMachineCatalogProperties, err } if isPowerManaged != catalog.GetIsPowerManaged() { @@ -214,7 +221,7 @@ func validateAndReturnMachineCatalogSessionSupport(ctx context.Context, client c diagnostics.AddError("Error validating associated Machine Catalogs", "All associated Machine Catalogs must either be power managed or non power managed.", ) - return sessionSupport, false, false, citrixorchestration.IDENTITYTYPE_UNKNOWN, err + return associatedMachineCatalogProperties, err } if isRemotePc != catalog.GetIsRemotePC() { @@ -222,22 +229,29 @@ func validateAndReturnMachineCatalogSessionSupport(ctx context.Context, client c diagnostics.AddError("Error validating associated Machine Catalogs", "All associated Machine Catalogs must either be Remote PC or non Remote PC.", ) - return sessionSupport, false, false, citrixorchestration.IDENTITYTYPE_UNKNOWN, err + return associatedMachineCatalogProperties, err } - if sessionSupport != nil && *sessionSupport != catalog.GetSessionSupport() { + if sessionSupport != "" && sessionSupport != catalog.GetSessionSupport() { err := fmt.Errorf("all associated machine catalogs must have the same session support") diagnostics.AddError("Error validating associated Machine Catalogs", "Ensure all associated Machine Catalogs have the same Session Support.") - return sessionSupport, false, false, citrixorchestration.IDENTITYTYPE_UNKNOWN, err + return associatedMachineCatalogProperties, err } - if sessionSupport == nil { - sessionSupportValue := catalog.GetSessionSupport() - sessionSupport = &sessionSupportValue + if allocationType != "" && allocationType != catalog.GetAllocationType() { + err := fmt.Errorf("all associated machine catalogs must have the same allocation type") + diagnostics.AddError("Error validating associated Machine Catalogs", "Ensure all associated Machine Catalogs have the same Allocation Type.") + return associatedMachineCatalogProperties, err } } - return sessionSupport, isPowerManaged, isRemotePc, identityType, err + associatedMachineCatalogProperties.SessionSupport = sessionSupport + associatedMachineCatalogProperties.AllocationType = allocationType + associatedMachineCatalogProperties.IdentityType = identityType + associatedMachineCatalogProperties.IsPowerManaged = isPowerManaged + associatedMachineCatalogProperties.IsRemotePcCatalog = isRemotePc + + return associatedMachineCatalogProperties, err } func getDeliveryGroupAddMachinesRequest(associatedMachineCatalogs []DeliveryGroupMachineCatalogModel) []citrixorchestration.DeliveryGroupAddMachinesRequestModel { @@ -317,7 +331,7 @@ func addRemoveMachinesFromDeliveryGroup(ctx context.Context, client *citrixdaasc existingAssociatedMachineCatalogsMap := createExistingCatalogsAndMachinesMap(deliveryGroupMachines) requestedAssociatedMachineCatalogsMap := map[string]bool{} - for _, associatedMachineCatalog := range plan.AssociatedMachineCatalogs { + for _, associatedMachineCatalog := range util.ObjectListToTypedArray[DeliveryGroupMachineCatalogModel](ctx, diagnostics, plan.AssociatedMachineCatalogs) { requestedAssociatedMachineCatalogsMap[associatedMachineCatalog.MachineCatalog.ValueString()] = true @@ -365,51 +379,9 @@ func addRemoveMachinesFromDeliveryGroup(ctx context.Context, client *citrixdaasc return nil } -func getSchemaForRestrictedAccessUsers(forDeliveryGroup bool) schema.NestedAttribute { - resource := "Delivery Group" - description := "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." - if !forDeliveryGroup { - resource = "Desktop" - description = "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. Required for Remote PC Delivery Groups." - } - - return schema.SingleNestedAttribute{ - Description: description, - Optional: true, - Attributes: map[string]schema.Attribute{ - "allow_list": schema.ListAttribute{ - ElementType: types.StringType, - Description: fmt.Sprintf("Users who can use this %s. Must be in `DOMAIN\\UserOrGroupName` or `user@domain.com` format", resource), - Optional: true, - Validators: []validator.List{ - listvalidator.ValueStringsAre( - validator.String( - stringvalidator.RegexMatches(regexp.MustCompile(fmt.Sprintf("%s|%s", util.SamRegex, util.UpnRegex)), "must be in `DOMAIN\\UserOrGroupName` or `user@domain.com` format"), - ), - ), - listvalidator.SizeAtLeast(1), - }, - }, - "block_list": schema.ListAttribute{ - ElementType: types.StringType, - Description: fmt.Sprintf("Users who cannot use this %s. A block list is meaningful only when used to block users in the allow list. Must be in `Domain\\UserOrGroupName` or `user@domain.com` format", resource), - Optional: true, - Validators: []validator.List{ - listvalidator.ValueStringsAre( - validator.String( - stringvalidator.RegexMatches(regexp.MustCompile(fmt.Sprintf("%s|%s", util.SamRegex, util.UpnRegex)), "must be in `DOMAIN\\UserOrGroupName` or `user@domain.com` format"), - ), - ), - listvalidator.SizeAtLeast(1), - }, - }, - }, - } -} - -func validatePowerTimeSchemes(diagnostics *diag.Diagnostics, powerTimeSchemes []DeliveryGroupPowerTimeScheme) { +func validatePowerTimeSchemes(ctx context.Context, diagnostics *diag.Diagnostics, powerTimeSchemes []DeliveryGroupPowerTimeScheme) { for _, powerTimeScheme := range powerTimeSchemes { - if powerTimeScheme.PoolSizeSchedule == nil { + if powerTimeScheme.PoolSizeSchedule.IsNull() { continue } @@ -419,7 +391,10 @@ func validatePowerTimeSchemes(diagnostics *diag.Diagnostics, powerTimeSchemes [] hoursPoolSizeArray := make([]int, 24) minutesPoolSizeArray := make([]int, 24) - for _, schedule := range powerTimeScheme.PoolSizeSchedule { + for _, schedule := range util.ObjectListToTypedArray[PowerTimeSchemePoolSizeScheduleRequestModel](ctx, diagnostics, powerTimeScheme.PoolSizeSchedule) { + if schedule.TimeRange.IsNull() { + continue + } timeRanges := strings.Split(schedule.TimeRange.ValueString(), "-") startTimes := strings.Split(timeRanges[0], ":") @@ -525,11 +500,11 @@ func validatePowerTimeSchemes(diagnostics *diag.Diagnostics, powerTimeSchemes [] } } -func validateRebootSchedules(diagnostics *diag.Diagnostics, rebootSchedules []DeliveryGroupRebootSchedule) { +func validateRebootSchedules(ctx context.Context, diagnostics *diag.Diagnostics, rebootSchedules []DeliveryGroupRebootSchedule) { for _, rebootSchedule := range rebootSchedules { switch freq := rebootSchedule.Frequency.ValueString(); freq { case "Weekly": - if len(rebootSchedule.DaysInWeek) == 0 { + if len(rebootSchedule.DaysInWeek.Elements()) == 0 { diagnostics.AddAttributeError( path.Root("days_in_week"), "Missing Attribute Configuration", @@ -554,7 +529,7 @@ func validateRebootSchedules(diagnostics *diag.Diagnostics, rebootSchedules []De } - if rebootSchedule.UseNaturalRebootSchedule.ValueBool() && rebootSchedule.DeliveryGroupRebootNotificationToUsers != nil { + if rebootSchedule.UseNaturalRebootSchedule.ValueBool() && !rebootSchedule.DeliveryGroupRebootNotificationToUsers.IsNull() { diagnostics.AddAttributeError( path.Root("reboot_notification_to_users"), "Incorrect Attribute Configuration", @@ -562,34 +537,66 @@ func validateRebootSchedules(diagnostics *diag.Diagnostics, rebootSchedules []De ) } - if rebootSchedule.DeliveryGroupRebootNotificationToUsers != nil && !rebootSchedule.DeliveryGroupRebootNotificationToUsers.NotificationDurationMinutes.IsNull() && !rebootSchedule.DeliveryGroupRebootNotificationToUsers.NotificationRepeatEvery5Minutes.IsNull() && - rebootSchedule.DeliveryGroupRebootNotificationToUsers.NotificationDurationMinutes.ValueInt64() != 15 { - diagnostics.AddAttributeError( - path.Root("notification_repeat_every_5_minutes"), - "Incorrect Attribute Configuration", - "NotificationRepeatEvery5Minutes can only be set to true when NotificationDurationMinutes is 15 minutes", - ) + if !rebootSchedule.DeliveryGroupRebootNotificationToUsers.IsNull() { + notification := util.ObjectValueToTypedObject[DeliveryGroupRebootNotificationToUsers](ctx, diagnostics, rebootSchedule.DeliveryGroupRebootNotificationToUsers) + if !notification.NotificationDurationMinutes.IsNull() && !notification.NotificationRepeatEvery5Minutes.IsNull() && + notification.NotificationDurationMinutes.ValueInt64() != 15 { + diagnostics.AddAttributeError( + path.Root("notification_repeat_every_5_minutes"), + "Incorrect Attribute Configuration", + "NotificationRepeatEvery5Minutes can only be set to true when NotificationDurationMinutes is 15 minutes", + ) + } } } } -func getRequestModelForDeliveryGroupCreate(diagnostics *diag.Diagnostics, plan DeliveryGroupResourceModel, catalogSessionSupport *citrixorchestration.SessionSupport, identityType citrixorchestration.IdentityType) (citrixorchestration.CreateDeliveryGroupRequestModel, error) { - deliveryGroupMachineCatalogsArray := getDeliveryGroupAddMachinesRequest(plan.AssociatedMachineCatalogs) - deliveryGroupDesktopsArray := parseDeliveryGroupDesktopsToClientModel(plan.Desktops) - deliveryGroupRebootScheduleArray := parseDeliveryGroupRebootScheduleToClientModel(plan.RebootSchedules) +func getRequestModelForDeliveryGroupCreate(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, plan DeliveryGroupResourceModel, associatedMachineCatalogProperties AssociatedMachineCatalogProperties) (citrixorchestration.CreateDeliveryGroupRequestModel, error) { + deliveryGroupMachineCatalogsArray := getDeliveryGroupAddMachinesRequest(util.ObjectListToTypedArray[DeliveryGroupMachineCatalogModel](ctx, diagnostics, plan.AssociatedMachineCatalogs)) + desktops := util.ObjectListToTypedArray[DeliveryGroupDesktop](ctx, diagnostics, plan.Desktops) + deliveryGroupDesktopsArray, err := verifyUsersAndParseDeliveryGroupDesktopsToClientModel(ctx, diagnostics, client, desktops) + + if err != nil { + return citrixorchestration.CreateDeliveryGroupRequestModel{}, err + } - var includedUsers []string - var excludedUsers []string + rebootSchedules := util.ObjectListToTypedArray[DeliveryGroupRebootSchedule](ctx, diagnostics, plan.RebootSchedules) + deliveryGroupRebootScheduleArray := parseDeliveryGroupRebootScheduleToClientModel(ctx, diagnostics, rebootSchedules) + + var httpResp *http.Response + var includedUserIds []string + var excludedUserIds []string includedUsersFilterEnabled := false excludedUsersFilterEnabled := false - if plan.RestrictedAccessUsers != nil { + if !plan.RestrictedAccessUsers.IsNull() { includedUsersFilterEnabled = true - includedUsers = util.ConvertBaseStringArrayToPrimitiveStringArray(plan.RestrictedAccessUsers.AllowList) + users := util.ObjectValueToTypedObject[RestrictedAccessUsers](ctx, diagnostics, plan.RestrictedAccessUsers) + includedUsers := util.StringSetToStringArray(ctx, diagnostics, users.AllowList) + includedUserIds, httpResp, err = util.GetUserIdsUsingIdentity(ctx, client, includedUsers) + + if err != nil { + diagnostics.AddError( + "Error fetching user details for delivery group", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return citrixorchestration.CreateDeliveryGroupRequestModel{}, err + } - if plan.RestrictedAccessUsers.BlockList != nil { + if !users.BlockList.IsNull() { excludedUsersFilterEnabled = true - excludedUsers = util.ConvertBaseStringArrayToPrimitiveStringArray(plan.RestrictedAccessUsers.BlockList) + excludedUsers := util.StringSetToStringArray(ctx, diagnostics, users.BlockList) + excludedUserIds, httpResp, err = util.GetUserIdsUsingIdentity(ctx, client, excludedUsers) + + if err != nil { + diagnostics.AddError( + "Error fetching user details for delivery group", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return citrixorchestration.CreateDeliveryGroupRequestModel{}, err + } } } @@ -597,8 +604,8 @@ func getRequestModelForDeliveryGroupCreate(diagnostics *diag.Diagnostics, plan D simpleAccessPolicy.SetAllowAnonymous(plan.AllowAnonymousAccess.ValueBool()) simpleAccessPolicy.SetIncludedUserFilterEnabled(includedUsersFilterEnabled) simpleAccessPolicy.SetExcludedUserFilterEnabled(excludedUsersFilterEnabled) - simpleAccessPolicy.SetIncludedUsers(includedUsers) - simpleAccessPolicy.SetExcludedUsers(excludedUsers) + simpleAccessPolicy.SetIncludedUsers(includedUserIds) + simpleAccessPolicy.SetExcludedUsers(excludedUserIds) var body citrixorchestration.CreateDeliveryGroupRequestModel body.SetName(plan.Name.ValueString()) @@ -617,7 +624,7 @@ func getRequestModelForDeliveryGroupCreate(diagnostics *diag.Diagnostics, plan D body.SetMinimumFunctionalLevel(*functionalLevel) deliveryKind := citrixorchestration.DELIVERYKIND_DESKTOPS_AND_APPS - if *catalogSessionSupport != citrixorchestration.SESSIONSUPPORT_MULTI_SESSION { + if associatedMachineCatalogProperties.SessionSupport != "" && associatedMachineCatalogProperties.SessionSupport != citrixorchestration.SESSIONSUPPORT_MULTI_SESSION { deliveryKind = citrixorchestration.DELIVERYKIND_DESKTOPS_ONLY } body.SetDeliveryType(deliveryKind) @@ -625,48 +632,74 @@ func getRequestModelForDeliveryGroupCreate(diagnostics *diag.Diagnostics, plan D body.SetDefaultDesktopPublishedName(plan.Name.ValueString()) body.SetSimpleAccessPolicy(simpleAccessPolicy) body.SetPolicySetGuid(plan.PolicySetId.ValueString()) - if identityType == citrixorchestration.IDENTITYTYPE_AZURE_AD { + if associatedMachineCatalogProperties.IdentityType == citrixorchestration.IDENTITYTYPE_AZURE_AD { body.SetMachineLogOnType(citrixorchestration.MACHINELOGONTYPE_AZURE_AD) - } else if identityType == citrixorchestration.IDENTITYTYPE_WORKGROUP { + } else if associatedMachineCatalogProperties.IdentityType == citrixorchestration.IDENTITYTYPE_WORKGROUP { body.SetMachineLogOnType(citrixorchestration.MACHINELOGONTYPE_LOCAL_MAPPED_ACCOUNT) } else { body.SetMachineLogOnType(citrixorchestration.MACHINELOGONTYPE_ACTIVE_DIRECTORY) } - if plan.AutoscaleSettings != nil { - body.SetAutoScaleEnabled(plan.AutoscaleSettings.AutoscaleEnabled.ValueBool()) - body.SetTimeZone(plan.AutoscaleSettings.Timezone.ValueString()) - body.SetPeakDisconnectTimeoutMinutes(int32(plan.AutoscaleSettings.PeakDisconnectTimeoutMinutes.ValueInt64())) - body.SetPeakLogOffAction(getSessionChangeHostingActionValue(plan.AutoscaleSettings.PeakLogOffAction.ValueString())) - body.SetPeakDisconnectAction(getSessionChangeHostingActionValue(plan.AutoscaleSettings.PeakDisconnectAction.ValueString())) - body.SetPeakExtendedDisconnectAction(getSessionChangeHostingActionValue(plan.AutoscaleSettings.PeakExtendedDisconnectAction.ValueString())) - body.SetOffPeakLogOffAction(getSessionChangeHostingActionValue(plan.AutoscaleSettings.OffPeakLogOffAction.ValueString())) - body.SetOffPeakDisconnectAction(getSessionChangeHostingActionValue(plan.AutoscaleSettings.OffPeakDisconnectAction.ValueString())) - body.SetOffPeakExtendedDisconnectAction(getSessionChangeHostingActionValue(plan.AutoscaleSettings.OffPeakExtendedDisconnectAction.ValueString())) - body.SetPeakExtendedDisconnectTimeoutMinutes(int32(plan.AutoscaleSettings.PeakExtendedDisconnectTimeoutMinutes.ValueInt64())) - body.SetOffPeakDisconnectTimeoutMinutes(int32(plan.AutoscaleSettings.OffPeakDisconnectTimeoutMinutes.ValueInt64())) - body.SetOffPeakExtendedDisconnectTimeoutMinutes(int32(plan.AutoscaleSettings.OffPeakExtendedDisconnectTimeoutMinutes.ValueInt64())) - body.SetPeakBufferSizePercent(int32(plan.AutoscaleSettings.PeakBufferSizePercent.ValueInt64())) - body.SetOffPeakBufferSizePercent(int32(plan.AutoscaleSettings.OffPeakBufferSizePercent.ValueInt64())) - body.SetPowerOffDelayMinutes(int32(plan.AutoscaleSettings.PowerOffDelayMinutes.ValueInt64())) - body.SetDisconnectPeakIdleSessionAfterSeconds(int32(plan.AutoscaleSettings.DisconnectPeakIdleSessionAfterSeconds.ValueInt64())) - body.SetDisconnectOffPeakIdleSessionAfterSeconds(int32(plan.AutoscaleSettings.DisconnectOffPeakIdleSessionAfterSeconds.ValueInt64())) - body.SetLogoffPeakDisconnectedSessionAfterSeconds(int32(plan.AutoscaleSettings.LogoffPeakDisconnectedSessionAfterSeconds.ValueInt64())) - body.SetLogoffOffPeakDisconnectedSessionAfterSeconds(int32(plan.AutoscaleSettings.LogoffOffPeakDisconnectedSessionAfterSeconds.ValueInt64())) - - powerTimeSchemes := parsePowerTimeSchemesPluginToClientModel(plan.AutoscaleSettings.PowerTimeSchemes) + if !plan.MakeResourcesAvailableInLHC.IsNull() { + body.SetReuseMachinesWithoutShutdownInOutage(plan.MakeResourcesAvailableInLHC.ValueBool()) + } + + if !plan.AutoscaleSettings.IsNull() { + autoscale := util.ObjectValueToTypedObject[DeliveryGroupPowerManagementSettings](ctx, diagnostics, plan.AutoscaleSettings) + body.SetAutoScaleEnabled(autoscale.AutoscaleEnabled.ValueBool()) + body.SetTimeZone(autoscale.Timezone.ValueString()) + body.SetPeakDisconnectTimeoutMinutes(int32(autoscale.PeakDisconnectTimeoutMinutes.ValueInt64())) + body.SetPeakLogOffAction(getSessionChangeHostingActionValue(autoscale.PeakLogOffAction.ValueString())) + body.SetPeakDisconnectAction(getSessionChangeHostingActionValue(autoscale.PeakDisconnectAction.ValueString())) + body.SetPeakExtendedDisconnectAction(getSessionChangeHostingActionValue(autoscale.PeakExtendedDisconnectAction.ValueString())) + body.SetOffPeakLogOffAction(getSessionChangeHostingActionValue(autoscale.OffPeakLogOffAction.ValueString())) + body.SetOffPeakDisconnectAction(getSessionChangeHostingActionValue(autoscale.OffPeakDisconnectAction.ValueString())) + body.SetOffPeakExtendedDisconnectAction(getSessionChangeHostingActionValue(autoscale.OffPeakExtendedDisconnectAction.ValueString())) + body.SetPeakExtendedDisconnectTimeoutMinutes(int32(autoscale.PeakExtendedDisconnectTimeoutMinutes.ValueInt64())) + body.SetOffPeakDisconnectTimeoutMinutes(int32(autoscale.OffPeakDisconnectTimeoutMinutes.ValueInt64())) + body.SetOffPeakExtendedDisconnectTimeoutMinutes(int32(autoscale.OffPeakExtendedDisconnectTimeoutMinutes.ValueInt64())) + body.SetPeakBufferSizePercent(int32(autoscale.PeakBufferSizePercent.ValueInt64())) + body.SetOffPeakBufferSizePercent(int32(autoscale.OffPeakBufferSizePercent.ValueInt64())) + body.SetPowerOffDelayMinutes(int32(autoscale.PowerOffDelayMinutes.ValueInt64())) + body.SetDisconnectPeakIdleSessionAfterSeconds(int32(autoscale.DisconnectPeakIdleSessionAfterSeconds.ValueInt64())) + body.SetDisconnectOffPeakIdleSessionAfterSeconds(int32(autoscale.DisconnectOffPeakIdleSessionAfterSeconds.ValueInt64())) + body.SetLogoffPeakDisconnectedSessionAfterSeconds(int32(autoscale.LogoffPeakDisconnectedSessionAfterSeconds.ValueInt64())) + body.SetLogoffOffPeakDisconnectedSessionAfterSeconds(int32(autoscale.LogoffOffPeakDisconnectedSessionAfterSeconds.ValueInt64())) + + powerTimeSchemes := parsePowerTimeSchemesPluginToClientModel(ctx, diagnostics, util.ObjectListToTypedArray[DeliveryGroupPowerTimeScheme](ctx, diagnostics, autoscale.PowerTimeSchemes)) body.SetPowerTimeSchemes(powerTimeSchemes) } + if !plan.Scopes.IsNull() { + plannedScopes := util.StringSetToStringArray(ctx, diagnostics, plan.Scopes) + body.SetScopes(plannedScopes) + } + if !plan.StoreFrontServers.IsNull() { + associatedStoreFrontServers := util.StringSetToStringArray(ctx, diagnostics, plan.StoreFrontServers) + var storeFrontServersList []citrixorchestration.StoreFrontServerRequestModel + for _, storeFrontServer := range associatedStoreFrontServers { + storeFrontServerModel := citrixorchestration.StoreFrontServerRequestModel{} + storeFrontServerModel.SetId(storeFrontServer) + storeFrontServersList = append(storeFrontServersList, storeFrontServerModel) + } + body.SetStoreFrontServersForHostedReceiver(storeFrontServersList) + } + return body, nil } -func getRequestModelForDeliveryGroupUpdate(diagnostics *diag.Diagnostics, plan DeliveryGroupResourceModel, currentDeliveryGroup *citrixorchestration.DeliveryGroupDetailResponseModel) (citrixorchestration.EditDeliveryGroupRequestModel, error) { - deliveryGroupDesktopsArray := parseDeliveryGroupDesktopsToClientModel(plan.Desktops) - deliveryGroupRebootScheduleArray := parseDeliveryGroupRebootScheduleToClientModel(plan.RebootSchedules) +func getRequestModelForDeliveryGroupUpdate(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, plan DeliveryGroupResourceModel, currentDeliveryGroup *citrixorchestration.DeliveryGroupDetailResponseModel) (citrixorchestration.EditDeliveryGroupRequestModel, error) { + desktops := util.ObjectListToTypedArray[DeliveryGroupDesktop](ctx, diagnostics, plan.Desktops) + deliveryGroupDesktopsArray, err := verifyUsersAndParseDeliveryGroupDesktopsToClientModel(ctx, diagnostics, client, desktops) + if err != nil { + return citrixorchestration.EditDeliveryGroupRequestModel{}, err + } + rebootSchedules := util.ObjectListToTypedArray[DeliveryGroupRebootSchedule](ctx, diagnostics, plan.RebootSchedules) + deliveryGroupRebootScheduleArray := parseDeliveryGroupRebootScheduleToClientModel(ctx, diagnostics, rebootSchedules) - includedUsers := []string{} - excludedUsers := []string{} + var httpResp *http.Response + includedUserIds := []string{} + excludedUserIds := []string{} includedUsersFilterEnabled := false excludedUsersFilterEnabled := false advancedAccessPolicies := []citrixorchestration.AdvancedAccessPolicyRequestModel{} @@ -677,7 +710,9 @@ func getRequestModelForDeliveryGroupUpdate(diagnostics *diag.Diagnostics, plan D allowedUser = citrixorchestration.ALLOWEDUSER_ANY } - if plan.RestrictedAccessUsers != nil { + if !plan.RestrictedAccessUsers.IsNull() { + users := util.ObjectValueToTypedObject[RestrictedAccessUsers](ctx, diagnostics, plan.RestrictedAccessUsers) + allowedUser = citrixorchestration.ALLOWEDUSER_FILTERED if plan.AllowAnonymousAccess.ValueBool() { @@ -685,11 +720,30 @@ func getRequestModelForDeliveryGroupUpdate(diagnostics *diag.Diagnostics, plan D } includedUsersFilterEnabled = true - includedUsers = util.ConvertBaseStringArrayToPrimitiveStringArray(plan.RestrictedAccessUsers.AllowList) + includedUsers := util.StringSetToStringArray(ctx, diagnostics, users.AllowList) + includedUserIds, httpResp, err = util.GetUserIdsUsingIdentity(ctx, client, includedUsers) + if err != nil { + diagnostics.AddError( + "Error fetching user details for delivery group", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return citrixorchestration.EditDeliveryGroupRequestModel{}, err + } - if plan.RestrictedAccessUsers.BlockList != nil { + if !users.BlockList.IsNull() { excludedUsersFilterEnabled = true - excludedUsers = util.ConvertBaseStringArrayToPrimitiveStringArray(plan.RestrictedAccessUsers.BlockList) + excludedUsers := util.StringSetToStringArray(ctx, diagnostics, users.BlockList) + excludedUserIds, httpResp, err = util.GetUserIdsUsingIdentity(ctx, client, excludedUsers) + + if err != nil { + diagnostics.AddError( + "Error fetching user details for delivery group", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return citrixorchestration.EditDeliveryGroupRequestModel{}, err + } } } @@ -698,9 +752,9 @@ func getRequestModelForDeliveryGroupUpdate(diagnostics *diag.Diagnostics, plan D var advancedAccessPolicyRequest citrixorchestration.AdvancedAccessPolicyRequestModel advancedAccessPolicyRequest.SetId(existingAdvancedAccessPolicy.GetId()) advancedAccessPolicyRequest.SetIncludedUserFilterEnabled(includedUsersFilterEnabled) - advancedAccessPolicyRequest.SetIncludedUsers(includedUsers) + advancedAccessPolicyRequest.SetIncludedUsers(includedUserIds) advancedAccessPolicyRequest.SetExcludedUserFilterEnabled(excludedUsersFilterEnabled) - advancedAccessPolicyRequest.SetExcludedUsers(excludedUsers) + advancedAccessPolicyRequest.SetExcludedUsers(excludedUserIds) advancedAccessPolicyRequest.SetAllowedUsers(allowedUser) advancedAccessPolicies = append(advancedAccessPolicies, advancedAccessPolicyRequest) } @@ -713,41 +767,62 @@ func getRequestModelForDeliveryGroupUpdate(diagnostics *diag.Diagnostics, plan D editDeliveryGroupRequestBody.SetRebootSchedules(deliveryGroupRebootScheduleArray) editDeliveryGroupRequestBody.SetAdvancedAccessPolicy(advancedAccessPolicies) + if !plan.Scopes.IsNull() { + plannedScopes := util.StringSetToStringArray(ctx, diagnostics, plan.Scopes) + editDeliveryGroupRequestBody.SetScopes(plannedScopes) + } + if plan.PolicySetId.ValueString() != "" { editDeliveryGroupRequestBody.SetPolicySetGuid(plan.PolicySetId.ValueString()) } else { editDeliveryGroupRequestBody.SetPolicySetGuid(util.DefaultSitePolicySetId) } - if plan.AutoscaleSettings != nil { + if !plan.MakeResourcesAvailableInLHC.IsNull() { + editDeliveryGroupRequestBody.SetReuseMachinesWithoutShutdownInOutage(plan.MakeResourcesAvailableInLHC.ValueBool()) + } + + if !plan.AutoscaleSettings.IsNull() { + autoscale := util.ObjectValueToTypedObject[DeliveryGroupPowerManagementSettings](ctx, diagnostics, plan.AutoscaleSettings) - if plan.AutoscaleSettings.Timezone.ValueString() != "" { - editDeliveryGroupRequestBody.SetTimeZone(plan.AutoscaleSettings.Timezone.ValueString()) + if autoscale.Timezone.ValueString() != "" { + editDeliveryGroupRequestBody.SetTimeZone(autoscale.Timezone.ValueString()) } - editDeliveryGroupRequestBody.SetAutoScaleEnabled(plan.AutoscaleSettings.AutoscaleEnabled.ValueBool()) - editDeliveryGroupRequestBody.SetPeakDisconnectTimeoutMinutes(int32(plan.AutoscaleSettings.PeakDisconnectTimeoutMinutes.ValueInt64())) - editDeliveryGroupRequestBody.SetPeakLogOffAction(getSessionChangeHostingActionValue(plan.AutoscaleSettings.PeakLogOffAction.ValueString())) - editDeliveryGroupRequestBody.SetPeakDisconnectAction(getSessionChangeHostingActionValue(plan.AutoscaleSettings.PeakDisconnectAction.ValueString())) - editDeliveryGroupRequestBody.SetPeakExtendedDisconnectAction(getSessionChangeHostingActionValue(plan.AutoscaleSettings.PeakExtendedDisconnectAction.ValueString())) - editDeliveryGroupRequestBody.SetPeakExtendedDisconnectTimeoutMinutes(int32(plan.AutoscaleSettings.PeakExtendedDisconnectTimeoutMinutes.ValueInt64())) - editDeliveryGroupRequestBody.SetOffPeakDisconnectTimeoutMinutes(int32(plan.AutoscaleSettings.OffPeakDisconnectTimeoutMinutes.ValueInt64())) - editDeliveryGroupRequestBody.SetOffPeakLogOffAction(getSessionChangeHostingActionValue(plan.AutoscaleSettings.OffPeakLogOffAction.ValueString())) - editDeliveryGroupRequestBody.SetOffPeakDisconnectAction(getSessionChangeHostingActionValue(plan.AutoscaleSettings.OffPeakDisconnectAction.ValueString())) - editDeliveryGroupRequestBody.SetOffPeakExtendedDisconnectAction(getSessionChangeHostingActionValue(plan.AutoscaleSettings.OffPeakExtendedDisconnectAction.ValueString())) - editDeliveryGroupRequestBody.SetOffPeakExtendedDisconnectTimeoutMinutes(int32(plan.AutoscaleSettings.OffPeakExtendedDisconnectTimeoutMinutes.ValueInt64())) - editDeliveryGroupRequestBody.SetPeakBufferSizePercent(int32(plan.AutoscaleSettings.PeakBufferSizePercent.ValueInt64())) - editDeliveryGroupRequestBody.SetOffPeakBufferSizePercent(int32(plan.AutoscaleSettings.OffPeakBufferSizePercent.ValueInt64())) - editDeliveryGroupRequestBody.SetPowerOffDelayMinutes(int32(plan.AutoscaleSettings.PowerOffDelayMinutes.ValueInt64())) - editDeliveryGroupRequestBody.SetDisconnectPeakIdleSessionAfterSeconds(int32(plan.AutoscaleSettings.DisconnectPeakIdleSessionAfterSeconds.ValueInt64())) - editDeliveryGroupRequestBody.SetDisconnectOffPeakIdleSessionAfterSeconds(int32(plan.AutoscaleSettings.DisconnectOffPeakIdleSessionAfterSeconds.ValueInt64())) - editDeliveryGroupRequestBody.SetLogoffPeakDisconnectedSessionAfterSeconds(int32(plan.AutoscaleSettings.LogoffPeakDisconnectedSessionAfterSeconds.ValueInt64())) - editDeliveryGroupRequestBody.SetLogoffOffPeakDisconnectedSessionAfterSeconds(int32(plan.AutoscaleSettings.LogoffOffPeakDisconnectedSessionAfterSeconds.ValueInt64())) - - powerTimeSchemes := parsePowerTimeSchemesPluginToClientModel(plan.AutoscaleSettings.PowerTimeSchemes) + editDeliveryGroupRequestBody.SetAutoScaleEnabled(autoscale.AutoscaleEnabled.ValueBool()) + editDeliveryGroupRequestBody.SetPeakDisconnectTimeoutMinutes(int32(autoscale.PeakDisconnectTimeoutMinutes.ValueInt64())) + editDeliveryGroupRequestBody.SetPeakLogOffAction(getSessionChangeHostingActionValue(autoscale.PeakLogOffAction.ValueString())) + editDeliveryGroupRequestBody.SetPeakDisconnectAction(getSessionChangeHostingActionValue(autoscale.PeakDisconnectAction.ValueString())) + editDeliveryGroupRequestBody.SetPeakExtendedDisconnectAction(getSessionChangeHostingActionValue(autoscale.PeakExtendedDisconnectAction.ValueString())) + editDeliveryGroupRequestBody.SetPeakExtendedDisconnectTimeoutMinutes(int32(autoscale.PeakExtendedDisconnectTimeoutMinutes.ValueInt64())) + editDeliveryGroupRequestBody.SetOffPeakDisconnectTimeoutMinutes(int32(autoscale.OffPeakDisconnectTimeoutMinutes.ValueInt64())) + editDeliveryGroupRequestBody.SetOffPeakLogOffAction(getSessionChangeHostingActionValue(autoscale.OffPeakLogOffAction.ValueString())) + editDeliveryGroupRequestBody.SetOffPeakDisconnectAction(getSessionChangeHostingActionValue(autoscale.OffPeakDisconnectAction.ValueString())) + editDeliveryGroupRequestBody.SetOffPeakExtendedDisconnectAction(getSessionChangeHostingActionValue(autoscale.OffPeakExtendedDisconnectAction.ValueString())) + editDeliveryGroupRequestBody.SetOffPeakExtendedDisconnectTimeoutMinutes(int32(autoscale.OffPeakExtendedDisconnectTimeoutMinutes.ValueInt64())) + editDeliveryGroupRequestBody.SetPeakBufferSizePercent(int32(autoscale.PeakBufferSizePercent.ValueInt64())) + editDeliveryGroupRequestBody.SetOffPeakBufferSizePercent(int32(autoscale.OffPeakBufferSizePercent.ValueInt64())) + editDeliveryGroupRequestBody.SetPowerOffDelayMinutes(int32(autoscale.PowerOffDelayMinutes.ValueInt64())) + editDeliveryGroupRequestBody.SetDisconnectPeakIdleSessionAfterSeconds(int32(autoscale.DisconnectPeakIdleSessionAfterSeconds.ValueInt64())) + editDeliveryGroupRequestBody.SetDisconnectOffPeakIdleSessionAfterSeconds(int32(autoscale.DisconnectOffPeakIdleSessionAfterSeconds.ValueInt64())) + editDeliveryGroupRequestBody.SetLogoffPeakDisconnectedSessionAfterSeconds(int32(autoscale.LogoffPeakDisconnectedSessionAfterSeconds.ValueInt64())) + editDeliveryGroupRequestBody.SetLogoffOffPeakDisconnectedSessionAfterSeconds(int32(autoscale.LogoffOffPeakDisconnectedSessionAfterSeconds.ValueInt64())) + + powerTimeSchemes := parsePowerTimeSchemesPluginToClientModel(ctx, diagnostics, util.ObjectListToTypedArray[DeliveryGroupPowerTimeScheme](ctx, diagnostics, autoscale.PowerTimeSchemes)) editDeliveryGroupRequestBody.SetPowerTimeSchemes(powerTimeSchemes) } + storeFrontServersList := []citrixorchestration.StoreFrontServerRequestModel{} + if !plan.StoreFrontServers.IsNull() { + associatedStoreFrontServers := util.StringSetToStringArray(ctx, diagnostics, plan.StoreFrontServers) + for _, storeFrontServer := range associatedStoreFrontServers { + storeFrontServerModel := citrixorchestration.StoreFrontServerRequestModel{} + storeFrontServerModel.SetId(storeFrontServer) + storeFrontServersList = append(storeFrontServersList, storeFrontServerModel) + } + } + editDeliveryGroupRequestBody.SetStoreFrontServersForHostedReceiver(storeFrontServersList) + functionalLevel, err := citrixorchestration.NewFunctionalLevelFromValue(plan.MinimumFunctionalLevel.ValueString()) if err != nil { diagnostics.AddError( @@ -761,26 +836,26 @@ func getRequestModelForDeliveryGroupUpdate(diagnostics *diag.Diagnostics, plan D return editDeliveryGroupRequestBody, nil } -func parsePowerTimeSchemesPluginToClientModel(powerTimeSchemes []DeliveryGroupPowerTimeScheme) []citrixorchestration.PowerTimeSchemeRequestModel { +func parsePowerTimeSchemesPluginToClientModel(ctx context.Context, diags *diag.Diagnostics, powerTimeSchemes []DeliveryGroupPowerTimeScheme) []citrixorchestration.PowerTimeSchemeRequestModel { res := []citrixorchestration.PowerTimeSchemeRequestModel{} for _, powerTimeScheme := range powerTimeSchemes { var powerTimeSchemeRequest citrixorchestration.PowerTimeSchemeRequestModel var daysOfWeek []citrixorchestration.TimeSchemeDays - for _, dayOfWeek := range powerTimeScheme.DaysOfWeek { - timeSchemeDay := getTimeSchemeDayValue(dayOfWeek.ValueString()) + for _, dayOfWeek := range util.StringSetToStringArray(ctx, diags, powerTimeScheme.DaysOfWeek) { + timeSchemeDay := getTimeSchemeDayValue(dayOfWeek) daysOfWeek = append(daysOfWeek, timeSchemeDay) } var poolSizeScheduleRequests []citrixorchestration.PoolSizeScheduleRequestModel - for _, poolSizeSchedule := range powerTimeScheme.PoolSizeSchedule { + for _, poolSizeSchedule := range util.ObjectListToTypedArray[PowerTimeSchemePoolSizeScheduleRequestModel](ctx, diags, powerTimeScheme.PoolSizeSchedule) { var poolSizeScheduleRequest citrixorchestration.PoolSizeScheduleRequestModel poolSizeScheduleRequest.SetTimeRange(poolSizeSchedule.TimeRange.ValueString()) poolSizeScheduleRequest.SetPoolSize(int32(poolSizeSchedule.PoolSize.ValueInt64())) poolSizeScheduleRequests = append(poolSizeScheduleRequests, poolSizeScheduleRequest) } - peakTimeRanges := util.ConvertBaseStringArrayToPrimitiveStringArray(powerTimeScheme.PeakTimeRanges) + peakTimeRanges := util.StringSetToStringArray(ctx, diags, powerTimeScheme.PeakTimeRanges) powerTimeSchemeRequest.SetDisplayName(powerTimeScheme.DisplayName.ValueString()) powerTimeSchemeRequest.SetPeakTimeRanges(peakTimeRanges) @@ -793,14 +868,14 @@ func parsePowerTimeSchemesPluginToClientModel(powerTimeSchemes []DeliveryGroupPo return res } -func parsePowerTimeSchemesClientToPluginModel(powerTimeSchemesResponse []citrixorchestration.PowerTimeSchemeResponseModel) []DeliveryGroupPowerTimeScheme { +func parsePowerTimeSchemesClientToPluginModel(ctx context.Context, diags *diag.Diagnostics, powerTimeSchemesResponse []citrixorchestration.PowerTimeSchemeResponseModel) []DeliveryGroupPowerTimeScheme { var res []DeliveryGroupPowerTimeScheme for _, powerTimeSchemeResponse := range powerTimeSchemesResponse { var deliveryGroupPowerTimeScheme DeliveryGroupPowerTimeScheme - var daysOfWeek []types.String + var daysOfWeek []string for _, dayOfWeek := range powerTimeSchemeResponse.GetDaysOfWeek() { - timeSchemeDay := types.StringValue(reflect.ValueOf(dayOfWeek).String()) + timeSchemeDay := string(dayOfWeek) daysOfWeek = append(daysOfWeek, timeSchemeDay) } @@ -817,11 +892,10 @@ func parsePowerTimeSchemesClientToPluginModel(powerTimeSchemesResponse []citrixo } deliveryGroupPowerTimeScheme.DisplayName = types.StringValue(powerTimeSchemeResponse.GetDisplayName()) - peakTimeRanges := util.ConvertPrimitiveStringArrayToBaseStringArray(powerTimeSchemeResponse.GetPeakTimeRanges()) - deliveryGroupPowerTimeScheme.PeakTimeRanges = peakTimeRanges + deliveryGroupPowerTimeScheme.PeakTimeRanges = util.StringArrayToStringSet(ctx, diags, powerTimeSchemeResponse.GetPeakTimeRanges()) deliveryGroupPowerTimeScheme.PoolUsingPercentage = types.BoolValue(powerTimeSchemeResponse.GetPoolUsingPercentage()) - deliveryGroupPowerTimeScheme.DaysOfWeek = daysOfWeek - deliveryGroupPowerTimeScheme.PoolSizeSchedule = poolSizeScheduleRequests + deliveryGroupPowerTimeScheme.DaysOfWeek = util.StringArrayToStringSet(ctx, diags, daysOfWeek) + deliveryGroupPowerTimeScheme.PoolSizeSchedule = util.TypedArrayToObjectList[PowerTimeSchemePoolSizeScheduleRequestModel](ctx, diags, poolSizeScheduleRequests) res = append(res, deliveryGroupPowerTimeScheme) } @@ -829,7 +903,7 @@ func parsePowerTimeSchemesClientToPluginModel(powerTimeSchemesResponse []citrixo return res } -func parseDeliveryGroupRebootScheduleToClientModel(rebootSchedules []DeliveryGroupRebootSchedule) []citrixorchestration.RebootScheduleRequestModel { +func parseDeliveryGroupRebootScheduleToClientModel(ctx context.Context, diags *diag.Diagnostics, rebootSchedules []DeliveryGroupRebootSchedule) []citrixorchestration.RebootScheduleRequestModel { res := []citrixorchestration.RebootScheduleRequestModel{} if rebootSchedules == nil { return res @@ -854,18 +928,20 @@ func parseDeliveryGroupRebootScheduleToClientModel(rebootSchedules []DeliveryGro rebootScheduleRequest.SetRebootDurationMinutes(int32(rebootSchedule.RebootDurationMinutes.ValueInt64())) rebootScheduleRequest.SetUseNaturalReboot(rebootSchedule.UseNaturalRebootSchedule.ValueBool()) if rebootSchedule.Frequency.ValueString() == "Weekly" { - rebootScheduleRequest.SetDaysInWeek(getRebootScheduleDaysInWeekActionValue(util.ConvertBaseStringArrayToPrimitiveStringArray(rebootSchedule.DaysInWeek))) + rebootScheduleRequest.SetDaysInWeek(getRebootScheduleDaysInWeekActionValue(util.StringSetToStringArray(ctx, diags, rebootSchedule.DaysInWeek))) } if rebootSchedule.Frequency.ValueString() == "Monthly" { rebootScheduleRequest.SetWeekInMonth(getRebootScheduleWeekActionValue(rebootSchedule.WeekInMonth.ValueString())) rebootScheduleRequest.SetDayInMonth(getRebootScheduleDaysActionValue(rebootSchedule.DayInMonth.ValueString())) } - if rebootSchedule.DeliveryGroupRebootNotificationToUsers != nil && !rebootSchedule.UseNaturalRebootSchedule.ValueBool() { - rebootScheduleRequest.SetWarningDurationMinutes(int32(rebootSchedule.DeliveryGroupRebootNotificationToUsers.NotificationDurationMinutes.ValueInt64())) //can only be 1 5 15, or 0 means no warning - rebootScheduleRequest.SetWarningTitle(rebootSchedule.DeliveryGroupRebootNotificationToUsers.NotificationTitle.ValueString()) - rebootScheduleRequest.SetWarningMessage(rebootSchedule.DeliveryGroupRebootNotificationToUsers.NotificationMessage.ValueString()) - if rebootSchedule.DeliveryGroupRebootNotificationToUsers.NotificationRepeatEvery5Minutes.ValueBool() { + if !rebootSchedule.DeliveryGroupRebootNotificationToUsers.IsNull() && !rebootSchedule.UseNaturalRebootSchedule.ValueBool() { + notification := util.ObjectValueToTypedObject[DeliveryGroupRebootNotificationToUsers](ctx, diags, rebootSchedule.DeliveryGroupRebootNotificationToUsers) + + rebootScheduleRequest.SetWarningDurationMinutes(int32(notification.NotificationDurationMinutes.ValueInt64())) //can only be 1 5 15, or 0 means no warning + rebootScheduleRequest.SetWarningTitle(notification.NotificationTitle.ValueString()) + rebootScheduleRequest.SetWarningMessage(notification.NotificationMessage.ValueString()) + if notification.NotificationRepeatEvery5Minutes.ValueBool() { rebootScheduleRequest.SetWarningRepeatIntervalMinutes(5) } else { rebootScheduleRequest.SetWarningRepeatIntervalMinutes(0) @@ -882,7 +958,7 @@ func parseDeliveryGroupRebootScheduleToClientModel(rebootSchedules []DeliveryGro } -func (schedule DeliveryGroupRebootSchedule) RefreshListItem(rebootSchedule citrixorchestration.RebootScheduleResponseModel) DeliveryGroupRebootSchedule { +func (schedule DeliveryGroupRebootSchedule) RefreshListItem(ctx context.Context, diags *diag.Diagnostics, rebootSchedule citrixorchestration.RebootScheduleResponseModel) util.ModelWithAttributes { schedule.Name = types.StringValue(rebootSchedule.GetName()) if rebootSchedule.GetDescription() != "" { schedule.Description = types.StringValue(rebootSchedule.GetDescription()) @@ -893,31 +969,27 @@ func (schedule DeliveryGroupRebootSchedule) RefreshListItem(rebootSchedule citri schedule.RestrictToTag = types.StringValue(*rebootSchedule.GetRestrictToTag().Name.Get()) } schedule.IgnoreMaintenanceMode = types.BoolValue(rebootSchedule.GetIgnoreMaintenanceMode()) //bug in orchestration side - schedule.Frequency = types.StringValue(reflect.ValueOf(rebootSchedule.GetFrequency()).String()) + schedule.Frequency = types.StringValue(string(rebootSchedule.GetFrequency())) - startDate := schedule.StartDate.ValueString() - if reflect.ValueOf(rebootSchedule.GetFrequency()).String() == "Weekly" { - res := []types.String{} + if rebootSchedule.GetFrequency() == citrixorchestration.REBOOTSCHEDULEFREQUENCY_WEEKLY { + res := []string{} for _, scheduleDay := range rebootSchedule.GetDaysInWeek() { - res = append(res, types.StringValue(reflect.ValueOf(scheduleDay).String())) + res = append(res, string(scheduleDay)) } - schedule.DaysInWeek = res - if startDate != "" && startDate[5:6] == rebootSchedule.GetStartDate()[5:6] { //same month for plan and remote schedule - schedule.StartDate = types.StringValue(startDate) - } - } else if reflect.ValueOf(rebootSchedule.GetFrequency()).String() == "Monthly" { - schedule.WeekInMonth = types.StringValue(reflect.ValueOf(rebootSchedule.GetWeekInMonth()).String()) - schedule.DayInMonth = types.StringValue(reflect.ValueOf(rebootSchedule.GetDayInMonth()).String()) - schedule.StartDate = types.StringValue(startDate) + schedule.DaysInWeek = util.StringArrayToStringSet(ctx, diags, res) + } else if rebootSchedule.GetFrequency() == citrixorchestration.REBOOTSCHEDULEFREQUENCY_MONTHLY { + schedule.WeekInMonth = types.StringValue(string(rebootSchedule.GetWeekInMonth())) + schedule.DayInMonth = types.StringValue(string(rebootSchedule.GetDayInMonth())) } else { - if startDate != "" { - schedule.StartDate = types.StringValue(rebootSchedule.GetStartDate()) - } + schedule.StartDate = types.StringValue(rebootSchedule.GetStartDate()) } schedule.FrequencyFactor = types.Int64Value(int64(rebootSchedule.GetFrequencyFactor())) - schedule.StartTime = types.StringValue(rebootSchedule.GetStartTime()[:5]) + if schedule.StartDate.IsNull() { + schedule.StartDate = types.StringValue(rebootSchedule.GetStartDate()) + } + if rebootSchedule.GetUseNaturalReboot() { schedule.UseNaturalRebootSchedule = types.BoolValue(true) } else { @@ -932,19 +1004,16 @@ func (schedule DeliveryGroupRebootSchedule) RefreshListItem(rebootSchedule citri if rebootSchedule.GetWarningRepeatIntervalMinutes() == 5 { notif.NotificationRepeatEvery5Minutes = types.BoolValue(true) } - schedule.DeliveryGroupRebootNotificationToUsers = ¬if + schedule.DeliveryGroupRebootNotificationToUsers = util.TypedObjectToObjectValue(ctx, diags, notif) } } return schedule } -func (dgDesktop DeliveryGroupDesktop) RefreshListItem(desktop citrixorchestration.DesktopResponseModel) DeliveryGroupDesktop { +func (dgDesktop DeliveryGroupDesktop) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, desktop citrixorchestration.DesktopResponseModel) util.ModelWithAttributes { dgDesktop.PublishedName = types.StringValue(desktop.GetPublishedName()) - if desktop.GetDescription() != "" { - dgDesktop.DesktopDescription = types.StringValue(desktop.GetDescription()) - } else { - dgDesktop.DesktopDescription = types.StringNull() - } + dgDesktop.DesktopDescription = types.StringValue(desktop.GetDescription()) + dgDesktop.Enabled = types.BoolValue(desktop.GetEnabled()) sessionReconnection := desktop.GetSessionReconnection() if sessionReconnection == citrixorchestration.SESSIONRECONNECTION_ALWAYS { @@ -953,31 +1022,34 @@ func (dgDesktop DeliveryGroupDesktop) RefreshListItem(desktop citrixorchestratio dgDesktop.EnableSessionRoaming = types.BoolValue(false) } + var users RestrictedAccessUsers if !desktop.GetIncludedUserFilterEnabled() { - dgDesktop.RestrictedAccessUsers = nil + if attributes, err := util.AttributeMapFromObject(users); err == nil { + dgDesktop.RestrictedAccessUsers = types.ObjectNull(attributes) + } else { + diagnostics.AddWarning("Error when creating null RestrictedAccessUsers", err.Error()) + } return dgDesktop } - if dgDesktop.RestrictedAccessUsers == nil { - dgDesktop.RestrictedAccessUsers = &RestrictedAccessUsers{} - } + users = util.ObjectValueToTypedObject[RestrictedAccessUsers](ctx, diagnostics, dgDesktop.RestrictedAccessUsers) includedUsers := desktop.GetIncludedUsers() excludedUsers := desktop.GetExcludedUsers() if len(includedUsers) == 0 { - dgDesktop.RestrictedAccessUsers.AllowList = nil + users.AllowList = types.SetNull(types.StringType) } else { - updatedAllowedUsers := refreshUsersList(dgDesktop.RestrictedAccessUsers.AllowList, includedUsers) - dgDesktop.RestrictedAccessUsers.AllowList = updatedAllowedUsers + users.AllowList = util.RefreshUsersList(ctx, diagnostics, users.AllowList, includedUsers) } if len(excludedUsers) == 0 { - dgDesktop.RestrictedAccessUsers.BlockList = nil + users.BlockList = types.SetNull(types.StringType) } else { - updatedBlockedUsers := refreshUsersList(dgDesktop.RestrictedAccessUsers.BlockList, excludedUsers) - dgDesktop.RestrictedAccessUsers.BlockList = updatedBlockedUsers + users.BlockList = util.RefreshUsersList(ctx, diagnostics, users.BlockList, excludedUsers) } + usersObj := util.TypedObjectToObjectValue(ctx, diagnostics, users) + dgDesktop.RestrictedAccessUsers = usersObj return dgDesktop } @@ -1027,11 +1099,11 @@ func getRebootScheduleDaysInWeekActionValue(v []string) []citrixorchestration.Re return res } -func parseDeliveryGroupDesktopsToClientModel(deliveryGroupDesktops []DeliveryGroupDesktop) []citrixorchestration.DesktopRequestModel { +func verifyUsersAndParseDeliveryGroupDesktopsToClientModel(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, deliveryGroupDesktops []DeliveryGroupDesktop) ([]citrixorchestration.DesktopRequestModel, error) { desktopRequests := []citrixorchestration.DesktopRequestModel{} if deliveryGroupDesktops == nil { - return desktopRequests + return desktopRequests, nil } for _, deliveryGroupDesktop := range deliveryGroupDesktops { @@ -1045,29 +1117,54 @@ func parseDeliveryGroupDesktopsToClientModel(deliveryGroupDesktops []DeliveryGro desktopRequest.SetEnabled(deliveryGroupDesktop.Enabled.ValueBool()) desktopRequest.SetSessionReconnection(sessionReconnection) - includedUsers := []string{} - excludedUsers := []string{} + includedUserIds := []string{} + excludedUserIds := []string{} + var err error + var httpResp *http.Response includedUsersFilterEnabled := false excludedUsersFilterEnabled := false - if deliveryGroupDesktop.RestrictedAccessUsers != nil { + if !deliveryGroupDesktop.RestrictedAccessUsers.IsNull() { + users := util.ObjectValueToTypedObject[RestrictedAccessUsers](ctx, diagnostics, deliveryGroupDesktop.RestrictedAccessUsers) + includedUsersFilterEnabled = true - includedUsers = util.ConvertBaseStringArrayToPrimitiveStringArray(deliveryGroupDesktop.RestrictedAccessUsers.AllowList) + includedUsers := util.StringSetToStringArray(ctx, diagnostics, users.AllowList) - if deliveryGroupDesktop.RestrictedAccessUsers.BlockList != nil { + // Call identity to make sure users exist. Extract the Ids from the reponse + includedUserIds, httpResp, err = util.GetUserIdsUsingIdentity(ctx, client, includedUsers) + if err != nil { + diagnostics.AddError( + "Error fetching user details for delivery group", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return desktopRequests, err + } + + if !users.BlockList.IsNull() { excludedUsersFilterEnabled = true - excludedUsers = util.ConvertBaseStringArrayToPrimitiveStringArray(deliveryGroupDesktop.RestrictedAccessUsers.BlockList) + excludedUsers := util.StringSetToStringArray(ctx, diagnostics, users.BlockList) + excludedUserIds, httpResp, err = util.GetUserIdsUsingIdentity(ctx, client, excludedUsers) + + if err != nil { + diagnostics.AddError( + "Error fetching user details for delivery group", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+util.ReadClientError(err), + ) + return desktopRequests, err + } } } desktopRequest.SetIncludedUserFilterEnabled(includedUsersFilterEnabled) desktopRequest.SetExcludedUserFilterEnabled(excludedUsersFilterEnabled) - desktopRequest.SetIncludedUsers(includedUsers) - desktopRequest.SetExcludedUsers(excludedUsers) + desktopRequest.SetIncludedUsers(includedUserIds) + desktopRequest.SetExcludedUsers(excludedUserIds) desktopRequests = append(desktopRequests, desktopRequest) } - return desktopRequests + return desktopRequests, nil } func getTimeSchemeDayValue(v string) citrixorchestration.TimeSchemeDays { @@ -1079,13 +1176,13 @@ func getTimeSchemeDayValue(v string) citrixorchestration.TimeSchemeDays { return *timeSchemeDay } -func (r DeliveryGroupResourceModel) updatePlanWithRebootSchedule(rebootSchedules *citrixorchestration.RebootScheduleResponseModelCollection) DeliveryGroupResourceModel { - schedules := util.RefreshListProperties[DeliveryGroupRebootSchedule, citrixorchestration.RebootScheduleResponseModel](r.RebootSchedules, "Name", rebootSchedules.GetItems(), "Name", "RefreshListItem") +func (r DeliveryGroupResourceModel) updatePlanWithRebootSchedule(ctx context.Context, diagnostics *diag.Diagnostics, rebootSchedules *citrixorchestration.RebootScheduleResponseModelCollection) DeliveryGroupResourceModel { + schedules := util.RefreshListValueProperties[DeliveryGroupRebootSchedule, citrixorchestration.RebootScheduleResponseModel](ctx, diagnostics, r.RebootSchedules, rebootSchedules.GetItems(), util.GetOrchestrationRebootScheduleKey) r.RebootSchedules = schedules return r } -func (r DeliveryGroupResourceModel) updatePlanWithAssociatedCatalogs(machines *citrixorchestration.MachineResponseModelCollection) DeliveryGroupResourceModel { +func (r DeliveryGroupResourceModel) updatePlanWithAssociatedCatalogs(ctx context.Context, diags *diag.Diagnostics, machines *citrixorchestration.MachineResponseModelCollection) DeliveryGroupResourceModel { machineCatalogMap := map[string]int{} for _, machine := range machines.GetItems() { @@ -1094,24 +1191,25 @@ func (r DeliveryGroupResourceModel) updatePlanWithAssociatedCatalogs(machines *c machineCatalogMap[machineCatalogId] += 1 } - r.AssociatedMachineCatalogs = []DeliveryGroupMachineCatalogModel{} + associatedMachineCatalogs := []DeliveryGroupMachineCatalogModel{} for key, val := range machineCatalogMap { var deliveryGroupMachineCatalogModel DeliveryGroupMachineCatalogModel deliveryGroupMachineCatalogModel.MachineCatalog = types.StringValue(key) deliveryGroupMachineCatalogModel.MachineCount = types.Int64Value(int64(val)) - r.AssociatedMachineCatalogs = append(r.AssociatedMachineCatalogs, deliveryGroupMachineCatalogModel) + associatedMachineCatalogs = append(associatedMachineCatalogs, deliveryGroupMachineCatalogModel) } + r.AssociatedMachineCatalogs = util.TypedArrayToObjectList[DeliveryGroupMachineCatalogModel](ctx, diags, associatedMachineCatalogs) return r } -func (r DeliveryGroupResourceModel) updatePlanWithDesktops(deliveryGroupDesktops *citrixorchestration.DesktopResponseModelCollection) DeliveryGroupResourceModel { - desktops := util.RefreshListProperties[DeliveryGroupDesktop, citrixorchestration.DesktopResponseModel](r.Desktops, "PublishedName", deliveryGroupDesktops.GetItems(), "PublishedName", "RefreshListItem") +func (r DeliveryGroupResourceModel) updatePlanWithDesktops(ctx context.Context, diagnostics *diag.Diagnostics, deliveryGroupDesktops *citrixorchestration.DesktopResponseModelCollection) DeliveryGroupResourceModel { + desktops := util.RefreshListValueProperties[DeliveryGroupDesktop, citrixorchestration.DesktopResponseModel](ctx, diagnostics, r.Desktops, deliveryGroupDesktops.GetItems(), util.GetOrchestrationDesktopKey) r.Desktops = desktops return r } -func updateDeliveryGroupUserAccessDetails(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, deliveryGroup *citrixorchestration.DeliveryGroupDetailResponseModel, deliveryGroupDesktops *citrixorchestration.DesktopResponseModelCollection) (*citrixorchestration.DeliveryGroupDetailResponseModel, *citrixorchestration.DesktopResponseModelCollection, error) { +func updateDeliveryGroupAndDesktopUsers(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, deliveryGroup *citrixorchestration.DeliveryGroupDetailResponseModel, deliveryGroupDesktops *citrixorchestration.DesktopResponseModelCollection) (*citrixorchestration.DeliveryGroupDetailResponseModel, *citrixorchestration.DesktopResponseModelCollection, error) { simpleAccessPolicy := deliveryGroup.GetSimpleAccessPolicy() updatedIncludedUsers, updatedExcludedUsers, err := updateIdentityUserDetails(ctx, client, diagnostics, simpleAccessPolicy.GetIncludedUsers(), simpleAccessPolicy.GetExcludedUsers()) if err != nil { @@ -1137,161 +1235,132 @@ func updateDeliveryGroupUserAccessDetails(ctx context.Context, client *citrixdaa return deliveryGroup, deliveryGroupDesktops, nil } -func verifyIdentityUserListCompleteness(inputUserNames []string, remoteUsers []citrixorchestration.IdentityUserResponseModel) error { - if len(remoteUsers) < len(inputUserNames) { - missingUsers := []string{} - for _, includedUser := range inputUserNames { - userIndex := slices.IndexFunc(remoteUsers, func(i citrixorchestration.IdentityUserResponseModel) bool { - return includedUser == i.GetSamName() || includedUser == i.GetPrincipalName() - }) - if userIndex == -1 { - missingUsers = append(missingUsers, includedUser) - } - } - - return fmt.Errorf("The following users could not be found: " + strings.Join(missingUsers, ", ")) - } - return nil -} - func updateIdentityUserDetails(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, includedUsers []citrixorchestration.IdentityUserResponseModel, excludedUsers []citrixorchestration.IdentityUserResponseModel) ([]citrixorchestration.IdentityUserResponseModel, []citrixorchestration.IdentityUserResponseModel, error) { includedUserNames := []string{} + var err error + var httpResp *http.Response for _, includedUser := range includedUsers { - if includedUser.GetSamName() != "" { - includedUserNames = append(includedUserNames, includedUser.GetSamName()) - } else if includedUser.GetPrincipalName() != "" { + if includedUser.GetPrincipalName() != "" { includedUserNames = append(includedUserNames, includedUser.GetPrincipalName()) + } else if includedUser.GetSamName() != "" { + includedUserNames = append(includedUserNames, includedUser.GetSamName()) } } if len(includedUserNames) > 0 { - getIncludedUsersRequest := client.ApiClient.IdentityAPIsDAAS.IdentityGetUsers(ctx) - getIncludedUsersRequest = getIncludedUsersRequest.User(includedUserNames) - getIncludedUsersRequest = getIncludedUsersRequest.Provider(citrixorchestration.IDENTITYPROVIDERTYPE_ALL) - includedUsersResponse, httpResp, err := citrixdaasclient.AddRequestData(getIncludedUsersRequest, client).Execute() + includedUsers, httpResp, err = util.GetUsersUsingIdentity(ctx, client, includedUserNames) if err != nil { diagnostics.AddError( - "Error fetching delivery group user details", + "Error fetching user details for delivery group", "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ "\nError message: "+util.ReadClientError(err), ) return nil, nil, err } - - err = verifyIdentityUserListCompleteness(includedUserNames, includedUsersResponse.GetItems()) - if err != nil { - diagnostics.AddError( - "Error fetching delivery group user details", - "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ - "\nError message: "+err.Error(), - ) - return nil, nil, err - } - - includedUsers = includedUsersResponse.GetItems() } excludedUserNames := []string{} for _, excludedUser := range excludedUsers { - if excludedUser.GetSamName() != "" { - excludedUserNames = append(excludedUserNames, excludedUser.GetSamName()) - } else if excludedUser.GetPrincipalName() != "" { + if excludedUser.GetPrincipalName() != "" { excludedUserNames = append(excludedUserNames, excludedUser.GetPrincipalName()) + } else if excludedUser.GetSamName() != "" { + excludedUserNames = append(excludedUserNames, excludedUser.GetSamName()) } } if len(excludedUserNames) > 0 { - getExcludedUsersRequest := client.ApiClient.IdentityAPIsDAAS.IdentityGetUsers(ctx) - getExcludedUsersRequest = getExcludedUsersRequest.User(excludedUserNames) - getExcludedUsersRequest = getExcludedUsersRequest.Provider(citrixorchestration.IDENTITYPROVIDERTYPE_ALL) - excludedUsersResponse, httpResp, err := citrixdaasclient.AddRequestData(getExcludedUsersRequest, client).Execute() + excludedUsers, httpResp, err = util.GetUsersUsingIdentity(ctx, client, excludedUserNames) if err != nil { diagnostics.AddError( - "Error fetching delivery group user details", + "Error fetching user details for delivery group", "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ "\nError message: "+util.ReadClientError(err), ) return nil, nil, err } - - err = verifyIdentityUserListCompleteness(excludedUserNames, excludedUsersResponse.GetItems()) - if err != nil { - diagnostics.AddError( - "Error fetching delivery group user details", - "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ - "\nError message: "+err.Error(), - ) - return nil, nil, err - } - - excludedUsers = excludedUsersResponse.GetItems() } return includedUsers, excludedUsers, nil } -func (r DeliveryGroupResourceModel) updatePlanWithRestrictedAccessUsers(deliveryGroup *citrixorchestration.DeliveryGroupDetailResponseModel) DeliveryGroupResourceModel { +func (r DeliveryGroupResourceModel) updatePlanWithRestrictedAccessUsers(ctx context.Context, diagnostics *diag.Diagnostics, deliveryGroup *citrixorchestration.DeliveryGroupDetailResponseModel) DeliveryGroupResourceModel { simpleAccessPolicy := deliveryGroup.GetSimpleAccessPolicy() if !r.AllowAnonymousAccess.IsNull() { r.AllowAnonymousAccess = types.BoolValue(simpleAccessPolicy.GetAllowAnonymous()) } - if !simpleAccessPolicy.GetIncludedUserFilterEnabled() { - r.RestrictedAccessUsers = nil + if !simpleAccessPolicy.GetIncludedUserFilterEnabled() || r.RestrictedAccessUsers.IsNull() { + if attributes, err := util.AttributeMapFromObject(RestrictedAccessUsers{}); err == nil { + r.RestrictedAccessUsers = types.ObjectNull(attributes) + } else { + diagnostics.AddWarning("Error when creating null RestrictedAccessUsers", err.Error()) + } return r } - if r.RestrictedAccessUsers == nil { - r.RestrictedAccessUsers = &RestrictedAccessUsers{} - } + users := util.ObjectValueToTypedObject[RestrictedAccessUsers](ctx, diagnostics, r.RestrictedAccessUsers) - updatedAllowList := refreshUsersList(r.RestrictedAccessUsers.AllowList, simpleAccessPolicy.GetIncludedUsers()) - r.RestrictedAccessUsers.AllowList = updatedAllowList + remoteIncludedUsers := simpleAccessPolicy.GetIncludedUsers() + if len(remoteIncludedUsers) == 0 { + users.AllowList = types.SetNull(types.StringType) + } else { + users.AllowList = util.RefreshUsersList(ctx, diagnostics, users.AllowList, simpleAccessPolicy.GetIncludedUsers()) + } if simpleAccessPolicy.GetExcludedUserFilterEnabled() { - updatedBlockList := refreshUsersList(r.RestrictedAccessUsers.BlockList, simpleAccessPolicy.GetExcludedUsers()) - r.RestrictedAccessUsers.BlockList = updatedBlockList + if len(simpleAccessPolicy.GetExcludedUsers()) == 0 { + users.BlockList = types.SetNull(types.StringType) + } else { + users.BlockList = util.RefreshUsersList(ctx, diagnostics, users.BlockList, simpleAccessPolicy.GetExcludedUsers()) + } } + r.RestrictedAccessUsers = util.TypedObjectToObjectValue(ctx, diagnostics, users) + return r } -func (r DeliveryGroupResourceModel) updatePlanWithAutoscaleSettings(deliveryGroup *citrixorchestration.DeliveryGroupDetailResponseModel, dgPowerTimeSchemes *citrixorchestration.PowerTimeSchemeResponseModelCollection) DeliveryGroupResourceModel { - if r.AutoscaleSettings == nil { +func (r DeliveryGroupResourceModel) updatePlanWithAutoscaleSettings(ctx context.Context, diags *diag.Diagnostics, deliveryGroup *citrixorchestration.DeliveryGroupDetailResponseModel, dgPowerTimeSchemes *citrixorchestration.PowerTimeSchemeResponseModelCollection) DeliveryGroupResourceModel { + if r.AutoscaleSettings.IsNull() { return r } - r.AutoscaleSettings.AutoscaleEnabled = types.BoolValue(deliveryGroup.GetAutoScaleEnabled()) - - if !r.AutoscaleSettings.Timezone.IsNull() { - r.AutoscaleSettings.Timezone = types.StringValue(deliveryGroup.GetTimeZone()) - } - - r.AutoscaleSettings.PeakDisconnectTimeoutMinutes = types.Int64Value(int64(deliveryGroup.GetPeakDisconnectTimeoutMinutes())) - r.AutoscaleSettings.PeakLogOffAction = types.StringValue(reflect.ValueOf(deliveryGroup.GetPeakLogOffAction()).String()) - r.AutoscaleSettings.PeakDisconnectAction = types.StringValue(reflect.ValueOf(deliveryGroup.GetPeakDisconnectAction()).String()) - r.AutoscaleSettings.PeakExtendedDisconnectAction = types.StringValue(reflect.ValueOf(deliveryGroup.GetPeakExtendedDisconnectAction()).String()) - r.AutoscaleSettings.PeakExtendedDisconnectTimeoutMinutes = types.Int64Value(int64(deliveryGroup.GetPeakExtendedDisconnectTimeoutMinutes())) - r.AutoscaleSettings.OffPeakDisconnectTimeoutMinutes = types.Int64Value(int64(deliveryGroup.GetOffPeakDisconnectTimeoutMinutes())) - r.AutoscaleSettings.OffPeakLogOffAction = types.StringValue(reflect.ValueOf(deliveryGroup.GetOffPeakLogOffAction()).String()) - r.AutoscaleSettings.OffPeakDisconnectAction = types.StringValue(reflect.ValueOf(deliveryGroup.GetOffPeakExtendedDisconnectAction()).String()) - r.AutoscaleSettings.OffPeakExtendedDisconnectAction = types.StringValue(reflect.ValueOf(deliveryGroup.GetOffPeakExtendedDisconnectAction()).String()) - r.AutoscaleSettings.OffPeakExtendedDisconnectTimeoutMinutes = types.Int64Value(int64(deliveryGroup.GetOffPeakExtendedDisconnectTimeoutMinutes())) - r.AutoscaleSettings.PeakBufferSizePercent = types.Int64Value(int64(deliveryGroup.GetPeakBufferSizePercent())) - r.AutoscaleSettings.OffPeakBufferSizePercent = types.Int64Value(int64(deliveryGroup.GetOffPeakBufferSizePercent())) - r.AutoscaleSettings.PowerOffDelayMinutes = types.Int64Value(int64(deliveryGroup.GetPowerOffDelayMinutes())) - r.AutoscaleSettings.DisconnectPeakIdleSessionAfterSeconds = types.Int64Value(int64(deliveryGroup.GetDisconnectPeakIdleSessionAfterSeconds())) - r.AutoscaleSettings.DisconnectOffPeakIdleSessionAfterSeconds = types.Int64Value(int64(deliveryGroup.GetDisconnectOffPeakIdleSessionAfterSeconds())) - r.AutoscaleSettings.LogoffPeakDisconnectedSessionAfterSeconds = types.Int64Value(int64(deliveryGroup.GetLogoffPeakDisconnectedSessionAfterSeconds())) - r.AutoscaleSettings.LogoffOffPeakDisconnectedSessionAfterSeconds = types.Int64Value(int64(deliveryGroup.GetLogoffOffPeakDisconnectedSessionAfterSeconds())) - parsedPowerTimeSchemes := parsePowerTimeSchemesClientToPluginModel(dgPowerTimeSchemes.GetItems()) - r.AutoscaleSettings.PowerTimeSchemes = preserveOrderInPowerTimeSchemes(r.AutoscaleSettings.PowerTimeSchemes, parsedPowerTimeSchemes) - + autoscale := util.ObjectValueToTypedObject[DeliveryGroupPowerManagementSettings](context.Background(), nil, r.AutoscaleSettings) + autoscale.AutoscaleEnabled = types.BoolValue(deliveryGroup.GetAutoScaleEnabled()) + + if !autoscale.Timezone.IsNull() { + autoscale.Timezone = types.StringValue(deliveryGroup.GetTimeZone()) + } + + autoscale.PeakDisconnectTimeoutMinutes = types.Int64Value(int64(deliveryGroup.GetPeakDisconnectTimeoutMinutes())) + autoscale.PeakLogOffAction = types.StringValue(string(deliveryGroup.GetPeakLogOffAction())) + autoscale.PeakDisconnectAction = types.StringValue(string(deliveryGroup.GetPeakDisconnectAction())) + autoscale.PeakExtendedDisconnectAction = types.StringValue(string(deliveryGroup.GetPeakExtendedDisconnectAction())) + autoscale.PeakExtendedDisconnectTimeoutMinutes = types.Int64Value(int64(deliveryGroup.GetPeakExtendedDisconnectTimeoutMinutes())) + autoscale.OffPeakDisconnectTimeoutMinutes = types.Int64Value(int64(deliveryGroup.GetOffPeakDisconnectTimeoutMinutes())) + autoscale.OffPeakLogOffAction = types.StringValue(string(deliveryGroup.GetOffPeakLogOffAction())) + autoscale.OffPeakDisconnectAction = types.StringValue(string(deliveryGroup.GetOffPeakExtendedDisconnectAction())) + autoscale.OffPeakExtendedDisconnectAction = types.StringValue(string(deliveryGroup.GetOffPeakExtendedDisconnectAction())) + autoscale.OffPeakExtendedDisconnectTimeoutMinutes = types.Int64Value(int64(deliveryGroup.GetOffPeakExtendedDisconnectTimeoutMinutes())) + autoscale.PeakBufferSizePercent = types.Int64Value(int64(deliveryGroup.GetPeakBufferSizePercent())) + autoscale.OffPeakBufferSizePercent = types.Int64Value(int64(deliveryGroup.GetOffPeakBufferSizePercent())) + autoscale.PowerOffDelayMinutes = types.Int64Value(int64(deliveryGroup.GetPowerOffDelayMinutes())) + autoscale.DisconnectPeakIdleSessionAfterSeconds = types.Int64Value(int64(deliveryGroup.GetDisconnectPeakIdleSessionAfterSeconds())) + autoscale.DisconnectOffPeakIdleSessionAfterSeconds = types.Int64Value(int64(deliveryGroup.GetDisconnectOffPeakIdleSessionAfterSeconds())) + autoscale.LogoffPeakDisconnectedSessionAfterSeconds = types.Int64Value(int64(deliveryGroup.GetLogoffPeakDisconnectedSessionAfterSeconds())) + autoscale.LogoffOffPeakDisconnectedSessionAfterSeconds = types.Int64Value(int64(deliveryGroup.GetLogoffOffPeakDisconnectedSessionAfterSeconds())) + + parsedPowerTimeSchemes := parsePowerTimeSchemesClientToPluginModel(ctx, diags, dgPowerTimeSchemes.GetItems()) + autoscalePowerTimeSchemes := util.ObjectListToTypedArray[DeliveryGroupPowerTimeScheme](ctx, diags, autoscale.PowerTimeSchemes) + parsedPowerTimeSchemes = preserveOrderInPowerTimeSchemes(ctx, diags, autoscalePowerTimeSchemes, parsedPowerTimeSchemes) + autoscale.PowerTimeSchemes = util.TypedArrayToObjectList[DeliveryGroupPowerTimeScheme](ctx, diags, parsedPowerTimeSchemes) + + r.AutoscaleSettings = util.TypedObjectToObjectValue(ctx, diags, autoscale) return r } -func preserveOrderInPowerTimeSchemes(powerTimeSchemeInPlan, powerTimeSchemesInRemote []DeliveryGroupPowerTimeScheme) []DeliveryGroupPowerTimeScheme { +func preserveOrderInPowerTimeSchemes(ctx context.Context, diags *diag.Diagnostics, powerTimeSchemeInPlan, powerTimeSchemesInRemote []DeliveryGroupPowerTimeScheme) []DeliveryGroupPowerTimeScheme { planPowerTimeSchemesMap := map[string]int{} for index, powerTimeScheme := range powerTimeSchemeInPlan { @@ -1303,7 +1372,10 @@ func preserveOrderInPowerTimeSchemes(powerTimeSchemeInPlan, powerTimeSchemesInRe if !exists { powerTimeSchemeInPlan = append(powerTimeSchemeInPlan, powerTimeScheme) } else { - powerTimeSchemeInPlan[index].PoolSizeSchedule = preserveOrderInPoolSizeSchedule(powerTimeSchemeInPlan[index].PoolSizeSchedule, powerTimeScheme.PoolSizeSchedule) + updatedPoolSizeSchedule := preserveOrderInPoolSizeSchedule( + util.ObjectListToTypedArray[PowerTimeSchemePoolSizeScheduleRequestModel](ctx, diags, powerTimeSchemeInPlan[index].PoolSizeSchedule), + util.ObjectListToTypedArray[PowerTimeSchemePoolSizeScheduleRequestModel](ctx, diags, powerTimeScheme.PoolSizeSchedule)) + powerTimeSchemeInPlan[index].PoolSizeSchedule = util.TypedArrayToObjectList[PowerTimeSchemePoolSizeScheduleRequestModel](ctx, diags, updatedPoolSizeSchedule) } planPowerTimeSchemesMap[powerTimeScheme.DisplayName.ValueString()] = -1 } @@ -1345,63 +1417,3 @@ func preserveOrderInPoolSizeSchedule(poolSizeScheduleInPlan, poolSizeScheduleInR return poolSizeSchedules } - -func refreshUsersList(users []basetypes.StringValue, usersInRemote []citrixorchestration.IdentityUserResponseModel) []basetypes.StringValue { - samNamesMap := map[string]int{} - upnMap := map[string]int{} - - for index, userInRemote := range usersInRemote { - userSamName := userInRemote.GetSamName() - userPrincipalName := userInRemote.GetPrincipalName() - if userSamName != "" { - samNamesMap[userSamName] = index - } - if userPrincipalName != "" { - upnMap[userPrincipalName] = index - } - } - - res := []basetypes.StringValue{} - for _, user := range users { - userStringValue := user.ValueString() - samRegex, _ := regexp.Compile(util.SamRegex) - if samRegex.MatchString(userStringValue) { - index, exists := samNamesMap[userStringValue] - if !exists { - continue - } - res = append(res, user) - samNamesMap[userStringValue] = -1 - userPrincipalName := usersInRemote[index].GetPrincipalName() - _, exists = upnMap[userPrincipalName] - if exists { - upnMap[userPrincipalName] = -1 - } - - continue - } - - upnRegex, _ := regexp.Compile(util.UpnRegex) - if upnRegex.MatchString(userStringValue) { - index, exists := upnMap[userStringValue] - if !exists { - continue - } - res = append(res, user) - upnMap[userStringValue] = -1 - samName := usersInRemote[index].GetSamName() - _, exists = samNamesMap[samName] - if exists { - samNamesMap[samName] = -1 - } - } - } - - for samName, index := range samNamesMap { - if index != -1 { // Users that are only in remote - res = append(res, types.StringValue(samName)) - } - } - - return res -} diff --git a/internal/daas/gac_settings/gac_settings_resource.go b/internal/daas/gac_settings/gac_settings_resource.go index db0026d..5fcbbaf 100644 --- a/internal/daas/gac_settings/gac_settings_resource.go +++ b/internal/daas/gac_settings/gac_settings_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package gac_settings diff --git a/internal/daas/gac_settings/gac_settings_resource_model.go b/internal/daas/gac_settings/gac_settings_resource_model.go index 07b78fa..6d9d6f7 100644 --- a/internal/daas/gac_settings/gac_settings_resource_model.go +++ b/internal/daas/gac_settings/gac_settings_resource_model.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package gac_settings diff --git a/internal/daas/hypervisor/aws_hypervisor_resource.go b/internal/daas/hypervisor/aws_hypervisor_resource.go index e350eb2..8a4f09e 100644 --- a/internal/daas/hypervisor/aws_hypervisor_resource.go +++ b/internal/daas/hypervisor/aws_hypervisor_resource.go @@ -1,23 +1,17 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor import ( "context" "net/http" - "regexp" 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-validators/stringvalidator" "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/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) // Ensure the implementation satisfies the expected interfaces. @@ -44,48 +38,7 @@ func (r *awsHypervisorResource) Metadata(_ context.Context, req resource.Metadat // Schema defines the schema for the resource. func (r *awsHypervisorResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages an AWS EC2 hypervisor.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the hypervisor.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the hypervisor.", - Required: true, - }, - "zone": schema.StringAttribute{ - Description: "Id of the zone the hypervisor is associated with.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "region": schema.StringAttribute{ - Description: "AWS region to connect to.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplaceIfConfigured(), - }, - }, - "api_key": schema.StringAttribute{ - Description: "The API key used to authenticate with the AWS APIs.", - Required: true, - }, - "secret_key": schema.StringAttribute{ - Description: "The secret key used to authenticate with the AWS APIs.", - Required: true, - Sensitive: true, - }, - }, - } + resp.Schema = GetAwsHypervisorSchema() } // Configure adds the provider configured client to the resource. @@ -114,7 +67,9 @@ func (r *awsHypervisorResource) Create(ctx context.Context, req resource.CreateR connectionDetails.SetName(plan.Name.ValueString()) connectionDetails.SetZone(plan.Zone.ValueString()) connectionDetails.SetConnectionType(citrixorchestration.HYPERVISORCONNECTIONTYPE_AWS) - + if !plan.Scopes.IsNull() { + connectionDetails.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } if plan.Region.IsNull() || plan.ApiKey.IsNull() || plan.SecretKey.IsNull() { resp.Diagnostics.AddError( "Error creating Hypervisor for AWS", @@ -138,7 +93,7 @@ func (r *awsHypervisorResource) Create(ctx context.Context, req resource.CreateR } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(hypervisor) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, hypervisor) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -176,7 +131,7 @@ func (r *awsHypervisorResource) Read(ctx context.Context, req resource.ReadReque } // Overwrite hypervisor with refreshed state - state = state.RefreshPropertyValues(hypervisor) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, hypervisor) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -218,6 +173,9 @@ func (r *awsHypervisorResource) Update(ctx context.Context, req resource.UpdateR editHypervisorRequestBody.SetConnectionType(citrixorchestration.HYPERVISORCONNECTIONTYPE_AWS) editHypervisorRequestBody.SetApiKey(plan.ApiKey.ValueString()) editHypervisorRequestBody.SetSecretKey(plan.SecretKey.ValueString()) + if !plan.Scopes.IsNull() { + editHypervisorRequestBody.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } // Patch hypervisor updatedHypervisor, err := UpdateHypervisor(ctx, r.client, &resp.Diagnostics, hypervisor, editHypervisorRequestBody) @@ -226,7 +184,7 @@ func (r *awsHypervisorResource) Update(ctx context.Context, req resource.UpdateR } // Update resource state with updated property values - plan = plan.RefreshPropertyValues(updatedHypervisor) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, updatedHypervisor) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) diff --git a/internal/daas/hypervisor/aws_hypervisor_resource_model.go b/internal/daas/hypervisor/aws_hypervisor_resource_model.go index 4944b02..b09a18c 100644 --- a/internal/daas/hypervisor/aws_hypervisor_resource_model.go +++ b/internal/daas/hypervisor/aws_hypervisor_resource_model.go @@ -1,32 +1,108 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor import ( + "context" + "regexp" + citrixorchestration "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/attr" + "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/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) // HypervisorResourceModel maps the resource schema data. type AwsHypervisorResourceModel struct { /**** Connection Details ****/ - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Zone types.String `tfsdk:"zone"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Zone types.String `tfsdk:"zone"` + Scopes types.Set `tfsdk:"scopes"` // Set[string] /** AWS EC2 Connection **/ Region types.String `tfsdk:"region"` ApiKey types.String `tfsdk:"api_key"` SecretKey types.String `tfsdk:"secret_key"` } -func (r AwsHypervisorResourceModel) RefreshPropertyValues(hypervisor *citrixorchestration.HypervisorDetailResponseModel) AwsHypervisorResourceModel { +func GetAwsHypervisorSchema() schema.Schema { + return schema.Schema{ + Description: "Manages an AWS EC2 hypervisor.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the hypervisor.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the hypervisor.", + Required: true, + }, + "zone": schema.StringAttribute{ + Description: "Id of the zone the hypervisor is associated with.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "region": schema.StringAttribute{ + Description: "AWS region to connect to.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + }, + }, + "api_key": schema.StringAttribute{ + Description: "The API key used to authenticate with the AWS APIs.", + Required: true, + }, + "secret_key": schema.StringAttribute{ + Description: "The secret key used to authenticate with the AWS APIs.", + Required: true, + Sensitive: true, + }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The IDs of the scopes for the hypervisor to be a part of.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), + 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 (r AwsHypervisorResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, hypervisor *citrixorchestration.HypervisorDetailResponseModel) AwsHypervisorResourceModel { r.Id = types.StringValue(hypervisor.GetId()) r.Name = types.StringValue(hypervisor.GetName()) hypZone := hypervisor.GetZone() r.Zone = types.StringValue(hypZone.GetId()) r.Region = types.StringValue(hypervisor.GetRegion()) r.ApiKey = types.StringValue(hypervisor.GetApiKey()) + scopeIds := util.GetIdsForScopeObjects(hypervisor.GetScopes()) + r.Scopes = util.StringArrayToStringSet(ctx, diagnostics, scopeIds) return r } diff --git a/internal/daas/hypervisor/azure_hypervisor_resource.go b/internal/daas/hypervisor/azure_hypervisor_resource.go index ca20431..1b583ab 100644 --- a/internal/daas/hypervisor/azure_hypervisor_resource.go +++ b/internal/daas/hypervisor/azure_hypervisor_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor @@ -6,7 +6,6 @@ import ( "context" "encoding/json" "net/http" - "regexp" "strconv" "time" @@ -14,14 +13,8 @@ import ( 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/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" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) const ( @@ -52,68 +45,7 @@ func (r *azureHypervisorResource) Metadata(_ context.Context, req resource.Metad // Schema defines the schema for the resource. func (r *azureHypervisorResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages an Azure hypervisor.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the hypervisor.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the hypervisor.", - Required: true, - }, - "zone": schema.StringAttribute{ - Description: "Id of the zone the hypervisor is associated with.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "application_id": schema.StringAttribute{ - Description: "Application ID of the service principal used to access the Azure APIs.", - Required: true, - }, - "application_secret": schema.StringAttribute{ - Description: "The Application Secret of the service principal used to access the Azure APIs.", - Required: true, - Sensitive: true, - }, - "application_secret_expiration_date": schema.StringAttribute{ - Description: "The expiration date of the application secret of the service principal used to access the Azure APIs. Format is YYYY-MM-DD.", - Optional: true, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(`^((?:19|20|21)\d\d)[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$`), "ensure date is valid and is in the format YYYY-MM-DD"), - }, - }, - "subscription_id": schema.StringAttribute{ - Description: "Azure Subscription ID.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplaceIfConfigured(), - }, - }, - "active_directory_id": schema.StringAttribute{ - Description: "Azure Active Directory ID.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplaceIfConfigured(), - }, - }, - "enable_azure_ad_device_management": schema.BoolAttribute{ - Description: "Enable Azure AD device management. Default is false.", - Optional: true, - Computed: true, - Default: booldefault.StaticBool(false), - }, - }, - } + resp.Schema = GetAzureHypervisorSchema() } // Configure adds the provider configured client to the resource. @@ -156,7 +88,9 @@ func (r *azureHypervisorResource) Create(ctx context.Context, req resource.Creat connectionDetails.SetMetadata(metadata) connectionDetails.SetSubscriptionId(plan.SubscriptionId.ValueString()) connectionDetails.SetActiveDirectoryId(plan.ActiveDirectoryId.ValueString()) - + if !plan.Scopes.IsNull() { + connectionDetails.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } // Set custom properties for enabling AzureAD Device Management customProperties := []citrixorchestration.NameValueStringPairModel{} enableAADDeviceManagementProperty := citrixorchestration.NameValueStringPairModel{} @@ -177,7 +111,7 @@ func (r *azureHypervisorResource) Create(ctx context.Context, req resource.Creat } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(hypervisor) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, hypervisor) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -215,7 +149,7 @@ func (r *azureHypervisorResource) Read(ctx context.Context, req resource.ReadReq } // Overwrite hypervisor with refreshed state - state = state.RefreshPropertyValues(hypervisor) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, hypervisor) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -259,6 +193,9 @@ func (r *azureHypervisorResource) Update(ctx context.Context, req resource.Updat editHypervisorRequestBody.SetApplicationSecret(plan.ApplicationSecret.ValueString()) metadata := getMetadataForAzureRmHypervisor(plan) editHypervisorRequestBody.SetMetadata(metadata) + if !plan.Scopes.IsNull() { + editHypervisorRequestBody.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } // Modify custom properties customPropertiesString := hypervisor.GetCustomProperties() @@ -291,7 +228,7 @@ func (r *azureHypervisorResource) Update(ctx context.Context, req resource.Updat } // Update resource state with updated property values - plan = plan.RefreshPropertyValues(updatedHypervisor) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, updatedHypervisor) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) diff --git a/internal/daas/hypervisor/azure_hypervisor_resource_model.go b/internal/daas/hypervisor/azure_hypervisor_resource_model.go index e0562d2..d61f923 100644 --- a/internal/daas/hypervisor/azure_hypervisor_resource_model.go +++ b/internal/daas/hypervisor/azure_hypervisor_resource_model.go @@ -1,22 +1,36 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor import ( + "context" "encoding/json" + "regexp" "strconv" citrixorchestration "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/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) // HypervisorResourceModel maps the resource schema data. type AzureHypervisorResourceModel struct { /**** Connection Details ****/ - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Zone types.String `tfsdk:"zone"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Zone types.String `tfsdk:"zone"` + Scopes types.Set `tfsdk:"scopes"` // Set[string] /** Azure Connection **/ ApplicationId types.String `tfsdk:"application_id"` ApplicationSecret types.String `tfsdk:"application_secret"` @@ -26,7 +40,87 @@ type AzureHypervisorResourceModel struct { EnableAzureADDeviceManagement types.Bool `tfsdk:"enable_azure_ad_device_management"` } -func (r AzureHypervisorResourceModel) RefreshPropertyValues(hypervisor *citrixorchestration.HypervisorDetailResponseModel) AzureHypervisorResourceModel { +func GetAzureHypervisorSchema() schema.Schema { + return schema.Schema{ + Description: "Manages an Azure hypervisor.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the hypervisor.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the hypervisor.", + Required: true, + }, + "zone": schema.StringAttribute{ + Description: "Id of the zone the hypervisor is associated with.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "application_id": schema.StringAttribute{ + Description: "Application ID of the service principal used to access the Azure APIs.", + Required: true, + }, + "application_secret": schema.StringAttribute{ + Description: "The Application Secret of the service principal used to access the Azure APIs.", + Required: true, + Sensitive: true, + }, + "application_secret_expiration_date": schema.StringAttribute{ + Description: "The expiration date of the application secret of the service principal used to access the Azure APIs. Format is YYYY-MM-DD.", + Optional: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^((?:19|20|21)\d\d)[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])$`), "ensure date is valid and is in the format YYYY-MM-DD"), + }, + }, + "subscription_id": schema.StringAttribute{ + Description: "Azure Subscription ID.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + }, + }, + "active_directory_id": schema.StringAttribute{ + Description: "Azure Active Directory ID.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + }, + }, + "enable_azure_ad_device_management": schema.BoolAttribute{ + Description: "Enable Azure AD device management. Default is false.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The IDs of the scopes for the hypervisor to be a part of.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), + 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 (r AzureHypervisorResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, hypervisor *citrixorchestration.HypervisorDetailResponseModel) AzureHypervisorResourceModel { r.Id = types.StringValue(hypervisor.GetId()) r.Name = types.StringValue(hypervisor.GetName()) hypZone := hypervisor.GetZone() @@ -34,6 +128,8 @@ func (r AzureHypervisorResourceModel) RefreshPropertyValues(hypervisor *citrixor r.ApplicationId = types.StringValue(hypervisor.GetApplicationId()) r.SubscriptionId = types.StringValue(hypervisor.GetSubscriptionId()) r.ActiveDirectoryId = types.StringValue(hypervisor.GetActiveDirectoryId()) + scopeIds := util.GetIdsForScopeObjects(hypervisor.GetScopes()) + r.Scopes = util.StringArrayToStringSet(ctx, diagnostics, scopeIds) customPropertiesString := hypervisor.GetCustomProperties() var customProperties []citrixorchestration.NameValueStringPairModel diff --git a/internal/daas/hypervisor/gcp_hypervisor_resource.go b/internal/daas/hypervisor/gcp_hypervisor_resource.go index e19eba6..65a8ed1 100644 --- a/internal/daas/hypervisor/gcp_hypervisor_resource.go +++ b/internal/daas/hypervisor/gcp_hypervisor_resource.go @@ -1,23 +1,17 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor import ( "context" "net/http" - "regexp" 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-validators/stringvalidator" "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/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) // Ensure the implementation satisfies the expected interfaces. @@ -44,41 +38,7 @@ func (r *gcpHypervisorResource) Metadata(_ context.Context, req resource.Metadat // Schema defines the schema for the resource. func (r *gcpHypervisorResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages a GCP hypervisor.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the hypervisor.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the hypervisor.", - Required: true, - }, - "zone": schema.StringAttribute{ - Description: "Id of the zone the hypervisor is associated with.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "service_account_id": schema.StringAttribute{ - Description: "The service account ID used to access the Google Cloud APIs.", - Required: true, - }, - "service_account_credentials": schema.StringAttribute{ - Description: "The JSON-encoded service account credentials used to access the Google Cloud APIs.", - Required: true, - Sensitive: true, - }, - }, - } + resp.Schema = GetGcpHypervisorSchema() } // Configure adds the provider configured client to the resource. @@ -107,6 +67,9 @@ func (r *gcpHypervisorResource) Create(ctx context.Context, req resource.CreateR connectionDetails.SetName(plan.Name.ValueString()) connectionDetails.SetZone(plan.Zone.ValueString()) connectionDetails.SetConnectionType(citrixorchestration.HYPERVISORCONNECTIONTYPE_GOOGLE_CLOUD_PLATFORM) + if !plan.Scopes.IsNull() { + connectionDetails.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } if plan.ServiceAccountId.IsNull() || plan.ServiceAccountCredentials.IsNull() { resp.Diagnostics.AddError( @@ -130,7 +93,7 @@ func (r *gcpHypervisorResource) Create(ctx context.Context, req resource.CreateR } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(hypervisor) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, hypervisor) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -168,7 +131,7 @@ func (r *gcpHypervisorResource) Read(ctx context.Context, req resource.ReadReque } // Overwrite hypervisor with refreshed state - state = state.RefreshPropertyValues(hypervisor) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, hypervisor) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -203,6 +166,9 @@ func (r *gcpHypervisorResource) Update(ctx context.Context, req resource.UpdateR editHypervisorRequestBody.SetConnectionType(citrixorchestration.HYPERVISORCONNECTIONTYPE_GOOGLE_CLOUD_PLATFORM) editHypervisorRequestBody.SetServiceAccountId(plan.ServiceAccountId.ValueString()) editHypervisorRequestBody.SetServiceAccountCredential(plan.ServiceAccountCredentials.ValueString()) + if !plan.Scopes.IsNull() { + editHypervisorRequestBody.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } // Patch hypervisor updatedHypervisor, err := UpdateHypervisor(ctx, r.client, &resp.Diagnostics, hypervisor, editHypervisorRequestBody) @@ -211,7 +177,7 @@ func (r *gcpHypervisorResource) Update(ctx context.Context, req resource.UpdateR } // Update resource state with updated property values - plan = plan.RefreshPropertyValues(updatedHypervisor) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, updatedHypervisor) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) diff --git a/internal/daas/hypervisor/gcp_hypervisor_resource_model.go b/internal/daas/hypervisor/gcp_hypervisor_resource_model.go index 32c520f..5a8e119 100644 --- a/internal/daas/hypervisor/gcp_hypervisor_resource_model.go +++ b/internal/daas/hypervisor/gcp_hypervisor_resource_model.go @@ -1,30 +1,99 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor import ( + "context" + "regexp" + citrixorchestration "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/attr" + "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/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) // HypervisorResourceModel maps the resource schema data. type GcpHypervisorResourceModel struct { /**** Connection Details ****/ - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Zone types.String `tfsdk:"zone"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Zone types.String `tfsdk:"zone"` + Scopes types.Set `tfsdk:"scopes"` // Set[string] /** GCP Connection **/ ServiceAccountId types.String `tfsdk:"service_account_id"` ServiceAccountCredentials types.String `tfsdk:"service_account_credentials"` } -func (r GcpHypervisorResourceModel) RefreshPropertyValues(hypervisor *citrixorchestration.HypervisorDetailResponseModel) GcpHypervisorResourceModel { +func GetGcpHypervisorSchema() schema.Schema { + return schema.Schema{ + Description: "Manages a GCP hypervisor.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the hypervisor.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the hypervisor.", + Required: true, + }, + "zone": schema.StringAttribute{ + Description: "Id of the zone the hypervisor is associated with.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "service_account_id": schema.StringAttribute{ + Description: "The service account ID used to access the Google Cloud APIs.", + Required: true, + }, + "service_account_credentials": schema.StringAttribute{ + Description: "The JSON-encoded service account credentials used to access the Google Cloud APIs.", + Required: true, + Sensitive: true, + }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The IDs of the scopes for the hypervisor to be a part of.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), + 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 (r GcpHypervisorResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, hypervisor *citrixorchestration.HypervisorDetailResponseModel) GcpHypervisorResourceModel { r.Id = types.StringValue(hypervisor.GetId()) r.Name = types.StringValue(hypervisor.GetName()) hypZone := hypervisor.GetZone() r.Zone = types.StringValue(hypZone.GetId()) r.ServiceAccountId = types.StringValue(hypervisor.GetServiceAccountId()) + scopeIds := util.GetIdsForScopeObjects(hypervisor.GetScopes()) + r.Scopes = util.StringArrayToStringSet(ctx, diagnostics, scopeIds) return r } diff --git a/internal/daas/hypervisor/hypervisor_common.go b/internal/daas/hypervisor/hypervisor_common.go index ba3aa6a..754adfb 100644 --- a/internal/daas/hypervisor/hypervisor_common.go +++ b/internal/daas/hypervisor/hypervisor_common.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor diff --git a/internal/daas/hypervisor/nutanix_hypervisor_resource.go b/internal/daas/hypervisor/nutanix_hypervisor_resource.go index 80a1042..e052434 100644 --- a/internal/daas/hypervisor/nutanix_hypervisor_resource.go +++ b/internal/daas/hypervisor/nutanix_hypervisor_resource.go @@ -1,26 +1,16 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor import ( "context" "net/http" - "regexp" 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-validators/int64validator" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/int64default" - "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" ) // Ensure the implementation satisfies the expected interfaces. @@ -55,88 +45,7 @@ func (r *nutanixHypervisorResource) Configure(_ context.Context, req resource.Co // Schema implements resource.Resource. func (r *nutanixHypervisorResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages a Nutanix AHV hypervisor.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the hypervisor.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the hypervisor.", - Required: true, - }, - "zone": schema.StringAttribute{ - Description: "Id of the zone the hypervisor is associated with.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "username": schema.StringAttribute{ - Description: "Username of the hypervisor.", - Required: true, - }, - "password": schema.StringAttribute{ - Description: "Password of the hypervisor.", - Required: true, - }, - "password_format": schema.StringAttribute{ - Description: "Password format of the hypervisor. Choose between Base64 and PlainText.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - string(citrixorchestration.IDENTITYPASSWORDFORMAT_BASE64), - string(citrixorchestration.IDENTITYPASSWORDFORMAT_PLAIN_TEXT), - ), - }, - }, - "addresses": schema.ListAttribute{ - ElementType: types.StringType, - Description: "Hypervisor address(es). At least one is required.", - Required: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - listvalidator.ValueStringsAre( - stringvalidator.RegexMatches(regexp.MustCompile(util.IPv4Regex), "must be a valid IPv4 address without protocol (http:// or https://) and port number"), - ), - }, - }, - "max_absolute_active_actions": schema.Int64Attribute{ - Description: "Maximum number of actions that can execute in parallel on the hypervisor. Default is 100.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(100), - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - "max_absolute_new_actions_per_minute": schema.Int64Attribute{ - Description: "Maximum number of actions that can be started on the hypervisor per-minute. Default is 10.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(10), - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - "max_power_actions_percentage_of_machines": schema.Int64Attribute{ - Description: "Maximum percentage of machines on the hypervisor which can have their power state changed simultaneously. Default is 20.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(20), - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - }, - } + resp.Schema = GetNutanixHypervisorSchema() } // ImportState implements resource.ResourceWithImportState. @@ -174,11 +83,14 @@ func (r *nutanixHypervisorResource) Create(ctx context.Context, req resource.Cre } connectionDetails.SetPasswordFormat(*pwdFormat) - addresses := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Addresses) + addresses := util.StringListToStringArray(ctx, &diags, plan.Addresses) connectionDetails.SetAddresses(addresses) connectionDetails.SetMaxAbsoluteActiveActions(int32(plan.MaxAbsoluteActiveActions.ValueInt64())) connectionDetails.SetMaxAbsoluteNewActionsPerMinute(int32(plan.MaxAbsoluteNewActionsPerMinute.ValueInt64())) connectionDetails.SetMaxPowerActionsPercentageOfMachines(int32(plan.MaxPowerActionsPercentageOfMachines.ValueInt64())) + if !plan.Scopes.IsNull() { + connectionDetails.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } var body citrixorchestration.CreateHypervisorRequestModel body.SetConnectionDetails(connectionDetails) @@ -190,7 +102,7 @@ func (r *nutanixHypervisorResource) Create(ctx context.Context, req resource.Cre } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(hypervisor) + plan = plan.RefreshPropertyValues(ctx, &diags, hypervisor) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -228,7 +140,7 @@ func (r *nutanixHypervisorResource) Read(ctx context.Context, req resource.ReadR } // Overwrite hypervisor with refreshed state - state = state.RefreshPropertyValues(hypervisor) + state = state.RefreshPropertyValues(ctx, &diags, hypervisor) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -272,12 +184,15 @@ func (r *nutanixHypervisorResource) Update(ctx context.Context, req resource.Upd } editHypervisorRequestBody.SetPasswordFormat(*pwdFormat) - addresses := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Addresses) + addresses := util.StringListToStringArray(ctx, &diags, plan.Addresses) editHypervisorRequestBody.SetAddresses(addresses) editHypervisorRequestBody.SetMaxAbsoluteActiveActions(int32(plan.MaxAbsoluteActiveActions.ValueInt64())) editHypervisorRequestBody.SetMaxAbsoluteNewActionsPerMinute(int32(plan.MaxAbsoluteNewActionsPerMinute.ValueInt64())) editHypervisorRequestBody.SetMaxPowerActionsPercentageOfMachines(int32(plan.MaxPowerActionsPercentageOfMachines.ValueInt64())) + if !plan.Scopes.IsNull() { + editHypervisorRequestBody.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } // Patch hypervisor updatedHypervisor, err := UpdateHypervisor(ctx, r.client, &resp.Diagnostics, hypervisor, editHypervisorRequestBody) @@ -286,7 +201,7 @@ func (r *nutanixHypervisorResource) Update(ctx context.Context, req resource.Upd } // Update resource state with updated property values - plan = plan.RefreshPropertyValues(updatedHypervisor) + plan = plan.RefreshPropertyValues(ctx, &diags, updatedHypervisor) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) diff --git a/internal/daas/hypervisor/nutanix_hypervisor_resource_model.go b/internal/daas/hypervisor/nutanix_hypervisor_resource_model.go index ed2e5a4..1d231c3 100644 --- a/internal/daas/hypervisor/nutanix_hypervisor_resource_model.go +++ b/internal/daas/hypervisor/nutanix_hypervisor_resource_model.go @@ -1,37 +1,155 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor import ( + "context" + "regexp" + citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) // HypervisorResourceModel maps the resource schema data. type NutanixHypervisorResourceModel struct { /**** Connection Details ****/ - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Zone types.String `tfsdk:"zone"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Zone types.String `tfsdk:"zone"` + Scopes types.Set `tfsdk:"scopes"` // Set[string] /** Nutanix Connection **/ - Username types.String `tfsdk:"username"` - Password types.String `tfsdk:"password"` - PasswordFormat types.String `tfsdk:"password_format"` - Addresses []types.String `tfsdk:"addresses"` - MaxAbsoluteActiveActions types.Int64 `tfsdk:"max_absolute_active_actions"` - MaxAbsoluteNewActionsPerMinute types.Int64 `tfsdk:"max_absolute_new_actions_per_minute"` - MaxPowerActionsPercentageOfMachines types.Int64 `tfsdk:"max_power_actions_percentage_of_machines"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + PasswordFormat types.String `tfsdk:"password_format"` + Addresses types.List `tfsdk:"addresses"` //List[string] + MaxAbsoluteActiveActions types.Int64 `tfsdk:"max_absolute_active_actions"` + MaxAbsoluteNewActionsPerMinute types.Int64 `tfsdk:"max_absolute_new_actions_per_minute"` + MaxPowerActionsPercentageOfMachines types.Int64 `tfsdk:"max_power_actions_percentage_of_machines"` +} + +func GetNutanixHypervisorSchema() schema.Schema { + return schema.Schema{ + Description: "Manages a Nutanix AHV hypervisor.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the hypervisor.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the hypervisor.", + Required: true, + }, + "zone": schema.StringAttribute{ + Description: "Id of the zone the hypervisor is associated with.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "username": schema.StringAttribute{ + Description: "Username of the hypervisor.", + Required: true, + }, + "password": schema.StringAttribute{ + Description: "Password of the hypervisor.", + Required: true, + }, + "password_format": schema.StringAttribute{ + Description: "Password format of the hypervisor. Choose between Base64 and PlainText.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(citrixorchestration.IDENTITYPASSWORDFORMAT_BASE64), + string(citrixorchestration.IDENTITYPASSWORDFORMAT_PLAIN_TEXT), + ), + }, + }, + "addresses": schema.ListAttribute{ + ElementType: types.StringType, + Description: "Hypervisor address(es). At least one is required.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.RegexMatches(regexp.MustCompile(util.IPv4Regex), "must be a valid IPv4 address without protocol (http:// or https://) and port number"), + ), + }, + }, + "max_absolute_active_actions": schema.Int64Attribute{ + Description: "Maximum number of actions that can execute in parallel on the hypervisor. Default is 100.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(100), + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + "max_absolute_new_actions_per_minute": schema.Int64Attribute{ + Description: "Maximum number of actions that can be started on the hypervisor per-minute. Default is 10.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(10), + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + "max_power_actions_percentage_of_machines": schema.Int64Attribute{ + Description: "Maximum percentage of machines on the hypervisor which can have their power state changed simultaneously. Default is 20.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(20), + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The IDs of the scopes for the hypervisor to be a part of.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), + 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 (r NutanixHypervisorResourceModel) RefreshPropertyValues(hypervisor *citrixorchestration.HypervisorDetailResponseModel) NutanixHypervisorResourceModel { +func (r NutanixHypervisorResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, hypervisor *citrixorchestration.HypervisorDetailResponseModel) NutanixHypervisorResourceModel { r.Id = types.StringValue(hypervisor.GetId()) r.Name = types.StringValue(hypervisor.GetName()) r.Username = types.StringValue(hypervisor.GetUserName()) - r.Addresses = util.RefreshList(r.Addresses, hypervisor.GetAddresses()) + r.Addresses = util.RefreshListValues(ctx, diagnostics, r.Addresses, hypervisor.GetAddresses()) r.MaxAbsoluteActiveActions = types.Int64Value(int64(hypervisor.GetMaxAbsoluteActiveActions())) r.MaxAbsoluteNewActionsPerMinute = types.Int64Value(int64(hypervisor.GetMaxAbsoluteNewActionsPerMinute())) r.MaxPowerActionsPercentageOfMachines = types.Int64Value(int64(hypervisor.GetMaxPowerActionsPercentageOfMachines())) + scopeIds := util.GetIdsForScopeObjects(hypervisor.GetScopes()) + r.Scopes = util.StringArrayToStringSet(ctx, diagnostics, scopeIds) hypZone := hypervisor.GetZone() r.Zone = types.StringValue(hypZone.GetId()) diff --git a/internal/daas/hypervisor/vsphere_hypervisor_resource.go b/internal/daas/hypervisor/vsphere_hypervisor_resource.go index 0e62a32..05c0916 100644 --- a/internal/daas/hypervisor/vsphere_hypervisor_resource.go +++ b/internal/daas/hypervisor/vsphere_hypervisor_resource.go @@ -1,27 +1,16 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor import ( "context" "net/http" - "regexp" 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-validators/int64validator" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/int64default" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" - "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" ) // Ensure the implementation satisfies the expected interfaces. @@ -56,99 +45,7 @@ func (r *vsphereHypervisorResource) Configure(_ context.Context, req resource.Co // Schema implements resource.Resource. func (r *vsphereHypervisorResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages a VMware vSphere hypervisor.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the hypervisor.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the hypervisor.", - Required: true, - }, - "zone": schema.StringAttribute{ - Description: "Id of the zone the hypervisor is associated with.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "username": schema.StringAttribute{ - Description: "Username of the hypervisor.", - Required: true, - }, - "password": schema.StringAttribute{ - Description: "Password of the hypervisor.", - Required: true, - }, - "password_format": schema.StringAttribute{ - Description: "Password format of the hypervisor. Choose between Base64 and PlainText.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - string(citrixorchestration.IDENTITYPASSWORDFORMAT_BASE64), - string(citrixorchestration.IDENTITYPASSWORDFORMAT_PLAIN_TEXT), - ), - }, - }, - "addresses": schema.ListAttribute{ - ElementType: types.StringType, - Description: "Hypervisor address(es). At least one is required.", - Required: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "ssl_thumbprints": schema.ListAttribute{ - ElementType: types.StringType, - Description: "SSL certificate thumbprints to consider acceptable for this connection. If not specified, and the hypervisor uses SSL for its connection, the SSL certificate's root certification authority and any intermediate certificates must be trusted.", - Optional: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.RequiresReplace(), - }, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - listvalidator.ValueStringsAre( - stringvalidator.RegexMatches(regexp.MustCompile(util.SslThumbprintRegex), "must be specified with SSL thumbprint without colons"), - ), - }, - }, - "max_absolute_active_actions": schema.Int64Attribute{ - Description: "Maximum number of actions that can execute in parallel on the hypervisor. Default is 40.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(40), - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - "max_absolute_new_actions_per_minute": schema.Int64Attribute{ - Description: "Maximum number of actions that can be started on the hypervisor per-minute. Default is 10.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(10), - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - "max_power_actions_percentage_of_machines": schema.Int64Attribute{ - Description: "Maximum percentage of machines on the hypervisor which can have their power state changed simultaneously. Default is 20.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(20), - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - }, - } + resp.Schema = GetVsphereHypervisorSchema() } // ImportState implements resource.ResourceWithImportState. @@ -185,18 +82,20 @@ func (r *vsphereHypervisorResource) Create(ctx context.Context, req resource.Cre } connectionDetails.SetPasswordFormat(*pwdFormat) - addresses := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Addresses) + addresses := util.StringListToStringArray(ctx, &diags, plan.Addresses) connectionDetails.SetAddresses(addresses) - if plan.SslThumbprints != nil { - sslThumbprints := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.SslThumbprints) + if !plan.SslThumbprints.IsNull() { + sslThumbprints := util.StringListToStringArray(ctx, &diags, plan.SslThumbprints) connectionDetails.SetSslThumbprints(sslThumbprints) } connectionDetails.SetMaxAbsoluteActiveActions(int32(plan.MaxAbsoluteActiveActions.ValueInt64())) connectionDetails.SetMaxAbsoluteNewActionsPerMinute(int32(plan.MaxAbsoluteNewActionsPerMinute.ValueInt64())) connectionDetails.SetMaxPowerActionsPercentageOfMachines(int32(plan.MaxPowerActionsPercentageOfMachines.ValueInt64())) - + if !plan.Scopes.IsNull() { + connectionDetails.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } var body citrixorchestration.CreateHypervisorRequestModel body.SetConnectionDetails(connectionDetails) @@ -207,7 +106,7 @@ func (r *vsphereHypervisorResource) Create(ctx context.Context, req resource.Cre } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(hypervisor) + plan = plan.RefreshPropertyValues(ctx, &diags, hypervisor) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -245,7 +144,7 @@ func (r *vsphereHypervisorResource) Read(ctx context.Context, req resource.ReadR } // Overwrite hypervisor with refreshed state - state = state.RefreshPropertyValues(hypervisor) + state = state.RefreshPropertyValues(ctx, &diags, hypervisor) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -289,15 +188,18 @@ func (r *vsphereHypervisorResource) Update(ctx context.Context, req resource.Upd } editHypervisorRequestBody.SetPasswordFormat(*pwdFormat) - addresses := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Addresses) + addresses := util.StringListToStringArray(ctx, &diags, plan.Addresses) editHypervisorRequestBody.SetAddresses(addresses) - sslThumbprints := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.SslThumbprints) + sslThumbprints := util.StringListToStringArray(ctx, &diags, plan.SslThumbprints) editHypervisorRequestBody.SetSslThumbprints(sslThumbprints) editHypervisorRequestBody.SetMaxAbsoluteActiveActions(int32(plan.MaxAbsoluteActiveActions.ValueInt64())) editHypervisorRequestBody.SetMaxAbsoluteNewActionsPerMinute(int32(plan.MaxAbsoluteNewActionsPerMinute.ValueInt64())) editHypervisorRequestBody.SetMaxPowerActionsPercentageOfMachines(int32(plan.MaxPowerActionsPercentageOfMachines.ValueInt64())) + if !plan.Scopes.IsNull() { + editHypervisorRequestBody.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } // Patch hypervisor updatedHypervisor, err := UpdateHypervisor(ctx, r.client, &resp.Diagnostics, hypervisor, editHypervisorRequestBody) @@ -306,7 +208,7 @@ func (r *vsphereHypervisorResource) Update(ctx context.Context, req resource.Upd } // Update resource state with updated property values - plan = plan.RefreshPropertyValues(updatedHypervisor) + plan = plan.RefreshPropertyValues(ctx, &diags, updatedHypervisor) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) diff --git a/internal/daas/hypervisor/vsphere_hypervisor_resource_model.go b/internal/daas/hypervisor/vsphere_hypervisor_resource_model.go index d90286d..1037246 100644 --- a/internal/daas/hypervisor/vsphere_hypervisor_resource_model.go +++ b/internal/daas/hypervisor/vsphere_hypervisor_resource_model.go @@ -1,46 +1,177 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor import ( + "context" + "regexp" + citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) // HypervisorResourceModel maps the resource schema data. type VsphereHypervisorResourceModel struct { /**** Connection Details ****/ - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Zone types.String `tfsdk:"zone"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Zone types.String `tfsdk:"zone"` + Scopes types.Set `tfsdk:"scopes"` // Set[string] /** Vsphere Connection **/ - Username types.String `tfsdk:"username"` - Password types.String `tfsdk:"password"` - PasswordFormat types.String `tfsdk:"password_format"` - Addresses []types.String `tfsdk:"addresses"` - SslThumbprints []types.String `tfsdk:"ssl_thumbprints"` - MaxAbsoluteActiveActions types.Int64 `tfsdk:"max_absolute_active_actions"` - MaxAbsoluteNewActionsPerMinute types.Int64 `tfsdk:"max_absolute_new_actions_per_minute"` - MaxPowerActionsPercentageOfMachines types.Int64 `tfsdk:"max_power_actions_percentage_of_machines"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + PasswordFormat types.String `tfsdk:"password_format"` + Addresses types.List `tfsdk:"addresses"` // List[string] + SslThumbprints types.List `tfsdk:"ssl_thumbprints"` // List[string] + MaxAbsoluteActiveActions types.Int64 `tfsdk:"max_absolute_active_actions"` + MaxAbsoluteNewActionsPerMinute types.Int64 `tfsdk:"max_absolute_new_actions_per_minute"` + MaxPowerActionsPercentageOfMachines types.Int64 `tfsdk:"max_power_actions_percentage_of_machines"` } -func (r VsphereHypervisorResourceModel) RefreshPropertyValues(hypervisor *citrixorchestration.HypervisorDetailResponseModel) VsphereHypervisorResourceModel { +func GetVsphereHypervisorSchema() schema.Schema { + return schema.Schema{ + Description: "Manages a VMware vSphere hypervisor.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the hypervisor.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the hypervisor.", + Required: true, + }, + "zone": schema.StringAttribute{ + Description: "Id of the zone the hypervisor is associated with.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "username": schema.StringAttribute{ + Description: "Username of the hypervisor.", + Required: true, + }, + "password": schema.StringAttribute{ + Description: "Password of the hypervisor.", + Required: true, + }, + "password_format": schema.StringAttribute{ + Description: "Password format of the hypervisor. Choose between Base64 and PlainText.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(citrixorchestration.IDENTITYPASSWORDFORMAT_BASE64), + string(citrixorchestration.IDENTITYPASSWORDFORMAT_PLAIN_TEXT), + ), + }, + }, + "addresses": schema.ListAttribute{ + ElementType: types.StringType, + Description: "Hypervisor address(es). At least one is required.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "ssl_thumbprints": schema.ListAttribute{ + ElementType: types.StringType, + Description: "SSL certificate thumbprints to consider acceptable for this connection. If not specified, and the hypervisor uses SSL for its connection, the SSL certificate's root certification authority and any intermediate certificates must be trusted.", + Optional: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.RegexMatches(regexp.MustCompile(util.SslThumbprintRegex), "must be specified with SSL thumbprint without colons"), + ), + }, + }, + "max_absolute_active_actions": schema.Int64Attribute{ + Description: "Maximum number of actions that can execute in parallel on the hypervisor. Default is 40.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(40), + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + "max_absolute_new_actions_per_minute": schema.Int64Attribute{ + Description: "Maximum number of actions that can be started on the hypervisor per-minute. Default is 10.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(10), + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + "max_power_actions_percentage_of_machines": schema.Int64Attribute{ + Description: "Maximum percentage of machines on the hypervisor which can have their power state changed simultaneously. Default is 20.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(20), + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The IDs of the scopes for the hypervisor to be a part of.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), + 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 (r VsphereHypervisorResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, hypervisor *citrixorchestration.HypervisorDetailResponseModel) VsphereHypervisorResourceModel { r.Id = types.StringValue(hypervisor.GetId()) r.Name = types.StringValue(hypervisor.GetName()) r.Username = types.StringValue(hypervisor.GetUserName()) - r.Addresses = util.RefreshList(r.Addresses, hypervisor.GetAddresses()) + r.Addresses = util.RefreshListValues(ctx, diagnostics, r.Addresses, hypervisor.GetAddresses()) r.MaxAbsoluteActiveActions = types.Int64Value(int64(hypervisor.GetMaxAbsoluteActiveActions())) r.MaxAbsoluteNewActionsPerMinute = types.Int64Value(int64(hypervisor.GetMaxAbsoluteNewActionsPerMinute())) r.MaxPowerActionsPercentageOfMachines = types.Int64Value(int64(hypervisor.GetMaxPowerActionsPercentageOfMachines())) - sslThumbprints := util.RefreshList(r.SslThumbprints, hypervisor.GetSslThumbprints()) - if len(sslThumbprints) > 0 { + sslThumbprints := util.RefreshListValues(ctx, diagnostics, r.SslThumbprints, hypervisor.GetSslThumbprints()) + if !sslThumbprints.IsNull() { r.SslThumbprints = sslThumbprints } else { - r.SslThumbprints = nil + r.SslThumbprints = types.ListNull(types.StringType) } + scopeIds := util.GetIdsForScopeObjects(hypervisor.GetScopes()) + r.Scopes = util.StringArrayToStringSet(ctx, diagnostics, scopeIds) + hypZone := hypervisor.GetZone() r.Zone = types.StringValue(hypZone.GetId()) return r diff --git a/internal/daas/hypervisor/xenserver_hypervisor_resource.go b/internal/daas/hypervisor/xenserver_hypervisor_resource.go index 2ad28de..05687c0 100644 --- a/internal/daas/hypervisor/xenserver_hypervisor_resource.go +++ b/internal/daas/hypervisor/xenserver_hypervisor_resource.go @@ -1,27 +1,16 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor import ( "context" "net/http" - "regexp" 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-validators/int64validator" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/int64default" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" - "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" ) // Ensure the implementation satisfies the expected interfaces. @@ -56,102 +45,7 @@ func (r *xenserverHypervisorResource) Configure(_ context.Context, req resource. // Schema implements resource.Resource. func (r *xenserverHypervisorResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages a XenServer hypervisor.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the hypervisor.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the hypervisor.", - Required: true, - }, - "zone": schema.StringAttribute{ - Description: "Id of the zone the hypervisor is associated with.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "username": schema.StringAttribute{ - Description: "Username of the hypervisor.", - Required: true, - }, - "password": schema.StringAttribute{ - Description: "Password of the hypervisor.", - Required: true, - }, - "password_format": schema.StringAttribute{ - Description: "Password format of the hypervisor. Choose between Base64 and PlainText.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - string(citrixorchestration.IDENTITYPASSWORDFORMAT_BASE64), - string(citrixorchestration.IDENTITYPASSWORDFORMAT_PLAIN_TEXT), - ), - }, - }, - "addresses": schema.ListAttribute{ - ElementType: types.StringType, - Description: "Hypervisor address(es). At least one is required.", - Required: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - listvalidator.ValueStringsAre( - stringvalidator.RegexMatches(regexp.MustCompile(util.IPv4RegexWithProtocol), "must be a valid IPv4 address prefixed with protocol (http:// or https://)"), - ), - }, - }, - "ssl_thumbprints": schema.ListAttribute{ - ElementType: types.StringType, - Description: "SSL certificate thumbprints to consider acceptable for this connection. If not specified, and the hypervisor uses SSL for its connection, the SSL certificate's root certification authority and any intermediate certificates must be trusted.", - Optional: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.RequiresReplace(), - }, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - listvalidator.ValueStringsAre( - stringvalidator.RegexMatches(regexp.MustCompile(util.SslThumbprintRegex), "must be specified with SSL thumbprint without colons"), - ), - }, - }, - "max_absolute_active_actions": schema.Int64Attribute{ - Description: "Maximum number of actions that can execute in parallel on the hypervisor. Default is 40.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(40), - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - "max_absolute_new_actions_per_minute": schema.Int64Attribute{ - Description: "Maximum number of actions that can be started on the hypervisor per-minute. Default is 10.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(10), - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - "max_power_actions_percentage_of_machines": schema.Int64Attribute{ - Description: "Maximum percentage of machines on the hypervisor which can have their power state changed simultaneously. Default is 20.", - Optional: true, - Computed: true, - Default: int64default.StaticInt64(20), - Validators: []validator.Int64{ - int64validator.AtLeast(1), - }, - }, - }, - } + resp.Schema = GetXenServerHypervisorSchema() } // ImportState implements resource.ResourceWithImportState. @@ -188,17 +82,20 @@ func (r *xenserverHypervisorResource) Create(ctx context.Context, req resource.C } connectionDetails.SetPasswordFormat(*pwdFormat) - addresses := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Addresses) + addresses := util.StringListToStringArray(ctx, &diags, plan.Addresses) connectionDetails.SetAddresses(addresses) - if plan.SslThumbprints != nil { - sslThumbprints := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.SslThumbprints) + if !plan.SslThumbprints.IsNull() { + sslThumbprints := util.StringListToStringArray(ctx, &diags, plan.SslThumbprints) connectionDetails.SetSslThumbprints(sslThumbprints) } connectionDetails.SetMaxAbsoluteActiveActions(int32(plan.MaxAbsoluteActiveActions.ValueInt64())) connectionDetails.SetMaxAbsoluteNewActionsPerMinute(int32(plan.MaxAbsoluteNewActionsPerMinute.ValueInt64())) connectionDetails.SetMaxPowerActionsPercentageOfMachines(int32(plan.MaxPowerActionsPercentageOfMachines.ValueInt64())) + if !plan.Scopes.IsNull() { + connectionDetails.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } var body citrixorchestration.CreateHypervisorRequestModel body.SetConnectionDetails(connectionDetails) @@ -210,7 +107,7 @@ func (r *xenserverHypervisorResource) Create(ctx context.Context, req resource.C } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(hypervisor) + plan = plan.RefreshPropertyValues(ctx, &diags, hypervisor) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -248,7 +145,7 @@ func (r *xenserverHypervisorResource) Read(ctx context.Context, req resource.Rea } // Overwrite hypervisor with refreshed state - state = state.RefreshPropertyValues(hypervisor) + state = state.RefreshPropertyValues(ctx, &diags, hypervisor) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -292,15 +189,18 @@ func (r *xenserverHypervisorResource) Update(ctx context.Context, req resource.U } editHypervisorRequestBody.SetPasswordFormat(*pwdFormat) - addresses := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Addresses) + addresses := util.StringListToStringArray(ctx, &diags, plan.Addresses) editHypervisorRequestBody.SetAddresses(addresses) - sslThumbprints := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.SslThumbprints) + sslThumbprints := util.StringListToStringArray(ctx, &diags, plan.SslThumbprints) editHypervisorRequestBody.SetSslThumbprints(sslThumbprints) editHypervisorRequestBody.SetMaxAbsoluteActiveActions(int32(plan.MaxAbsoluteActiveActions.ValueInt64())) editHypervisorRequestBody.SetMaxAbsoluteNewActionsPerMinute(int32(plan.MaxAbsoluteNewActionsPerMinute.ValueInt64())) editHypervisorRequestBody.SetMaxPowerActionsPercentageOfMachines(int32(plan.MaxPowerActionsPercentageOfMachines.ValueInt64())) + if !plan.Scopes.IsNull() { + editHypervisorRequestBody.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + } // Patch hypervisor updatedHypervisor, err := UpdateHypervisor(ctx, r.client, &resp.Diagnostics, hypervisor, editHypervisorRequestBody) @@ -309,7 +209,7 @@ func (r *xenserverHypervisorResource) Update(ctx context.Context, req resource.U } // Update resource state with updated property values - plan = plan.RefreshPropertyValues(updatedHypervisor) + plan = plan.RefreshPropertyValues(ctx, &diags, updatedHypervisor) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) diff --git a/internal/daas/hypervisor/xenserver_hypervisor_resource_model.go b/internal/daas/hypervisor/xenserver_hypervisor_resource_model.go index 8de9a0b..74afd17 100644 --- a/internal/daas/hypervisor/xenserver_hypervisor_resource_model.go +++ b/internal/daas/hypervisor/xenserver_hypervisor_resource_model.go @@ -1,44 +1,177 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor import ( + "context" + "regexp" + citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) // HypervisorResourceModel maps the resource schema data. type XenserverHypervisorResourceModel struct { /**** Connection Details ****/ - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Zone types.String `tfsdk:"zone"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Zone types.String `tfsdk:"zone"` + Scopes types.Set `tfsdk:"scopes"` // Set[string] /** Xenserver Connection **/ - Username types.String `tfsdk:"username"` - Password types.String `tfsdk:"password"` - PasswordFormat types.String `tfsdk:"password_format"` - Addresses []types.String `tfsdk:"addresses"` - SslThumbprints []types.String `tfsdk:"ssl_thumbprints"` - MaxAbsoluteActiveActions types.Int64 `tfsdk:"max_absolute_active_actions"` - MaxAbsoluteNewActionsPerMinute types.Int64 `tfsdk:"max_absolute_new_actions_per_minute"` - MaxPowerActionsPercentageOfMachines types.Int64 `tfsdk:"max_power_actions_percentage_of_machines"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + PasswordFormat types.String `tfsdk:"password_format"` + Addresses types.List `tfsdk:"addresses"` // List[string] + SslThumbprints types.List `tfsdk:"ssl_thumbprints"` //List[string] + MaxAbsoluteActiveActions types.Int64 `tfsdk:"max_absolute_active_actions"` + MaxAbsoluteNewActionsPerMinute types.Int64 `tfsdk:"max_absolute_new_actions_per_minute"` + MaxPowerActionsPercentageOfMachines types.Int64 `tfsdk:"max_power_actions_percentage_of_machines"` +} + +func GetXenServerHypervisorSchema() schema.Schema { + return schema.Schema{ + Description: "Manages a XenServer hypervisor.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the hypervisor.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the hypervisor.", + Required: true, + }, + "zone": schema.StringAttribute{ + Description: "Id of the zone the hypervisor is associated with.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "username": schema.StringAttribute{ + Description: "Username of the hypervisor.", + Required: true, + }, + "password": schema.StringAttribute{ + Description: "Password of the hypervisor.", + Required: true, + }, + "password_format": schema.StringAttribute{ + Description: "Password format of the hypervisor. Choose between Base64 and PlainText.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(citrixorchestration.IDENTITYPASSWORDFORMAT_BASE64), + string(citrixorchestration.IDENTITYPASSWORDFORMAT_PLAIN_TEXT), + ), + }, + }, + "addresses": schema.ListAttribute{ + ElementType: types.StringType, + Description: "Hypervisor address(es). At least one is required.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.RegexMatches(regexp.MustCompile(util.IPv4RegexWithProtocol), "must be a valid IPv4 address prefixed with protocol (http:// or https://)"), + ), + }, + }, + "ssl_thumbprints": schema.ListAttribute{ + ElementType: types.StringType, + Description: "SSL certificate thumbprints to consider acceptable for this connection. If not specified, and the hypervisor uses SSL for its connection, the SSL certificate's root certification authority and any intermediate certificates must be trusted.", + Optional: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.RegexMatches(regexp.MustCompile(util.SslThumbprintRegex), "must be specified with SSL thumbprint without colons"), + ), + }, + }, + "max_absolute_active_actions": schema.Int64Attribute{ + Description: "Maximum number of actions that can execute in parallel on the hypervisor. Default is 40.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(40), + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + "max_absolute_new_actions_per_minute": schema.Int64Attribute{ + Description: "Maximum number of actions that can be started on the hypervisor per-minute. Default is 10.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(10), + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + "max_power_actions_percentage_of_machines": schema.Int64Attribute{ + Description: "Maximum percentage of machines on the hypervisor which can have their power state changed simultaneously. Default is 20.", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(20), + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The IDs of the scopes for the hypervisor to be a part of.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), + 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 (r XenserverHypervisorResourceModel) RefreshPropertyValues(hypervisor *citrixorchestration.HypervisorDetailResponseModel) XenserverHypervisorResourceModel { +func (r XenserverHypervisorResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, hypervisor *citrixorchestration.HypervisorDetailResponseModel) XenserverHypervisorResourceModel { r.Id = types.StringValue(hypervisor.GetId()) r.Name = types.StringValue(hypervisor.GetName()) r.Username = types.StringValue(hypervisor.GetUserName()) - r.Addresses = util.RefreshList(r.Addresses, hypervisor.GetAddresses()) + r.Addresses = util.RefreshListValues(ctx, diagnostics, r.Addresses, hypervisor.GetAddresses()) r.MaxAbsoluteActiveActions = types.Int64Value(int64(hypervisor.GetMaxAbsoluteActiveActions())) r.MaxAbsoluteNewActionsPerMinute = types.Int64Value(int64(hypervisor.GetMaxAbsoluteNewActionsPerMinute())) r.MaxPowerActionsPercentageOfMachines = types.Int64Value(int64(hypervisor.GetMaxPowerActionsPercentageOfMachines())) + scopeIds := util.GetIdsForScopeObjects(hypervisor.GetScopes()) + r.Scopes = util.StringArrayToStringSet(ctx, diagnostics, scopeIds) - sslThumbprints := util.RefreshList(r.SslThumbprints, hypervisor.GetSslThumbprints()) - if len(sslThumbprints) > 0 { + sslThumbprints := util.RefreshListValues(ctx, diagnostics, r.SslThumbprints, hypervisor.GetSslThumbprints()) + if !sslThumbprints.IsNull() { r.SslThumbprints = sslThumbprints } else { - r.SslThumbprints = nil + r.SslThumbprints = types.ListNull(types.StringType) } hypZone := hypervisor.GetZone() diff --git a/internal/daas/hypervisor_resource_pool/aws_hypervisor_resource_pool_resource.go b/internal/daas/hypervisor_resource_pool/aws_hypervisor_resource_pool_resource.go index 3067acb..f7a1bc4 100644 --- a/internal/daas/hypervisor_resource_pool/aws_hypervisor_resource_pool_resource.go +++ b/internal/daas/hypervisor_resource_pool/aws_hypervisor_resource_pool_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor_resource_pool @@ -6,21 +6,13 @@ import ( "context" "fmt" "net/http" - "regexp" "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-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/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" ) // Ensure the implementation satisfies the expected interfaces. @@ -46,54 +38,7 @@ func (r *awsHypervisorResourcePoolResource) Metadata(_ context.Context, req reso } func (r *awsHypervisorResourcePoolResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages an AWS EC2 hypervisor resource pool.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the resource pool.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the resource pool. Name should be unique across all hypervisors.", - Required: true, - }, - "hypervisor": schema.StringAttribute{ - Description: "Id of the hypervisor for which the resource pool needs to be created.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "vpc": schema.StringAttribute{ - Description: "Name of the virtual private cloud.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "subnets": schema.ListAttribute{ - ElementType: types.StringType, - Description: "List of subnets to allocate VDAs within the virtual private cloud.", - Required: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "availability_zone": schema.StringAttribute{ - Description: "The name of the availability zone resource to use for provisioning operations in this resource pool.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplaceIfConfigured(), - }, - }, - }, - } + resp.Schema = GetAwsHypervisorResourcePoolSchema() } // Configure adds the provider configured client to the resource. @@ -146,7 +91,7 @@ func (r *awsHypervisorResourcePoolResource) Create(ctx context.Context, req reso resourcePoolDetails.SetVirtualPrivateCloud(vpcPath) availabilityZonePath := fmt.Sprintf("%s/%s.availabilityzone", vpcPath, plan.AvailabilityZone.ValueString()) resourcePoolDetails.SetAvailabilityZone(availabilityZonePath) - planSubnet := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Subnets) + planSubnet := util.StringListToStringArray(ctx, &diags, plan.Subnets) subnets, err := util.GetFilteredResourcePathList(ctx, r.client, hypervisorId, availabilityZonePath, util.NetworkResourceType, planSubnet, hypervisorConnectionType, hypervisor.GetPluginId()) if err != nil { resp.Diagnostics.AddError( @@ -156,7 +101,7 @@ func (r *awsHypervisorResourcePoolResource) Create(ctx context.Context, req reso return } - if len(plan.Subnets) != len(subnets) { + if len(plan.Subnets.Elements()) != len(subnets) { resp.Diagnostics.AddError( "Error creating Hypervisor Resource Pool for AWS", "Subnet contains invalid value.", @@ -172,7 +117,7 @@ func (r *awsHypervisorResourcePoolResource) Create(ctx context.Context, req reso return } - plan = plan.RefreshPropertyValues(resourcePool) + plan = plan.RefreshPropertyValues(ctx, &diags, resourcePool) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -203,7 +148,7 @@ func (r *awsHypervisorResourcePoolResource) Read(ctx context.Context, req resour } // Override with refreshed state - state = state.RefreshPropertyValues(resourcePool) + state = state.RefreshPropertyValues(ctx, &diags, resourcePool) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -235,7 +180,7 @@ func (r *awsHypervisorResourcePoolResource) Update(ctx context.Context, req reso editHypervisorResourcePool.SetName(plan.Name.ValueString()) editHypervisorResourcePool.SetConnectionType(citrixorchestration.HYPERVISORCONNECTIONTYPE_AWS) - planSubnet := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Subnets) + planSubnet := util.StringListToStringArray(ctx, &diags, plan.Subnets) availabilityZonePath := fmt.Sprintf("%s.virtualprivatecloud/%s.availabilityzone", plan.Vpc.ValueString(), plan.AvailabilityZone.ValueString()) subnets, err := util.GetFilteredResourcePathList(ctx, r.client, plan.Hypervisor.ValueString(), availabilityZonePath, util.NetworkResourceType, planSubnet, citrixorchestration.HYPERVISORCONNECTIONTYPE_AWS, "") if err != nil { @@ -248,7 +193,7 @@ func (r *awsHypervisorResourcePoolResource) Update(ctx context.Context, req reso return } - plan = plan.RefreshPropertyValues(updatedResourcePool) + plan = plan.RefreshPropertyValues(ctx, &diags, updatedResourcePool) // Set state to fully populated data diags = resp.State.Set(ctx, plan) diff --git a/internal/daas/hypervisor_resource_pool/aws_hypervisor_resource_pool_resource_model.go b/internal/daas/hypervisor_resource_pool/aws_hypervisor_resource_pool_resource_model.go index 70d2185..8d26fa9 100644 --- a/internal/daas/hypervisor_resource_pool/aws_hypervisor_resource_pool_resource_model.go +++ b/internal/daas/hypervisor_resource_pool/aws_hypervisor_resource_pool_resource_model.go @@ -1,12 +1,21 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor_resource_pool 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/listvalidator" + "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" ) @@ -15,13 +24,64 @@ type AwsHypervisorResourcePoolResourceModel struct { Name types.String `tfsdk:"name"` Hypervisor types.String `tfsdk:"hypervisor"` /**** Resource Pool Details ****/ - Vpc types.String `tfsdk:"vpc"` - Subnets []types.String `tfsdk:"subnets"` + Vpc types.String `tfsdk:"vpc"` + Subnets types.List `tfsdk:"subnets"` // List[string] /** AWS Resource Pool **/ AvailabilityZone types.String `tfsdk:"availability_zone"` } -func (r AwsHypervisorResourcePoolResourceModel) RefreshPropertyValues(resourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) AwsHypervisorResourcePoolResourceModel { +func GetAwsHypervisorResourcePoolSchema() schema.Schema { + return schema.Schema{ + Description: "Manages an AWS EC2 hypervisor resource pool.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the resource pool.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the resource pool. Name should be unique across all hypervisors.", + Required: true, + }, + "hypervisor": schema.StringAttribute{ + Description: "Id of the hypervisor for which the resource pool needs to be created.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "vpc": schema.StringAttribute{ + Description: "Name of the virtual private cloud.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "subnets": schema.ListAttribute{ + ElementType: types.StringType, + Description: "Subnets to allocate VDAs within the virtual private cloud.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "availability_zone": schema.StringAttribute{ + Description: "The name of the availability zone resource to use for provisioning operations in this resource pool.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + }, + }, + }, + } +} + +func (r AwsHypervisorResourcePoolResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, resourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) AwsHypervisorResourcePoolResourceModel { r.Id = types.StringValue(resourcePool.GetId()) r.Name = types.StringValue(resourcePool.GetName()) @@ -38,7 +98,7 @@ func (r AwsHypervisorResourcePoolResourceModel) RefreshPropertyValues(resourcePo name := model.GetName() res = append(res, strings.Split(name, " ")[0]) } - r.Subnets = util.ConvertPrimitiveStringArrayToBaseStringArray(res) + r.Subnets = util.RefreshListValues(ctx, diagnostics, r.Subnets, res) return r } diff --git a/internal/daas/hypervisor_resource_pool/azure_hypervisor_resource_pool_resource.go b/internal/daas/hypervisor_resource_pool/azure_hypervisor_resource_pool_resource.go index 1466b42..7c95bbb 100644 --- a/internal/daas/hypervisor_resource_pool/azure_hypervisor_resource_pool_resource.go +++ b/internal/daas/hypervisor_resource_pool/azure_hypervisor_resource_pool_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor_resource_pool @@ -6,22 +6,13 @@ import ( "context" "fmt" "net/http" - "regexp" "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/go-azure-helpers/resourcemanager/location" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/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" ) // Ensure the implementation satisfies the expected interfaces. @@ -47,68 +38,7 @@ func (r *azureHypervisorResourcePoolResource) Metadata(_ context.Context, req re } func (r *azureHypervisorResourcePoolResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages an Azure hypervisor resource pool.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the resource pool.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the resource pool. Name should be unique across all hypervisors.", - Required: true, - }, - "hypervisor": schema.StringAttribute{ - Description: "Id of the hypervisor for which the resource pool needs to be created.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "virtual_network_resource_group": schema.StringAttribute{ - Description: "The name of the resource group where the vnet resides.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "virtual_network": schema.StringAttribute{ - Description: "Name of the cloud virtual network.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "subnets": schema.ListAttribute{ - ElementType: types.StringType, - Description: "List of subnets to allocate VDAs within the virtual network.", - Required: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "region": schema.StringAttribute{ - Description: "Cloud Region where the virtual network sits in.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplaceIf( - func(_ context.Context, req planmodifier.StringRequest, resp *stringplanmodifier.RequiresReplaceIfFuncResponse) { - resp.RequiresReplace = !req.ConfigValue.IsNull() && !req.StateValue.IsNull() && - (location.Normalize(req.ConfigValue.ValueString()) != location.Normalize(req.StateValue.ValueString())) - }, - "Force replacement when region changes, unless changing between Azure region name (East US) and Id (eastus)", - "Force replacement when region changes, unless changing between Azure region name (East US) and Id (eastus)", - ), - }, - }, - }, - } + resp.Schema = GetAzureHypervisorResourcePoolSchema() } // Configure adds the provider configured client to the resource. @@ -178,14 +108,14 @@ func (r *azureHypervisorResourcePoolResource) Create(ctx context.Context, req re } resourcePoolDetails.SetVirtualNetwork(vnetPath) //Checking the subnet - if len(plan.Subnets) == 0 { + if plan.Subnets.IsNull() { resp.Diagnostics.AddError( "Error creating Hypervisor Resource Pool for Azure", "Subnet is missing.", ) return } - planSubnet := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Subnets) + planSubnet := util.StringListToStringArray(ctx, &diags, plan.Subnets) subnets, err := util.GetFilteredResourcePathList(ctx, r.client, hypervisorId, fmt.Sprintf("%s/virtualprivatecloud.folder/%s", regionPath, vnetPath), util.NetworkResourceType, planSubnet, hypervisorConnectionType, hypervisor.GetPluginId()) if err != nil { @@ -196,7 +126,7 @@ func (r *azureHypervisorResourcePoolResource) Create(ctx context.Context, req re return } - if len(plan.Subnets) != len(subnets) { + if len(plan.Subnets.Elements()) != len(subnets) { resp.Diagnostics.AddError( "Error creating Hypervisor Resource Pool for Azure", "Subnet contains invalid value.", @@ -211,7 +141,7 @@ func (r *azureHypervisorResourcePoolResource) Create(ctx context.Context, req re return } - plan = plan.RefreshPropertyValues(resourcePool) + plan = plan.RefreshPropertyValues(ctx, &diags, resourcePool) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -242,7 +172,7 @@ func (r *azureHypervisorResourcePoolResource) Read(ctx context.Context, req reso } // Override with refreshed state - state = state.RefreshPropertyValues(resourcePool) + state = state.RefreshPropertyValues(ctx, &diags, resourcePool) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -274,7 +204,7 @@ func (r *azureHypervisorResourcePoolResource) Update(ctx context.Context, req re editHypervisorResourcePool.SetName(plan.Name.ValueString()) editHypervisorResourcePool.SetConnectionType(citrixorchestration.HYPERVISORCONNECTIONTYPE_AZURE_RM) - planSubnet := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Subnets) + planSubnet := util.StringListToStringArray(ctx, &diags, plan.Subnets) regionPath := fmt.Sprintf("%s.region", plan.Region.ValueString()) resourceGroupPath := fmt.Sprintf("%s.resourcegroup", plan.VirtualNetworkResourceGroup.ValueString()) vnetPath := fmt.Sprintf("%s.virtualprivatecloud", plan.VirtualNetwork.ValueString()) @@ -289,7 +219,7 @@ func (r *azureHypervisorResourcePoolResource) Update(ctx context.Context, req re return } - plan = plan.RefreshPropertyValues(updatedResourcePool) + plan = plan.RefreshPropertyValues(ctx, &diags, updatedResourcePool) // Set state to fully populated data diags = resp.State.Set(ctx, plan) diff --git a/internal/daas/hypervisor_resource_pool/azure_hypervisor_resource_pool_resource_model.go b/internal/daas/hypervisor_resource_pool/azure_hypervisor_resource_pool_resource_model.go index 643d86c..c785497 100644 --- a/internal/daas/hypervisor_resource_pool/azure_hypervisor_resource_pool_resource_model.go +++ b/internal/daas/hypervisor_resource_pool/azure_hypervisor_resource_pool_resource_model.go @@ -1,12 +1,22 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor_resource_pool import ( + "context" + "regexp" "strings" "github.com/citrix/citrix-daas-rest-go/citrixorchestration" "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/go-azure-helpers/resourcemanager/location" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "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" ) @@ -15,14 +25,79 @@ type AzureHypervisorResourcePoolResourceModel struct { Name types.String `tfsdk:"name"` Hypervisor types.String `tfsdk:"hypervisor"` /**** Resource Pool Details ****/ - Region types.String `tfsdk:"region"` - VirtualNetwork types.String `tfsdk:"virtual_network"` - Subnets []types.String `tfsdk:"subnets"` + Region types.String `tfsdk:"region"` + VirtualNetwork types.String `tfsdk:"virtual_network"` + Subnets types.List `tfsdk:"subnets"` // List[string] /** Azure Resource Pool **/ VirtualNetworkResourceGroup types.String `tfsdk:"virtual_network_resource_group"` } -func (r AzureHypervisorResourcePoolResourceModel) RefreshPropertyValues(resourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) AzureHypervisorResourcePoolResourceModel { +func GetAzureHypervisorResourcePoolSchema() schema.Schema { + return schema.Schema{ + Description: "Manages an Azure hypervisor resource pool.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the resource pool.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the resource pool. Name should be unique across all hypervisors.", + Required: true, + }, + "hypervisor": schema.StringAttribute{ + Description: "Id of the hypervisor for which the resource pool needs to be created.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "virtual_network_resource_group": schema.StringAttribute{ + Description: "The name of the resource group where the vnet resides.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "virtual_network": schema.StringAttribute{ + Description: "Name of the cloud virtual network.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "subnets": schema.ListAttribute{ + ElementType: types.StringType, + Description: "Subnets to allocate VDAs within the virtual network.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "region": schema.StringAttribute{ + Description: "Cloud Region where the virtual network sits in.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIf( + func(_ context.Context, req planmodifier.StringRequest, resp *stringplanmodifier.RequiresReplaceIfFuncResponse) { + resp.RequiresReplace = !req.ConfigValue.IsNull() && !req.StateValue.IsNull() && + (location.Normalize(req.ConfigValue.ValueString()) != location.Normalize(req.StateValue.ValueString())) + }, + "Force replacement when region changes, unless changing between Azure region name (East US) and Id (eastus)", + "Force replacement when region changes, unless changing between Azure region name (East US) and Id (eastus)", + ), + }, + }, + }, + } +} + +func (r AzureHypervisorResourcePoolResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, resourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) AzureHypervisorResourcePoolResourceModel { r.Id = types.StringValue(resourcePool.GetId()) r.Name = types.StringValue(resourcePool.GetName()) @@ -42,7 +117,7 @@ func (r AzureHypervisorResourcePoolResourceModel) RefreshPropertyValues(resource for _, model := range resourcePool.GetSubnets() { res = append(res, model.GetName()) } - r.Subnets = util.ConvertPrimitiveStringArrayToBaseStringArray(res) + r.Subnets = util.RefreshListValues(ctx, diagnostics, r.Subnets, res) return r } diff --git a/internal/daas/hypervisor_resource_pool/gcp_hypervisor_resource_pool_resource.go b/internal/daas/hypervisor_resource_pool/gcp_hypervisor_resource_pool_resource.go index 61a30d4..352e6dc 100644 --- a/internal/daas/hypervisor_resource_pool/gcp_hypervisor_resource_pool_resource.go +++ b/internal/daas/hypervisor_resource_pool/gcp_hypervisor_resource_pool_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor_resource_pool @@ -6,23 +6,13 @@ import ( "context" "fmt" "net/http" - "regexp" "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/go-azure-helpers/resourcemanager/location" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/boolplanmodifier" - "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" ) // Ensure the implementation satisfies the expected interfaces. @@ -48,75 +38,7 @@ func (r *gcpHypervisorResourcePoolResource) Metadata(_ context.Context, req reso } func (r *gcpHypervisorResourcePoolResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages a GCP hypervisor resource pool.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the resource pool.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the resource pool. Name should be unique across all hypervisors.", - Required: true, - }, - "hypervisor": schema.StringAttribute{ - Description: "Id of the hypervisor for which the resource pool needs to be created.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "vpc": schema.StringAttribute{ - Description: "Name of the cloud virtual network.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "subnets": schema.ListAttribute{ - ElementType: types.StringType, - Description: "List of subnets to allocate VDAs within the virtual network.", - Required: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "region": schema.StringAttribute{ - Description: "Cloud Region where the virtual network sits in.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplaceIf( - func(_ context.Context, req planmodifier.StringRequest, resp *stringplanmodifier.RequiresReplaceIfFuncResponse) { - resp.RequiresReplace = !req.ConfigValue.IsNull() && !req.StateValue.IsNull() && - (location.Normalize(req.ConfigValue.ValueString()) != location.Normalize(req.StateValue.ValueString())) - }, - "Force replacement when region changes, unless changing between GCP region name (East US) and Id (eastus)", - "Force replacement when region changes, unless changing between GCP region name (East US) and Id (eastus)", - ), - }, - }, - "project_name": schema.StringAttribute{ - Description: "GCP Project name.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplaceIfConfigured(), - }, - }, - "shared_vpc": schema.BoolAttribute{ - Description: "Indicate whether the GCP Virtual Private Cloud is a shared VPC.", - Optional: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplaceIfConfigured(), - }, - }, - }, - } + resp.Schema = GetGcpHypervisorResourcePoolSchema() } // Configure adds the provider configured client to the resource. @@ -174,14 +96,14 @@ func (r *gcpHypervisorResourcePoolResource) Create(ctx context.Context, req reso } resourcePoolDetails.SetVirtualPrivateCloud(vnetPath) //Checking the subnet - if len(plan.Subnets) == 0 { + if plan.Subnets.IsNull() { resp.Diagnostics.AddError( "Error creating Hypervisor Resource Pool for GCP", "Subnet is missing.", ) return } - planSubnet := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Subnets) + planSubnet := util.StringListToStringArray(ctx, &diags, plan.Subnets) subnets, err := util.GetFilteredResourcePathList(ctx, r.client, hypervisorId, vnetPath, util.NetworkResourceType, planSubnet, hypervisorConnectionType, hypervisor.GetPluginId()) if err != nil { @@ -192,7 +114,7 @@ func (r *gcpHypervisorResourcePoolResource) Create(ctx context.Context, req reso return } - if len(plan.Subnets) != len(subnets) { + if len(plan.Subnets.Elements()) != len(subnets) { resp.Diagnostics.AddError( "Error creating Hypervisor Resource Pool for GCP", "Subnet contains invalid value.", @@ -207,7 +129,7 @@ func (r *gcpHypervisorResourcePoolResource) Create(ctx context.Context, req reso return } - plan = plan.RefreshPropertyValues(resourcePool) + plan = plan.RefreshPropertyValues(ctx, &diags, resourcePool) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -238,7 +160,7 @@ func (r *gcpHypervisorResourcePoolResource) Read(ctx context.Context, req resour } // Override with refreshed state - state = state.RefreshPropertyValues(resourcePool) + state = state.RefreshPropertyValues(ctx, &diags, resourcePool) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -270,7 +192,7 @@ func (r *gcpHypervisorResourcePoolResource) Update(ctx context.Context, req reso editHypervisorResourcePool.SetName(plan.Name.ValueString()) editHypervisorResourcePool.SetConnectionType(citrixorchestration.HYPERVISORCONNECTIONTYPE_GOOGLE_CLOUD_PLATFORM) - planSubnet := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Subnets) + planSubnet := util.StringListToStringArray(ctx, &diags, plan.Subnets) regionPath := fmt.Sprintf("%s.project/%s.region", plan.ProjectName.ValueString(), plan.Region.ValueString()) vnetPath := fmt.Sprintf("%s/%s.virtualprivatecloud", regionPath, plan.Vpc.ValueString()) if plan.SharedVpc.ValueBool() { @@ -288,7 +210,7 @@ func (r *gcpHypervisorResourcePoolResource) Update(ctx context.Context, req reso return } - plan = plan.RefreshPropertyValues(updatedResourcePool) + plan = plan.RefreshPropertyValues(ctx, &diags, updatedResourcePool) // Set state to fully populated data diags = resp.State.Set(ctx, plan) diff --git a/internal/daas/hypervisor_resource_pool/gcp_hypervisor_resource_pool_resource_model.go b/internal/daas/hypervisor_resource_pool/gcp_hypervisor_resource_pool_resource_model.go index 2279607..61b8828 100644 --- a/internal/daas/hypervisor_resource_pool/gcp_hypervisor_resource_pool_resource_model.go +++ b/internal/daas/hypervisor_resource_pool/gcp_hypervisor_resource_pool_resource_model.go @@ -1,12 +1,23 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor_resource_pool import ( + "context" + "regexp" "strings" "github.com/citrix/citrix-daas-rest-go/citrixorchestration" "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/go-azure-helpers/resourcemanager/location" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "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/boolplanmodifier" + "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" ) @@ -15,15 +26,87 @@ type GcpHypervisorResourcePoolResourceModel struct { Name types.String `tfsdk:"name"` Hypervisor types.String `tfsdk:"hypervisor"` /**** Resource Pool Details ****/ - Region types.String `tfsdk:"region"` - Vpc types.String `tfsdk:"vpc"` - Subnets []types.String `tfsdk:"subnets"` + Region types.String `tfsdk:"region"` + Vpc types.String `tfsdk:"vpc"` + Subnets types.List `tfsdk:"subnets"` // List[string] /** GCP Resource Pool **/ ProjectName types.String `tfsdk:"project_name"` SharedVpc types.Bool `tfsdk:"shared_vpc"` } -func (r GcpHypervisorResourcePoolResourceModel) RefreshPropertyValues(resourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) GcpHypervisorResourcePoolResourceModel { +func GetGcpHypervisorResourcePoolSchema() schema.Schema { + return schema.Schema{ + Description: "Manages a GCP hypervisor resource pool.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the resource pool.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the resource pool. Name should be unique across all hypervisors.", + Required: true, + }, + "hypervisor": schema.StringAttribute{ + Description: "Id of the hypervisor for which the resource pool needs to be created.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "vpc": schema.StringAttribute{ + Description: "Name of the cloud virtual network.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "subnets": schema.ListAttribute{ + ElementType: types.StringType, + Description: "Subnets to allocate VDAs within the virtual network.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "region": schema.StringAttribute{ + Description: "Cloud Region where the virtual network sits in.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIf( + func(_ context.Context, req planmodifier.StringRequest, resp *stringplanmodifier.RequiresReplaceIfFuncResponse) { + resp.RequiresReplace = !req.ConfigValue.IsNull() && !req.StateValue.IsNull() && + (location.Normalize(req.ConfigValue.ValueString()) != location.Normalize(req.StateValue.ValueString())) + }, + "Force replacement when region changes, unless changing between GCP region name (East US) and Id (eastus)", + "Force replacement when region changes, unless changing between GCP region name (East US) and Id (eastus)", + ), + }, + }, + "project_name": schema.StringAttribute{ + Description: "GCP Project name.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + }, + }, + "shared_vpc": schema.BoolAttribute{ + Description: "Indicate whether the GCP Virtual Private Cloud is a shared VPC.", + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplaceIfConfigured(), + }, + }, + }, + } +} + +func (r GcpHypervisorResourcePoolResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, resourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) GcpHypervisorResourcePoolResourceModel { r.Id = types.StringValue(resourcePool.GetId()) r.Name = types.StringValue(resourcePool.GetName()) @@ -43,7 +126,7 @@ func (r GcpHypervisorResourcePoolResourceModel) RefreshPropertyValues(resourcePo for _, model := range resourcePool.GetNetworks() { res = append(res, model.GetName()) } - r.Subnets = util.ConvertPrimitiveStringArrayToBaseStringArray(res) + r.Subnets = util.RefreshListValues(ctx, diagnostics, r.Subnets, res) vpcType := vpc.GetObjectTypeName() if vpcType == "sharedvirtualprivatecloud" { 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 5cfc46e..fc1f890 100644 --- a/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_common.go +++ b/internal/daas/hypervisor_resource_pool/hypervisor_resource_pool_common.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor_resource_pool @@ -16,18 +16,25 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) +// ensure HypervisorStorageModel implements RefreshableListItemWithAttributes +var _ util.RefreshableListItemWithAttributes[citrixorchestration.HypervisorStorageResourceResponseModel] = HypervisorStorageModel{} + type HypervisorStorageModel struct { StorageName types.String `tfsdk:"storage_name"` Superseded types.Bool `tfsdk:"superseded"` } -func (v HypervisorStorageModel) RefreshListItem(remote citrixorchestration.HypervisorStorageResourceResponseModel) HypervisorStorageModel { +func (h HypervisorStorageModel) GetKey() string { + return h.StorageName.ValueString() +} + +func (v HypervisorStorageModel) RefreshListItem(_ context.Context, _ *diag.Diagnostics, remote citrixorchestration.HypervisorStorageResourceResponseModel) util.ModelWithAttributes { v.StorageName = types.StringValue(remote.GetName()) v.Superseded = types.BoolValue(remote.GetSuperseded()) return v } -func GetNestedAttributeObjectSchmeaForStorege() schema.NestedAttributeObject { +func (HypervisorStorageModel) GetSchema() schema.NestedAttributeObject { return schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "storage_name": schema.StringAttribute{ @@ -44,6 +51,10 @@ func GetNestedAttributeObjectSchmeaForStorege() schema.NestedAttributeObject { } } +func (HypervisorStorageModel) GetAttributes() map[string]schema.Attribute { + return HypervisorStorageModel{}.GetSchema().Attributes +} + // Create creates the resource and sets the initial Terraform state. func CreateHypervisorResourcePool(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, hypervisor citrixorchestration.HypervisorDetailResponseModel, resourcePoolDetails citrixorchestration.CreateHypervisorResourcePoolRequestModel) (*citrixorchestration.HypervisorResourcePoolDetailResponseModel, error) { // Create new hypervisor resource pool diff --git a/internal/daas/hypervisor_resource_pool/nutanix_hypervisor_resource_pool_resource.go b/internal/daas/hypervisor_resource_pool/nutanix_hypervisor_resource_pool_resource.go index 660de0b..18594d3 100644 --- a/internal/daas/hypervisor_resource_pool/nutanix_hypervisor_resource_pool_resource.go +++ b/internal/daas/hypervisor_resource_pool/nutanix_hypervisor_resource_pool_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor_resource_pool @@ -6,22 +6,14 @@ import ( "context" "fmt" "net/http" - "regexp" "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-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/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" ) // Ensure the implementation satisfies the expected interfaces. @@ -46,40 +38,7 @@ func (*nutanixHypervisorResourcePoolResource) Metadata(_ context.Context, req re } func (*nutanixHypervisorResourcePoolResource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages a Nutanix AHV hypervisor resource pool.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the resource pool.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the resource pool. Name should be unique across all hypervisors.", - Required: true, - }, - "hypervisor": schema.StringAttribute{ - Description: "Id of the hypervisor for which the resource pool needs to be created.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "networks": schema.ListAttribute{ - ElementType: types.StringType, - Description: "List of networks for allocating resources.", - Required: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - }, - } + resp.Schema = GetNutanixHypervisorResourcePoolSchema() } func (r *nutanixHypervisorResourcePoolResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { @@ -133,7 +92,7 @@ func (r *nutanixHypervisorResourcePoolResource) Create(ctx context.Context, req return } - plan = plan.RefreshPropertyValues(resourcePool) + plan = plan.RefreshPropertyValues(ctx, &diags, resourcePool) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -164,7 +123,7 @@ func (r *nutanixHypervisorResourcePoolResource) Read(ctx context.Context, req re } // Override with refreshed state - state = state.RefreshPropertyValues(resourcePool) + state = state.RefreshPropertyValues(ctx, &diags, resourcePool) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -227,7 +186,7 @@ func (r *nutanixHypervisorResourcePoolResource) Update(ctx context.Context, req return } - plan = plan.RefreshPropertyValues(resourcePool) + plan = plan.RefreshPropertyValues(ctx, &diags, resourcePool) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -288,7 +247,7 @@ func (plan NutanixHypervisorResourcePoolResourceModel) GetNetworksList(ctx conte action = "creating" } - networkNames := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Networks) + networkNames := util.StringListToStringArray(ctx, diags, plan.Networks) networks, err := util.GetFilteredResourcePathList(ctx, client, hypervisorId, "", util.NetworkResourceType, networkNames, hypervisorConnectionType, pluginId) if len(networks) == 0 { errDetail := "No network found for the given network names" diff --git a/internal/daas/hypervisor_resource_pool/nutanix_hypervisor_resource_pool_resource_model.go b/internal/daas/hypervisor_resource_pool/nutanix_hypervisor_resource_pool_resource_model.go index 6508bfa..36b966b 100644 --- a/internal/daas/hypervisor_resource_pool/nutanix_hypervisor_resource_pool_resource_model.go +++ b/internal/daas/hypervisor_resource_pool/nutanix_hypervisor_resource_pool_resource_model.go @@ -1,10 +1,20 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. 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/listvalidator" + "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" ) @@ -13,10 +23,47 @@ type NutanixHypervisorResourcePoolResourceModel struct { Name types.String `tfsdk:"name"` Hypervisor types.String `tfsdk:"hypervisor"` /**** Resource Pool Details ****/ - Networks []types.String `tfsdk:"networks"` + Networks types.List `tfsdk:"networks"` // List[string] +} + +func GetNutanixHypervisorResourcePoolSchema() schema.Schema { + return schema.Schema{ + Description: "Manages a Nutanix AHV hypervisor resource pool.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the resource pool.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the resource pool. Name should be unique across all hypervisors.", + Required: true, + }, + "hypervisor": schema.StringAttribute{ + Description: "Id of the hypervisor for which the resource pool needs to be created.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "networks": schema.ListAttribute{ + ElementType: types.StringType, + Description: "Networks for allocating resources.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + }, + } } -func (r NutanixHypervisorResourcePoolResourceModel) RefreshPropertyValues(resourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) NutanixHypervisorResourcePoolResourceModel { +func (r NutanixHypervisorResourcePoolResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, resourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) NutanixHypervisorResourcePoolResourceModel { r.Id = types.StringValue(resourcePool.GetId()) r.Name = types.StringValue(resourcePool.GetName()) @@ -28,7 +75,7 @@ func (r NutanixHypervisorResourcePoolResourceModel) RefreshPropertyValues(resour for _, network := range resourcePool.GetNetworks() { remoteNetwork = append(remoteNetwork, network.GetName()) } - r.Networks = util.RefreshList(r.Networks, remoteNetwork) + r.Networks = util.RefreshListValues(ctx, diagnostics, r.Networks, remoteNetwork) return r } diff --git a/internal/daas/hypervisor_resource_pool/vsphere_hypervisor_resource_pool_resource.go b/internal/daas/hypervisor_resource_pool/vsphere_hypervisor_resource_pool_resource.go index 02e88c6..0d6ede6 100644 --- a/internal/daas/hypervisor_resource_pool/vsphere_hypervisor_resource_pool_resource.go +++ b/internal/daas/hypervisor_resource_pool/vsphere_hypervisor_resource_pool_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor_resource_pool @@ -6,24 +6,14 @@ import ( "context" "fmt" "net/http" - "regexp" "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-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" - "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" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) @@ -66,7 +56,8 @@ func (r *vsphereHypervisorResourcePoolResource) ModifyPlan(ctx context.Context, return } - for _, storageModel := range plan.Storage { + storage := util.ObjectListToTypedArray[HypervisorStorageModel](ctx, &resp.Diagnostics, plan.Storage) + for _, storageModel := range storage { if storageModel.Superseded.ValueBool() { resp.Diagnostics.AddAttributeError( path.Root("storage"), @@ -76,7 +67,8 @@ func (r *vsphereHypervisorResourcePoolResource) ModifyPlan(ctx context.Context, } } - for _, storageModel := range plan.TemporaryStorage { + temporaryStorage := util.ObjectListToTypedArray[HypervisorStorageModel](ctx, &resp.Diagnostics, plan.TemporaryStorage) + for _, storageModel := range temporaryStorage { if storageModel.Superseded.ValueBool() { resp.Diagnostics.AddAttributeError( path.Root("temporary_storage"), @@ -93,88 +85,7 @@ func (r *vsphereHypervisorResourcePoolResource) Metadata(_ context.Context, req } func (r *vsphereHypervisorResourcePoolResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages a VMware vSphere hypervisor resource pool.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the resource pool.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the resource pool. Name should be unique across all hypervisors.", - Required: true, - }, - "hypervisor": schema.StringAttribute{ - Description: "Id of the hypervisor for which the resource pool needs to be created.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "cluster": schema.SingleNestedAttribute{ - Description: "Details of the cluster where resources reside and new resources will be created.", - Required: true, - Attributes: map[string]schema.Attribute{ - "datacenter": schema.StringAttribute{ - Description: "The name of the datacenter.", - Required: true, - }, - "cluster_name": schema.StringAttribute{ - Description: "The name of the cluster.", - Optional: true, - Validators: []validator.String{ - stringvalidator.AtLeastOneOf(path.Expressions{ - path.MatchRelative().AtParent().AtName("host"), - }...), - }, - }, - "host": schema.StringAttribute{ - Description: "The IP address or FQDN of the host.", - Optional: true, - }, - }, - }, - "networks": schema.ListAttribute{ - ElementType: types.StringType, - Description: "List of networks for allocating resources.", - Required: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "storage": schema.ListNestedAttribute{ - Description: "List of hypervisor storage to use for OS data.", - Required: true, - NestedObject: GetNestedAttributeObjectSchmeaForStorege(), - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "temporary_storage": schema.ListNestedAttribute{ - Description: "List of hypervisor storage to use for temporary data.", - Required: true, - NestedObject: GetNestedAttributeObjectSchmeaForStorege(), - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "use_local_storage_caching": schema.BoolAttribute{ - Description: "Indicates whether intellicache is enabled to reduce load on the shared storage device. Will only be effective when shared storage is used. Default value is `false`.", - Optional: true, - Computed: true, - Default: booldefault.StaticBool(false), - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - }, - }, - } + resp.Schema = GetVsphereHypervisorResourcePoolSchema() } // Configure adds the provider configured client to the resource. @@ -206,23 +117,24 @@ func (r *vsphereHypervisorResourcePoolResource) Create(ctx context.Context, req folderPath := hypervisor.GetXDPath() var relativePath string - resource, err := util.GetSingleHypervisorResource(ctx, r.client, hypervisorId, folderPath, plan.Cluster.Datacenter.ValueString(), "datacenter", "", hypervisor) + cluster := util.ObjectValueToTypedObject[VsphereHypervisorClusterModel](ctx, &resp.Diagnostics, plan.Cluster) + resource, err := util.GetSingleHypervisorResource(ctx, r.client, hypervisorId, folderPath, cluster.Datacenter.ValueString(), "datacenter", "", hypervisor) if err != nil { resp.Diagnostics.AddError( "Error creating Hypervisor Resource Pool for vSphere", - fmt.Sprintf("Failed to resolve resource %s, error: %s", plan.Cluster.Datacenter.ValueString(), err.Error()), + fmt.Sprintf("Failed to resolve resource %s, error: %s", cluster.Datacenter.ValueString(), err.Error()), ) return } folderPath = resource.GetXDPath() relativePath = resource.GetRelativePath() - if !plan.Cluster.ClusterName.IsNull() { - resource, err = util.GetSingleHypervisorResource(ctx, r.client, hypervisorId, folderPath, plan.Cluster.ClusterName.ValueString(), "cluster", "", hypervisor) + if !cluster.ClusterName.IsNull() { + resource, err = util.GetSingleHypervisorResource(ctx, r.client, hypervisorId, folderPath, cluster.ClusterName.ValueString(), "cluster", "", hypervisor) if err != nil { resp.Diagnostics.AddError( "Error creating Hypervisor Resource Pool for vSphere", - fmt.Sprintf("Failed to resolve resource %s, error: %s", plan.Cluster.ClusterName.ValueString(), err.Error()), + fmt.Sprintf("Failed to resolve resource %s, error: %s", cluster.ClusterName.ValueString(), err.Error()), ) return } @@ -230,12 +142,12 @@ func (r *vsphereHypervisorResourcePoolResource) Create(ctx context.Context, req relativePath = resource.GetRelativePath() } - if !plan.Cluster.Host.IsNull() { - resource, err = util.GetSingleHypervisorResource(ctx, r.client, hypervisorId, folderPath, plan.Cluster.Host.ValueString(), "computeresource", "", hypervisor) + if !cluster.Host.IsNull() { + resource, err = util.GetSingleHypervisorResource(ctx, r.client, hypervisorId, folderPath, cluster.Host.ValueString(), "computeresource", "", hypervisor) if err != nil { resp.Diagnostics.AddError( "Error creating Hypervisor Resource Pool for vSphere", - fmt.Sprintf("Failed to resolve resource %s, error: %s", plan.Cluster.Host.ValueString(), err.Error()), + fmt.Sprintf("Failed to resolve resource %s, error: %s", cluster.Host.ValueString(), err.Error()), ) return } @@ -274,7 +186,7 @@ func (r *vsphereHypervisorResourcePoolResource) Create(ctx context.Context, req return } - plan = plan.RefreshPropertyValues(resourcePool) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, resourcePool) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -304,7 +216,7 @@ func (r *vsphereHypervisorResourcePoolResource) Read(ctx context.Context, req re } // Override with refreshed state - state = state.RefreshPropertyValues(resourcePool) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, resourcePool) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -396,7 +308,7 @@ func (r *vsphereHypervisorResourcePoolResource) Update(ctx context.Context, req return } - plan = plan.RefreshPropertyValues(updatedResourcePool) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, updatedResourcePool) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -456,7 +368,8 @@ func (plan VsphereHypervisorResourcePoolResourceModel) GetStorageList(ctx contex } storage := []basetypes.StringValue{} - for _, storageModel := range plan.Storage { + storageFromPlan := util.ObjectListToTypedArray[HypervisorStorageModel](ctx, diags, plan.Storage) + for _, storageModel := range storageFromPlan { if storageModel.Superseded.ValueBool() == forSuperseded { storage = append(storage, storageModel.StorageName) } @@ -464,13 +377,14 @@ func (plan VsphereHypervisorResourcePoolResourceModel) GetStorageList(ctx contex storageNames := util.ConvertBaseStringArrayToPrimitiveStringArray(storage) hypervisorId := hypervisor.GetId() hypervisorConnectionType := hypervisor.GetConnectionType() - folderPath := fmt.Sprintf("%s\\%s.datacenter", hypervisor.GetXDPath(), plan.Cluster.Datacenter.ValueString()) - if !plan.Cluster.ClusterName.IsNull() { - folderPath = fmt.Sprintf("%s\\%s.cluster", folderPath, plan.Cluster.ClusterName.ValueString()) + cluster := util.ObjectValueToTypedObject[VsphereHypervisorClusterModel](ctx, diags, plan.Cluster) + folderPath := fmt.Sprintf("%s\\%s.datacenter", hypervisor.GetXDPath(), cluster.Datacenter.ValueString()) + if !cluster.ClusterName.IsNull() { + folderPath = fmt.Sprintf("%s\\%s.cluster", folderPath, cluster.ClusterName.ValueString()) } - if !plan.Cluster.Host.IsNull() { - folderPath = fmt.Sprintf("%s\\%s.computeresource", folderPath, plan.Cluster.Host.ValueString()) + if !cluster.Host.IsNull() { + folderPath = fmt.Sprintf("%s\\%s.computeresource", folderPath, cluster.Host.ValueString()) } storages, err := util.GetFilteredResourcePathList(ctx, client, hypervisorId, folderPath, util.StorageResourceType, storageNames, hypervisorConnectionType, hypervisor.GetPluginId()) @@ -487,7 +401,8 @@ func (plan VsphereHypervisorResourcePoolResourceModel) GetStorageList(ctx contex } tempStorage := []basetypes.StringValue{} - for _, storageModel := range plan.TemporaryStorage { + temporaryStorageFromPlan := util.ObjectListToTypedArray[HypervisorStorageModel](ctx, diags, plan.TemporaryStorage) + for _, storageModel := range temporaryStorageFromPlan { if storageModel.Superseded.ValueBool() == forSuperseded { tempStorage = append(tempStorage, storageModel.StorageName) } @@ -517,16 +432,17 @@ func (plan VsphereHypervisorResourcePoolResourceModel) GetNetworksList(ctx conte action = "creating" } - folderPath := fmt.Sprintf("%s\\%s.datacenter", hypervisor.GetXDPath(), plan.Cluster.Datacenter.ValueString()) - if !plan.Cluster.ClusterName.IsNull() { - folderPath = fmt.Sprintf("%s\\%s.cluster", folderPath, plan.Cluster.ClusterName.ValueString()) + cluster := util.ObjectValueToTypedObject[VsphereHypervisorClusterModel](ctx, diags, plan.Cluster) + folderPath := fmt.Sprintf("%s\\%s.datacenter", hypervisor.GetXDPath(), cluster.Datacenter.ValueString()) + if !cluster.ClusterName.IsNull() { + folderPath = fmt.Sprintf("%s\\%s.cluster", folderPath, cluster.ClusterName.ValueString()) } - if !plan.Cluster.Host.IsNull() { - folderPath = fmt.Sprintf("%s\\%s.computeresource", folderPath, plan.Cluster.Host.ValueString()) + if !cluster.Host.IsNull() { + folderPath = fmt.Sprintf("%s\\%s.computeresource", folderPath, cluster.Host.ValueString()) } - networkNames := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Networks) + networkNames := util.StringListToStringArray(ctx, diags, plan.Networks) networks, err := util.GetFilteredResourcePathList(ctx, client, hypervisorId, folderPath, util.NetworkResourceType, networkNames, hypervisorConnectionType, hypervisor.GetPluginId()) if len(networks) == 0 { errDetail := "No network found for the given network names" diff --git a/internal/daas/hypervisor_resource_pool/vsphere_hypervisor_resource_pool_resource_model.go b/internal/daas/hypervisor_resource_pool/vsphere_hypervisor_resource_pool_resource_model.go index e1468c5..4f39e56 100644 --- a/internal/daas/hypervisor_resource_pool/vsphere_hypervisor_resource_pool_resource_model.go +++ b/internal/daas/hypervisor_resource_pool/vsphere_hypervisor_resource_pool_resource_model.go @@ -1,10 +1,23 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. 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/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/boolplanmodifier" + "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" ) @@ -14,19 +27,112 @@ type VsphereHypervisorClusterModel struct { Host types.String `tfsdk:"host"` } +func (VsphereHypervisorClusterModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Details of the cluster where resources reside and new resources will be created.", + Required: true, + Attributes: map[string]schema.Attribute{ + "datacenter": schema.StringAttribute{ + Description: "The name of the datacenter.", + Required: true, + }, + "cluster_name": schema.StringAttribute{ + Description: "The name of the cluster.", + Optional: true, + Validators: []validator.String{ + stringvalidator.AtLeastOneOf(path.Expressions{ + path.MatchRelative().AtParent().AtName("host"), + }...), + }, + }, + "host": schema.StringAttribute{ + Description: "The IP address or FQDN of the host.", + Optional: true, + }, + }, + } +} + +func (VsphereHypervisorClusterModel) GetAttributes() map[string]schema.Attribute { + return VsphereHypervisorClusterModel{}.GetSchema().Attributes +} + type VsphereHypervisorResourcePoolResourceModel struct { Id types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` Hypervisor types.String `tfsdk:"hypervisor"` /**** Resource Pool Details ****/ - Cluster *VsphereHypervisorClusterModel `tfsdk:"cluster"` - Networks []types.String `tfsdk:"networks"` - Storage []HypervisorStorageModel `tfsdk:"storage"` - TemporaryStorage []HypervisorStorageModel `tfsdk:"temporary_storage"` - UseLocalStorageCaching types.Bool `tfsdk:"use_local_storage_caching"` + Cluster types.Object `tfsdk:"cluster"` //VsphereHypervisorClusterModel + Networks types.List `tfsdk:"networks"` // List[string] + Storage types.List `tfsdk:"storage"` // List[HypervisorStorageModel] + TemporaryStorage types.List `tfsdk:"temporary_storage"` // List[HypervisorStorageModel] + UseLocalStorageCaching types.Bool `tfsdk:"use_local_storage_caching"` +} + +func GetVsphereHypervisorResourcePoolSchema() schema.Schema { + return schema.Schema{ + Description: "Manages a VMware vSphere hypervisor resource pool.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the resource pool.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the resource pool. Name should be unique across all hypervisors.", + Required: true, + }, + "hypervisor": schema.StringAttribute{ + Description: "Id of the hypervisor for which the resource pool needs to be created.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "cluster": VsphereHypervisorClusterModel{}.GetSchema(), + "networks": schema.ListAttribute{ + ElementType: types.StringType, + Description: "Networks for allocating resources.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "storage": schema.ListNestedAttribute{ + Description: "Storage resources to use for OS data.", + Required: true, + NestedObject: HypervisorStorageModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "temporary_storage": schema.ListNestedAttribute{ + Description: "Storage resources to use for temporary data.", + Required: true, + NestedObject: HypervisorStorageModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "use_local_storage_caching": schema.BoolAttribute{ + Description: "Indicates whether intellicache is enabled to reduce load on the shared storage device. Will only be effective when shared storage is used. Default value is `false`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + }, + } } -func (r VsphereHypervisorResourcePoolResourceModel) RefreshPropertyValues(resourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) VsphereHypervisorResourcePoolResourceModel { +func (r VsphereHypervisorResourcePoolResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, resourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) VsphereHypervisorResourcePoolResourceModel { r.Id = types.StringValue(resourcePool.GetId()) r.Name = types.StringValue(resourcePool.GetName()) @@ -40,9 +146,9 @@ func (r VsphereHypervisorResourcePoolResourceModel) RefreshPropertyValues(resour for _, network := range resourcePool.GetNetworks() { remoteNetwork = append(remoteNetwork, network.GetName()) } - r.Networks = util.RefreshList(r.Networks, remoteNetwork) - r.Storage = util.RefreshListProperties[HypervisorStorageModel, citrixorchestration.HypervisorStorageResourceResponseModel](r.Storage, "StorageName", resourcePool.GetStorage(), "Name", "RefreshListItem") - r.TemporaryStorage = util.RefreshListProperties[HypervisorStorageModel, citrixorchestration.HypervisorStorageResourceResponseModel](r.TemporaryStorage, "StorageName", resourcePool.GetTemporaryStorage(), "Name", "RefreshListItem") + r.Networks = util.RefreshListValues(ctx, diagnostics, r.Networks, remoteNetwork) + r.Storage = util.RefreshListValueProperties[HypervisorStorageModel, citrixorchestration.HypervisorStorageResourceResponseModel](ctx, diagnostics, r.Storage, resourcePool.GetStorage(), util.GetOrchestrationHypervisorStorageKey) + r.TemporaryStorage = util.RefreshListValueProperties[HypervisorStorageModel, citrixorchestration.HypervisorStorageResourceResponseModel](ctx, diagnostics, r.TemporaryStorage, resourcePool.GetTemporaryStorage(), util.GetOrchestrationHypervisorStorageKey) return r } diff --git a/internal/daas/hypervisor_resource_pool/xenserver_hypervisor_resource_pool_resource.go b/internal/daas/hypervisor_resource_pool/xenserver_hypervisor_resource_pool_resource.go index 75ea7dc..8323b43 100644 --- a/internal/daas/hypervisor_resource_pool/xenserver_hypervisor_resource_pool_resource.go +++ b/internal/daas/hypervisor_resource_pool/xenserver_hypervisor_resource_pool_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package hypervisor_resource_pool @@ -6,24 +6,14 @@ import ( "context" "fmt" "net/http" - "regexp" "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-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" - "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" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) @@ -50,65 +40,7 @@ func (r *xenserverHypervisorResourcePoolResource) Metadata(_ context.Context, re } func (r *xenserverHypervisorResourcePoolResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages an XenServer hypervisor resource pool.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the resource pool.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the resource pool. Name should be unique across all hypervisors.", - Required: true, - }, - "hypervisor": schema.StringAttribute{ - Description: "Id of the hypervisor for which the resource pool needs to be created.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "networks": schema.ListAttribute{ - ElementType: types.StringType, - Description: "List of networks for allocating resources.", - Required: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "storage": schema.ListNestedAttribute{ - Description: "List of hypervisor storage to use for OS data.", - Required: true, - NestedObject: GetNestedAttributeObjectSchmeaForStorege(), - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "temporary_storage": schema.ListNestedAttribute{ - Description: "List of hypervisor storage to use for temporary data.", - Required: true, - NestedObject: GetNestedAttributeObjectSchmeaForStorege(), - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "use_local_storage_caching": schema.BoolAttribute{ - Description: "Indicates whether intellicache is enabled to reduce load on the shared storage device. Will only be effective when shared storage is used. Default value is `false`.", - Optional: true, - Computed: true, - Default: booldefault.StaticBool(false), - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - }, - }, - } + resp.Schema = GetXenserverHypervisorResourcePoolSchema() } // Configure adds the provider configured client to the resource. @@ -171,7 +103,7 @@ func (r *xenserverHypervisorResourcePoolResource) Create(ctx context.Context, re return } - plan = plan.RefreshPropertyValues(resourcePool) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, resourcePool) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -202,7 +134,7 @@ func (r *xenserverHypervisorResourcePoolResource) Read(ctx context.Context, req } // Override with refreshed state - state = state.RefreshPropertyValues(resourcePool) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, resourcePool) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -295,7 +227,7 @@ func (r *xenserverHypervisorResourcePoolResource) Update(ctx context.Context, re return } - plan = plan.RefreshPropertyValues(updatedResourcePool) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, updatedResourcePool) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -355,7 +287,8 @@ func (plan XenserverHypervisorResourcePoolResourceModel) GetStorageList(ctx cont } storage := []basetypes.StringValue{} - for _, storageModel := range plan.Storage { + storageFromPlan := util.ObjectListToTypedArray[HypervisorStorageModel](ctx, diags, plan.Storage) + for _, storageModel := range storageFromPlan { if storageModel.Superseded.ValueBool() == forSuperseded { storage = append(storage, storageModel.StorageName) } @@ -378,7 +311,8 @@ func (plan XenserverHypervisorResourcePoolResourceModel) GetStorageList(ctx cont } tempStorage := []basetypes.StringValue{} - for _, storageModel := range plan.TemporaryStorage { + temporaryStorageFromPlan := util.ObjectListToTypedArray[HypervisorStorageModel](ctx, diags, plan.TemporaryStorage) + for _, storageModel := range temporaryStorageFromPlan { if storageModel.Superseded.ValueBool() == forSuperseded { tempStorage = append(tempStorage, storageModel.StorageName) } @@ -408,7 +342,7 @@ func (plan XenserverHypervisorResourcePoolResourceModel) GetNetworksList(ctx con action = "creating" } - networkNames := util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Networks) + networkNames := util.StringListToStringArray(ctx, diags, plan.Networks) networks, err := util.GetFilteredResourcePathList(ctx, client, hypervisorId, "", util.NetworkResourceType, networkNames, hypervisorConnectionType, hypervisor.GetPluginId()) if len(networks) == 0 { errDetail := "No network found for the given network names" diff --git a/internal/daas/hypervisor_resource_pool/xenserver_hypervisor_resource_pool_resource_model.go b/internal/daas/hypervisor_resource_pool/xenserver_hypervisor_resource_pool_resource_model.go index 3fc230a..d346c84 100644 --- a/internal/daas/hypervisor_resource_pool/xenserver_hypervisor_resource_pool_resource_model.go +++ b/internal/daas/hypervisor_resource_pool/xenserver_hypervisor_resource_pool_resource_model.go @@ -1,10 +1,22 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. 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/listvalidator" + "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/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "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" ) @@ -13,13 +25,75 @@ type XenserverHypervisorResourcePoolResourceModel struct { Name types.String `tfsdk:"name"` Hypervisor types.String `tfsdk:"hypervisor"` /**** Resource Pool Details ****/ - Networks []types.String `tfsdk:"networks"` - Storage []HypervisorStorageModel `tfsdk:"storage"` - TemporaryStorage []HypervisorStorageModel `tfsdk:"temporary_storage"` - UseLocalStorageCaching types.Bool `tfsdk:"use_local_storage_caching"` + Networks types.List `tfsdk:"networks"` //List[string] + Storage types.List `tfsdk:"storage"` //List[HypervisorStorageModel] + TemporaryStorage types.List `tfsdk:"temporary_storage"` //List[HypervisorStorageModel] + UseLocalStorageCaching types.Bool `tfsdk:"use_local_storage_caching"` } -func (r XenserverHypervisorResourcePoolResourceModel) RefreshPropertyValues(resourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) XenserverHypervisorResourcePoolResourceModel { +func GetXenserverHypervisorResourcePoolSchema() schema.Schema { + return schema.Schema{ + Description: "Manages an XenServer hypervisor resource pool.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the resource pool.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the resource pool. Name should be unique across all hypervisors.", + Required: true, + }, + "hypervisor": schema.StringAttribute{ + Description: "Id of the hypervisor for which the resource pool needs to be created.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "networks": schema.ListAttribute{ + ElementType: types.StringType, + Description: "Networks for allocating resources.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "storage": schema.ListNestedAttribute{ + Description: "List of hypervisor storage to use for OS data.", + Required: true, + NestedObject: HypervisorStorageModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "temporary_storage": schema.ListNestedAttribute{ + Description: "List of hypervisor storage to use for temporary data.", + Required: true, + NestedObject: HypervisorStorageModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "use_local_storage_caching": schema.BoolAttribute{ + Description: "Indicates whether intellicache is enabled to reduce load on the shared storage device. Will only be effective when shared storage is used. Default value is `false`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (r XenserverHypervisorResourcePoolResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, resourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) XenserverHypervisorResourcePoolResourceModel { r.Id = types.StringValue(resourcePool.GetId()) r.Name = types.StringValue(resourcePool.GetName()) @@ -33,10 +107,9 @@ func (r XenserverHypervisorResourcePoolResourceModel) RefreshPropertyValues(reso for _, network := range resourcePool.GetNetworks() { remoteNetwork = append(remoteNetwork, network.GetName()) } - r.Networks = util.RefreshList(r.Networks, remoteNetwork) - - r.Storage = util.RefreshListProperties[HypervisorStorageModel, citrixorchestration.HypervisorStorageResourceResponseModel](r.Storage, "StorageName", resourcePool.GetStorage(), "Name", "RefreshListItem") - r.TemporaryStorage = util.RefreshListProperties[HypervisorStorageModel, citrixorchestration.HypervisorStorageResourceResponseModel](r.TemporaryStorage, "StorageName", resourcePool.GetTemporaryStorage(), "Name", "RefreshListItem") + r.Networks = util.RefreshListValues(ctx, diagnostics, r.Networks, remoteNetwork) + r.Storage = util.RefreshListValueProperties[HypervisorStorageModel, citrixorchestration.HypervisorStorageResourceResponseModel](ctx, diagnostics, r.Storage, resourcePool.GetStorage(), util.GetOrchestrationHypervisorStorageKey) + r.TemporaryStorage = util.RefreshListValueProperties[HypervisorStorageModel, citrixorchestration.HypervisorStorageResourceResponseModel](ctx, diagnostics, r.TemporaryStorage, resourcePool.GetTemporaryStorage(), util.GetOrchestrationHypervisorStorageKey) return r } diff --git a/internal/daas/machine_catalog/machine_catalog_common_utils.go b/internal/daas/machine_catalog/machine_catalog_common_utils.go index a70f21b..9b22bbd 100644 --- a/internal/daas/machine_catalog/machine_catalog_common_utils.go +++ b/internal/daas/machine_catalog/machine_catalog_common_utils.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package machine_catalog @@ -78,6 +78,11 @@ func getRequestModelForCreateMachineCatalog(plan MachineCatalogResourceModel, ct } body.SetMinimumFunctionalLevel(*functionalLevel) + if !plan.Scopes.IsNull() { + plannedScopes := util.StringSetToStringArray(ctx, diagnostics, plan.Scopes) + body.SetScopes(plannedScopes) + } + if *provisioningType == citrixorchestration.PROVISIONINGTYPE_MCS { provisioningScheme, err := getProvSchemeForMcsCatalog(plan, ctx, client, diagnostics, isOnPremises) if err != nil { @@ -97,10 +102,10 @@ func getRequestModelForCreateMachineCatalog(plan MachineCatalogResourceModel, ct body.SetIsRemotePC(plan.IsRemotePc.ValueBool()) if isRemotePcCatalog { - remotePCEnrollmentScopes := getRemotePcEnrollmentScopes(plan, true) + remotePCEnrollmentScopes := getRemotePcEnrollmentScopes(ctx, diagnostics, plan, true) body.SetRemotePCEnrollmentScopes(remotePCEnrollmentScopes) } else { - machinesRequest, err = getMachinesForManualCatalogs(ctx, client, plan.MachineAccounts) + machinesRequest, err = getMachinesForManualCatalogs(ctx, diagnostics, client, util.ObjectListToTypedArray[MachineAccountsModel](ctx, diagnostics, plan.MachineAccounts)) if err != nil { diagnostics.AddError( "Error creating Machine Catalog", @@ -136,6 +141,11 @@ func getRequestModelForUpdateMachineCatalog(plan MachineCatalogResourceModel, ct } body.SetMinimumFunctionalLevel(*functionalLevel) + if !plan.Scopes.IsNull() { + plannedScopes := util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes) + body.SetScopes(plannedScopes) + } + provisioningType, err := citrixorchestration.NewProvisioningTypeFromValue(plan.ProvisioningType.ValueString()) if err != nil { resp.Diagnostics.AddError( @@ -149,18 +159,19 @@ func getRequestModelForUpdateMachineCatalog(plan MachineCatalogResourceModel, ct if *provisioningType == citrixorchestration.PROVISIONINGTYPE_MANUAL { if plan.IsRemotePc.ValueBool() { - remotePCEnrollmentScopes := getRemotePcEnrollmentScopes(plan, false) + remotePCEnrollmentScopes := getRemotePcEnrollmentScopes(ctx, &resp.Diagnostics, plan, false) body.SetRemotePCEnrollmentScopes(remotePCEnrollmentScopes) } return &body, nil } - if !checkIfProvSchemeIsCloudOnly(plan, isOnPremises, &resp.Diagnostics) { - return nil, fmt.Errorf("identity type %s is not supported in OnPremises environment. ", plan.ProvisioningScheme.IdentityType.ValueString()) + provSchemeModel := util.ObjectValueToTypedObject[ProvisioningSchemeModel](ctx, &resp.Diagnostics, plan.ProvisioningScheme) + if !checkIfProvSchemeIsCloudOnly(ctx, &resp.Diagnostics, provSchemeModel, isOnPremises) { + return nil, fmt.Errorf("identity type %s is not supported in OnPremises environment. ", provSchemeModel.IdentityType.ValueString()) } - body, err = setProvSchemePropertiesForUpdateCatalog(plan, body, ctx, client, &resp.Diagnostics) + body, err = setProvSchemePropertiesForUpdateCatalog(provSchemeModel, body, ctx, client, &resp.Diagnostics) if err != nil { return nil, err } @@ -168,14 +179,14 @@ func getRequestModelForUpdateMachineCatalog(plan MachineCatalogResourceModel, ct return &body, nil } -func checkIfProvSchemeIsCloudOnly(plan MachineCatalogResourceModel, isOnPremises bool, diagnostics *diag.Diagnostics) bool { - if plan.ProvisioningScheme.IdentityType.ValueString() == string(citrixorchestration.IDENTITYTYPE_AZURE_AD) || - plan.ProvisioningScheme.IdentityType.ValueString() == string(citrixorchestration.IDENTITYTYPE_WORKGROUP) { +func checkIfProvSchemeIsCloudOnly(ctx context.Context, diagnostics *diag.Diagnostics, provisoningScheme ProvisioningSchemeModel, isOnPremises bool) bool { + if provisoningScheme.IdentityType.ValueString() == string(citrixorchestration.IDENTITYTYPE_AZURE_AD) || + provisoningScheme.IdentityType.ValueString() == string(citrixorchestration.IDENTITYTYPE_WORKGROUP) { if isOnPremises { diagnostics.AddAttributeError( path.Root("identity_type"), "Unsupported Machine Catalog Configuration", - fmt.Sprintf("Identity type %s is not supported in OnPremises environment. ", string(plan.ProvisioningScheme.IdentityType.ValueString())), + fmt.Sprintf("Identity type %s is not supported in OnPremises environment. ", string(provisoningScheme.IdentityType.ValueString())), ) return false @@ -184,7 +195,7 @@ func checkIfProvSchemeIsCloudOnly(plan MachineCatalogResourceModel, isOnPremises return true } -func generateBatchApiHeaders(client *citrixdaasclient.CitrixDaasClient, plan MachineCatalogResourceModel, generateCredentialHeader bool) ([]citrixorchestration.NameValueStringPairModel, *http.Response, error) { +func generateBatchApiHeaders(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, provisioningSchemePlan ProvisioningSchemeModel, generateCredentialHeader bool) ([]citrixorchestration.NameValueStringPairModel, *http.Response, error) { headers := []citrixorchestration.NameValueStringPairModel{} cwsAuthToken, httpResp, err := client.SignIn() @@ -201,8 +212,8 @@ func generateBatchApiHeaders(client *citrixdaasclient.CitrixDaasClient, plan Mac headers = append(headers, header) } - if generateCredentialHeader && plan.ProvisioningScheme.MachineDomainIdentity != nil { - adminCredentialHeader := generateAdminCredentialHeader(plan) + if generateCredentialHeader && !provisioningSchemePlan.MachineDomainIdentity.IsNull() { + adminCredentialHeader := generateAdminCredentialHeader(util.ObjectValueToTypedObject[MachineDomainIdentityModel](ctx, diagnostics, provisioningSchemePlan.MachineDomainIdentity)) var header citrixorchestration.NameValueStringPairModel header.SetName("X-AdminCredential") header.SetValue(adminCredentialHeader) @@ -219,8 +230,8 @@ func readMachineCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaas return catalog, httpResp, err } -func deleteMachinesFromCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, resp *resource.UpdateResponse, plan MachineCatalogResourceModel, machinesToDelete []citrixorchestration.MachineResponseModel, catalogNameOrId string, isMcsCatalog bool) error { - batchApiHeaders, httpResp, err := generateBatchApiHeaders(client, plan, false) +func deleteMachinesFromCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, resp *resource.UpdateResponse, provisioningSchemePlan ProvisioningSchemeModel, machinesToDelete []citrixorchestration.MachineResponseModel, catalogNameOrId string, isMcsCatalog bool) error { + batchApiHeaders, httpResp, err := generateBatchApiHeaders(ctx, &resp.Diagnostics, client, provisioningSchemePlan, false) txId := citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp) if err != nil { resp.Diagnostics.AddError( @@ -291,7 +302,7 @@ func deleteMachinesFromCatalog(ctx context.Context, client *citrixdaasclient.Cit } } - batchApiHeaders, httpResp, err = generateBatchApiHeaders(client, plan, isMcsCatalog) + batchApiHeaders, httpResp, err = generateBatchApiHeaders(ctx, &resp.Diagnostics, client, provisioningSchemePlan, isMcsCatalog) txId = citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp) if err != nil { resp.Diagnostics.AddError( @@ -357,7 +368,7 @@ func allocationTypeEnumToString(conn citrixorchestration.AllocationType) string } } -func (scope RemotePcOuModel) RefreshListItem(remote citrixorchestration.RemotePCEnrollmentScopeResponseModel) RemotePcOuModel { +func (scope RemotePcOuModel) RefreshListItem(_ context.Context, _ *diag.Diagnostics, remote citrixorchestration.RemotePCEnrollmentScopeResponseModel) util.ModelWithAttributes { scope.OUName = types.StringValue(remote.GetOU()) scope.IncludeSubFolders = types.BoolValue(remote.GetIncludeSubfolders()) diff --git a/internal/daas/machine_catalog/machine_catalog_manual_utils.go b/internal/daas/machine_catalog/machine_catalog_manual_utils.go index 561ae1d..dfee965 100644 --- a/internal/daas/machine_catalog/machine_catalog_manual_utils.go +++ b/internal/daas/machine_catalog/machine_catalog_manual_utils.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package machine_catalog @@ -12,11 +12,12 @@ import ( "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/resource" "github.com/hashicorp/terraform-plugin-framework/types" ) -func getMachinesForManualCatalogs(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, machineAccounts []MachineAccountsModel) ([]citrixorchestration.AddMachineToMachineCatalogRequestModel, error) { +func getMachinesForManualCatalogs(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, machineAccounts []MachineAccountsModel) ([]citrixorchestration.AddMachineToMachineCatalogRequestModel, error) { if machineAccounts == nil { return nil, nil } @@ -34,7 +35,9 @@ func getMachinesForManualCatalogs(ctx context.Context, client *citrixdaasclient. } } - for _, machine := range machineAccount.Machines { + machines := util.ObjectListToTypedArray[MachineCatalogMachineModel](ctx, diagnostics, machineAccount.Machines) + + for _, machine := range machines { addMachineRequest := citrixorchestration.AddMachineToMachineCatalogRequestModel{} addMachineRequest.SetMachineName(machine.MachineAccount.ValueString()) @@ -167,17 +170,17 @@ func deleteMachinesFromManualCatalog(ctx context.Context, client *citrixdaasclie } } - return deleteMachinesFromCatalog(ctx, client, resp, MachineCatalogResourceModel{}, machinesToDelete, catalogNameOrId, false) + return deleteMachinesFromCatalog(ctx, client, resp, ProvisioningSchemeModel{}, machinesToDelete, catalogNameOrId, false) } -func addMachinesToManualCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, resp *resource.UpdateResponse, addMachinesList []MachineAccountsModel, catalogIdOrName string) error { +func addMachinesToManualCatalog(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, resp *resource.UpdateResponse, addMachinesList []MachineAccountsModel, catalogIdOrName string) error { if len(addMachinesList) < 1 { // no machines to add return nil } - addMachinesRequest, err := getMachinesForManualCatalogs(ctx, client, addMachinesList) + addMachinesRequest, err := getMachinesForManualCatalogs(ctx, diagnostics, client, addMachinesList) if err != nil { resp.Diagnostics.AddError( "Error adding machines(s) to Machine Catalog "+catalogIdOrName, @@ -187,7 +190,7 @@ func addMachinesToManualCatalog(ctx context.Context, client *citrixdaasclient.Ci return err } - batchApiHeaders, httpResp, err := generateBatchApiHeaders(client, MachineCatalogResourceModel{}, false) + batchApiHeaders, httpResp, err := generateBatchApiHeaders(ctx, &resp.Diagnostics, client, ProvisioningSchemeModel{}, false) txId := citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp) if err != nil { resp.Diagnostics.AddError( @@ -245,14 +248,16 @@ func addMachinesToManualCatalog(ctx context.Context, client *citrixdaasclient.Ci return nil } -func createAddAndRemoveMachinesListForManualCatalogs(state, plan MachineCatalogResourceModel) ([]MachineAccountsModel, map[string]bool) { +func createAddAndRemoveMachinesListForManualCatalogs(ctx context.Context, diagnostics *diag.Diagnostics, state, plan MachineCatalogResourceModel) ([]MachineAccountsModel, map[string]bool) { addMachinesList := []MachineAccountsModel{} existingMachineAccounts := map[string]map[string]bool{} // create map for existing machines marking all machines for deletion - if state.MachineAccounts != nil { - for _, machineAccount := range state.MachineAccounts { - for _, machine := range machineAccount.Machines { + if !state.MachineAccounts.IsNull() { + machineAccounts := util.ObjectListToTypedArray[MachineAccountsModel](ctx, diagnostics, state.MachineAccounts) + for _, machineAccount := range machineAccounts { + machines := util.ObjectListToTypedArray[MachineCatalogMachineModel](ctx, diagnostics, machineAccount.Machines) + for _, machine := range machines { machineMap, exists := existingMachineAccounts[machineAccount.Hypervisor.ValueString()] if !exists { existingMachineAccounts[machineAccount.Hypervisor.ValueString()] = map[string]bool{} @@ -264,10 +269,12 @@ func createAddAndRemoveMachinesListForManualCatalogs(state, plan MachineCatalogR } // iterate over plan and if machine already exists, mark false for deletion. If not, add it to the addMachineList - if plan.MachineAccounts != nil { - for _, machineAccount := range plan.MachineAccounts { + if !plan.MachineAccounts.IsNull() { + machineAccounts := util.ObjectListToTypedArray[MachineAccountsModel](ctx, diagnostics, plan.MachineAccounts) + for _, machineAccount := range machineAccounts { machineAccountMachines := []MachineCatalogMachineModel{} - for _, machine := range machineAccount.Machines { + machines := util.ObjectListToTypedArray[MachineCatalogMachineModel](ctx, diagnostics, machineAccount.Machines) + for _, machine := range machines { if existingMachineAccounts[machineAccount.Hypervisor.ValueString()][strings.ToLower(machine.MachineAccount.ValueString())] { // Machine exists. Mark false for deletion existingMachineAccounts[machineAccount.Hypervisor.ValueString()][strings.ToLower(machine.MachineAccount.ValueString())] = false @@ -280,7 +287,7 @@ func createAddAndRemoveMachinesListForManualCatalogs(state, plan MachineCatalogR if len(machineAccountMachines) > 0 { var addMachineAccount MachineAccountsModel addMachineAccount.Hypervisor = machineAccount.Hypervisor - addMachineAccount.Machines = machineAccountMachines + addMachineAccount.Machines = util.TypedArrayToObjectList[MachineCatalogMachineModel](ctx, diagnostics, machineAccountMachines) addMachinesList = append(addMachinesList, addMachineAccount) } } @@ -299,9 +306,9 @@ func createAddAndRemoveMachinesListForManualCatalogs(state, plan MachineCatalogR return addMachinesList, deleteMachinesMap } -func (r MachineCatalogResourceModel) updateCatalogWithMachines(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, machines *citrixorchestration.MachineResponseModelCollection) MachineCatalogResourceModel { +func (r MachineCatalogResourceModel) updateCatalogWithMachines(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, machines *citrixorchestration.MachineResponseModelCollection) MachineCatalogResourceModel { if machines == nil { - r.MachineAccounts = nil + r.MachineAccounts = util.TypedArrayToObjectList[MachineAccountsModel](ctx, diagnostics, nil) return r } @@ -310,10 +317,12 @@ func (r MachineCatalogResourceModel) updateCatalogWithMachines(ctx context.Conte machineMapFromRemote[strings.ToLower(machine.GetName())] = machine } - if r.MachineAccounts != nil { + if !r.MachineAccounts.IsNull() { machinesNotPresetInRemote := map[string]bool{} - for _, machineAccount := range r.MachineAccounts { - for _, machineFromPlan := range machineAccount.Machines { + machineAccounts := util.ObjectListToTypedArray[MachineAccountsModel](ctx, diagnostics, r.MachineAccounts) + for _, machineAccount := range machineAccounts { + machines := util.ObjectListToTypedArray[MachineCatalogMachineModel](ctx, diagnostics, machineAccount.Machines) + for _, machineFromPlan := range machines { machineFromPlanName := machineFromPlan.MachineAccount.ValueString() machineFromRemote, exists := machineMapFromRemote[strings.ToLower(machineFromPlanName)] if !exists { @@ -396,20 +405,22 @@ func (r MachineCatalogResourceModel) updateCatalogWithMachines(ctx context.Conte } } - machineAccounts := []MachineAccountsModel{} - for _, machineAccount := range r.MachineAccounts { - machines := []MachineCatalogMachineModel{} - for _, machine := range machineAccount.Machines { + machineAccountsArray := []MachineAccountsModel{} + machineAccountsModel := util.ObjectListToTypedArray[MachineAccountsModel](ctx, diagnostics, r.MachineAccounts) + for _, machineAccount := range machineAccountsModel { + machineAccountMachines := []MachineCatalogMachineModel{} + machines := util.ObjectListToTypedArray[MachineCatalogMachineModel](ctx, diagnostics, machineAccount.Machines) + for _, machine := range machines { if machinesNotPresetInRemote[strings.ToLower(machine.MachineAccount.ValueString())] { continue } - machines = append(machines, machine) + machineAccountMachines = append(machineAccountMachines, machine) } - machineAccount.Machines = machines - machineAccounts = append(machineAccounts, machineAccount) + machineAccount.Machines = util.TypedArrayToObjectList[MachineCatalogMachineModel](ctx, diagnostics, machineAccountMachines) + machineAccountsArray = append(machineAccountsArray, machineAccount) } - r.MachineAccounts = machineAccounts + r.MachineAccounts = util.TypedArrayToObjectList[MachineAccountsModel](ctx, diagnostics, machineAccountsArray) } // go over any machines that are in remote but were not in plan @@ -475,37 +486,38 @@ func (r MachineCatalogResourceModel) updateCatalogWithMachines(ctx context.Conte newMachines[hypId] = append(newMachines[hypId], machineModel) } - if len(newMachines) > 0 && r.MachineAccounts == nil { - r.MachineAccounts = []MachineAccountsModel{} + if len(newMachines) > 0 && r.MachineAccounts.IsNull() { + r.MachineAccounts = util.TypedArrayToObjectList[MachineAccountsModel](ctx, diagnostics, []MachineAccountsModel{}) } machineAccountMap := map[string]int{} - for index, machineAccount := range r.MachineAccounts { + machineAccounts := util.ObjectListToTypedArray[MachineAccountsModel](ctx, diagnostics, r.MachineAccounts) + for index, machineAccount := range machineAccounts { machineAccountMap[machineAccount.Hypervisor.ValueString()] = index } for hypId, machines := range newMachines { machineAccIndex, exists := machineAccountMap[hypId] if exists { - machAccounts := r.MachineAccounts + machAccounts := util.ObjectListToTypedArray[MachineAccountsModel](ctx, diagnostics, r.MachineAccounts) machineAccount := machAccounts[machineAccIndex] - if machineAccount.Machines == nil { - machineAccount.Machines = []MachineCatalogMachineModel{} + if machineAccount.Machines.IsNull() { + machineAccount.Machines = util.TypedArrayToObjectList[MachineCatalogMachineModel](ctx, diagnostics, []MachineCatalogMachineModel{}) } - machineAccountMachines := machineAccount.Machines + machineAccountMachines := util.ObjectListToTypedArray[MachineCatalogMachineModel](ctx, diagnostics, machineAccount.Machines) machineAccountMachines = append(machineAccountMachines, machines...) - machineAccount.Machines = machineAccountMachines + machineAccount.Machines = util.TypedArrayToObjectList[MachineCatalogMachineModel](ctx, diagnostics, machineAccountMachines) machAccounts[machineAccIndex] = machineAccount - r.MachineAccounts = machAccounts + r.MachineAccounts = util.TypedArrayToObjectList[MachineAccountsModel](ctx, diagnostics, machAccounts) continue } var machineAccount MachineAccountsModel machineAccount.Hypervisor = types.StringValue(hypId) - machineAccount.Machines = machines - machAccounts := r.MachineAccounts + machineAccount.Machines = util.TypedArrayToObjectList[MachineCatalogMachineModel](ctx, diagnostics, machines) + machAccounts := util.ObjectListToTypedArray[MachineAccountsModel](ctx, diagnostics, r.MachineAccounts) machAccounts = append(machAccounts, machineAccount) machineAccountMap[hypId] = len(machAccounts) - 1 - r.MachineAccounts = machAccounts + r.MachineAccounts = util.TypedArrayToObjectList[MachineAccountsModel](ctx, diagnostics, machAccounts) } return r diff --git a/internal/daas/machine_catalog/machine_catalog_mcs_utils.go b/internal/daas/machine_catalog/machine_catalog_mcs_utils.go index 4235c06..289c330 100644 --- a/internal/daas/machine_catalog/machine_catalog_mcs_utils.go +++ b/internal/daas/machine_catalog/machine_catalog_mcs_utils.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package machine_catalog @@ -7,7 +7,6 @@ import ( "encoding/base64" "fmt" "net/http" - "reflect" "strconv" "strings" @@ -39,21 +38,22 @@ var MappedCustomProperties = map[string]string{ } func getProvSchemeForMcsCatalog(plan MachineCatalogResourceModel, ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics, isOnPremises bool) (*citrixorchestration.CreateMachineCatalogProvisioningSchemeRequestModel, error) { - if !checkIfProvSchemeIsCloudOnly(plan, isOnPremises, diagnostics) { - return nil, fmt.Errorf("identity type %s is not supported in OnPremises environment. ", plan.ProvisioningScheme.IdentityType.ValueString()) + provSchemeModel := util.ObjectValueToTypedObject[ProvisioningSchemeModel](ctx, diagnostics, plan.ProvisioningScheme) + if !checkIfProvSchemeIsCloudOnly(ctx, diagnostics, provSchemeModel, isOnPremises) { + return nil, fmt.Errorf("identity type %s is not supported in OnPremises environment. ", provSchemeModel.IdentityType.ValueString()) } - hypervisor, err := util.GetHypervisor(ctx, client, diagnostics, plan.ProvisioningScheme.Hypervisor.ValueString()) + hypervisor, err := util.GetHypervisor(ctx, client, diagnostics, provSchemeModel.Hypervisor.ValueString()) if err != nil { return nil, err } - hypervisorResourcePool, err := util.GetHypervisorResourcePool(ctx, client, diagnostics, plan.ProvisioningScheme.Hypervisor.ValueString(), plan.ProvisioningScheme.HypervisorResourcePool.ValueString()) + hypervisorResourcePool, err := util.GetHypervisorResourcePool(ctx, client, diagnostics, provSchemeModel.Hypervisor.ValueString(), provSchemeModel.HypervisorResourcePool.ValueString()) if err != nil { return nil, err } - provisioningScheme, err := buildProvSchemeForMcsCatalog(ctx, client, diagnostics, plan, hypervisor, hypervisorResourcePool) + provisioningScheme, err := buildProvSchemeForMcsCatalog(ctx, client, diagnostics, util.ObjectValueToTypedObject[ProvisioningSchemeModel](ctx, diagnostics, plan.ProvisioningScheme), hypervisor, hypervisorResourcePool) if err != nil { return nil, err } @@ -61,11 +61,11 @@ func getProvSchemeForMcsCatalog(plan MachineCatalogResourceModel, ctx context.Co return provisioningScheme, nil } -func buildProvSchemeForMcsCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diag *diag.Diagnostics, plan MachineCatalogResourceModel, hypervisor *citrixorchestration.HypervisorDetailResponseModel, hypervisorResourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) (*citrixorchestration.CreateMachineCatalogProvisioningSchemeRequestModel, error) { - +func buildProvSchemeForMcsCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diag *diag.Diagnostics, provisioningSchemePlan ProvisioningSchemeModel, hypervisor *citrixorchestration.HypervisorDetailResponseModel, hypervisorResourcePool *citrixorchestration.HypervisorResourcePoolDetailResponseModel) (*citrixorchestration.CreateMachineCatalogProvisioningSchemeRequestModel, error) { var machineAccountCreationRules citrixorchestration.MachineAccountCreationRulesRequestModel - machineAccountCreationRules.SetNamingScheme(plan.ProvisioningScheme.MachineAccountCreationRules.NamingScheme.ValueString()) - namingScheme, err := citrixorchestration.NewNamingSchemeTypeFromValue(plan.ProvisioningScheme.MachineAccountCreationRules.NamingSchemeType.ValueString()) + machineAccountCreationRulesModel := util.ObjectValueToTypedObject[MachineAccountCreationRulesModel](ctx, diag, provisioningSchemePlan.MachineAccountCreationRules) + machineAccountCreationRules.SetNamingScheme(machineAccountCreationRulesModel.NamingScheme.ValueString()) + namingScheme, err := citrixorchestration.NewNamingSchemeTypeFromValue(machineAccountCreationRulesModel.NamingSchemeType.ValueString()) if err != nil { diag.AddError( "Error creating Machine Catalog", @@ -75,31 +75,34 @@ func buildProvSchemeForMcsCatalog(ctx context.Context, client *citrixdaasclient. } machineAccountCreationRules.SetNamingSchemeType(*namingScheme) - if plan.ProvisioningScheme.MachineDomainIdentity != nil { - machineAccountCreationRules.SetDomain(plan.ProvisioningScheme.MachineDomainIdentity.Domain.ValueString()) - machineAccountCreationRules.SetOU(plan.ProvisioningScheme.MachineDomainIdentity.Ou.ValueString()) + if !provisioningSchemePlan.MachineDomainIdentity.IsNull() { + machineDomainIdentityModel := util.ObjectValueToTypedObject[MachineDomainIdentityModel](ctx, diag, provisioningSchemePlan.MachineDomainIdentity) + machineAccountCreationRules.SetDomain(machineDomainIdentityModel.Domain.ValueString()) + machineAccountCreationRules.SetOU(machineDomainIdentityModel.Ou.ValueString()) } + azureMachineConfigModel := util.ObjectValueToTypedObject[AzureMachineConfigModel](ctx, diag, provisioningSchemePlan.AzureMachineConfig) + var provisioningScheme citrixorchestration.CreateMachineCatalogProvisioningSchemeRequestModel - provisioningScheme.SetNumTotalMachines(int32(plan.ProvisioningScheme.NumTotalMachines.ValueInt64())) - identityType := citrixorchestration.IdentityType(plan.ProvisioningScheme.IdentityType.ValueString()) + provisioningScheme.SetNumTotalMachines(int32(provisioningSchemePlan.NumTotalMachines.ValueInt64())) + identityType := citrixorchestration.IdentityType(provisioningSchemePlan.IdentityType.ValueString()) provisioningScheme.SetIdentityType(identityType) provisioningScheme.SetWorkGroupMachines(identityType == citrixorchestration.IDENTITYTYPE_AZURE_AD || identityType == citrixorchestration.IDENTITYTYPE_WORKGROUP) // AzureAD and Workgroup identity types are non-domain joined - if identityType == citrixorchestration.IDENTITYTYPE_AZURE_AD && plan.ProvisioningScheme.AzureMachineConfig.EnrollInIntune.ValueBool() { + if identityType == citrixorchestration.IDENTITYTYPE_AZURE_AD && azureMachineConfigModel.EnrollInIntune.ValueBool() { provisioningScheme.SetDeviceManagementType(citrixorchestration.DEVICEMANAGEMENTTYPE_INTUNE) } provisioningScheme.SetMachineAccountCreationRules(machineAccountCreationRules) - provisioningScheme.SetResourcePool(plan.ProvisioningScheme.HypervisorResourcePool.ValueString()) + provisioningScheme.SetResourcePool(provisioningSchemePlan.HypervisorResourcePool.ValueString()) if hypervisor.GetConnectionType() != citrixorchestration.HYPERVISORCONNECTIONTYPE_CUSTOM || hypervisor.GetPluginId() != util.NUTANIX_PLUGIN_ID { - customProperties := parseCustomPropertiesToClientModel(*plan.ProvisioningScheme, hypervisor.ConnectionType) + customProperties := parseCustomPropertiesToClientModel(ctx, diag, provisioningSchemePlan, hypervisor.ConnectionType) provisioningScheme.SetCustomProperties(customProperties) } switch hypervisor.GetConnectionType() { case citrixorchestration.HYPERVISORCONNECTIONTYPE_AZURE_RM: - serviceOffering := plan.ProvisioningScheme.AzureMachineConfig.ServiceOffering.ValueString() + serviceOffering := azureMachineConfigModel.ServiceOffering.ValueString() queryPath := "serviceoffering.folder" serviceOfferingPath, err := util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), queryPath, serviceOffering, util.ServiceOfferingResourceType, "") if err != nil { @@ -111,17 +114,18 @@ func buildProvSchemeForMcsCatalog(ctx context.Context, client *citrixdaasclient. } provisioningScheme.SetServiceOfferingPath(serviceOfferingPath) - sharedSubscription := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.SharedSubscription.ValueString() - resourceGroup := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.ResourceGroup.ValueString() - masterImage := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.MasterImage.ValueString() + azureMasterImageModel := util.ObjectValueToTypedObject[AzureMasterImageModel](ctx, diag, azureMachineConfigModel.AzureMasterImage) + sharedSubscription := azureMasterImageModel.SharedSubscription.ValueString() + resourceGroup := azureMasterImageModel.ResourceGroup.ValueString() + masterImage := azureMasterImageModel.MasterImage.ValueString() imagePath := "" imageBasePath := "image.folder" if sharedSubscription != "" { imageBasePath = fmt.Sprintf("image.folder\\%s.sharedsubscription", sharedSubscription) } if masterImage != "" { - storageAccount := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.StorageAccount.ValueString() - container := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.Container.ValueString() + storageAccount := azureMasterImageModel.StorageAccount.ValueString() + container := azureMasterImageModel.Container.ValueString() if storageAccount != "" && container != "" { queryPath = fmt.Sprintf( "%s\\%s.resourcegroup\\%s.storageaccount\\%s.container", @@ -151,10 +155,11 @@ func buildProvSchemeForMcsCatalog(ctx context.Context, client *citrixdaasclient. return nil, err } } - } else if plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.GalleryImage != nil { - gallery := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.GalleryImage.Gallery.ValueString() - definition := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.GalleryImage.Definition.ValueString() - version := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.GalleryImage.Version.ValueString() + } else if !azureMasterImageModel.GalleryImage.IsNull() { + azureGalleryImage := util.ObjectValueToTypedObject[GalleryImageModel](ctx, diag, azureMasterImageModel.GalleryImage) + gallery := azureGalleryImage.Gallery.ValueString() + definition := azureGalleryImage.Definition.ValueString() + version := azureGalleryImage.Version.ValueString() if gallery != "" && definition != "" { queryPath = fmt.Sprintf( "%s\\%s.resourcegroup\\%s.gallery\\%s.imagedefinition", @@ -175,26 +180,31 @@ func buildProvSchemeForMcsCatalog(ctx context.Context, client *citrixdaasclient. provisioningScheme.SetMasterImagePath(imagePath) - machineProfile := plan.ProvisioningScheme.AzureMachineConfig.MachineProfile - if machineProfile != nil { - machineProfilePath, err := handleMachineProfileForAzureMcsCatalog(ctx, client, diag, hypervisor.GetName(), hypervisorResourcePool.GetName(), *machineProfile, "creating") + masterImageNote := azureMachineConfigModel.MasterImageNote.ValueString() + provisioningScheme.SetMasterImageNote(masterImageNote) + + machineProfile := azureMachineConfigModel.MachineProfile + if !machineProfile.IsNull() { + machineProfilePath, err := handleMachineProfileForAzureMcsCatalog(ctx, client, diag, hypervisor.GetName(), hypervisorResourcePool.GetName(), util.ObjectValueToTypedObject[AzureMachineProfileModel](ctx, diag, machineProfile), "creating") if err != nil { return nil, err } provisioningScheme.SetMachineProfilePath(machineProfilePath) } - if plan.ProvisioningScheme.AzureMachineConfig.WritebackCache != nil { + if !azureMachineConfigModel.WritebackCache.IsNull() { + azureWbcModel := util.ObjectValueToTypedObject[AzureWritebackCacheModel](ctx, diag, azureMachineConfigModel.WritebackCache) provisioningScheme.SetUseWriteBackCache(true) - provisioningScheme.SetWriteBackCacheDiskSizeGB(int32(plan.ProvisioningScheme.AzureMachineConfig.WritebackCache.WriteBackCacheDiskSizeGB.ValueInt64())) - if !plan.ProvisioningScheme.AzureMachineConfig.WritebackCache.WriteBackCacheMemorySizeMB.IsNull() { - provisioningScheme.SetWriteBackCacheMemorySizeMB(int32(plan.ProvisioningScheme.AzureMachineConfig.WritebackCache.WriteBackCacheMemorySizeMB.ValueInt64())) + provisioningScheme.SetWriteBackCacheDiskSizeGB(int32(azureWbcModel.WriteBackCacheDiskSizeGB.ValueInt64())) + if !azureWbcModel.WriteBackCacheMemorySizeMB.IsNull() { + provisioningScheme.SetWriteBackCacheMemorySizeMB(int32(azureWbcModel.WriteBackCacheMemorySizeMB.ValueInt64())) } } - if plan.ProvisioningScheme.AzureMachineConfig.DiskEncryptionSet != nil { - diskEncryptionSet := plan.ProvisioningScheme.AzureMachineConfig.DiskEncryptionSet.DiskEncryptionSetName.ValueString() - diskEncryptionSetRg := plan.ProvisioningScheme.AzureMachineConfig.DiskEncryptionSet.DiskEncryptionSetResourceGroup.ValueString() + if !azureMachineConfigModel.DiskEncryptionSet.IsNull() { + diskEncryptionSetModel := util.ObjectValueToTypedObject[AzureDiskEncryptionSetModel](ctx, diag, azureMachineConfigModel.DiskEncryptionSet) + diskEncryptionSet := diskEncryptionSetModel.DiskEncryptionSetName.ValueString() + diskEncryptionSetRg := diskEncryptionSetModel.DiskEncryptionSetResourceGroup.ValueString() des, err := util.GetSingleResourceFromHypervisor(ctx, client, hypervisor.GetId(), hypervisorResourcePool.GetId(), fmt.Sprintf("%s\\diskencryptionset.folder", hypervisorResourcePool.GetXDPath()), diskEncryptionSet, "", diskEncryptionSetRg) if err != nil { diag.AddError( @@ -208,7 +218,8 @@ func buildProvSchemeForMcsCatalog(ctx context.Context, client *citrixdaasclient. provisioningScheme.SetCustomProperties(customProp) } case citrixorchestration.HYPERVISORCONNECTIONTYPE_AWS: - inputServiceOffering := plan.ProvisioningScheme.AwsMachineConfig.ServiceOffering.ValueString() + awsMachineConfig := util.ObjectValueToTypedObject[AwsMachineConfigModel](ctx, diag, provisioningSchemePlan.AwsMachineConfig) + inputServiceOffering := awsMachineConfig.ServiceOffering.ValueString() serviceOffering, err := util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetId(), hypervisorResourcePool.GetId(), "", inputServiceOffering, util.ServiceOfferingResourceType, "") if err != nil { @@ -220,21 +231,23 @@ func buildProvSchemeForMcsCatalog(ctx context.Context, client *citrixdaasclient. } provisioningScheme.SetServiceOfferingPath(serviceOffering) - masterImage := plan.ProvisioningScheme.AwsMachineConfig.MasterImage.ValueString() - imageId := fmt.Sprintf("%s (%s)", masterImage, plan.ProvisioningScheme.AwsMachineConfig.ImageAmi.ValueString()) + masterImage := awsMachineConfig.MasterImage.ValueString() + imageId := fmt.Sprintf("%s (%s)", masterImage, awsMachineConfig.ImageAmi.ValueString()) imagePath, err := util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", imageId, util.TemplateResourceType, "") if err != nil { diag.AddError( "Error creating Machine Catalog", - fmt.Sprintf("Failed to locate AWS image %s with AMI %s, error: %s", masterImage, plan.ProvisioningScheme.AwsMachineConfig.ImageAmi.ValueString(), err.Error()), + fmt.Sprintf("Failed to locate AWS image %s with AMI %s, error: %s", masterImage, awsMachineConfig.ImageAmi.ValueString(), err.Error()), ) return nil, err } provisioningScheme.SetMasterImagePath(imagePath) + masterImageNote := awsMachineConfig.MasterImageNote.ValueString() + provisioningScheme.SetMasterImageNote(masterImageNote) + securityGroupPaths := []string{} - for _, sg := range plan.ProvisioningScheme.AwsMachineConfig.SecurityGroups { - securityGroup := sg.ValueString() + for _, securityGroup := range util.StringListToStringArray(ctx, diag, awsMachineConfig.SecurityGroups) { securityGroupPath, err := util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", securityGroup, util.SecurityGroupResourceType, "") if err != nil { diag.AddError( @@ -248,7 +261,7 @@ func buildProvSchemeForMcsCatalog(ctx context.Context, client *citrixdaasclient. } provisioningScheme.SetSecurityGroups(securityGroupPaths) - tenancyType, err := citrixorchestration.NewTenancyTypeFromValue(plan.ProvisioningScheme.AwsMachineConfig.TenancyType.ValueString()) + tenancyType, err := citrixorchestration.NewTenancyTypeFromValue(awsMachineConfig.TenancyType.ValueString()) if err != nil { diag.AddError( "Error creating Machine Catalog", @@ -260,9 +273,10 @@ func buildProvSchemeForMcsCatalog(ctx context.Context, client *citrixdaasclient. provisioningScheme.SetTenancyType(*tenancyType) case citrixorchestration.HYPERVISORCONNECTIONTYPE_GOOGLE_CLOUD_PLATFORM: + gcpMachineConfig := util.ObjectValueToTypedObject[GcpMachineConfigModel](ctx, diag, provisioningSchemePlan.GcpMachineConfig) imagePath := "" - snapshot := plan.ProvisioningScheme.GcpMachineConfig.MachineSnapshot.ValueString() - imageVm := plan.ProvisioningScheme.GcpMachineConfig.MasterImage.ValueString() + snapshot := gcpMachineConfig.MachineSnapshot.ValueString() + imageVm := gcpMachineConfig.MasterImage.ValueString() if snapshot != "" { queryPath := fmt.Sprintf("%s.vm", imageVm) imagePath, err = util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), queryPath, snapshot, util.SnapshotResourceType, "") @@ -286,104 +300,124 @@ func buildProvSchemeForMcsCatalog(ctx context.Context, client *citrixdaasclient. provisioningScheme.SetMasterImagePath(imagePath) - machineProfile := plan.ProvisioningScheme.GcpMachineConfig.MachineProfile.ValueString() + masterImageNote := gcpMachineConfig.MasterImageNote.ValueString() + provisioningScheme.SetMasterImageNote(masterImageNote) + + machineProfile := gcpMachineConfig.MachineProfile.ValueString() if machineProfile != "" { machineProfilePath, err := util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", machineProfile, util.VirtualMachineResourceType, "") if err != nil { diag.AddError( "Error creating Machine Catalog", - fmt.Sprintf("Failed to locate machine profile %s on GCP, error: %s", plan.ProvisioningScheme.GcpMachineConfig.MachineProfile.ValueString(), err.Error()), + fmt.Sprintf("Failed to locate machine profile %s on GCP, error: %s", gcpMachineConfig.MachineProfile.ValueString(), err.Error()), ) return nil, err } provisioningScheme.SetMachineProfilePath(machineProfilePath) } - if plan.ProvisioningScheme.GcpMachineConfig.WritebackCache != nil { + if !gcpMachineConfig.WritebackCache.IsNull() { + writeBackCacheModel := util.ObjectValueToTypedObject[GcpWritebackCacheModel](ctx, diag, gcpMachineConfig.WritebackCache) provisioningScheme.SetUseWriteBackCache(true) - provisioningScheme.SetWriteBackCacheDiskSizeGB(int32(plan.ProvisioningScheme.GcpMachineConfig.WritebackCache.WriteBackCacheDiskSizeGB.ValueInt64())) - if !plan.ProvisioningScheme.GcpMachineConfig.WritebackCache.WriteBackCacheMemorySizeMB.IsNull() { - provisioningScheme.SetWriteBackCacheMemorySizeMB(int32(plan.ProvisioningScheme.GcpMachineConfig.WritebackCache.WriteBackCacheMemorySizeMB.ValueInt64())) + provisioningScheme.SetWriteBackCacheDiskSizeGB(int32(writeBackCacheModel.WriteBackCacheDiskSizeGB.ValueInt64())) + if !writeBackCacheModel.WriteBackCacheMemorySizeMB.IsNull() { + provisioningScheme.SetWriteBackCacheMemorySizeMB(int32(writeBackCacheModel.WriteBackCacheMemorySizeMB.ValueInt64())) } } case citrixorchestration.HYPERVISORCONNECTIONTYPE_V_CENTER: - provisioningScheme.SetMemoryMB(int32(plan.ProvisioningScheme.VsphereMachineConfig.MemoryMB.ValueInt64())) - provisioningScheme.SetCpuCount(int32(plan.ProvisioningScheme.VsphereMachineConfig.CpuCount.ValueInt64())) + vSphereMachineConfig := util.ObjectValueToTypedObject[VsphereMachineConfigModel](ctx, diag, provisioningSchemePlan.VsphereMachineConfig) + provisioningScheme.SetMemoryMB(int32(vSphereMachineConfig.MemoryMB.ValueInt64())) + provisioningScheme.SetCpuCount(int32(vSphereMachineConfig.CpuCount.ValueInt64())) - image := plan.ProvisioningScheme.VsphereMachineConfig.MasterImageVm.ValueString() - snapshot := plan.ProvisioningScheme.VsphereMachineConfig.ImageSnapshot.ValueString() + image := vSphereMachineConfig.MasterImageVm.ValueString() + snapshot := vSphereMachineConfig.ImageSnapshot.ValueString() imagePath, err := getOnPremImagePath(ctx, client, diag, hypervisor.GetName(), hypervisorResourcePool.GetName(), image, snapshot, "creating") if err != nil { return nil, err } provisioningScheme.SetMasterImagePath(imagePath) - if plan.ProvisioningScheme.VsphereMachineConfig.WritebackCache != nil { + masterImageNote := vSphereMachineConfig.MasterImageNote.ValueString() + provisioningScheme.SetMasterImageNote(masterImageNote) + + if !vSphereMachineConfig.WritebackCache.IsNull() { provisioningScheme.SetUseWriteBackCache(true) - provisioningScheme.SetWriteBackCacheDiskSizeGB(int32(plan.ProvisioningScheme.VsphereMachineConfig.WritebackCache.WriteBackCacheDiskSizeGB.ValueInt64())) - if !plan.ProvisioningScheme.VsphereMachineConfig.WritebackCache.WriteBackCacheMemorySizeMB.IsNull() { - provisioningScheme.SetWriteBackCacheMemorySizeMB(int32(plan.ProvisioningScheme.VsphereMachineConfig.WritebackCache.WriteBackCacheMemorySizeMB.ValueInt64())) + writeBackCacheModel := util.ObjectValueToTypedObject[VsphereWritebackCacheModel](ctx, diag, vSphereMachineConfig.WritebackCache) + provisioningScheme.SetWriteBackCacheDiskSizeGB(int32(writeBackCacheModel.WriteBackCacheDiskSizeGB.ValueInt64())) + if !writeBackCacheModel.WriteBackCacheMemorySizeMB.IsNull() { + provisioningScheme.SetWriteBackCacheMemorySizeMB(int32(writeBackCacheModel.WriteBackCacheMemorySizeMB.ValueInt64())) } - if !plan.ProvisioningScheme.VsphereMachineConfig.WritebackCache.WriteBackCacheDriveLetter.IsNull() { - provisioningScheme.SetWriteBackCacheDriveLetter(plan.ProvisioningScheme.VsphereMachineConfig.WritebackCache.WriteBackCacheDriveLetter.ValueString()) + if !writeBackCacheModel.WriteBackCacheDriveLetter.IsNull() { + provisioningScheme.SetWriteBackCacheDriveLetter(writeBackCacheModel.WriteBackCacheDriveLetter.ValueString()) } } case citrixorchestration.HYPERVISORCONNECTIONTYPE_XEN_SERVER: - provisioningScheme.SetCpuCount(int32(plan.ProvisioningScheme.XenserverMachineConfig.CpuCount.ValueInt64())) - provisioningScheme.SetMemoryMB(int32(plan.ProvisioningScheme.XenserverMachineConfig.MemoryMB.ValueInt64())) + xenserverMachineConfig := util.ObjectValueToTypedObject[XenserverMachineConfigModel](ctx, diag, provisioningSchemePlan.XenserverMachineConfig) + provisioningScheme.SetCpuCount(int32(xenserverMachineConfig.CpuCount.ValueInt64())) + provisioningScheme.SetMemoryMB(int32(xenserverMachineConfig.MemoryMB.ValueInt64())) - image := plan.ProvisioningScheme.XenserverMachineConfig.MasterImageVm.ValueString() - snapshot := plan.ProvisioningScheme.XenserverMachineConfig.ImageSnapshot.ValueString() + image := xenserverMachineConfig.MasterImageVm.ValueString() + snapshot := xenserverMachineConfig.ImageSnapshot.ValueString() imagePath, err := getOnPremImagePath(ctx, client, diag, hypervisor.GetName(), hypervisorResourcePool.GetName(), image, snapshot, "creating") if err != nil { return nil, err } provisioningScheme.SetMasterImagePath(imagePath) - if plan.ProvisioningScheme.XenserverMachineConfig.WritebackCache != nil { + masterImageNote := xenserverMachineConfig.MasterImageNote.ValueString() + provisioningScheme.SetMasterImageNote(masterImageNote) + + if xenserverMachineConfig.WritebackCache.IsNull() { provisioningScheme.SetUseWriteBackCache(true) - provisioningScheme.SetWriteBackCacheDiskSizeGB(int32(plan.ProvisioningScheme.XenserverMachineConfig.WritebackCache.WriteBackCacheDiskSizeGB.ValueInt64())) - if !plan.ProvisioningScheme.XenserverMachineConfig.WritebackCache.WriteBackCacheMemorySizeMB.IsNull() { - provisioningScheme.SetWriteBackCacheMemorySizeMB(int32(plan.ProvisioningScheme.XenserverMachineConfig.WritebackCache.WriteBackCacheMemorySizeMB.ValueInt64())) + writeBackCacheModel := util.ObjectValueToTypedObject[XenserverWritebackCacheModel](ctx, diag, xenserverMachineConfig.WritebackCache) + provisioningScheme.SetWriteBackCacheDiskSizeGB(int32(writeBackCacheModel.WriteBackCacheDiskSizeGB.ValueInt64())) + if !writeBackCacheModel.WriteBackCacheMemorySizeMB.IsNull() { + provisioningScheme.SetWriteBackCacheMemorySizeMB(int32(writeBackCacheModel.WriteBackCacheMemorySizeMB.ValueInt64())) } } case citrixorchestration.HYPERVISORCONNECTIONTYPE_CUSTOM: + nutanixMachineConfig := util.ObjectValueToTypedObject[NutanixMachineConfigModel](ctx, diag, provisioningSchemePlan.NutanixMachineConfig) if hypervisor.GetPluginId() != util.NUTANIX_PLUGIN_ID { return nil, fmt.Errorf("unsupported hypervisor plugin %s", hypervisor.GetPluginId()) } - provisioningScheme.SetMemoryMB(int32(plan.ProvisioningScheme.NutanixMachineConfigModel.MemoryMB.ValueInt64())) - provisioningScheme.SetCpuCount(int32(plan.ProvisioningScheme.NutanixMachineConfigModel.CpuCount.ValueInt64())) - provisioningScheme.SetCoresPerCpuCount(int32(plan.ProvisioningScheme.NutanixMachineConfigModel.CoresPerCpuCount.ValueInt64())) + provisioningScheme.SetMemoryMB(int32(nutanixMachineConfig.MemoryMB.ValueInt64())) + provisioningScheme.SetCpuCount(int32(nutanixMachineConfig.CpuCount.ValueInt64())) + provisioningScheme.SetCoresPerCpuCount(int32(nutanixMachineConfig.CoresPerCpuCount.ValueInt64())) - imagePath, err := util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", plan.ProvisioningScheme.NutanixMachineConfigModel.MasterImage.ValueString(), util.TemplateResourceType, "") + imagePath, err := util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", nutanixMachineConfig.MasterImage.ValueString(), util.TemplateResourceType, "") if err != nil { diag.AddError( "Error creating Machine Catalog", - fmt.Sprintf("Failed to locate master image %s on NUTANIX, error: %s", plan.ProvisioningScheme.NutanixMachineConfigModel.MasterImage.ValueString(), err.Error()), + fmt.Sprintf("Failed to locate master image %s on NUTANIX, error: %s", nutanixMachineConfig.MasterImage.ValueString(), err.Error()), ) return nil, err } - containerId, err := util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", plan.ProvisioningScheme.NutanixMachineConfigModel.Container.ValueString(), util.StorageResourceType, "") + containerId, err := util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", nutanixMachineConfig.Container.ValueString(), util.StorageResourceType, "") if err != nil { diag.AddError( "Error creating Machine Catalog", - fmt.Sprintf("Failed to locate container %s on NUTANIX, error: %s", plan.ProvisioningScheme.NutanixMachineConfigModel.Container.ValueString(), err.Error()), + fmt.Sprintf("Failed to locate container %s on NUTANIX, error: %s", nutanixMachineConfig.Container.ValueString(), err.Error()), ) return nil, err } provisioningScheme.SetMasterImagePath(imagePath) + + masterImageNote := nutanixMachineConfig.MasterImageNote.ValueString() + provisioningScheme.SetMasterImageNote(masterImageNote) + customProperties := provisioningScheme.GetCustomProperties() util.AppendNameValueStringPair(&customProperties, "NutanixContainerId", containerId) provisioningScheme.SetCustomProperties(customProperties) } - if plan.ProvisioningScheme.NetworkMapping != nil { - networkMapping, err := parseNetworkMappingToClientModel(plan.ProvisioningScheme.NetworkMapping, hypervisorResourcePool, hypervisor.GetPluginId()) + if !provisioningSchemePlan.NetworkMapping.IsNull() { + networkMappingModel := util.ObjectListToTypedArray[NetworkMappingModel](ctx, diag, provisioningSchemePlan.NetworkMapping) + networkMapping, err := parseNetworkMappingToClientModel(networkMappingModel, hypervisorResourcePool, hypervisor.GetPluginId()) if err != nil { diag.AddError( "Error creating Machine Catalog", @@ -397,13 +431,13 @@ func buildProvSchemeForMcsCatalog(ctx context.Context, client *citrixdaasclient. return &provisioningScheme, nil } -func setProvSchemePropertiesForUpdateCatalog(plan MachineCatalogResourceModel, body citrixorchestration.UpdateMachineCatalogRequestModel, ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics) (citrixorchestration.UpdateMachineCatalogRequestModel, error) { - hypervisor, err := util.GetHypervisor(ctx, client, diagnostics, plan.ProvisioningScheme.Hypervisor.ValueString()) +func setProvSchemePropertiesForUpdateCatalog(provisioningSchemePlan ProvisioningSchemeModel, body citrixorchestration.UpdateMachineCatalogRequestModel, ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics *diag.Diagnostics) (citrixorchestration.UpdateMachineCatalogRequestModel, error) { + hypervisor, err := util.GetHypervisor(ctx, client, diagnostics, provisioningSchemePlan.Hypervisor.ValueString()) if err != nil { return body, err } - hypervisorResourcePool, err := util.GetHypervisorResourcePool(ctx, client, diagnostics, plan.ProvisioningScheme.Hypervisor.ValueString(), plan.ProvisioningScheme.HypervisorResourcePool.ValueString()) + hypervisorResourcePool, err := util.GetHypervisorResourcePool(ctx, client, diagnostics, provisioningSchemePlan.Hypervisor.ValueString(), provisioningSchemePlan.HypervisorResourcePool.ValueString()) if err != nil { return body, err } @@ -411,7 +445,8 @@ func setProvSchemePropertiesForUpdateCatalog(plan MachineCatalogResourceModel, b // Resolve resource path for service offering and master image switch hypervisor.GetConnectionType() { case citrixorchestration.HYPERVISORCONNECTIONTYPE_AZURE_RM: - serviceOffering := plan.ProvisioningScheme.AzureMachineConfig.ServiceOffering.ValueString() + azureMachineConfigModel := util.ObjectValueToTypedObject[AzureMachineConfigModel](ctx, diagnostics, provisioningSchemePlan.AzureMachineConfig) + serviceOffering := azureMachineConfigModel.ServiceOffering.ValueString() queryPath := "serviceoffering.folder" serviceOfferingPath, err := util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), queryPath, serviceOffering, util.ServiceOfferingResourceType, "") if err != nil { @@ -423,7 +458,8 @@ func setProvSchemePropertiesForUpdateCatalog(plan MachineCatalogResourceModel, b } body.SetServiceOfferingPath(serviceOfferingPath) case citrixorchestration.HYPERVISORCONNECTIONTYPE_AWS: - inputServiceOffering := plan.ProvisioningScheme.AwsMachineConfig.ServiceOffering.ValueString() + awsMachineConfig := util.ObjectValueToTypedObject[AwsMachineConfigModel](ctx, nil, provisioningSchemePlan.AwsMachineConfig) + inputServiceOffering := awsMachineConfig.ServiceOffering.ValueString() serviceOffering, err := util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetId(), hypervisorResourcePool.GetId(), "", inputServiceOffering, util.ServiceOfferingResourceType, "") if err != nil { @@ -436,8 +472,7 @@ func setProvSchemePropertiesForUpdateCatalog(plan MachineCatalogResourceModel, b body.SetServiceOfferingPath(serviceOffering) securityGroupPaths := []string{} - for _, sg := range plan.ProvisioningScheme.AwsMachineConfig.SecurityGroups { - securityGroup := sg.ValueString() + for _, securityGroup := range util.StringListToStringArray(ctx, diagnostics, awsMachineConfig.SecurityGroups) { securityGroupPath, err := util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", securityGroup, util.SecurityGroupResourceType, "") if err != nil { diagnostics.AddError( @@ -452,15 +487,18 @@ func setProvSchemePropertiesForUpdateCatalog(plan MachineCatalogResourceModel, b body.SetSecurityGroups(securityGroupPaths) case citrixorchestration.HYPERVISORCONNECTIONTYPE_GOOGLE_CLOUD_PLATFORM: case citrixorchestration.HYPERVISORCONNECTIONTYPE_XEN_SERVER: - body.SetCpuCount(int32(plan.ProvisioningScheme.XenserverMachineConfig.CpuCount.ValueInt64())) - body.SetMemoryMB(int32(plan.ProvisioningScheme.XenserverMachineConfig.MemoryMB.ValueInt64())) + xenserverMachineConfig := util.ObjectValueToTypedObject[XenserverMachineConfigModel](ctx, nil, provisioningSchemePlan.XenserverMachineConfig) + body.SetCpuCount(int32(xenserverMachineConfig.CpuCount.ValueInt64())) + body.SetMemoryMB(int32(xenserverMachineConfig.MemoryMB.ValueInt64())) case citrixorchestration.HYPERVISORCONNECTIONTYPE_V_CENTER: - body.SetCpuCount(int32(plan.ProvisioningScheme.VsphereMachineConfig.CpuCount.ValueInt64())) - body.SetMemoryMB(int32(plan.ProvisioningScheme.VsphereMachineConfig.MemoryMB.ValueInt64())) + vSphereMachineConfig := util.ObjectValueToTypedObject[VsphereMachineConfigModel](ctx, nil, provisioningSchemePlan.VsphereMachineConfig) + body.SetCpuCount(int32(vSphereMachineConfig.CpuCount.ValueInt64())) + body.SetMemoryMB(int32(vSphereMachineConfig.MemoryMB.ValueInt64())) } - if plan.ProvisioningScheme.NetworkMapping != nil { - networkMapping, err := parseNetworkMappingToClientModel(plan.ProvisioningScheme.NetworkMapping, hypervisorResourcePool, hypervisor.GetPluginId()) + if !provisioningSchemePlan.NetworkMapping.IsNull() { + networkMappingModel := util.ObjectListToTypedArray[NetworkMappingModel](ctx, diagnostics, provisioningSchemePlan.NetworkMapping) + networkMapping, err := parseNetworkMappingToClientModel(networkMappingModel, hypervisorResourcePool, hypervisor.GetPluginId()) if err != nil { diagnostics.AddError( "Error updating Machine Catalog", @@ -471,21 +509,21 @@ func setProvSchemePropertiesForUpdateCatalog(plan MachineCatalogResourceModel, b body.SetNetworkMapping(networkMapping) } - customProperties := parseCustomPropertiesToClientModel(*plan.ProvisioningScheme, hypervisor.ConnectionType) + customProperties := parseCustomPropertiesToClientModel(ctx, diagnostics, provisioningSchemePlan, hypervisor.ConnectionType) body.SetCustomProperties(customProperties) return body, nil } -func generateAdminCredentialHeader(plan MachineCatalogResourceModel) string { - credential := fmt.Sprintf("%s\\%s:%s", plan.ProvisioningScheme.MachineDomainIdentity.Domain.ValueString(), plan.ProvisioningScheme.MachineDomainIdentity.ServiceAccount.ValueString(), plan.ProvisioningScheme.MachineDomainIdentity.ServiceAccountPassword.ValueString()) +func generateAdminCredentialHeader(machineDomainIdentityModel MachineDomainIdentityModel) string { + credential := fmt.Sprintf("%s\\%s:%s", machineDomainIdentityModel.Domain.ValueString(), machineDomainIdentityModel.ServiceAccount.ValueString(), machineDomainIdentityModel.ServiceAccountPassword.ValueString()) encodedData := base64.StdEncoding.EncodeToString([]byte(credential)) header := fmt.Sprintf("Basic %s", encodedData) return header } -func deleteMachinesFromMcsCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, resp *resource.UpdateResponse, catalog *citrixorchestration.MachineCatalogDetailResponseModel, plan MachineCatalogResourceModel) error { +func deleteMachinesFromMcsCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, resp *resource.UpdateResponse, catalog *citrixorchestration.MachineCatalogDetailResponseModel, provisioningSchemePlan ProvisioningSchemeModel) error { catalogId := catalog.GetId() catalogName := catalog.GetName() @@ -502,7 +540,7 @@ func deleteMachinesFromMcsCatalog(ctx context.Context, client *citrixdaasclient. return err } - machineDeleteRequestCount := int(catalog.GetTotalCount()) - int(plan.ProvisioningScheme.NumTotalMachines.ValueInt64()) + machineDeleteRequestCount := int(catalog.GetTotalCount()) - int(provisioningSchemePlan.NumTotalMachines.ValueInt64()) machinesToDelete := []citrixorchestration.MachineResponseModel{} for _, machine := range getMachinesResponse.GetItems() { @@ -528,18 +566,19 @@ func deleteMachinesFromMcsCatalog(ctx context.Context, client *citrixdaasclient. return err } - return deleteMachinesFromCatalog(ctx, client, resp, plan, machinesToDelete, catalogName, true) + return deleteMachinesFromCatalog(ctx, client, resp, provisioningSchemePlan, machinesToDelete, catalogName, true) } -func addMachinesToMcsCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, resp *resource.UpdateResponse, catalog *citrixorchestration.MachineCatalogDetailResponseModel, plan MachineCatalogResourceModel) error { +func addMachinesToMcsCatalog(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, resp *resource.UpdateResponse, catalog *citrixorchestration.MachineCatalogDetailResponseModel, provisioningSchemePlan ProvisioningSchemeModel) error { catalogId := catalog.GetId() catalogName := catalog.GetName() - addMachinesCount := int32(plan.ProvisioningScheme.NumTotalMachines.ValueInt64()) - catalog.GetTotalCount() + addMachinesCount := int32(provisioningSchemePlan.NumTotalMachines.ValueInt64()) - catalog.GetTotalCount() var updateMachineAccountCreationRule citrixorchestration.UpdateMachineAccountCreationRulesRequestModel - updateMachineAccountCreationRule.SetNamingScheme(plan.ProvisioningScheme.MachineAccountCreationRules.NamingScheme.ValueString()) - namingScheme, err := citrixorchestration.NewNamingSchemeTypeFromValue(plan.ProvisioningScheme.MachineAccountCreationRules.NamingSchemeType.ValueString()) + machineAccountCreationRulesModel := util.ObjectValueToTypedObject[MachineAccountCreationRulesModel](ctx, &resp.Diagnostics, provisioningSchemePlan.MachineAccountCreationRules) + updateMachineAccountCreationRule.SetNamingScheme(machineAccountCreationRulesModel.NamingScheme.ValueString()) + namingScheme, err := citrixorchestration.NewNamingSchemeTypeFromValue(machineAccountCreationRulesModel.NamingSchemeType.ValueString()) if err != nil { resp.Diagnostics.AddError( "Error adding Machine to Machine Catalog "+catalogName, @@ -548,9 +587,10 @@ func addMachinesToMcsCatalog(ctx context.Context, client *citrixdaasclient.Citri return err } updateMachineAccountCreationRule.SetNamingSchemeType(*namingScheme) - if plan.ProvisioningScheme.MachineDomainIdentity != nil { - updateMachineAccountCreationRule.SetDomain(plan.ProvisioningScheme.MachineDomainIdentity.Domain.ValueString()) - updateMachineAccountCreationRule.SetOU(plan.ProvisioningScheme.MachineDomainIdentity.Ou.ValueString()) + if !provisioningSchemePlan.MachineDomainIdentity.IsNull() { + machineDomainIdentityModel := util.ObjectValueToTypedObject[MachineDomainIdentityModel](ctx, &resp.Diagnostics, provisioningSchemePlan.MachineDomainIdentity) + updateMachineAccountCreationRule.SetDomain(machineDomainIdentityModel.Domain.ValueString()) + updateMachineAccountCreationRule.SetOU(machineDomainIdentityModel.Ou.ValueString()) } var addMachineRequestBody citrixorchestration.AddMachineToMachineCatalogDetailRequestModel @@ -565,7 +605,7 @@ func addMachinesToMcsCatalog(ctx context.Context, client *citrixdaasclient.Citri return err } - batchApiHeaders, httpResp, err := generateBatchApiHeaders(client, plan, true) + batchApiHeaders, httpResp, err := generateBatchApiHeaders(ctx, &resp.Diagnostics, client, provisioningSchemePlan, true) txId := citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp) if err != nil { resp.Diagnostics.AddError( @@ -646,32 +686,44 @@ func updateCatalogImageAndMachineProfile(ctx context.Context, client *citrixdaas provScheme := catalog.GetProvisioningScheme() masterImage := provScheme.GetMasterImage() + currentDiskImage := provScheme.GetCurrentDiskImage() machineProfile := provScheme.GetMachineProfile() - hypervisor, errResp := util.GetHypervisor(ctx, client, &resp.Diagnostics, plan.ProvisioningScheme.Hypervisor.ValueString()) + provisioningSchemePlan := util.ObjectValueToTypedObject[ProvisioningSchemeModel](ctx, &resp.Diagnostics, plan.ProvisioningScheme) + + hypervisor, errResp := util.GetHypervisor(ctx, client, &resp.Diagnostics, provisioningSchemePlan.Hypervisor.ValueString()) if errResp != nil { return errResp } - hypervisorResourcePool, errResp := util.GetHypervisorResourcePool(ctx, client, &resp.Diagnostics, plan.ProvisioningScheme.Hypervisor.ValueString(), plan.ProvisioningScheme.HypervisorResourcePool.ValueString()) + hypervisorResourcePool, errResp := util.GetHypervisorResourcePool(ctx, client, &resp.Diagnostics, provisioningSchemePlan.Hypervisor.ValueString(), provisioningSchemePlan.HypervisorResourcePool.ValueString()) if errResp != nil { return errResp } // Check if XDPath has changed for the image imagePath := "" + masterImageNote := "" machineProfilePath := "" var err error updateCustomProperties := []citrixorchestration.NameValueStringPairModel{} + + // Set default reboot options + var rebootOption citrixorchestration.RebootMachinesRequestModel + rebootOption.SetRebootDuration(-1) + rebootOption.SetWarningDuration(-1) + switch hypervisor.GetConnectionType() { case citrixorchestration.HYPERVISORCONNECTIONTYPE_AZURE_RM: - newImage := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.MasterImage.ValueString() - resourceGroup := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.ResourceGroup.ValueString() - azureMachineProfile := plan.ProvisioningScheme.AzureMachineConfig.MachineProfile + azureMachineConfigModel := util.ObjectValueToTypedObject[AzureMachineConfigModel](ctx, &resp.Diagnostics, provisioningSchemePlan.AzureMachineConfig) + azureMasterImageModel := util.ObjectValueToTypedObject[AzureMasterImageModel](ctx, &resp.Diagnostics, azureMachineConfigModel.AzureMasterImage) + newImage := azureMasterImageModel.MasterImage.ValueString() + resourceGroup := azureMasterImageModel.ResourceGroup.ValueString() + azureMachineProfile := azureMachineConfigModel.MachineProfile if newImage != "" { - storageAccount := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.StorageAccount.ValueString() - container := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.Container.ValueString() + storageAccount := azureMasterImageModel.StorageAccount.ValueString() + container := azureMasterImageModel.Container.ValueString() if storageAccount != "" && container != "" { queryPath := fmt.Sprintf( "image.folder\\%s.resourcegroup\\%s.storageaccount\\%s.container", @@ -699,10 +751,11 @@ func updateCatalogImageAndMachineProfile(ctx context.Context, client *citrixdaas return err } } - } else if plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.GalleryImage != nil { - gallery := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.GalleryImage.Gallery.ValueString() - definition := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.GalleryImage.Definition.ValueString() - version := plan.ProvisioningScheme.AzureMachineConfig.AzureMasterImage.GalleryImage.Version.ValueString() + } else if !azureMasterImageModel.GalleryImage.IsNull() { + azureGalleryImage := util.ObjectValueToTypedObject[GalleryImageModel](ctx, &resp.Diagnostics, azureMasterImageModel.GalleryImage) + gallery := azureGalleryImage.Gallery.ValueString() + definition := azureGalleryImage.Definition.ValueString() + version := azureGalleryImage.Version.ValueString() if gallery != "" && definition != "" { queryPath := fmt.Sprintf( "image.folder\\%s.resourcegroup\\%s.gallery\\%s.imagedefinition", @@ -720,36 +773,73 @@ func updateCatalogImageAndMachineProfile(ctx context.Context, client *citrixdaas } } - if azureMachineProfile != nil { - machineProfilePath, err = handleMachineProfileForAzureMcsCatalog(ctx, client, &resp.Diagnostics, hypervisor.GetName(), hypervisorResourcePool.GetName(), *azureMachineProfile, "updating") + masterImageNote = azureMachineConfigModel.MasterImageNote.ValueString() + + // Set reboot options if configured + if !azureMachineConfigModel.ImageUpdateRebootOptions.IsNull() { + rebootOptionsPlan := util.ObjectValueToTypedObject[ImageUpdateRebootOptionsModel](ctx, &resp.Diagnostics, azureMachineConfigModel.ImageUpdateRebootOptions) + rebootOption.SetRebootDuration(int32(rebootOptionsPlan.RebootDuration.ValueInt64())) + warningDuration := int32(rebootOptionsPlan.WarningDuration.ValueInt64()) + rebootOption.SetWarningDuration(warningDuration) + if warningDuration > 0 { + // if warning duration is not 0, it's set in plan and requires warning message body + rebootOption.SetWarningMessage(rebootOptionsPlan.WarningMessage.ValueString()) + if !rebootOptionsPlan.WarningRepeatInterval.IsNull() { + rebootOption.SetWarningRepeatInterval(int32(rebootOptionsPlan.WarningRepeatInterval.ValueInt64())) + } + } + } + + if !azureMachineProfile.IsNull() { + machineProfilePath, err = handleMachineProfileForAzureMcsCatalog(ctx, client, &resp.Diagnostics, hypervisor.GetName(), hypervisorResourcePool.GetName(), util.ObjectValueToTypedObject[AzureMachineProfileModel](ctx, &resp.Diagnostics, azureMachineProfile), "updating") if err != nil { return err } } - if plan.ProvisioningScheme.AzureMachineConfig.UseAzureComputeGallery != nil { + if !azureMachineConfigModel.UseAzureComputeGallery.IsNull() { + azureComputeGalleryModel := util.ObjectValueToTypedObject[AzureComputeGallerySettings](ctx, &resp.Diagnostics, azureMachineConfigModel.UseAzureComputeGallery) util.AppendNameValueStringPair(&updateCustomProperties, "UseSharedImageGallery", "true") - util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaRatio", strconv.Itoa(int(plan.ProvisioningScheme.AzureMachineConfig.UseAzureComputeGallery.ReplicaRatio.ValueInt64()))) - util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaMaximum", strconv.Itoa(int(plan.ProvisioningScheme.AzureMachineConfig.UseAzureComputeGallery.ReplicaMaximum.ValueInt64()))) + util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaRatio", strconv.Itoa(int(azureComputeGalleryModel.ReplicaRatio.ValueInt64()))) + util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaMaximum", strconv.Itoa(int(azureComputeGalleryModel.ReplicaMaximum.ValueInt64()))) } else { util.AppendNameValueStringPair(&updateCustomProperties, "UseSharedImageGallery", "false") util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaRatio", "") util.AppendNameValueStringPair(&updateCustomProperties, "SharedImageGalleryReplicaMaximum", "") } case citrixorchestration.HYPERVISORCONNECTIONTYPE_AWS: - imageId := fmt.Sprintf("%s (%s)", plan.ProvisioningScheme.AwsMachineConfig.MasterImage.ValueString(), plan.ProvisioningScheme.AwsMachineConfig.ImageAmi.ValueString()) + awsMachineConfig := util.ObjectValueToTypedObject[AwsMachineConfigModel](ctx, &resp.Diagnostics, provisioningSchemePlan.AwsMachineConfig) + imageId := fmt.Sprintf("%s (%s)", awsMachineConfig.MasterImage.ValueString(), awsMachineConfig.ImageAmi.ValueString()) imagePath, err = util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", imageId, util.TemplateResourceType, "") if err != nil { resp.Diagnostics.AddError( "Error updating Machine Catalog", - fmt.Sprintf("Failed to locate AWS image %s with AMI %s, error: %s", plan.ProvisioningScheme.AwsMachineConfig.MasterImage.ValueString(), plan.ProvisioningScheme.AwsMachineConfig.ImageAmi.ValueString(), err.Error()), + fmt.Sprintf("Failed to locate AWS image %s with AMI %s, error: %s", awsMachineConfig.MasterImage.ValueString(), awsMachineConfig.ImageAmi.ValueString(), err.Error()), ) return err } + + masterImageNote = awsMachineConfig.MasterImageNote.ValueString() + + // Set reboot options if configured + if !awsMachineConfig.ImageUpdateRebootOptions.IsNull() { + rebootOptionsPlan := util.ObjectValueToTypedObject[ImageUpdateRebootOptionsModel](ctx, &resp.Diagnostics, awsMachineConfig.ImageUpdateRebootOptions) + rebootOption.SetRebootDuration(int32(rebootOptionsPlan.RebootDuration.ValueInt64())) + warningDuration := int32(rebootOptionsPlan.WarningDuration.ValueInt64()) + rebootOption.SetWarningDuration(warningDuration) + if warningDuration > 0 { + // if warning duration is not 0, it's set in plan and requires warning message body + rebootOption.SetWarningMessage(rebootOptionsPlan.WarningMessage.ValueString()) + if !rebootOptionsPlan.WarningRepeatInterval.IsNull() { + rebootOption.SetWarningRepeatInterval(int32(rebootOptionsPlan.WarningRepeatInterval.ValueInt64())) + } + } + } case citrixorchestration.HYPERVISORCONNECTIONTYPE_GOOGLE_CLOUD_PLATFORM: - newImage := plan.ProvisioningScheme.GcpMachineConfig.MasterImage.ValueString() - snapshot := plan.ProvisioningScheme.GcpMachineConfig.MachineSnapshot.ValueString() - gcpMachineProfile := plan.ProvisioningScheme.GcpMachineConfig.MachineProfile.ValueString() + gcpMachineConfig := util.ObjectValueToTypedObject[GcpMachineConfigModel](ctx, &resp.Diagnostics, provisioningSchemePlan.GcpMachineConfig) + newImage := gcpMachineConfig.MasterImage.ValueString() + snapshot := gcpMachineConfig.MachineSnapshot.ValueString() + gcpMachineProfile := gcpMachineConfig.MachineProfile.ValueString() if snapshot != "" { queryPath := fmt.Sprintf("%s.vm", newImage) @@ -771,41 +861,113 @@ func updateCatalogImageAndMachineProfile(ctx context.Context, client *citrixdaas return err } } + + masterImageNote = gcpMachineConfig.MasterImageNote.ValueString() + + // Set reboot options if configured + if !gcpMachineConfig.ImageUpdateRebootOptions.IsNull() { + rebootOptionsPlan := util.ObjectValueToTypedObject[ImageUpdateRebootOptionsModel](ctx, &resp.Diagnostics, gcpMachineConfig.ImageUpdateRebootOptions) + rebootOption.SetRebootDuration(int32(rebootOptionsPlan.RebootDuration.ValueInt64())) + warningDuration := int32(rebootOptionsPlan.WarningDuration.ValueInt64()) + rebootOption.SetWarningDuration(warningDuration) + if warningDuration > 0 { + // if warning duration is not 0, it's set in plan and requires warning message body + rebootOption.SetWarningMessage(rebootOptionsPlan.WarningMessage.ValueString()) + if !rebootOptionsPlan.WarningRepeatInterval.IsNull() { + rebootOption.SetWarningRepeatInterval(int32(rebootOptionsPlan.WarningRepeatInterval.ValueInt64())) + } + } + } + if gcpMachineProfile != "" { - machineProfilePath, err = util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", plan.ProvisioningScheme.GcpMachineConfig.MachineProfile.ValueString(), util.VirtualMachineResourceType, "") + machineProfilePath, err = util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", gcpMachineConfig.MachineProfile.ValueString(), util.VirtualMachineResourceType, "") if err != nil { resp.Diagnostics.AddError( "Error updating Machine Catalog", - fmt.Sprintf("Failed to locate machine profile %s on GCP, error: %s", plan.ProvisioningScheme.GcpMachineConfig.MachineProfile.ValueString(), err.Error()), + fmt.Sprintf("Failed to locate machine profile %s on GCP, error: %s", gcpMachineConfig.MachineProfile.ValueString(), err.Error()), ) return err } } case citrixorchestration.HYPERVISORCONNECTIONTYPE_V_CENTER: - newImage := plan.ProvisioningScheme.VsphereMachineConfig.MasterImageVm.ValueString() - snapshot := plan.ProvisioningScheme.VsphereMachineConfig.ImageSnapshot.ValueString() + vSphereMachineConfig := util.ObjectValueToTypedObject[VsphereMachineConfigModel](ctx, &resp.Diagnostics, provisioningSchemePlan.VsphereMachineConfig) + newImage := vSphereMachineConfig.MasterImageVm.ValueString() + snapshot := vSphereMachineConfig.ImageSnapshot.ValueString() imagePath, err = getOnPremImagePath(ctx, client, &resp.Diagnostics, hypervisor.GetName(), hypervisorResourcePool.GetName(), newImage, snapshot, "updating") if err != nil { return err } + + masterImageNote = vSphereMachineConfig.MasterImageNote.ValueString() + + // Set reboot options if configured + if !vSphereMachineConfig.ImageUpdateRebootOptions.IsNull() { + rebootOptionsPlan := util.ObjectValueToTypedObject[ImageUpdateRebootOptionsModel](ctx, &resp.Diagnostics, vSphereMachineConfig.ImageUpdateRebootOptions) + rebootOption.SetRebootDuration(int32(rebootOptionsPlan.RebootDuration.ValueInt64())) + warningDuration := int32(rebootOptionsPlan.WarningDuration.ValueInt64()) + rebootOption.SetWarningDuration(warningDuration) + if warningDuration > 0 { + // if warning duration is not 0, it's set in plan and requires warning message body + rebootOption.SetWarningMessage(rebootOptionsPlan.WarningMessage.ValueString()) + if !rebootOptionsPlan.WarningRepeatInterval.IsNull() { + rebootOption.SetWarningRepeatInterval(int32(rebootOptionsPlan.WarningRepeatInterval.ValueInt64())) + } + } + } case citrixorchestration.HYPERVISORCONNECTIONTYPE_XEN_SERVER: - newImage := plan.ProvisioningScheme.XenserverMachineConfig.MasterImageVm.ValueString() - snapshot := plan.ProvisioningScheme.XenserverMachineConfig.ImageSnapshot.ValueString() + xenserverMachineConfig := util.ObjectValueToTypedObject[XenserverMachineConfigModel](ctx, &resp.Diagnostics, provisioningSchemePlan.XenserverMachineConfig) + newImage := xenserverMachineConfig.MasterImageVm.ValueString() + snapshot := xenserverMachineConfig.ImageSnapshot.ValueString() imagePath, err = getOnPremImagePath(ctx, client, &resp.Diagnostics, hypervisor.GetName(), hypervisorResourcePool.GetName(), newImage, snapshot, "updating") if err != nil { return err } + + masterImageNote = xenserverMachineConfig.MasterImageNote.ValueString() + + // Set reboot options if configured + if !xenserverMachineConfig.ImageUpdateRebootOptions.IsNull() { + rebootOptionsPlan := util.ObjectValueToTypedObject[ImageUpdateRebootOptionsModel](ctx, &resp.Diagnostics, xenserverMachineConfig.ImageUpdateRebootOptions) + rebootOption.SetRebootDuration(int32(rebootOptionsPlan.RebootDuration.ValueInt64())) + warningDuration := int32(rebootOptionsPlan.WarningDuration.ValueInt64()) + rebootOption.SetWarningDuration(warningDuration) + if warningDuration > 0 { + // if warning duration is not 0, it's set in plan and requires warning message body + rebootOption.SetWarningMessage(rebootOptionsPlan.WarningMessage.ValueString()) + if !rebootOptionsPlan.WarningRepeatInterval.IsNull() { + rebootOption.SetWarningRepeatInterval(int32(rebootOptionsPlan.WarningRepeatInterval.ValueInt64())) + } + } + } case citrixorchestration.HYPERVISORCONNECTIONTYPE_CUSTOM: + nutanixMachineConfig := util.ObjectValueToTypedObject[NutanixMachineConfigModel](ctx, &resp.Diagnostics, provisioningSchemePlan.NutanixMachineConfig) if hypervisor.GetPluginId() == util.NUTANIX_PLUGIN_ID { - imagePath, err = util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", plan.ProvisioningScheme.NutanixMachineConfigModel.MasterImage.ValueString(), util.TemplateResourceType, "") + imagePath, err = util.GetSingleResourcePathFromHypervisor(ctx, client, hypervisor.GetName(), hypervisorResourcePool.GetName(), "", nutanixMachineConfig.MasterImage.ValueString(), util.TemplateResourceType, "") if err != nil { resp.Diagnostics.AddError( "Error updating Machine Catalog", - fmt.Sprintf("Failed to locate master image %s on NUTANIX, error: %s", plan.ProvisioningScheme.NutanixMachineConfigModel.MasterImage.ValueString(), err.Error()), + fmt.Sprintf("Failed to locate master image %s on NUTANIX, error: %s", nutanixMachineConfig.MasterImage.ValueString(), err.Error()), ) return err } + + masterImageNote = nutanixMachineConfig.MasterImageNote.ValueString() + + // Set reboot options if configured + if !nutanixMachineConfig.ImageUpdateRebootOptions.IsNull() { + rebootOptionsPlan := util.ObjectValueToTypedObject[ImageUpdateRebootOptionsModel](ctx, &resp.Diagnostics, nutanixMachineConfig.ImageUpdateRebootOptions) + rebootOption.SetRebootDuration(int32(rebootOptionsPlan.RebootDuration.ValueInt64())) + warningDuration := int32(rebootOptionsPlan.WarningDuration.ValueInt64()) + rebootOption.SetWarningDuration(warningDuration) + if warningDuration > 0 { + // if warning duration is not 0, it's set in plan and requires warning message body + rebootOption.SetWarningMessage(rebootOptionsPlan.WarningMessage.ValueString()) + if !rebootOptionsPlan.WarningRepeatInterval.IsNull() { + rebootOption.SetWarningRepeatInterval(int32(rebootOptionsPlan.WarningRepeatInterval.ValueInt64())) + } + } + } } } @@ -816,18 +978,12 @@ func updateCatalogImageAndMachineProfile(ctx context.Context, client *citrixdaas } } - if masterImage.GetXDPath() == imagePath { + if masterImage.GetXDPath() == imagePath && currentDiskImage.GetMasterImageNote() == masterImageNote { return nil } // Update Master Image for Machine Catalog var updateProvisioningSchemeModel citrixorchestration.UpdateMachineCatalogProvisioningSchemeRequestModel - var rebootOption citrixorchestration.RebootMachinesRequestModel - - // Update the image immediately - rebootOption.SetRebootDuration(60) - rebootOption.SetWarningDuration(15) - rebootOption.SetWarningMessage("Warning: An important update is about to be installed. To ensure that no loss of data occurs, save any outstanding work and close all applications.") functionalLevel, err := citrixorchestration.NewFunctionalLevelFromValue(plan.MinimumFunctionalLevel.ValueString()) if err != nil { @@ -838,11 +994,14 @@ func updateCatalogImageAndMachineProfile(ctx context.Context, client *citrixdaas return err } - updateProvisioningSchemeModel.SetRebootOptions(rebootOption) updateProvisioningSchemeModel.SetMinimumFunctionalLevel(*functionalLevel) updateProvisioningSchemeModel.SetMasterImagePath(imagePath) + updateProvisioningSchemeModel.SetStoreOldImage(true) + updateProvisioningSchemeModel.SetMasterImageNote(masterImageNote) + updateProvisioningSchemeModel.SetRebootOptions(rebootOption) + if len(updateCustomProperties) > 0 { updateProvisioningSchemeModel.SetCustomProperties(updateCustomProperties) } @@ -866,11 +1025,8 @@ func updateCatalogImageAndMachineProfile(ctx context.Context, client *citrixdaas return nil } -func (r MachineCatalogResourceModel) updateCatalogWithProvScheme(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, catalog *citrixorchestration.MachineCatalogDetailResponseModel, connectionType *citrixorchestration.HypervisorConnectionType, pluginId string) MachineCatalogResourceModel { - if r.ProvisioningScheme == nil { - r.ProvisioningScheme = &ProvisioningSchemeModel{} - } - +func (r MachineCatalogResourceModel) updateCatalogWithProvScheme(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, catalog *citrixorchestration.MachineCatalogDetailResponseModel, connectionType *citrixorchestration.HypervisorConnectionType, pluginId string) MachineCatalogResourceModel { + provSchemeModel := util.ObjectValueToTypedObject[ProvisioningSchemeModel](ctx, diagnostics, r.ProvisioningScheme) provScheme := catalog.GetProvisioningScheme() resourcePool := provScheme.GetResourcePool() hypervisor := resourcePool.GetHypervisor() @@ -879,71 +1035,79 @@ func (r MachineCatalogResourceModel) updateCatalogWithProvScheme(ctx context.Con customProperties := provScheme.GetCustomProperties() // Refresh Hypervisor and Resource Pool - r.ProvisioningScheme.Hypervisor = types.StringValue(hypervisor.GetId()) - r.ProvisioningScheme.HypervisorResourcePool = types.StringValue(resourcePool.GetId()) + provSchemeModel.Hypervisor = types.StringValue(hypervisor.GetId()) + provSchemeModel.HypervisorResourcePool = types.StringValue(resourcePool.GetId()) switch *connectionType { case citrixorchestration.HYPERVISORCONNECTIONTYPE_AZURE_RM: - if r.ProvisioningScheme.AzureMachineConfig == nil { - r.ProvisioningScheme.AzureMachineConfig = &AzureMachineConfigModel{} - } - - r.ProvisioningScheme.AzureMachineConfig.RefreshProperties(*catalog) - + azureMachineConfigModel := util.ObjectValueToTypedObject[AzureMachineConfigModel](ctx, diagnostics, provSchemeModel.AzureMachineConfig) + azureMachineConfigModel.RefreshProperties(ctx, diagnostics, *catalog) + provSchemeModel.AzureMachineConfig = util.TypedObjectToObjectValue(ctx, diagnostics, azureMachineConfigModel) for _, stringPair := range customProperties { - if stringPair.GetName() == "Zones" && !r.ProvisioningScheme.AvailabilityZones.IsNull() { - r.ProvisioningScheme.AvailabilityZones = types.StringValue(stringPair.GetValue()) + if stringPair.GetName() == "Zones" && stringPair.GetValue() != "" { + availability_zones := strings.Split(stringPair.GetValue(), ",") + provSchemeModel.AvailabilityZones = util.StringArrayToStringList(ctx, diagnostics, availability_zones) } } case citrixorchestration.HYPERVISORCONNECTIONTYPE_AWS: - if r.ProvisioningScheme.AwsMachineConfig == nil { - r.ProvisioningScheme.AwsMachineConfig = &AwsMachineConfigModel{} + awsMachineConfig := util.ObjectValueToTypedObject[AwsMachineConfigModel](ctx, diagnostics, provSchemeModel.AwsMachineConfig) + if provSchemeModel.AwsMachineConfig.IsNull() { + awsMachineConfig = AwsMachineConfigModel{} } else { - serviceOfferingObject, err := util.GetSingleResourceFromHypervisor(ctx, client, hypervisor.GetId(), resourcePool.GetId(), "", provScheme.GetServiceOffering(), util.ServiceOfferingResourceType, "") - if err == nil { + if serviceOfferingObject, err := util.GetSingleResourceFromHypervisor(ctx, client, hypervisor.GetId(), resourcePool.GetId(), "", provScheme.GetServiceOffering(), util.ServiceOfferingResourceType, ""); err == nil { provScheme.SetServiceOffering(serviceOfferingObject.GetId()) catalog.SetProvisioningScheme(provScheme) + } else { + diagnostics.AddError("Could not find AWS service offering "+provScheme.GetServiceOffering(), err.Error()) } } - r.ProvisioningScheme.AwsMachineConfig.RefreshProperties(*catalog) - + awsMachineConfig.RefreshProperties(ctx, diagnostics, *catalog) + provSchemeModel.AwsMachineConfig = util.TypedObjectToObjectValue(ctx, diagnostics, awsMachineConfig) for _, stringPair := range customProperties { if stringPair.GetName() == "Zones" { - r.ProvisioningScheme.AvailabilityZones = types.StringValue(stringPair.GetValue()) + availability_zones := strings.Split(stringPair.GetValue(), ",") + provSchemeModel.AvailabilityZones = util.StringArrayToStringList(ctx, diagnostics, availability_zones) } } case citrixorchestration.HYPERVISORCONNECTIONTYPE_GOOGLE_CLOUD_PLATFORM: - if r.ProvisioningScheme.GcpMachineConfig == nil { - r.ProvisioningScheme.GcpMachineConfig = &GcpMachineConfigModel{} + gcpMachineConfig := util.ObjectValueToTypedObject[GcpMachineConfigModel](ctx, diagnostics, provSchemeModel.GcpMachineConfig) + if provSchemeModel.GcpMachineConfig.IsNull() { + gcpMachineConfig = GcpMachineConfigModel{} } - r.ProvisioningScheme.GcpMachineConfig.RefreshProperties(*catalog) - + gcpMachineConfig.RefreshProperties(ctx, diagnostics, *catalog) + provSchemeModel.GcpMachineConfig = util.TypedObjectToObjectValue(ctx, diagnostics, gcpMachineConfig) for _, stringPair := range customProperties { - if stringPair.GetName() == "CatalogZones" && !r.ProvisioningScheme.AvailabilityZones.IsNull() { - r.ProvisioningScheme.AvailabilityZones = types.StringValue(stringPair.GetValue()) + if stringPair.GetName() == "CatalogZones" && stringPair.GetValue() != "" { + availability_zones := strings.Split(stringPair.GetValue(), ",") + provSchemeModel.AvailabilityZones = util.StringArrayToStringList(ctx, diagnostics, availability_zones) } } case citrixorchestration.HYPERVISORCONNECTIONTYPE_V_CENTER: - if r.ProvisioningScheme.VsphereMachineConfig == nil { - r.ProvisioningScheme.VsphereMachineConfig = &VsphereMachineConfigModel{} + vSphereMachineConfig := util.ObjectValueToTypedObject[VsphereMachineConfigModel](ctx, diagnostics, provSchemeModel.VsphereMachineConfig) + if provSchemeModel.VsphereMachineConfig.IsNull() { + vSphereMachineConfig = VsphereMachineConfigModel{} } - - r.ProvisioningScheme.VsphereMachineConfig.RefreshProperties(*catalog) + vSphereMachineConfig.RefreshProperties(ctx, diagnostics, *catalog) + provSchemeModel.VsphereMachineConfig = util.TypedObjectToObjectValue(ctx, diagnostics, vSphereMachineConfig) case citrixorchestration.HYPERVISORCONNECTIONTYPE_XEN_SERVER: - if r.ProvisioningScheme.XenserverMachineConfig == nil { - r.ProvisioningScheme.XenserverMachineConfig = &XenserverMachineConfigModel{} + xenserverMachineConfig := util.ObjectValueToTypedObject[XenserverMachineConfigModel](ctx, diagnostics, provSchemeModel.XenserverMachineConfig) + if provSchemeModel.XenserverMachineConfig.IsNull() { + xenserverMachineConfig = XenserverMachineConfigModel{} } - r.ProvisioningScheme.XenserverMachineConfig.RefreshProperties(*catalog) + xenserverMachineConfig.RefreshProperties(ctx, diagnostics, *catalog) + provSchemeModel.XenserverMachineConfig = util.TypedObjectToObjectValue(ctx, diagnostics, xenserverMachineConfig) case citrixorchestration.HYPERVISORCONNECTIONTYPE_CUSTOM: if pluginId == util.NUTANIX_PLUGIN_ID { - if r.ProvisioningScheme.NutanixMachineConfigModel == nil { - r.ProvisioningScheme.NutanixMachineConfigModel = &NutanixMachineConfigModel{} + nutanixMachineConfig := util.ObjectValueToTypedObject[NutanixMachineConfigModel](ctx, diagnostics, provSchemeModel.NutanixMachineConfig) + if provSchemeModel.NutanixMachineConfig.IsNull() { + nutanixMachineConfig = NutanixMachineConfigModel{} } - r.ProvisioningScheme.NutanixMachineConfigModel.RefreshProperties(*catalog) + nutanixMachineConfig.RefreshProperties(*catalog) + provSchemeModel.NutanixMachineConfig = util.TypedObjectToObjectValue(ctx, diagnostics, nutanixMachineConfig) } } @@ -952,7 +1116,8 @@ func (r MachineCatalogResourceModel) updateCatalogWithProvScheme(ctx context.Con remoteCustomProperties[customProperty.GetName()] = customProperty.GetValue() } refreshedCustomProperties := []CustomPropertyModel{} - for _, customProperty := range r.ProvisioningScheme.CustomProperties { + customPropertiesModel := util.ObjectListToTypedArray[CustomPropertyModel](ctx, diagnostics, provSchemeModel.CustomProperties) + for _, customProperty := range customPropertiesModel { if value, ok := remoteCustomProperties[customProperty.Name.ValueString()]; ok { newProperty := CustomPropertyModel{} newProperty.Name = customProperty.Name @@ -962,109 +1127,113 @@ func (r MachineCatalogResourceModel) updateCatalogWithProvScheme(ctx context.Con } if len(refreshedCustomProperties) == 0 { - r.ProvisioningScheme.CustomProperties = nil + provSchemeModel.CustomProperties = util.TypedArrayToObjectList[CustomPropertyModel](ctx, diagnostics, nil) } else { - r.ProvisioningScheme.CustomProperties = refreshedCustomProperties + provSchemeModel.CustomProperties = util.TypedArrayToObjectList[CustomPropertyModel](ctx, diagnostics, refreshedCustomProperties) } // Refresh Total Machine Count - r.ProvisioningScheme.NumTotalMachines = types.Int64Value(int64(provScheme.GetMachineCount())) + provSchemeModel.NumTotalMachines = types.Int64Value(int64(provScheme.GetMachineCount())) // Refresh Identity Type if identityType := types.StringValue(string(provScheme.GetIdentityType())); identityType.ValueString() != "" { - r.ProvisioningScheme.IdentityType = identityType + provSchemeModel.IdentityType = identityType } else { - r.ProvisioningScheme.IdentityType = types.StringNull() + provSchemeModel.IdentityType = types.StringNull() } // Refresh Network Mapping networkMaps := provScheme.GetNetworkMaps() - if len(networkMaps) > 0 && r.ProvisioningScheme.NetworkMapping != nil { - r.ProvisioningScheme.NetworkMapping = util.RefreshListProperties[NetworkMappingModel, citrixorchestration.NetworkMapResponseModel](r.ProvisioningScheme.NetworkMapping, "NetworkDevice", networkMaps, "DeviceId", "RefreshListItem") + if len(networkMaps) > 0 && !provSchemeModel.NetworkMapping.IsNull() { + provSchemeModel.NetworkMapping = util.RefreshListValueProperties[NetworkMappingModel, citrixorchestration.NetworkMapResponseModel](ctx, diagnostics, provSchemeModel.NetworkMapping, networkMaps, util.GetOrchestrationNetworkMappingKey) } else { - r.ProvisioningScheme.NetworkMapping = nil + provSchemeModel.NetworkMapping = util.TypedArrayToObjectList[NetworkMappingModel](ctx, diagnostics, nil) } // Identity Pool Properties - if r.ProvisioningScheme.MachineAccountCreationRules == nil { - r.ProvisioningScheme.MachineAccountCreationRules = &MachineAccountCreationRulesModel{} - } - r.ProvisioningScheme.MachineAccountCreationRules.NamingScheme = types.StringValue(machineAccountCreateRules.GetNamingScheme()) + machineAccountCreationRulesModel := MachineAccountCreationRulesModel{} + machineAccountCreationRulesModel.NamingScheme = types.StringValue(machineAccountCreateRules.GetNamingScheme()) namingSchemeType := machineAccountCreateRules.GetNamingSchemeType() - r.ProvisioningScheme.MachineAccountCreationRules.NamingSchemeType = types.StringValue(reflect.ValueOf(namingSchemeType).String()) + machineAccountCreationRulesModel.NamingSchemeType = types.StringValue(string(namingSchemeType)) + provSchemeModel.MachineAccountCreationRules = util.TypedObjectToObjectValue(ctx, diagnostics, machineAccountCreationRulesModel) // Domain Identity Properties if provScheme.GetIdentityType() == citrixorchestration.IDENTITYTYPE_AZURE_AD || provScheme.GetIdentityType() == citrixorchestration.IDENTITYTYPE_WORKGROUP { + r.ProvisioningScheme = util.TypedObjectToObjectValue(ctx, diagnostics, provSchemeModel) return r } - if r.ProvisioningScheme.MachineDomainIdentity == nil { - r.ProvisioningScheme.MachineDomainIdentity = &MachineDomainIdentityModel{} - } + machineDomainIdentityModel := util.ObjectValueToTypedObject[MachineDomainIdentityModel](ctx, diagnostics, provSchemeModel.MachineDomainIdentity) if domain.GetName() != "" { - r.ProvisioningScheme.MachineDomainIdentity.Domain = types.StringValue(domain.GetName()) + machineDomainIdentityModel.Domain = types.StringValue(domain.GetName()) } if machineAccountCreateRules.GetOU() != "" { - r.ProvisioningScheme.MachineDomainIdentity.Ou = types.StringValue(machineAccountCreateRules.GetOU()) + machineDomainIdentityModel.Ou = types.StringValue(machineAccountCreateRules.GetOU()) } + provSchemeModel.MachineDomainIdentity = util.TypedObjectToObjectValue(ctx, diagnostics, machineDomainIdentityModel) + r.ProvisioningScheme = util.TypedObjectToObjectValue(ctx, diagnostics, provSchemeModel) return r } -func parseCustomPropertiesToClientModel(provisioningScheme ProvisioningSchemeModel, connectionType citrixorchestration.HypervisorConnectionType) []citrixorchestration.NameValueStringPairModel { +func parseCustomPropertiesToClientModel(ctx context.Context, diagnostics *diag.Diagnostics, provisioningScheme ProvisioningSchemeModel, connectionType citrixorchestration.HypervisorConnectionType) []citrixorchestration.NameValueStringPairModel { var res = &[]citrixorchestration.NameValueStringPairModel{} switch connectionType { case citrixorchestration.HYPERVISORCONNECTIONTYPE_AZURE_RM: + azureMachineConfigModel := util.ObjectValueToTypedObject[AzureMachineConfigModel](ctx, diagnostics, provisioningScheme.AzureMachineConfig) if !provisioningScheme.AvailabilityZones.IsNull() { - util.AppendNameValueStringPair(res, "Zones", provisioningScheme.AvailabilityZones.ValueString()) + availability_zones := util.StringListToStringArray(ctx, diagnostics, provisioningScheme.AvailabilityZones) + util.AppendNameValueStringPair(res, "Zones", strings.Join(availability_zones, ",")) } else { util.AppendNameValueStringPair(res, "Zones", "") } - if !provisioningScheme.AzureMachineConfig.StorageType.IsNull() { - if provisioningScheme.AzureMachineConfig.StorageType.ValueString() == util.AzureEphemeralOSDisk { + if !azureMachineConfigModel.StorageType.IsNull() { + if azureMachineConfigModel.StorageType.ValueString() == util.AzureEphemeralOSDisk { util.AppendNameValueStringPair(res, "UseEphemeralOsDisk", "true") } else { - util.AppendNameValueStringPair(res, "StorageType", provisioningScheme.AzureMachineConfig.StorageType.ValueString()) + util.AppendNameValueStringPair(res, "StorageType", azureMachineConfigModel.StorageType.ValueString()) } } - if !provisioningScheme.AzureMachineConfig.VdaResourceGroup.IsNull() { - util.AppendNameValueStringPair(res, "ResourceGroups", provisioningScheme.AzureMachineConfig.VdaResourceGroup.ValueString()) + if !azureMachineConfigModel.VdaResourceGroup.IsNull() { + util.AppendNameValueStringPair(res, "ResourceGroups", azureMachineConfigModel.VdaResourceGroup.ValueString()) } - if !provisioningScheme.AzureMachineConfig.UseManagedDisks.IsNull() { - if provisioningScheme.AzureMachineConfig.UseManagedDisks.ValueBool() { + if !azureMachineConfigModel.UseManagedDisks.IsNull() { + if azureMachineConfigModel.UseManagedDisks.ValueBool() { util.AppendNameValueStringPair(res, "UseManagedDisks", "true") } else { util.AppendNameValueStringPair(res, "UseManagedDisks", "false") } } - if provisioningScheme.AzureMachineConfig.WritebackCache != nil { - if !provisioningScheme.AzureMachineConfig.WritebackCache.WBCDiskStorageType.IsNull() { - util.AppendNameValueStringPair(res, "WBCDiskStorageType", provisioningScheme.AzureMachineConfig.WritebackCache.WBCDiskStorageType.ValueString()) + if !azureMachineConfigModel.WritebackCache.IsNull() { + azureWbcModel := util.ObjectValueToTypedObject[AzureWritebackCacheModel](ctx, diagnostics, azureMachineConfigModel.WritebackCache) + if !azureWbcModel.WBCDiskStorageType.IsNull() { + util.AppendNameValueStringPair(res, "WBCDiskStorageType", azureWbcModel.WBCDiskStorageType.ValueString()) } - if provisioningScheme.AzureMachineConfig.WritebackCache.PersistWBC.ValueBool() { + if azureWbcModel.PersistWBC.ValueBool() { util.AppendNameValueStringPair(res, "PersistWBC", "true") - if provisioningScheme.AzureMachineConfig.WritebackCache.StorageCostSaving.ValueBool() { + if azureWbcModel.StorageCostSaving.ValueBool() { util.AppendNameValueStringPair(res, "StorageTypeAtShutdown", "Standard_LRS") } } - if provisioningScheme.AzureMachineConfig.WritebackCache.PersistOsDisk.ValueBool() { + if azureWbcModel.PersistOsDisk.ValueBool() { util.AppendNameValueStringPair(res, "PersistOsDisk", "true") - if provisioningScheme.AzureMachineConfig.WritebackCache.PersistVm.ValueBool() { + if azureWbcModel.PersistVm.ValueBool() { util.AppendNameValueStringPair(res, "PersistVm", "true") } } } - licenseType := provisioningScheme.AzureMachineConfig.LicenseType.ValueString() + licenseType := azureMachineConfigModel.LicenseType.ValueString() util.AppendNameValueStringPair(res, "LicenseType", licenseType) - if provisioningScheme.AzureMachineConfig.UseAzureComputeGallery != nil { + if !azureMachineConfigModel.UseAzureComputeGallery.IsNull() { + azureComputeGalleryModel := util.ObjectValueToTypedObject[AzureComputeGallerySettings](ctx, diagnostics, azureMachineConfigModel.UseAzureComputeGallery) util.AppendNameValueStringPair(res, "UseSharedImageGallery", "true") - util.AppendNameValueStringPair(res, "SharedImageGalleryReplicaRatio", strconv.Itoa(int(provisioningScheme.AzureMachineConfig.UseAzureComputeGallery.ReplicaRatio.ValueInt64()))) - util.AppendNameValueStringPair(res, "SharedImageGalleryReplicaMaximum", strconv.Itoa(int(provisioningScheme.AzureMachineConfig.UseAzureComputeGallery.ReplicaMaximum.ValueInt64()))) + util.AppendNameValueStringPair(res, "SharedImageGalleryReplicaRatio", strconv.Itoa(int(azureComputeGalleryModel.ReplicaRatio.ValueInt64()))) + util.AppendNameValueStringPair(res, "SharedImageGalleryReplicaMaximum", strconv.Itoa(int(azureComputeGalleryModel.ReplicaMaximum.ValueInt64()))) } else { util.AppendNameValueStringPair(res, "UseSharedImageGallery", "false") util.AppendNameValueStringPair(res, "SharedImageGalleryReplicaRatio", "") @@ -1072,23 +1241,27 @@ func parseCustomPropertiesToClientModel(provisioningScheme ProvisioningSchemeMod } case citrixorchestration.HYPERVISORCONNECTIONTYPE_AWS: if !provisioningScheme.AvailabilityZones.IsNull() { - util.AppendNameValueStringPair(res, "Zones", provisioningScheme.AvailabilityZones.ValueString()) + availability_zones := util.StringListToStringArray(ctx, diagnostics, provisioningScheme.AvailabilityZones) + util.AppendNameValueStringPair(res, "Zones", strings.Join(availability_zones, ",")) } case citrixorchestration.HYPERVISORCONNECTIONTYPE_GOOGLE_CLOUD_PLATFORM: + gcpMachineConfig := util.ObjectValueToTypedObject[GcpMachineConfigModel](context.Background(), nil, provisioningScheme.GcpMachineConfig) if !provisioningScheme.AvailabilityZones.IsNull() { - util.AppendNameValueStringPair(res, "CatalogZones", provisioningScheme.AvailabilityZones.ValueString()) + availability_zones := util.StringListToStringArray(ctx, diagnostics, provisioningScheme.AvailabilityZones) + util.AppendNameValueStringPair(res, "CatalogZones", strings.Join(availability_zones, ",")) } - if !provisioningScheme.GcpMachineConfig.StorageType.IsNull() { - util.AppendNameValueStringPair(res, "StorageType", provisioningScheme.GcpMachineConfig.StorageType.ValueString()) + if !gcpMachineConfig.StorageType.IsNull() { + util.AppendNameValueStringPair(res, "StorageType", gcpMachineConfig.StorageType.ValueString()) } - if provisioningScheme.GcpMachineConfig.WritebackCache != nil { - if !provisioningScheme.GcpMachineConfig.WritebackCache.WBCDiskStorageType.IsNull() { - util.AppendNameValueStringPair(res, "WBCDiskStorageType", provisioningScheme.GcpMachineConfig.WritebackCache.WBCDiskStorageType.ValueString()) + if !gcpMachineConfig.WritebackCache.IsNull() { + writebackCacheModel := util.ObjectValueToTypedObject[GcpWritebackCacheModel](context.Background(), nil, gcpMachineConfig.WritebackCache) + if !writebackCacheModel.WBCDiskStorageType.IsNull() { + util.AppendNameValueStringPair(res, "WBCDiskStorageType", writebackCacheModel.WBCDiskStorageType.ValueString()) } - if provisioningScheme.GcpMachineConfig.WritebackCache.PersistWBC.ValueBool() { + if writebackCacheModel.PersistWBC.ValueBool() { util.AppendNameValueStringPair(res, "PersistWBC", "true") } - if provisioningScheme.GcpMachineConfig.WritebackCache.PersistOsDisk.ValueBool() { + if writebackCacheModel.PersistOsDisk.ValueBool() { util.AppendNameValueStringPair(res, "PersistOsDisk", "true") } } diff --git a/internal/daas/machine_catalog/machine_catalog_remote_pc_utils.go b/internal/daas/machine_catalog/machine_catalog_remote_pc_utils.go index d111dc9..cfd20b2 100644 --- a/internal/daas/machine_catalog/machine_catalog_remote_pc_utils.go +++ b/internal/daas/machine_catalog/machine_catalog_remote_pc_utils.go @@ -1,17 +1,21 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package machine_catalog import ( + "context" + "github.com/citrix/citrix-daas-rest-go/citrixorchestration" "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" ) -func getRemotePcEnrollmentScopes(plan MachineCatalogResourceModel, includeMachines bool) []citrixorchestration.RemotePCEnrollmentScopeRequestModel { +func getRemotePcEnrollmentScopes(ctx context.Context, diagnostics *diag.Diagnostics, plan MachineCatalogResourceModel, includeMachines bool) []citrixorchestration.RemotePCEnrollmentScopeRequestModel { remotePCEnrollmentScopes := []citrixorchestration.RemotePCEnrollmentScopeRequestModel{} - if plan.RemotePcOus != nil { - for _, ou := range plan.RemotePcOus { + if !plan.RemotePcOus.IsNull() { + remotePcOus := util.ObjectListToTypedArray[RemotePcOuModel](ctx, diagnostics, plan.RemotePcOus) + for _, ou := range remotePcOus { var remotePCEnrollmentScope citrixorchestration.RemotePCEnrollmentScopeRequestModel remotePCEnrollmentScope.SetIncludeSubfolders(ou.IncludeSubFolders.ValueBool()) remotePCEnrollmentScope.SetOU(ou.OUName.ValueString()) @@ -20,9 +24,11 @@ func getRemotePcEnrollmentScopes(plan MachineCatalogResourceModel, includeMachin } } - if includeMachines && plan.MachineAccounts != nil { - for _, machineAccount := range plan.MachineAccounts { - for _, machine := range machineAccount.Machines { + if includeMachines && !plan.MachineAccounts.IsNull() { + machineAccounts := util.ObjectListToTypedArray[MachineAccountsModel](ctx, diagnostics, plan.MachineAccounts) + for _, machineAccount := range machineAccounts { + machines := util.ObjectListToTypedArray[MachineCatalogMachineModel](ctx, diagnostics, machineAccount.Machines) + for _, machine := range machines { var remotePCEnrollmentScope citrixorchestration.RemotePCEnrollmentScopeRequestModel remotePCEnrollmentScope.SetIncludeSubfolders(false) remotePCEnrollmentScope.SetOU(machine.MachineAccount.ValueString()) @@ -35,11 +41,13 @@ func getRemotePcEnrollmentScopes(plan MachineCatalogResourceModel, includeMachin return remotePCEnrollmentScopes } -func (r MachineCatalogResourceModel) updateCatalogWithRemotePcConfig(catalog *citrixorchestration.MachineCatalogDetailResponseModel) MachineCatalogResourceModel { - if catalog.GetProvisioningType() == citrixorchestration.PROVISIONINGTYPE_MANUAL || !r.IsRemotePc.IsNull() { +func (r MachineCatalogResourceModel) updateCatalogWithRemotePcConfig(ctx context.Context, diagnostics *diag.Diagnostics, catalog *citrixorchestration.MachineCatalogDetailResponseModel) MachineCatalogResourceModel { + if catalog.GetProvisioningType() == citrixorchestration.PROVISIONINGTYPE_MANUAL { r.IsRemotePc = types.BoolValue(catalog.GetIsRemotePC()) + } else { + r.IsRemotePc = types.BoolNull() } - rpcOUs := util.RefreshListProperties[RemotePcOuModel, citrixorchestration.RemotePCEnrollmentScopeResponseModel](r.RemotePcOus, "OUName", catalog.GetRemotePCEnrollmentScopes(), "OU", "RefreshListItem") + rpcOUs := util.RefreshListValueProperties[RemotePcOuModel, citrixorchestration.RemotePCEnrollmentScopeResponseModel](ctx, diagnostics, r.RemotePcOus, catalog.GetRemotePCEnrollmentScopes(), util.GetOrchestrationRemotePcOuKey) r.RemotePcOus = rpcOUs return r } diff --git a/internal/daas/machine_catalog/machine_catalog_resource.go b/internal/daas/machine_catalog/machine_catalog_resource.go index 80c43b0..239be24 100644 --- a/internal/daas/machine_catalog/machine_catalog_resource.go +++ b/internal/daas/machine_catalog/machine_catalog_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package machine_catalog @@ -41,7 +41,7 @@ func (r *machineCatalogResource) Metadata(_ context.Context, req resource.Metada // Schema defines the schema for the resource. func (r *machineCatalogResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = getSchemaForMachineCatalogResource() + resp.Schema = GetSchema() } // Configure adds the provider configured client to the resource. @@ -72,9 +72,11 @@ func (r *machineCatalogResource) Create(ctx context.Context, req resource.Create createMachineCatalogRequest := r.client.ApiClient.MachineCatalogsAPIsDAAS.MachineCatalogsCreateMachineCatalog(ctx) + provSchemeModel := util.ObjectValueToTypedObject[ProvisioningSchemeModel](ctx, &resp.Diagnostics, plan.ProvisioningScheme) + // Add domain credential header - if plan.ProvisioningType.ValueString() == string(citrixorchestration.PROVISIONINGTYPE_MCS) && plan.ProvisioningScheme.MachineDomainIdentity != nil { - header := generateAdminCredentialHeader(plan) + if plan.ProvisioningType.ValueString() == string(citrixorchestration.PROVISIONINGTYPE_MCS) && !provSchemeModel.MachineDomainIdentity.IsNull() { + header := generateAdminCredentialHeader(util.ObjectValueToTypedObject[MachineDomainIdentityModel](ctx, &resp.Diagnostics, provSchemeModel.MachineDomainIdentity)) createMachineCatalogRequest = createMachineCatalogRequest.XAdminCredential(header) } @@ -128,7 +130,7 @@ func (r *machineCatalogResource) Create(ctx context.Context, req resource.Create } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(ctx, r.client, catalog, &connectionType, machines, pluginId) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, r.client, catalog, &connectionType, machines, pluginId) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -180,7 +182,7 @@ func (r *machineCatalogResource) Read(ctx context.Context, req resource.ReadRequ pluginId = hypervisor.GetPluginId() } // Overwrite items with refreshed state - state = state.RefreshPropertyValues(ctx, r.client, catalog, connectionType, machineCatalogMachines, pluginId) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, r.client, catalog, connectionType, machineCatalogMachines, pluginId) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -247,28 +249,29 @@ func (r *machineCatalogResource) Update(ctx context.Context, req resource.Update if *provisioningType == citrixorchestration.PROVISIONINGTYPE_MANUAL { // For manual, compare state and plan to find machines to add and delete - addMachinesList, deleteMachinesMap := createAddAndRemoveMachinesListForManualCatalogs(state, plan) + addMachinesList, deleteMachinesMap := createAddAndRemoveMachinesListForManualCatalogs(ctx, &resp.Diagnostics, state, plan) - addMachinesToManualCatalog(ctx, r.client, resp, addMachinesList, catalogId) + addMachinesToManualCatalog(ctx, &resp.Diagnostics, r.client, resp, addMachinesList, catalogId) deleteMachinesFromManualCatalog(ctx, r.client, resp, deleteMachinesMap, catalogId) } else { + provSchemeModel := util.ObjectValueToTypedObject[ProvisioningSchemeModel](ctx, &resp.Diagnostics, plan.ProvisioningScheme) err = updateCatalogImageAndMachineProfile(ctx, r.client, resp, catalog, plan) if err != nil { return } - if catalog.GetTotalCount() > int32(plan.ProvisioningScheme.NumTotalMachines.ValueInt64()) { + if catalog.GetTotalCount() > int32(provSchemeModel.NumTotalMachines.ValueInt64()) { // delete machines from machine catalog - err = deleteMachinesFromMcsCatalog(ctx, r.client, resp, catalog, plan) + err = deleteMachinesFromMcsCatalog(ctx, r.client, resp, catalog, provSchemeModel) if err != nil { return } } - if catalog.GetTotalCount() < int32(plan.ProvisioningScheme.NumTotalMachines.ValueInt64()) { + if catalog.GetTotalCount() < int32(provSchemeModel.NumTotalMachines.ValueInt64()) { // add machines to machine catalog - err = addMachinesToMcsCatalog(ctx, r.client, resp, catalog, plan) + err = addMachinesToMcsCatalog(ctx, r.client, resp, catalog, provSchemeModel) if err != nil { return } @@ -301,7 +304,7 @@ func (r *machineCatalogResource) Update(ctx context.Context, req resource.Update } // Update resource state with updated items and timestamp - plan = plan.RefreshPropertyValues(ctx, r.client, catalog, &connectionType, machines, pluginId) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, r.client, catalog, &connectionType, machines, pluginId) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) @@ -345,16 +348,17 @@ func (r *machineCatalogResource) Delete(ctx context.Context, req resource.Delete deleteMachineCatalogRequest := r.client.ApiClient.MachineCatalogsAPIsDAAS.MachineCatalogsDeleteMachineCatalog(ctx, catalogId) deleteAccountOption := citrixorchestration.MACHINEACCOUNTDELETEOPTION_NONE deleteVmOption := false - if catalog.ProvisioningType == citrixorchestration.PROVISIONINGTYPE_MCS { + if catalog.GetProvisioningType() == citrixorchestration.PROVISIONINGTYPE_MCS { provScheme := catalog.GetProvisioningScheme() identityType := provScheme.GetIdentityType() if identityType == citrixorchestration.IDENTITYTYPE_ACTIVE_DIRECTORY || identityType == citrixorchestration.IDENTITYTYPE_HYBRID_AZURE_AD { // If there's no provisioning scheme in state, there will not be any machines create by MCS. // Therefore we will just omit credential for removing machine accounts. - if catalog.ProvisioningScheme != nil { + if !state.ProvisioningScheme.IsNull() { // Add domain credential header - header := generateAdminCredentialHeader(state) + provSchemeModel := util.ObjectValueToTypedObject[ProvisioningSchemeModel](ctx, &resp.Diagnostics, state.ProvisioningScheme) + header := generateAdminCredentialHeader(util.ObjectValueToTypedObject[MachineDomainIdentityModel](ctx, &resp.Diagnostics, provSchemeModel.MachineDomainIdentity)) deleteMachineCatalogRequest = deleteMachineCatalogRequest.XAdminCredential(header) } @@ -412,7 +416,7 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc azureAD := string(citrixorchestration.IDENTITYTYPE_AZURE_AD) if data.ProvisioningType.ValueString() == provisioningTypeMcs { - if data.ProvisioningScheme == nil { + if data.ProvisioningScheme.IsNull() { resp.Diagnostics.AddAttributeError( path.Root("provisioning_scheme"), "Missing Attribute Configuration", @@ -420,12 +424,14 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc ) } else { // Validate Provisioning Scheme - if data.ProvisioningScheme.AzureMachineConfig != nil { + provSchemeModel := util.ObjectValueToTypedObject[ProvisioningSchemeModel](ctx, &resp.Diagnostics, data.ProvisioningScheme) + if !provSchemeModel.AzureMachineConfig.IsNull() { + azureMachineConfigModel := util.ObjectValueToTypedObject[AzureMachineConfigModel](ctx, &resp.Diagnostics, provSchemeModel.AzureMachineConfig) // Validate Azure Machine Config - if data.ProvisioningScheme.AzureMachineConfig.WritebackCache != nil { + if !azureMachineConfigModel.WritebackCache.IsNull() { // Validate Writeback Cache - wbc := data.ProvisioningScheme.AzureMachineConfig.WritebackCache - if !wbc.PersistOsDisk.ValueBool() && wbc.PersistVm.ValueBool() { + azureWbcModel := util.ObjectValueToTypedObject[AzureWritebackCacheModel](ctx, &resp.Diagnostics, azureMachineConfigModel.WritebackCache) + if !azureWbcModel.PersistOsDisk.ValueBool() && azureWbcModel.PersistVm.ValueBool() { resp.Diagnostics.AddAttributeError( path.Root("persist_vm"), "Incorrect Attribute Configuration", @@ -433,7 +439,7 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc ) } - if !wbc.PersistWBC.ValueBool() && wbc.StorageCostSaving.ValueBool() { + if !azureWbcModel.PersistWBC.ValueBool() && azureWbcModel.StorageCostSaving.ValueBool() { resp.Diagnostics.AddAttributeError( path.Root("storage_cost_saving"), "Incorrect Attribute Configuration", @@ -443,8 +449,8 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc } // Validate Azure Intune Enrollment - if data.ProvisioningScheme.IdentityType.ValueString() != azureAD && - !data.ProvisioningScheme.AzureMachineConfig.EnrollInIntune.IsNull() { + if provSchemeModel.IdentityType.ValueString() != azureAD && + !azureMachineConfigModel.EnrollInIntune.IsNull() { resp.Diagnostics.AddAttributeError( path.Root("enroll_in_intune"), "Incorrect Attribute Configuration", @@ -452,7 +458,7 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc ) } if data.AllocationType.ValueString() != allocationTypeStatic && - data.ProvisioningScheme.AzureMachineConfig.EnrollInIntune.ValueBool() { + azureMachineConfigModel.EnrollInIntune.ValueBool() { resp.Diagnostics.AddAttributeError( path.Root("enroll_in_intune"), "Incorrect Attribute Configuration", @@ -460,9 +466,9 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc ) } - if data.ProvisioningScheme.AzureMachineConfig.StorageType.ValueString() == util.AzureEphemeralOSDisk { + if azureMachineConfigModel.StorageType.ValueString() == util.AzureEphemeralOSDisk { // Validate Azure Ephemeral OS Disk - if !data.ProvisioningScheme.AzureMachineConfig.UseManagedDisks.ValueBool() { + if !azureMachineConfigModel.UseManagedDisks.ValueBool() { resp.Diagnostics.AddAttributeError( path.Root("use_managed_disks"), "Incorrect Attribute Configuration", @@ -470,7 +476,7 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc ) } - if data.ProvisioningScheme.AzureMachineConfig.UseAzureComputeGallery == nil { + if azureMachineConfigModel.UseAzureComputeGallery.IsNull() { resp.Diagnostics.AddAttributeError( path.Root("use_azure_compute_gallery"), "Missing Attribute Configuration", @@ -478,10 +484,61 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc ) } } + + if !azureMachineConfigModel.ImageUpdateRebootOptions.IsNull() { + // Validate Image Update Reboot Options + rebootOptions := util.ObjectValueToTypedObject[ImageUpdateRebootOptionsModel](ctx, &resp.Diagnostics, azureMachineConfigModel.ImageUpdateRebootOptions) + rebootOptions.ValidateConfig(&resp.Diagnostics) + } + } + + if !provSchemeModel.AwsMachineConfig.IsNull() { + awsMachineConfigModel := util.ObjectValueToTypedObject[AwsMachineConfigModel](ctx, &resp.Diagnostics, provSchemeModel.AwsMachineConfig) + if !awsMachineConfigModel.ImageUpdateRebootOptions.IsNull() { + // Validate Image Update Reboot Options + rebootOptions := util.ObjectValueToTypedObject[ImageUpdateRebootOptionsModel](ctx, &resp.Diagnostics, awsMachineConfigModel.ImageUpdateRebootOptions) + rebootOptions.ValidateConfig(&resp.Diagnostics) + } + } + + if !provSchemeModel.GcpMachineConfig.IsNull() { + gcpMachineConfigModel := util.ObjectValueToTypedObject[GcpMachineConfigModel](ctx, &resp.Diagnostics, provSchemeModel.GcpMachineConfig) + if !gcpMachineConfigModel.ImageUpdateRebootOptions.IsNull() { + // Validate Image Update Reboot Options + rebootOptions := util.ObjectValueToTypedObject[ImageUpdateRebootOptionsModel](ctx, &resp.Diagnostics, gcpMachineConfigModel.ImageUpdateRebootOptions) + rebootOptions.ValidateConfig(&resp.Diagnostics) + } + } + + if !provSchemeModel.VsphereMachineConfig.IsNull() { + vSphereMachineConfigModel := util.ObjectValueToTypedObject[VsphereMachineConfigModel](ctx, &resp.Diagnostics, provSchemeModel.VsphereMachineConfig) + if !vSphereMachineConfigModel.ImageUpdateRebootOptions.IsNull() { + // Validate Image Update Reboot Options + rebootOptions := util.ObjectValueToTypedObject[ImageUpdateRebootOptionsModel](ctx, &resp.Diagnostics, vSphereMachineConfigModel.ImageUpdateRebootOptions) + rebootOptions.ValidateConfig(&resp.Diagnostics) + } + } + + if !provSchemeModel.XenserverMachineConfig.IsNull() { + xenserverMachineConfigModel := util.ObjectValueToTypedObject[XenserverMachineConfigModel](ctx, &resp.Diagnostics, provSchemeModel.XenserverMachineConfig) + if !xenserverMachineConfigModel.ImageUpdateRebootOptions.IsNull() { + // Validate Image Update Reboot Options + rebootOptions := util.ObjectValueToTypedObject[ImageUpdateRebootOptionsModel](ctx, &resp.Diagnostics, xenserverMachineConfigModel.ImageUpdateRebootOptions) + rebootOptions.ValidateConfig(&resp.Diagnostics) + } + } + + if !provSchemeModel.NutanixMachineConfig.IsNull() { + nutanixMachineConfigModel := util.ObjectValueToTypedObject[NutanixMachineConfigModel](ctx, &resp.Diagnostics, provSchemeModel.NutanixMachineConfig) + if !nutanixMachineConfigModel.ImageUpdateRebootOptions.IsNull() { + // Validate Image Update Reboot Options + rebootOptions := util.ObjectValueToTypedObject[ImageUpdateRebootOptionsModel](ctx, &resp.Diagnostics, nutanixMachineConfigModel.ImageUpdateRebootOptions) + rebootOptions.ValidateConfig(&resp.Diagnostics) + } } } - if data.MachineAccounts != nil { + if !data.MachineAccounts.IsNull() { resp.Diagnostics.AddAttributeError( path.Root("machine_accounts"), "Incorrect Attribute Configuration", @@ -489,24 +546,24 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc ) } - if data.IsRemotePc.ValueBool() { + if !data.IsRemotePc.IsNull() { resp.Diagnostics.AddAttributeError( path.Root("is_remote_pc"), "Incorrect Attribute Configuration", - fmt.Sprintf("Remote PC access catalog cannot be created when provisioning_type is %s.", provisioningTypeMcs), + fmt.Sprintf("is_remote_pc cannot be configured when provisioning_type is %s.", provisioningTypeMcs), ) } - if !data.IsPowerManaged.IsNull() && !data.IsPowerManaged.ValueBool() { + if !data.IsPowerManaged.IsNull() { resp.Diagnostics.AddAttributeError( path.Root("is_power_managed"), "Incorrect Attribute Configuration", - fmt.Sprintf("Machines have to be power managed when provisioning_type is %s.", provisioningTypeMcs), + fmt.Sprintf("is_power_managed cannot be configured when provisioning_type is %s.", provisioningTypeMcs), ) } data.IsPowerManaged = types.BoolValue(true) // set power managed to true for MCS catalog - } else { + } else if data.ProvisioningType.ValueString() == provisioningTypeManual { // Manual provisioning type if data.IsPowerManaged.IsNull() { resp.Diagnostics.AddAttributeError( @@ -524,7 +581,7 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc ) } - if data.ProvisioningScheme != nil { + if !data.ProvisioningScheme.IsNull() { resp.Diagnostics.AddAttributeError( path.Root("provisioning_scheme"), "Incorrect Attribute Configuration", @@ -533,8 +590,9 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc } if data.IsPowerManaged.ValueBool() { - if data.MachineAccounts != nil { - for _, machineAccount := range data.MachineAccounts { + if !data.MachineAccounts.IsNull() { + machineAccounts := util.ObjectListToTypedArray[MachineAccountsModel](ctx, &resp.Diagnostics, data.MachineAccounts) + for _, machineAccount := range machineAccounts { if machineAccount.Hypervisor.IsNull() { resp.Diagnostics.AddAttributeError( path.Root("machine_accounts"), @@ -543,7 +601,8 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc ) } - for _, machine := range machineAccount.Machines { + machines := util.ObjectListToTypedArray[MachineCatalogMachineModel](ctx, &resp.Diagnostics, machineAccount.Machines) + for _, machine := range machines { if machine.MachineName.IsNull() { resp.Diagnostics.AddAttributeError( path.Root("machine_accounts"), @@ -584,8 +643,10 @@ func (r *machineCatalogResource) ValidateConfig(ctx context.Context, req resourc } } - if data.ProvisioningScheme != nil && data.ProvisioningScheme.CustomProperties != nil { - for _, customProperty := range data.ProvisioningScheme.CustomProperties { + provSchemeModel := util.ObjectValueToTypedObject[ProvisioningSchemeModel](ctx, &resp.Diagnostics, data.ProvisioningScheme) + if !data.ProvisioningScheme.IsNull() && !provSchemeModel.CustomProperties.IsNull() { + customProperties := util.ObjectListToTypedArray[CustomPropertyModel](ctx, &resp.Diagnostics, provSchemeModel.CustomProperties) + for _, customProperty := range customProperties { propertyName := customProperty.Name.ValueString() if val, ok := MappedCustomProperties[propertyName]; ok { resp.Diagnostics.AddAttributeError( diff --git a/internal/daas/machine_catalog/machine_catalog_resource_model.go b/internal/daas/machine_catalog/machine_catalog_resource_model.go index 1919f7e..1d7cb67 100644 --- a/internal/daas/machine_catalog/machine_catalog_resource_model.go +++ b/internal/daas/machine_catalog/machine_catalog_resource_model.go @@ -1,41 +1,79 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package machine_catalog import ( "context" - "reflect" "regexp" "strings" citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" citrixclient "github.com/citrix/citrix-daas-rest-go/client" "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/citrix/terraform-provider-citrix/internal/validators" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "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/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) // MachineCatalogResourceModel maps the resource schema data. type MachineCatalogResourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - IsPowerManaged types.Bool `tfsdk:"is_power_managed"` - IsRemotePc types.Bool `tfsdk:"is_remote_pc"` - AllocationType types.String `tfsdk:"allocation_type"` - SessionSupport types.String `tfsdk:"session_support"` - Zone types.String `tfsdk:"zone"` - VdaUpgradeType types.String `tfsdk:"vda_upgrade_type"` - ProvisioningType types.String `tfsdk:"provisioning_type"` - ProvisioningScheme *ProvisioningSchemeModel `tfsdk:"provisioning_scheme"` - MachineAccounts []MachineAccountsModel `tfsdk:"machine_accounts"` - RemotePcOus []RemotePcOuModel `tfsdk:"remote_pc_ous"` - MinimumFunctionalLevel types.String `tfsdk:"minimum_functional_level"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + IsPowerManaged types.Bool `tfsdk:"is_power_managed"` + IsRemotePc types.Bool `tfsdk:"is_remote_pc"` + AllocationType types.String `tfsdk:"allocation_type"` + SessionSupport types.String `tfsdk:"session_support"` + Zone types.String `tfsdk:"zone"` + VdaUpgradeType types.String `tfsdk:"vda_upgrade_type"` + ProvisioningType types.String `tfsdk:"provisioning_type"` + ProvisioningScheme types.Object `tfsdk:"provisioning_scheme"` // ProvisioningSchemeModel + MachineAccounts types.List `tfsdk:"machine_accounts"` // List[MachineAccountsModel] + RemotePcOus types.List `tfsdk:"remote_pc_ous"` // List[RemotePcOuModel] + MinimumFunctionalLevel types.String `tfsdk:"minimum_functional_level"` + Scopes types.Set `tfsdk:"scopes"` //Set[String] } type MachineAccountsModel struct { - Hypervisor types.String `tfsdk:"hypervisor"` - Machines []MachineCatalogMachineModel `tfsdk:"machines"` + Hypervisor types.String `tfsdk:"hypervisor"` + Machines types.List `tfsdk:"machines"` +} + +func (MachineAccountsModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "hypervisor": schema.StringAttribute{ + Description: "The Id of the hypervisor in which the machines reside. Required only if `is_power_managed = true`", + Optional: true, + }, + "machines": schema.ListNestedAttribute{ + Description: "Machines to add to the catalog", + Required: true, + NestedObject: MachineCatalogMachineModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + }, + } +} + +func (MachineAccountsModel) GetAttributes() map[string]schema.Attribute { + return MachineAccountsModel{}.GetSchema().Attributes } type MachineCatalogMachineModel struct { @@ -50,23 +88,175 @@ type MachineCatalogMachineModel struct { Host types.String `tfsdk:"host"` } +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.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.SamRegex), "must be in the format DOMAIN\\MACHINE"), + }, + }, + "machine_name": schema.StringAttribute{ + Description: "The name of the machine. Required only if `is_power_managed = true`", + Optional: true, + }, + "region": schema.StringAttribute{ + Description: "**[Azure, GCP: Required]** The region in which the machine resides. Required only if `is_power_managed = true`", + Optional: true, + }, + "resource_group_name": schema.StringAttribute{ + Description: "**[Azure: Required]** The resource group in which the machine resides. Required only if `is_power_managed = true`", + Optional: true, + }, + "project_name": schema.StringAttribute{ + Description: "**[GCP: Required]** The project name in which the machine resides. Required only if `is_power_managed = true`", + Optional: true, + }, + "availability_zone": schema.StringAttribute{ + Description: "**[AWS: Required]** The availability zone in which the machine resides. Required only if `is_power_managed = true`", + Optional: true, + }, + "datacenter": schema.StringAttribute{ + Description: "**[vSphere: Required]** The datacenter in which the machine resides. Required only if `is_power_managed = true`", + Optional: true, + }, + "cluster": schema.StringAttribute{ + Description: "**[vSphere: Optional]** The cluster in which the machine resides. To be used only if `is_power_managed = true`", + Optional: true, + }, + "host": schema.StringAttribute{ + Description: "**[vSphere: Required]** The IP address or FQDN of the host in which the machine resides. Required only if `is_power_managed = true`", + Optional: true, + }, + }, + } +} + +func (MachineCatalogMachineModel) GetAttributes() map[string]schema.Attribute { + return MachineCatalogMachineModel{}.GetSchema().Attributes +} + // ProvisioningSchemeModel maps the nested provisioning scheme resource schema data. type ProvisioningSchemeModel struct { - Hypervisor types.String `tfsdk:"hypervisor"` - HypervisorResourcePool types.String `tfsdk:"hypervisor_resource_pool"` - AzureMachineConfig *AzureMachineConfigModel `tfsdk:"azure_machine_config"` - AwsMachineConfig *AwsMachineConfigModel `tfsdk:"aws_machine_config"` - GcpMachineConfig *GcpMachineConfigModel `tfsdk:"gcp_machine_config"` - VsphereMachineConfig *VsphereMachineConfigModel `tfsdk:"vsphere_machine_config"` - XenserverMachineConfig *XenserverMachineConfigModel `tfsdk:"xenserver_machine_config"` - NutanixMachineConfigModel *NutanixMachineConfigModel `tfsdk:"nutanix_machine_config"` - NumTotalMachines types.Int64 `tfsdk:"number_of_total_machines"` - NetworkMapping []NetworkMappingModel `tfsdk:"network_mapping"` - AvailabilityZones types.String `tfsdk:"availability_zones"` - IdentityType types.String `tfsdk:"identity_type"` - MachineDomainIdentity *MachineDomainIdentityModel `tfsdk:"machine_domain_identity"` - MachineAccountCreationRules *MachineAccountCreationRulesModel `tfsdk:"machine_account_creation_rules"` - CustomProperties []CustomPropertyModel `tfsdk:"custom_properties"` + Hypervisor types.String `tfsdk:"hypervisor"` + HypervisorResourcePool types.String `tfsdk:"hypervisor_resource_pool"` + AzureMachineConfig types.Object `tfsdk:"azure_machine_config"` // AzureMachineConfigModel + AwsMachineConfig types.Object `tfsdk:"aws_machine_config"` // AwsMachineConfigModel + GcpMachineConfig types.Object `tfsdk:"gcp_machine_config"` // GcpMachineConfigModel + VsphereMachineConfig types.Object `tfsdk:"vsphere_machine_config"` // VsphereMachineConfigModel + XenserverMachineConfig types.Object `tfsdk:"xenserver_machine_config"` // XenserverMachineConfigModel + NutanixMachineConfig types.Object `tfsdk:"nutanix_machine_config"` // NutanixMachineConfigModel + NumTotalMachines types.Int64 `tfsdk:"number_of_total_machines"` + NetworkMapping types.List `tfsdk:"network_mapping"` // List[NetworkMappingModel] + AvailabilityZones types.List `tfsdk:"availability_zones"` // List[string] + IdentityType types.String `tfsdk:"identity_type"` + MachineDomainIdentity types.Object `tfsdk:"machine_domain_identity"` // MachineDomainIdentityModel + MachineAccountCreationRules types.Object `tfsdk:"machine_account_creation_rules"` // MachineAccountCreationRulesModel + CustomProperties types.List `tfsdk:"custom_properties"` // List[CustomPropertyModel] +} + +func (ProvisioningSchemeModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Machine catalog provisioning scheme. Required when `provisioning_type = MCS`", + Optional: true, + Attributes: map[string]schema.Attribute{ + "hypervisor": schema.StringAttribute{ + Description: "Id of the hypervisor for creating the machines. Required only if using power managed machines.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "hypervisor_resource_pool": schema.StringAttribute{ + Description: "Id of the hypervisor resource pool that will be used for provisioning operations.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + "azure_machine_config": AzureMachineConfigModel{}.GetSchema(), + "aws_machine_config": AwsMachineConfigModel{}.GetSchema(), + "gcp_machine_config": GcpMachineConfigModel{}.GetSchema(), + "vsphere_machine_config": VsphereMachineConfigModel{}.GetSchema(), + "xenserver_machine_config": XenserverMachineConfigModel{}.GetSchema(), + "nutanix_machine_config": NutanixMachineConfigModel{}.GetSchema(), + "machine_domain_identity": MachineDomainIdentityModel{}.GetSchema(), + "number_of_total_machines": schema.Int64Attribute{ + Description: "Number of VDA machines allocated in the catalog.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + }, + "network_mapping": schema.ListNestedAttribute{ + Description: "Specifies how the attached NICs are mapped to networks. If this parameter is omitted, provisioned VMs are created with a single NIC, which is mapped to the default network in the hypervisor resource pool. If this parameter is supplied, machines are created with the number of NICs specified in the map, and each NIC is attached to the specified network." + "
" + + "Required when `provisioning_scheme.identity_type` is `AzureAD`.", + Optional: true, + NestedObject: NetworkMappingModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "availability_zones": schema.ListAttribute{ + Description: "The Availability Zones for provisioning virtual machines.", + Optional: true, + ElementType: types.StringType, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "identity_type": schema.StringAttribute{ + Description: "The identity type of the machines to be created. Supported values are`ActiveDirectory`, `AzureAD`, and `HybridAzureAD`.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(citrixorchestration.IDENTITYTYPE_ACTIVE_DIRECTORY), + string(citrixorchestration.IDENTITYTYPE_AZURE_AD), + string(citrixorchestration.IDENTITYTYPE_HYBRID_AZURE_AD), + string(citrixorchestration.IDENTITYTYPE_WORKGROUP), + ), + validators.AlsoRequiresOnValues( + []string{ + string(citrixorchestration.IDENTITYTYPE_ACTIVE_DIRECTORY), + }, + path.MatchRelative().AtParent().AtName("machine_domain_identity"), + ), + validators.AlsoRequiresOnValues( + []string{ + string(citrixorchestration.IDENTITYTYPE_HYBRID_AZURE_AD), + }, + path.MatchRelative().AtParent().AtName("machine_domain_identity"), + ), + validators.AlsoRequiresOnValues( + []string{ + string(citrixorchestration.IDENTITYTYPE_AZURE_AD), + }, + path.MatchRelative().AtParent().AtName("azure_machine_config"), + path.MatchRelative().AtParent().AtName("azure_machine_config").AtName("machine_profile"), + path.MatchRelative().AtParent().AtName("network_mapping"), + ), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "machine_account_creation_rules": MachineAccountCreationRulesModel{}.GetSchema(), + "custom_properties": schema.ListNestedAttribute{ + Description: "**This is an advanced feature. Use with caution.** Custom properties to be set for the machine catalog. For properties that are already supported as a terraform configuration field, please use terraform field instead.", + Optional: true, + NestedObject: CustomPropertyModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + }, + } +} + +func (ProvisioningSchemeModel) GetAttributes() map[string]schema.Attribute { + return ProvisioningSchemeModel{}.GetSchema().Attributes } type CustomPropertyModel struct { @@ -74,6 +264,25 @@ type CustomPropertyModel struct { Value types.String `tfsdk:"value"` } +func (CustomPropertyModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "Name of the custom property.", + Required: true, + }, + "value": schema.StringAttribute{ + Description: "Value of the custom property.", + Required: true, + }, + }, + } +} + +func (CustomPropertyModel) GetAttributes() map[string]schema.Attribute { + return CustomPropertyModel{}.GetSchema().Attributes +} + type MachineDomainIdentityModel struct { Domain types.String `tfsdk:"domain"` Ou types.String `tfsdk:"domain_ou"` @@ -81,36 +290,283 @@ type MachineDomainIdentityModel struct { ServiceAccountPassword types.String `tfsdk:"service_account_password"` } +func (MachineDomainIdentityModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "The domain identity for machines in the machine catalog." + "
" + + "Required when identity_type is set to `ActiveDirectory`", + 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.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.DomainFqdnRegex), "must be in FQDN format"), + }, + }, + "domain_ou": schema.StringAttribute{ + Description: "The organization unit that computer accounts will be created into.", + Optional: true, + }, + "service_account": schema.StringAttribute{ + Description: "Service account for the domain. Only the username is required; do not include the domain name.", + Required: true, + }, + "service_account_password": schema.StringAttribute{ + Description: "Service account password for the domain.", + Required: true, + Sensitive: true, + }, + }, + } +} + +func (MachineDomainIdentityModel) GetAttributes() map[string]schema.Attribute { + return MachineDomainIdentityModel{}.GetSchema().Attributes +} + // MachineAccountCreationRulesModel maps the nested machine account creation rules resource schema data. type MachineAccountCreationRulesModel struct { NamingScheme types.String `tfsdk:"naming_scheme"` NamingSchemeType types.String `tfsdk:"naming_scheme_type"` } +func (MachineAccountCreationRulesModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Rules specifying how Active Directory machine accounts should be created when machines are provisioned.", + Required: true, + Attributes: map[string]schema.Attribute{ + "naming_scheme": schema.StringAttribute{ + Description: "Defines the template name for AD accounts created in the identity pool.", + Required: true, + }, + "naming_scheme_type": schema.StringAttribute{ + Description: "Type of naming scheme. This defines the format of the variable part of the AD account names that will be created. Choose between `Numeric`, `Alphabetic` and `Unicode`.", + Required: true, + Validators: []validator.String{ + util.GetValidatorFromEnum(citrixorchestration.AllowedAccountNamingSchemeTypeEnumValues), + }, + }, + }, + } +} + +func (MachineAccountCreationRulesModel) GetAttributes() map[string]schema.Attribute { + return MachineAccountCreationRulesModel{}.GetSchema().Attributes +} + +// ensure NetworkMappingModel implements RefreshableListItemWithAttributes +var _ util.RefreshableListItemWithAttributes[citrixorchestration.NetworkMapResponseModel] = NetworkMappingModel{} + // NetworkMappingModel maps the nested network mapping resource schema data. type NetworkMappingModel struct { NetworkDevice types.String `tfsdk:"network_device"` Network types.String `tfsdk:"network"` } +func (n NetworkMappingModel) GetKey() string { + return n.NetworkDevice.ValueString() +} + +func (NetworkMappingModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "network_device": schema.StringAttribute{ + Description: "Name or Id of the network device.", + Required: true, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.Expressions{ + path.MatchRelative().AtParent().AtName("network"), + }...), + }, + }, + "network": schema.StringAttribute{ + Description: "The name of the virtual network that the device should be attached to. This must be a subnet within a Virtual Private Cloud item in the resource pool to which the Machine Catalog is associated." + "
" + + "For AWS, please specify the network mask of the network you want to use within the VPC.", + Required: true, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.Expressions{ + path.MatchRelative().AtParent().AtName("network_device"), + }...), + }, + }, + }, + } +} + +func (NetworkMappingModel) GetAttributes() map[string]schema.Attribute { + return NetworkMappingModel{}.GetSchema().Attributes +} + +// ensure RemotePcOuModel implements RefreshableListItemWithAttributes +var _ util.RefreshableListItemWithAttributes[citrixorchestration.RemotePCEnrollmentScopeResponseModel] = RemotePcOuModel{} + type RemotePcOuModel struct { IncludeSubFolders types.Bool `tfsdk:"include_subfolders"` OUName types.String `tfsdk:"ou_name"` } -func (r MachineCatalogResourceModel) RefreshPropertyValues(ctx context.Context, client *citrixclient.CitrixDaasClient, catalog *citrixorchestration.MachineCatalogDetailResponseModel, connectionType *citrixorchestration.HypervisorConnectionType, machines *citrixorchestration.MachineResponseModelCollection, pluginId string) MachineCatalogResourceModel { +func (r RemotePcOuModel) GetKey() string { + return r.OUName.ValueString() +} + +func (RemotePcOuModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "include_subfolders": schema.BoolAttribute{ + Description: "Specify if subfolders should be included.", + Required: true, + }, + "ou_name": schema.StringAttribute{ + Description: "Name of the OU.", + Required: true, + }, + }, + } +} + +func (RemotePcOuModel) GetAttributes() map[string]schema.Attribute { + return RemotePcOuModel{}.GetSchema().Attributes +} + +func GetSchema() schema.Schema { + return schema.Schema{ + Description: "Manages a machine catalog.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the machine catalog.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the machine catalog.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "description": schema.StringAttribute{ + Description: "Description of the machine catalog.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "is_power_managed": schema.BoolAttribute{ + Description: "Specify if the machines in the machine catalog will be power managed.", + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "is_remote_pc": schema.BoolAttribute{ + Description: "Specify if this catalog is for Remote PC access.", + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "allocation_type": schema.StringAttribute{ + Description: "Denotes how the machines in the catalog are allocated to a user. Choose between `Static` and `Random`. Allocation type should be `Random` when `session_support = MultiSession`.", + Required: true, + Validators: []validator.String{ + util.GetValidatorFromEnum(citrixorchestration.AllowedAllocationTypeEnumValues), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "session_support": schema.StringAttribute{ + Description: "Session support type. Choose between `SingleSession` and `MultiSession`. Session support should be SingleSession when `is_remote_pc = true`.", + Required: true, + Validators: []validator.String{ + util.GetValidatorFromEnum(citrixorchestration.AllowedSessionSupportEnumValues), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "zone": schema.StringAttribute{ + Description: "Id of the zone the machine catalog is associated with.", + Required: true, + }, + "vda_upgrade_type": schema.StringAttribute{ + Description: "Type of Vda Upgrade. Choose between LTSR and CR. When omitted, Vda Upgrade is disabled.", + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "LTSR", + "CR", + ), + }, + }, + "provisioning_type": schema.StringAttribute{ + Description: "Specifies how the machines are provisioned in the catalog.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + string(citrixorchestration.PROVISIONINGTYPE_MCS), + string(citrixorchestration.PROVISIONINGTYPE_MANUAL), + ), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "machine_accounts": schema.ListNestedAttribute{ + Description: "Machine accounts to add to the catalog. Only to be used when using `provisioning_type = MANUAL`", + Optional: true, + NestedObject: MachineAccountsModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "remote_pc_ous": schema.ListNestedAttribute{ + Description: "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`.", + Optional: true, + NestedObject: RemotePcOuModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "minimum_functional_level": schema.StringAttribute{ + Description: "Specifies the minimum functional level for the VDA machines in the catalog. Defaults to `L7_20`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("L7_20"), + Validators: []validator.String{ + stringvalidator.OneOfCaseInsensitive(util.GetAllowedFunctionalLevelValues()...), + }, + }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The IDs of the scopes for the machine catalog to be a part of.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + validator.String( + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + ), + ), + }, + }, + "provisioning_scheme": ProvisioningSchemeModel{}.GetSchema(), + }, + } +} + +func (r MachineCatalogResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixclient.CitrixDaasClient, catalog *citrixorchestration.MachineCatalogDetailResponseModel, connectionType *citrixorchestration.HypervisorConnectionType, machines *citrixorchestration.MachineResponseModelCollection, pluginId string) MachineCatalogResourceModel { // Machine Catalog Properties r.Id = types.StringValue(catalog.GetId()) r.Name = types.StringValue(catalog.GetName()) - if catalog.GetDescription() != "" { - r.Description = types.StringValue(catalog.GetDescription()) - } else { - r.Description = types.StringNull() - } + r.Description = types.StringValue(catalog.GetDescription()) allocationType := catalog.GetAllocationType() r.AllocationType = types.StringValue(allocationTypeEnumToString(allocationType)) sessionSupport := catalog.GetSessionSupport() - r.SessionSupport = types.StringValue(reflect.ValueOf(sessionSupport).String()) + r.SessionSupport = types.StringValue(string(sessionSupport)) minimumFunctionalLevel := catalog.GetMinimumFunctionalLevel() r.MinimumFunctionalLevel = types.StringValue(string(minimumFunctionalLevel)) @@ -128,29 +584,38 @@ func (r MachineCatalogResourceModel) RefreshPropertyValues(ctx context.Context, provtype := catalog.GetProvisioningType() r.ProvisioningType = types.StringValue(string(provtype)) - if provtype == citrixorchestration.PROVISIONINGTYPE_MANUAL || !r.IsPowerManaged.IsNull() { + if provtype == citrixorchestration.PROVISIONINGTYPE_MANUAL { r.IsPowerManaged = types.BoolValue(catalog.GetIsPowerManaged()) + } else { + r.IsPowerManaged = types.BoolNull() } if catalog.ProvisioningType == citrixorchestration.PROVISIONINGTYPE_MANUAL { // Handle machines - r = r.updateCatalogWithMachines(ctx, client, machines) + r = r.updateCatalogWithMachines(ctx, diagnostics, client, machines) } - r = r.updateCatalogWithRemotePcConfig(catalog) + r = r.updateCatalogWithRemotePcConfig(ctx, diagnostics, catalog) if catalog.ProvisioningScheme == nil { - r.ProvisioningScheme = nil + if attributesMap, err := util.AttributeMapFromObject(ProvisioningSchemeModel{}); err == nil { + r.ProvisioningScheme = types.ObjectNull(attributesMap) + } else { + diagnostics.AddWarning("Error when creating null ProvisioningSchemeModel", err.Error()) + } return r } + scopeIds := util.GetIdsForScopeObjects(catalog.GetScopes()) + r.Scopes = util.StringArrayToStringSet(ctx, diagnostics, scopeIds) + // Provisioning Scheme Properties - r = r.updateCatalogWithProvScheme(ctx, client, catalog, connectionType, pluginId) + r = r.updateCatalogWithProvScheme(ctx, diagnostics, client, catalog, connectionType, pluginId) return r } -func (networkMapping NetworkMappingModel) RefreshListItem(nic citrixorchestration.NetworkMapResponseModel) NetworkMappingModel { +func (networkMapping NetworkMappingModel) RefreshListItem(_ context.Context, _ *diag.Diagnostics, nic citrixorchestration.NetworkMapResponseModel) util.ModelWithAttributes { networkMapping.NetworkDevice = types.StringValue(nic.GetDeviceId()) network := nic.GetNetwork() segments := strings.Split(network.GetXDPath(), "\\") diff --git a/internal/daas/machine_catalog/machine_catalog_schema_utils.go b/internal/daas/machine_catalog/machine_catalog_schema_utils.go deleted file mode 100644 index 15ec158..0000000 --- a/internal/daas/machine_catalog/machine_catalog_schema_utils.go +++ /dev/null @@ -1,877 +0,0 @@ -// Copyright © 2023. Citrix Systems, Inc. - -package machine_catalog - -import ( - "context" - "regexp" - - citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" - "github.com/citrix/terraform-provider-citrix/internal/util" - "github.com/citrix/terraform-provider-citrix/internal/validators" - "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" - "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" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -func getSchemaForMachineCatalogResource() schema.Schema { - return schema.Schema{ - Description: "Manages a machine catalog.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the machine catalog.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the machine catalog.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "description": schema.StringAttribute{ - Description: "Description of the machine catalog.", - Optional: true, - }, - "is_power_managed": schema.BoolAttribute{ - Description: "Specify if the machines in the machine catalog will be power managed.", - Optional: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - }, - "is_remote_pc": schema.BoolAttribute{ - Description: "Specify if this catalog is for Remote PC access.", - Optional: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - }, - "allocation_type": schema.StringAttribute{ - Description: "Denotes how the machines in the catalog are allocated to a user. Choose between `Static` and `Random`. Allocation type should be `Random` when `session_support = MultiSession`.", - Required: true, - Validators: []validator.String{ - util.GetValidatorFromEnum(citrixorchestration.AllowedAllocationTypeEnumValues), - }, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "session_support": schema.StringAttribute{ - Description: "Session support type. Choose between `SingleSession` and `MultiSession`. Session support should be SingleSession when `is_remote_pc = true`.", - Required: true, - Validators: []validator.String{ - util.GetValidatorFromEnum(citrixorchestration.AllowedSessionSupportEnumValues), - }, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "zone": schema.StringAttribute{ - Description: "Id of the zone the machine catalog is associated with.", - Required: true, - }, - "vda_upgrade_type": schema.StringAttribute{ - Description: "Type of Vda Upgrade. Choose between LTSR and CR. When omitted, Vda Upgrade is disabled.", - Optional: true, - Validators: []validator.String{ - stringvalidator.OneOf( - "LTSR", - "CR", - ), - }, - }, - "provisioning_type": schema.StringAttribute{ - Description: "Specifies how the machines are provisioned in the catalog.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - string(citrixorchestration.PROVISIONINGTYPE_MCS), - string(citrixorchestration.PROVISIONINGTYPE_MANUAL), - ), - }, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "machine_accounts": schema.ListNestedAttribute{ - Description: "List of machine accounts to add to the catalog. Only to be used when using `provisioning_type = MANUAL`", - Optional: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "hypervisor": schema.StringAttribute{ - Description: "The Id of the hypervisor in which the machines reside. Required only if `is_power_managed = true`", - Optional: true, - }, - "machines": schema.ListNestedAttribute{ - Description: "List of machines", - Required: true, - NestedObject: 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.", - Required: true, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.SamRegex), "must be in the format DOMAIN\\MACHINE"), - }, - }, - "machine_name": schema.StringAttribute{ - Description: "The name of the machine. Required only if `is_power_managed = true`", - Optional: true, - }, - "region": schema.StringAttribute{ - Description: "**[Azure, GCP: Required]** The region in which the machine resides. Required only if `is_power_managed = true`", - Optional: true, - }, - "resource_group_name": schema.StringAttribute{ - Description: "**[Azure: Required]** The resource group in which the machine resides. Required only if `is_power_managed = true`", - Optional: true, - }, - "project_name": schema.StringAttribute{ - Description: "**[GCP: Required]** The project name in which the machine resides. Required only if `is_power_managed = true`", - Optional: true, - }, - "availability_zone": schema.StringAttribute{ - Description: "**[AWS: Required]** The availability zone in which the machine resides. Required only if `is_power_managed = true`", - Optional: true, - }, - "datacenter": schema.StringAttribute{ - Description: "**[vSphere: Required]** The datacenter in which the machine resides. Required only if `is_power_managed = true`", - Optional: true, - }, - "cluster": schema.StringAttribute{ - Description: "**[vSphere: Optional]** The cluster in which the machine resides. To be used only if `is_power_managed = true`", - Optional: true, - }, - "host": schema.StringAttribute{ - Description: "**[vSphere: Required]** The IP address or FQDN of the host in which the machine resides. Required only if `is_power_managed = true`", - Optional: true, - }, - }, - }, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - }, - }, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "remote_pc_ous": schema.ListNestedAttribute{ - Description: "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`.", - Optional: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "include_subfolders": schema.BoolAttribute{ - Description: "Specify if subfolders should be included.", - Required: true, - }, - "ou_name": schema.StringAttribute{ - Description: "Name of the OU.", - Required: true, - }, - }, - }, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "minimum_functional_level": schema.StringAttribute{ - Description: "Specifies the minimum functional level for the VDA machines in the catalog. Defaults to `L7_20`.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString("L7_20"), - Validators: []validator.String{ - stringvalidator.OneOfCaseInsensitive(util.GetAllowedFunctionalLevelValues()...), - }, - }, - "provisioning_scheme": schema.SingleNestedAttribute{ - Description: "Machine catalog provisioning scheme. Required when `provisioning_type = MCS`", - Optional: true, - Attributes: map[string]schema.Attribute{ - "hypervisor": schema.StringAttribute{ - Description: "Id of the hypervisor for creating the machines. Required only if using power managed machines.", - Required: true, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "hypervisor_resource_pool": schema.StringAttribute{ - Description: "Id of the hypervisor resource pool that will be used for provisioning operations.", - Required: true, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), - }, - }, - "azure_machine_config": schema.SingleNestedAttribute{ - Description: "Machine Configuration For Azure MCS catalog.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "service_offering": schema.StringAttribute{ - Description: "The Azure VM Sku to use when creating machines.", - Required: true, - }, - "azure_master_image": schema.SingleNestedAttribute{ - Description: "Details of the Azure Image to use for creating machines.", - Required: true, - Attributes: map[string]schema.Attribute{ - "resource_group": schema.StringAttribute{ - Description: "The Azure Resource Group where the image VHD / managed disk / snapshot for creating machines is located.", - Required: true, - }, - "shared_subscription": schema.StringAttribute{ - Description: "The Azure Subscription ID where the image VHD / managed disk / snapshot for creating machines is located. Only required if the image is not in the same subscription of the hypervisor.", - Optional: true, - }, - "master_image": schema.StringAttribute{ - Description: "The name of the virtual machine snapshot or VM template that will be used. This identifies the hard disk to be used and the default values for the memory and processors. Omit this field if you want to use gallery_image.", - Optional: true, - }, - "storage_account": schema.StringAttribute{ - Description: "The Azure Storage Account where the image VHD for creating machines is located. Only applicable to Azure VHD image blob.", - Optional: true, - Validators: []validator.String{ - stringvalidator.AlsoRequires(path.Expressions{ - path.MatchRelative().AtParent().AtName("container"), - }...), - stringvalidator.AlsoRequires(path.Expressions{ - path.MatchRelative().AtParent().AtName("resource_group"), - }...), - }, - }, - "container": schema.StringAttribute{ - Description: "The Azure Storage Account Container where the image VHD for creating machines is located. Only applicable to Azure VHD image blob.", - Optional: true, - Validators: []validator.String{ - stringvalidator.AlsoRequires(path.Expressions{ - path.MatchRelative().AtParent().AtName("storage_account"), - }...), - stringvalidator.AlsoRequires(path.Expressions{ - path.MatchRelative().AtParent().AtName("resource_group"), - }...), - }, - }, - "gallery_image": schema.SingleNestedAttribute{ - Description: "Details of the Azure Image Gallery image to use for creating machines. Only Applicable to Azure Image Gallery image.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "gallery": schema.StringAttribute{ - Description: "The Azure Image Gallery where the image for creating machines is located. Only applicable to Azure Image Gallery image.", - Required: true, - }, - "definition": schema.StringAttribute{ - Description: "The image definition for the image to be used in the Azure Image Gallery. Only applicable to Azure Image Gallery image.", - Required: true, - }, - "version": schema.StringAttribute{ - Description: "The image version for the image to be used in the Azure Image Gallery. Only applicable to Azure Image Gallery image.", - Required: true, - }, - }, - Validators: []validator.Object{ - objectvalidator.AlsoRequires(path.Expressions{ - path.MatchRelative().AtParent().AtName("resource_group"), - }...), - objectvalidator.ConflictsWith(path.Expressions{ - path.MatchRelative().AtParent().AtName("storage_account"), - }...), - objectvalidator.ConflictsWith(path.Expressions{ - path.MatchRelative().AtParent().AtName("container"), - }...), - objectvalidator.ConflictsWith(path.Expressions{ - path.MatchRelative().AtParent().AtName("master_image"), - }...), - }, - }, - }, - }, - "storage_type": schema.StringAttribute{ - Description: "Storage account type used for provisioned virtual machine disks on Azure. Storage types include: `Standard_LRS`, `StandardSSD_LRS` and `Premium_LRS`.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - util.StandardLRS, - util.StandardSSDLRS, - util.Premium_LRS, - util.AzureEphemeralOSDisk, - ), - }, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplaceIf( - func(_ context.Context, req planmodifier.StringRequest, resp *stringplanmodifier.RequiresReplaceIfFuncResponse) { - resp.RequiresReplace = req.StateValue.ValueString() == util.AzureEphemeralOSDisk || req.PlanValue.ValueString() == util.AzureEphemeralOSDisk - }, - "Updating storage_type is not allowed when using Azure Ephemeral OS Disk.", - "Updating storage_type is not allowed when using Azure Ephemeral OS Disk.", - ), - }, - }, - "use_azure_compute_gallery": schema.SingleNestedAttribute{ - Description: "Use this to place prepared image in Azure Compute Gallery. Required when `storage_type = Azure_Ephemeral_OS_Disk`.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "replica_ratio": schema.Int64Attribute{ - Description: "The ratio of virtual machines to image replicas that you want Azure to keep.", - Required: true, - }, - "replica_maximum": schema.Int64Attribute{ - Description: "The maximum number of image replicas that you want Azure to keep.", - Required: true, - }, - }, - }, - "license_type": schema.StringAttribute{ - Description: "Windows license type used to provision virtual machines in Azure at the base compute rate. License types include: `Windows_Client` and `Windows_Server`.", - Optional: true, - Validators: []validator.String{ - stringvalidator.OneOf( - util.WindowsClientLicenseType, - util.WindowsServerLicenseType, - ), - }, - }, - "enroll_in_intune": schema.BoolAttribute{ - Description: "Specify whether to enroll machines in Microsoft Intune. Use this property only when `identity_type` is set to `AzureAD`.", - Optional: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - }, - "disk_encryption_set": schema.SingleNestedAttribute{ - Description: "The configuration for Disk Encryption Set (DES). The DES must be in the same subscription and region as your resources. If your master image is encrypted with a DES, use the same DES when creating this machine catalog. When using a DES, if you later disable the key with which the corresponding DES is associated in Azure, you can no longer power on the machines in this catalog or add machines to it.", - Optional: true, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.RequiresReplace(), - }, - Attributes: map[string]schema.Attribute{ - "disk_encryption_set_name": schema.StringAttribute{ - Description: "The name of the disk encryption set.", - Required: true, - }, - "disk_encryption_set_resource_group": schema.StringAttribute{ - Description: "The name of the resource group in which the disk encryption set resides.", - Required: true, - }, - }, - }, - "vda_resource_group": schema.StringAttribute{ - Description: "Designated resource group where the VDA VMs will be located on Azure.", - Optional: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "use_managed_disks": schema.BoolAttribute{ - Description: "Indicate whether to use Azure managed disks for the provisioned virtual machine.", - Optional: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - }, - "machine_profile": schema.SingleNestedAttribute{ - Description: "The name of the virtual machine or template spec that will be used to identify the default value for the tags, virtual machine size, boot diagnostics, host cache property of OS disk, accelerated networking and availability zone." + "
" + - "Required when identity_type is set to `AzureAD`", - Optional: true, - Attributes: map[string]schema.Attribute{ - "machine_profile_vm_name": schema.StringAttribute{ - Description: "The name of the machine profile virtual machine.", - Optional: true, - }, - "machine_profile_template_spec_name": schema.StringAttribute{ - Description: "The name of the machine profile template spec.", - Optional: true, - Validators: []validator.String{ - stringvalidator.AlsoRequires(path.Expressions{ - path.MatchRelative().AtParent().AtName("machine_profile_template_spec_version"), - }...), - stringvalidator.ExactlyOneOf(path.Expressions{ - path.MatchRelative().AtParent().AtName("machine_profile_vm_name"), - }...), - }, - }, - "machine_profile_template_spec_version": schema.StringAttribute{ - Description: "The version of the machine profile template spec.", - Optional: true, - Validators: []validator.String{ - stringvalidator.AlsoRequires(path.Expressions{ - path.MatchRelative().AtParent().AtName("machine_profile_template_spec_name"), - }...), - }, - }, - "machine_profile_resource_group": schema.StringAttribute{ - Description: "The name of the resource group where the machine profile VM or template spec is located.", - Required: true, - }, - }, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.RequiresReplaceIf( - func(_ context.Context, req planmodifier.ObjectRequest, resp *objectplanmodifier.RequiresReplaceIfFuncResponse) { - resp.RequiresReplace = req.ConfigValue.IsNull() != req.StateValue.IsNull() - }, - "Force replace when machine_profile is added or removed. Update is allowed only if previously set.", - "Force replace when machine_profile is added or removed. Update is allowed only if previously set.", - ), - }, - }, - "writeback_cache": schema.SingleNestedAttribute{ - Description: "Write-back Cache config. Leave this empty to disable Write-back Cache. Write-back Cache requires Machine image with Write-back Cache plugin installed.", - Optional: true, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.RequiresReplace(), - }, - Attributes: map[string]schema.Attribute{ - "persist_wbc": schema.BoolAttribute{ - Description: "Persist Write-back Cache", - Required: true, - }, - "wbc_disk_storage_type": schema.StringAttribute{ - Description: "Type of naming scheme. Choose between Numeric and Alphabetic.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - "StandardSSD_LRS", - "Standard_LRS", - "Premium_LRS", - ), - }, - }, - "persist_os_disk": schema.BoolAttribute{ - Description: "Persist the OS disk when power cycling the non-persistent provisioned virtual machine.", - Required: true, - }, - "persist_vm": schema.BoolAttribute{ - Description: "Persist the non-persistent provisioned virtual machine in Azure environments when power cycling. This property only applies when the PersistOsDisk property is set to True.", - Required: true, - }, - "storage_cost_saving": schema.BoolAttribute{ - Description: "Save storage cost by downgrading the storage type of the disk to Standard HDD when VM shut down.", - Required: true, - }, - "writeback_cache_disk_size_gb": schema.Int64Attribute{ - Description: "The size in GB of any temporary storage disk used by the write back cache.", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(0), - }, - }, - "writeback_cache_memory_size_mb": schema.Int64Attribute{ - Description: "The size of the in-memory write back cache in MB.", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(0), - }, - }, - }, - }, - }, - }, - "aws_machine_config": schema.SingleNestedAttribute{ - Description: "Machine Configuration For AWS EC2 MCS catalog.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "service_offering": schema.StringAttribute{ - Description: "The AWS VM Sku to use when creating machines.", - Required: true, - Validators: []validator.String{ - stringvalidator.RegexMatches( - regexp.MustCompile(util.AwsEc2InstanceTypeRegex), - "must follow AWS EC2 instance type naming convention in lower case. Eg: t2.micro, m5.large, etc.", - ), - }, - }, - "master_image": schema.StringAttribute{ - Description: "The name of the virtual machine image that will be used.", - Required: true, - }, - "image_ami": schema.StringAttribute{ - Description: "AMI of the AWS image to be used as the template image for the machine catalog.", - Required: true, - }, - "security_groups": schema.ListAttribute{ - ElementType: types.StringType, - Description: "List of security groups to associate with the machine. When omitted, the default security group of the VPC will be used by default.", - Required: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - "tenancy_type": schema.StringAttribute{ - Description: "Tenancy type of the machine. Choose between `Shared`, `Instance` and `Host`.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - "Shared", - "Instance", - "Host", - ), - }, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - }, - }, - "gcp_machine_config": schema.SingleNestedAttribute{ - Description: "Machine Configuration For GCP MCS catalog.", - Optional: true, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.RequiresReplace(), - }, - Attributes: map[string]schema.Attribute{ - "master_image": schema.StringAttribute{ - Description: "The name of the virtual machine snapshot or VM template that will be used. This identifies the hard disk to be used and the default values for the memory and processors.", - Required: true, - }, - "machine_profile": schema.StringAttribute{ - Description: "The name of the virtual machine template that will be used to identify the default value for the tags, virtual machine size, boot diagnostics, host cache property of OS disk, accelerated networking and availability zone. If not specified, the VM specified in master_image will be used as template.", - Optional: true, - }, - "machine_snapshot": schema.StringAttribute{ - Description: "The name of the virtual machine snapshot of a GCP VM that will be used as master image.", - Optional: true, - }, - "storage_type": schema.StringAttribute{ - Description: "Storage type used for provisioned virtual machine disks on GCP. Storage types include: `pd-standar`, `pd-balanced`, `pd-ssd` and `pd-extreme`.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - "pd-standard", - "pd-balanced", - "pd-ssd", - "pd-extreme", - ), - }, - }, - "writeback_cache": schema.SingleNestedAttribute{ - Description: "Write-back Cache config. Leave this empty to disable Write-back Cache.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "persist_wbc": schema.BoolAttribute{ - Description: "Persist Write-back Cache", - Required: true, - }, - "wbc_disk_storage_type": schema.StringAttribute{ - Description: "Type of naming scheme. Choose between Numeric and Alphabetic.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - "pd-standard", - "pd-balanced", - "pd-ssd", - ), - }, - }, - "persist_os_disk": schema.BoolAttribute{ - Description: "Persist the OS disk when power cycling the non-persistent provisioned virtual machine.", - Required: true, - }, - "writeback_cache_disk_size_gb": schema.Int64Attribute{ - Description: "The size in GB of any temporary storage disk used by the write back cache.", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(0), - }, - }, - "writeback_cache_memory_size_mb": schema.Int64Attribute{ - Description: "The size of the in-memory write back cache in MB.", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(0), - }, - }, - }, - }, - }, - }, - "vsphere_machine_config": schema.SingleNestedAttribute{ - Description: "Machine Configuration for vSphere MCS catalog.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "master_image_vm": schema.StringAttribute{ - Description: "The name of the virtual machine that will be used as master image. This property is case sensitive.", - Required: true, - }, - "image_snapshot": schema.StringAttribute{ - Description: "The Snapshot of the virtual machine specified in `master_image_vm`. Specify the relative path of the snapshot. Eg: snaphost-1/snapshot-2/snapshot-3. This property is case sensitive.", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "cpu_count": schema.Int64Attribute{ - Description: "The number of processors that virtual machines created from the provisioning scheme should use.", - Required: true, - }, - "memory_mb": schema.Int64Attribute{ - Description: "The maximum amount of memory that virtual machines created from the provisioning scheme should use.", - Required: true, - }, - "writeback_cache": schema.SingleNestedAttribute{ - Description: "Write-back Cache config. Leave this empty to disable Write-back Cache.", - Optional: true, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.RequiresReplace(), - }, - Attributes: map[string]schema.Attribute{ - "writeback_cache_disk_size_gb": schema.Int64Attribute{ - Description: "The size in GB of any temporary storage disk used by the write back cache.", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(0), - }, - }, - "writeback_cache_memory_size_mb": schema.Int64Attribute{ - Description: "The size of the in-memory write back cache in MB.", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(0), - }, - }, - "writeback_cache_drive_letter": schema.StringAttribute{ - Description: "The drive letter assigned for write back cache disk.", - Optional: true, - Validators: []validator.String{ - stringvalidator.LengthBetween(1, 1), - }, - }, - }, - }, - }, - }, - "xenserver_machine_config": schema.SingleNestedAttribute{ - Description: "Machine Configuration For XenServer MCS catalog.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "master_image_vm": schema.StringAttribute{ - Description: "The name of the virtual machine that will be used as master image. This property is case sensitive.", - Required: true, - }, - "image_snapshot": schema.StringAttribute{ - Description: "The Snapshot of the virtual machine specified in `master_image_vm`. Specify the relative path of the snapshot. Eg: snaphost-1/snapshot-2/snapshot-3. This property is case sensitive.", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "cpu_count": schema.Int64Attribute{ - Description: "Number of CPU cores for the VDA VMs.", - Required: true, - }, - "memory_mb": schema.Int64Attribute{ - Description: "Size of the memory in MB for the VDA VMs.", - Required: true, - }, - "writeback_cache": schema.SingleNestedAttribute{ - Description: "Write-back Cache config. Leave this empty to disable Write-back Cache.", - Optional: true, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.RequiresReplace(), - }, - Attributes: map[string]schema.Attribute{ - "writeback_cache_disk_size_gb": schema.Int64Attribute{ - Description: "The size in GB of any temporary storage disk used by the write back cache.", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(0), - }, - }, - "writeback_cache_memory_size_mb": schema.Int64Attribute{ - Description: "The size of the in-memory write back cache in MB.", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(0), - }, - }, - }, - }, - }, - }, - "nutanix_machine_config": schema.SingleNestedAttribute{ - Description: "Machine Configuration For Nutanix MCS catalog.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "container": schema.StringAttribute{ - Description: "The name of the container where the virtual machines' identity disks will be placed.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "master_image": schema.StringAttribute{ - Description: "The name of the master image that will be the template for all virtual machines in this catalog.", - Required: true, - }, - "cpu_count": schema.Int64Attribute{ - Description: "The number of processors that virtual machines created from the provisioning scheme should use.", - Required: true, - }, - "cores_per_cpu_count": schema.Int64Attribute{ - Description: "The number of cores per processor that virtual machines created from the provisioning scheme should use.", - Required: true, - }, - "memory_mb": schema.Int64Attribute{ - Description: "The maximum amount of memory that virtual machines created from the provisioning scheme should use.", - Required: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - }, - }, - }, - "machine_domain_identity": schema.SingleNestedAttribute{ - Description: "The domain identity for machines in the machine catalog." + "
" + - "Required when identity_type is set to `ActiveDirectory`", - 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.", - Required: true, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.DomainFqdnRegex), "must be in FQDN format"), - }, - }, - "domain_ou": schema.StringAttribute{ - Description: "The organization unit that computer accounts will be created into.", - Optional: true, - }, - "service_account": schema.StringAttribute{ - Description: "Service account for the domain. Only the username is required; do not include the domain name.", - Required: true, - }, - "service_account_password": schema.StringAttribute{ - Description: "Service account password for the domain.", - Required: true, - Sensitive: true, - }, - }, - }, - "number_of_total_machines": schema.Int64Attribute{ - Description: "Number of VDA machines allocated in the catalog.", - Required: true, - Validators: []validator.Int64{ - int64validator.AtLeast(0), - }, - }, - "network_mapping": schema.ListNestedAttribute{ - Description: "Specifies how the attached NICs are mapped to networks. If this parameter is omitted, provisioned VMs are created with a single NIC, which is mapped to the default network in the hypervisor resource pool. If this parameter is supplied, machines are created with the number of NICs specified in the map, and each NIC is attached to the specified network." + "
" + - "Required when `provisioning_scheme.identity_type` is `AzureAD`.", - Optional: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "network_device": schema.StringAttribute{ - Description: "Name or Id of the network device.", - Required: true, - Validators: []validator.String{ - stringvalidator.AlsoRequires(path.Expressions{ - path.MatchRelative().AtParent().AtName("network"), - }...), - }, - }, - "network": schema.StringAttribute{ - Description: "The name of the virtual network that the device should be attached to. This must be a subnet within a Virtual Private Cloud item in the resource pool to which the Machine Catalog is associated." + "
" + - "For AWS, please specify the network mask of the network you want to use within the VPC.", - Required: true, - Validators: []validator.String{ - stringvalidator.AlsoRequires(path.Expressions{ - path.MatchRelative().AtParent().AtName("network_device"), - }...), - }, - }, - }, - }, - }, - "availability_zones": schema.StringAttribute{ - Description: "The Availability Zones for provisioning virtual machines. Use a comma as a delimiter for multiple availability_zones.", - Optional: true, - }, - "identity_type": schema.StringAttribute{ - Description: "The identity type of the machines to be created. Supported values are`ActiveDirectory`, `AzureAD`, and `HybridAzureAD`.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf( - string(citrixorchestration.IDENTITYTYPE_ACTIVE_DIRECTORY), - string(citrixorchestration.IDENTITYTYPE_AZURE_AD), - string(citrixorchestration.IDENTITYTYPE_HYBRID_AZURE_AD), - string(citrixorchestration.IDENTITYTYPE_WORKGROUP), - ), - validators.AlsoRequiresOnValues( - []string{ - string(citrixorchestration.IDENTITYTYPE_ACTIVE_DIRECTORY), - }, - path.MatchRelative().AtParent().AtName("machine_domain_identity"), - ), - validators.AlsoRequiresOnValues( - []string{ - string(citrixorchestration.IDENTITYTYPE_HYBRID_AZURE_AD), - }, - path.MatchRelative().AtParent().AtName("machine_domain_identity"), - ), - validators.AlsoRequiresOnValues( - []string{ - string(citrixorchestration.IDENTITYTYPE_AZURE_AD), - }, - path.MatchRelative().AtParent().AtName("azure_machine_config"), - path.MatchRelative().AtParent().AtName("azure_machine_config").AtName("machine_profile"), - path.MatchRelative().AtParent().AtName("network_mapping"), - ), - }, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "machine_account_creation_rules": schema.SingleNestedAttribute{ - Description: "Rules specifying how Active Directory machine accounts should be created when machines are provisioned.", - Required: true, - Attributes: map[string]schema.Attribute{ - "naming_scheme": schema.StringAttribute{ - Description: "Defines the template name for AD accounts created in the identity pool.", - Required: true, - }, - "naming_scheme_type": schema.StringAttribute{ - Description: "Type of naming scheme. This defines the format of the variable part of the AD account names that will be created. Choose between `Numeric`, `Alphabetic` and `Unicode`.", - Required: true, - Validators: []validator.String{ - util.GetValidatorFromEnum(citrixorchestration.AllowedAccountNamingSchemeTypeEnumValues), - }, - }, - }, - }, - "custom_properties": schema.ListNestedAttribute{ - Description: "**This is an advanced feature. Use with caution.** Custom properties to be set for the machine catalog. For properties that are already supported as a terraform configuration field, please use terraform field instead.", - Optional: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "name": schema.StringAttribute{ - Description: "Name of the custom property.", - Required: true, - }, - "value": schema.StringAttribute{ - Description: "Value of the custom property.", - Required: true, - }, - }, - }, - }, - }, - }, - }, - } -} diff --git a/internal/daas/machine_catalog/machine_config.go b/internal/daas/machine_catalog/machine_config.go index 678d313..e614826 100644 --- a/internal/daas/machine_catalog/machine_config.go +++ b/internal/daas/machine_catalog/machine_config.go @@ -1,13 +1,29 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package machine_catalog import ( + "context" + "regexp" "strconv" "strings" "github.com/citrix/citrix-daas-rest-go/citrixorchestration" "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" + "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" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "golang.org/x/exp/slices" ) @@ -15,60 +31,389 @@ import ( type AzureMachineConfigModel struct { ServiceOffering types.String `tfsdk:"service_offering"` /** Azure Hypervisor **/ - AzureMasterImage *AzureMasterImageModel `tfsdk:"azure_master_image"` - VdaResourceGroup types.String `tfsdk:"vda_resource_group"` - StorageType types.String `tfsdk:"storage_type"` - UseAzureComputeGallery *AzureComputeGallerySettings `tfsdk:"use_azure_compute_gallery"` - LicenseType types.String `tfsdk:"license_type"` - UseManagedDisks types.Bool `tfsdk:"use_managed_disks"` - MachineProfile *AzureMachineProfileModel `tfsdk:"machine_profile"` - WritebackCache *AzureWritebackCacheModel `tfsdk:"writeback_cache"` - DiskEncryptionSet *AzureDiskEncryptionSetModel `tfsdk:"disk_encryption_set"` - EnrollInIntune types.Bool `tfsdk:"enroll_in_intune"` + AzureMasterImage types.Object `tfsdk:"azure_master_image"` + MasterImageNote types.String `tfsdk:"master_image_note"` + ImageUpdateRebootOptions types.Object `tfsdk:"image_update_reboot_options"` + VdaResourceGroup types.String `tfsdk:"vda_resource_group"` + StorageType types.String `tfsdk:"storage_type"` + UseAzureComputeGallery types.Object `tfsdk:"use_azure_compute_gallery"` + LicenseType types.String `tfsdk:"license_type"` + UseManagedDisks types.Bool `tfsdk:"use_managed_disks"` + MachineProfile types.Object `tfsdk:"machine_profile"` + WritebackCache types.Object `tfsdk:"writeback_cache"` + DiskEncryptionSet types.Object `tfsdk:"disk_encryption_set"` + EnrollInIntune types.Bool `tfsdk:"enroll_in_intune"` +} + +func (AzureMachineConfigModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Machine Configuration For Azure MCS catalog.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "service_offering": schema.StringAttribute{ + Description: "The Azure VM Sku to use when creating machines.", + Required: true, + }, + "azure_master_image": AzureMasterImageModel{}.GetSchema(), + "master_image_note": schema.StringAttribute{ + Description: "The note for the master image.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "image_update_reboot_options": ImageUpdateRebootOptionsModel{}.GetSchema(), + "storage_type": schema.StringAttribute{ + Description: "Storage account type used for provisioned virtual machine disks on Azure. Storage types include: `Standard_LRS`, `StandardSSD_LRS` and `Premium_LRS`.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + util.StandardLRS, + util.StandardSSDLRS, + util.Premium_LRS, + util.AzureEphemeralOSDisk, + ), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIf( + func(_ context.Context, req planmodifier.StringRequest, resp *stringplanmodifier.RequiresReplaceIfFuncResponse) { + resp.RequiresReplace = req.StateValue.ValueString() == util.AzureEphemeralOSDisk || req.PlanValue.ValueString() == util.AzureEphemeralOSDisk + }, + "Updating storage_type is not allowed when using Azure Ephemeral OS Disk.", + "Updating storage_type is not allowed when using Azure Ephemeral OS Disk.", + ), + }, + }, + "use_azure_compute_gallery": AzureComputeGallerySettings{}.GetSchema(), + "license_type": schema.StringAttribute{ + Description: "Windows license type used to provision virtual machines in Azure at the base compute rate. License types include: `Windows_Client` and `Windows_Server`.", + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf( + util.WindowsClientLicenseType, + util.WindowsServerLicenseType, + ), + }, + }, + "enroll_in_intune": schema.BoolAttribute{ + Description: "Specify whether to enroll machines in Microsoft Intune. Use this property only when `identity_type` is set to `AzureAD`.", + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "disk_encryption_set": AzureDiskEncryptionSetModel{}.GetSchema(), + "vda_resource_group": schema.StringAttribute{ + Description: "Designated resource group where the VDA VMs will be located on Azure.", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "use_managed_disks": schema.BoolAttribute{ + Description: "Indicate whether to use Azure managed disks for the provisioned virtual machine.", + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "machine_profile": AzureMachineProfileModel{}.GetSchema(), + "writeback_cache": AzureWritebackCacheModel{}.GetSchema(), + }, + } +} + +func (AzureMachineConfigModel) GetAttributes() map[string]schema.Attribute { + return AzureMachineConfigModel{}.GetSchema().Attributes } type AwsMachineConfigModel struct { - ServiceOffering types.String `tfsdk:"service_offering"` - MasterImage types.String `tfsdk:"master_image"` + ServiceOffering types.String `tfsdk:"service_offering"` + MasterImage types.String `tfsdk:"master_image"` + MasterImageNote types.String `tfsdk:"master_image_note"` + ImageUpdateRebootOptions types.Object `tfsdk:"image_update_reboot_options"` /** AWS Hypervisor **/ - ImageAmi types.String `tfsdk:"image_ami"` - SecurityGroups []types.String `tfsdk:"security_groups"` - TenancyType types.String `tfsdk:"tenancy_type"` + ImageAmi types.String `tfsdk:"image_ami"` + SecurityGroups types.List `tfsdk:"security_groups"` // List[String] + TenancyType types.String `tfsdk:"tenancy_type"` +} + +func (AwsMachineConfigModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Machine Configuration For AWS EC2 MCS catalog.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "service_offering": schema.StringAttribute{ + Description: "The AWS VM Sku to use when creating machines.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches( + regexp.MustCompile(util.AwsEc2InstanceTypeRegex), + "must follow AWS EC2 instance type naming convention in lower case. Eg: t2.micro, m5.large, etc.", + ), + }, + }, + "master_image": schema.StringAttribute{ + Description: "The name of the virtual machine image that will be used.", + Required: true, + }, + "image_ami": schema.StringAttribute{ + Description: "AMI of the AWS image to be used as the template image for the machine catalog.", + Required: true, + }, + "master_image_note": schema.StringAttribute{ + Description: "The note for the master image.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "image_update_reboot_options": ImageUpdateRebootOptionsModel{}.GetSchema(), + "security_groups": schema.ListAttribute{ + ElementType: types.StringType, + Description: "Security groups to associate with the machine. When omitted, the default security group of the VPC will be used by default.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "tenancy_type": schema.StringAttribute{ + Description: "Tenancy type of the machine. Choose between `Shared`, `Instance` and `Host`.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "Shared", + "Instance", + "Host", + ), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (AwsMachineConfigModel) GetAttributes() map[string]schema.Attribute { + return AwsMachineConfigModel{}.GetSchema().Attributes } type GcpMachineConfigModel struct { - MasterImage types.String `tfsdk:"master_image"` + MasterImage types.String `tfsdk:"master_image"` + MasterImageNote types.String `tfsdk:"master_image_note"` + ImageUpdateRebootOptions types.Object `tfsdk:"image_update_reboot_options"` /** GCP Hypervisor **/ - MachineProfile types.String `tfsdk:"machine_profile"` - MachineSnapshot types.String `tfsdk:"machine_snapshot"` - StorageType types.String `tfsdk:"storage_type"` - WritebackCache *GcpWritebackCacheModel `tfsdk:"writeback_cache"` + MachineProfile types.String `tfsdk:"machine_profile"` + MachineSnapshot types.String `tfsdk:"machine_snapshot"` + StorageType types.String `tfsdk:"storage_type"` + WritebackCache types.Object `tfsdk:"writeback_cache"` // GcpWritebackCacheModel +} + +func (GcpMachineConfigModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Machine Configuration For GCP MCS catalog.", + Optional: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.RequiresReplace(), + }, + Attributes: map[string]schema.Attribute{ + "master_image": schema.StringAttribute{ + Description: "The name of the virtual machine snapshot or VM template that will be used. This identifies the hard disk to be used and the default values for the memory and processors.", + Required: true, + }, + "master_image_note": schema.StringAttribute{ + Description: "The note for the master image.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "image_update_reboot_options": ImageUpdateRebootOptionsModel{}.GetSchema(), + "machine_profile": schema.StringAttribute{ + Description: "The name of the virtual machine template that will be used to identify the default value for the tags, virtual machine size, boot diagnostics, host cache property of OS disk, accelerated networking and availability zone. If not specified, the VM specified in master_image will be used as template.", + Optional: true, + }, + "machine_snapshot": schema.StringAttribute{ + Description: "The name of the virtual machine snapshot of a GCP VM that will be used as master image.", + Optional: true, + }, + "storage_type": schema.StringAttribute{ + Description: "Storage type used for provisioned virtual machine disks on GCP. Storage types include: `pd-standar`, `pd-balanced`, `pd-ssd` and `pd-extreme`.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "pd-standard", + "pd-balanced", + "pd-ssd", + "pd-extreme", + ), + }, + }, + "writeback_cache": GcpWritebackCacheModel{}.GetSchema(), + }, + } +} + +func (GcpMachineConfigModel) GetAttributes() map[string]schema.Attribute { + return GcpMachineConfigModel{}.GetSchema().Attributes } type VsphereMachineConfigModel struct { /** Vsphere Hypervisor **/ - MasterImageVm types.String `tfsdk:"master_image_vm"` - ImageSnapshot types.String `tfsdk:"image_snapshot"` - CpuCount types.Int64 `tfsdk:"cpu_count"` - MemoryMB types.Int64 `tfsdk:"memory_mb"` - WritebackCache *VsphereWritebackCacheModel `tfsdk:"writeback_cache"` + MasterImageVm types.String `tfsdk:"master_image_vm"` + ImageSnapshot types.String `tfsdk:"image_snapshot"` + MasterImageNote types.String `tfsdk:"master_image_note"` + ImageUpdateRebootOptions types.Object `tfsdk:"image_update_reboot_options"` + CpuCount types.Int64 `tfsdk:"cpu_count"` + MemoryMB types.Int64 `tfsdk:"memory_mb"` + WritebackCache types.Object `tfsdk:"writeback_cache"` // VsphereWritebackCacheModel +} + +func (VsphereMachineConfigModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Machine Configuration for vSphere MCS catalog.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "master_image_vm": schema.StringAttribute{ + Description: "The name of the virtual machine that will be used as master image. This property is case sensitive.", + Required: true, + }, + "image_snapshot": schema.StringAttribute{ + Description: "The Snapshot of the virtual machine specified in `master_image_vm`. Specify the relative path of the snapshot. Eg: snaphost-1/snapshot-2/snapshot-3. This property is case sensitive.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "master_image_note": schema.StringAttribute{ + Description: "The note for the master image.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "image_update_reboot_options": ImageUpdateRebootOptionsModel{}.GetSchema(), + "cpu_count": schema.Int64Attribute{ + Description: "The number of processors that virtual machines created from the provisioning scheme should use.", + Required: true, + }, + "memory_mb": schema.Int64Attribute{ + Description: "The maximum amount of memory that virtual machines created from the provisioning scheme should use.", + Required: true, + }, + "writeback_cache": VsphereWritebackCacheModel{}.GetSchema(), + }, + } +} + +func (VsphereMachineConfigModel) GetAttributes() map[string]schema.Attribute { + return VsphereMachineConfigModel{}.GetSchema().Attributes } type XenserverMachineConfigModel struct { /** XenServer Hypervisor **/ - MasterImageVm types.String `tfsdk:"master_image_vm"` - ImageSnapshot types.String `tfsdk:"image_snapshot"` - CpuCount types.Int64 `tfsdk:"cpu_count"` - MemoryMB types.Int64 `tfsdk:"memory_mb"` - WritebackCache *XenserverWritebackCacheModel `tfsdk:"writeback_cache"` + MasterImageVm types.String `tfsdk:"master_image_vm"` + ImageSnapshot types.String `tfsdk:"image_snapshot"` + MasterImageNote types.String `tfsdk:"master_image_note"` + ImageUpdateRebootOptions types.Object `tfsdk:"image_update_reboot_options"` + CpuCount types.Int64 `tfsdk:"cpu_count"` + MemoryMB types.Int64 `tfsdk:"memory_mb"` + WritebackCache types.Object `tfsdk:"writeback_cache"` // XenserverWritebackCacheModel +} + +func (XenserverMachineConfigModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Machine Configuration For XenServer MCS catalog.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "master_image_vm": schema.StringAttribute{ + Description: "The name of the virtual machine that will be used as master image. This property is case sensitive.", + Required: true, + }, + "image_snapshot": schema.StringAttribute{ + Description: "The Snapshot of the virtual machine specified in `master_image_vm`. Specify the relative path of the snapshot. Eg: snaphost-1/snapshot-2/snapshot-3. This property is case sensitive.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "master_image_note": schema.StringAttribute{ + Description: "The note for the master image.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "image_update_reboot_options": ImageUpdateRebootOptionsModel{}.GetSchema(), + "cpu_count": schema.Int64Attribute{ + Description: "Number of CPU cores for the VDA VMs.", + Required: true, + }, + "memory_mb": schema.Int64Attribute{ + Description: "Size of the memory in MB for the VDA VMs.", + Required: true, + }, + "writeback_cache": XenserverWritebackCacheModel{}.GetSchema(), + }, + } +} + +func (XenserverMachineConfigModel) GetAttributes() map[string]schema.Attribute { + return XenserverMachineConfigModel{}.GetSchema().Attributes } type NutanixMachineConfigModel struct { - Container types.String `tfsdk:"container"` - MasterImage types.String `tfsdk:"master_image"` - CpuCount types.Int64 `tfsdk:"cpu_count"` - CoresPerCpuCount types.Int64 `tfsdk:"cores_per_cpu_count"` - MemoryMB types.Int64 `tfsdk:"memory_mb"` + Container types.String `tfsdk:"container"` + MasterImage types.String `tfsdk:"master_image"` + MasterImageNote types.String `tfsdk:"master_image_note"` + ImageUpdateRebootOptions types.Object `tfsdk:"image_update_reboot_options"` + CpuCount types.Int64 `tfsdk:"cpu_count"` + CoresPerCpuCount types.Int64 `tfsdk:"cores_per_cpu_count"` + MemoryMB types.Int64 `tfsdk:"memory_mb"` +} + +func (NutanixMachineConfigModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Machine Configuration For Nutanix MCS catalog.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "container": schema.StringAttribute{ + Description: "The name of the container where the virtual machines' identity disks will be placed.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "master_image": schema.StringAttribute{ + Description: "The name of the master image that will be the template for all virtual machines in this catalog.", + Required: true, + }, + "master_image_note": schema.StringAttribute{ + Description: "The note for the master image.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "image_update_reboot_options": ImageUpdateRebootOptionsModel{}.GetSchema(), + "cpu_count": schema.Int64Attribute{ + Description: "The number of processors that virtual machines created from the provisioning scheme should use.", + Required: true, + }, + "cores_per_cpu_count": schema.Int64Attribute{ + Description: "The number of cores per processor that virtual machines created from the provisioning scheme should use.", + Required: true, + }, + "memory_mb": schema.Int64Attribute{ + Description: "The maximum amount of memory that virtual machines created from the provisioning scheme should use.", + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (NutanixMachineConfigModel) GetAttributes() map[string]schema.Attribute { + return NutanixMachineConfigModel{}.GetSchema().Attributes } type GalleryImageModel struct { @@ -77,13 +422,102 @@ type GalleryImageModel struct { Version types.String `tfsdk:"version"` } +func (GalleryImageModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Details of the Azure Image Gallery image to use for creating machines. Only Applicable to Azure Image Gallery image.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "gallery": schema.StringAttribute{ + Description: "The Azure Image Gallery where the image for creating machines is located. Only applicable to Azure Image Gallery image.", + Required: true, + }, + "definition": schema.StringAttribute{ + Description: "The image definition for the image to be used in the Azure Image Gallery. Only applicable to Azure Image Gallery image.", + Required: true, + }, + "version": schema.StringAttribute{ + Description: "The image version for the image to be used in the Azure Image Gallery. Only applicable to Azure Image Gallery image.", + Required: true, + }, + }, + Validators: []validator.Object{ + objectvalidator.AlsoRequires(path.Expressions{ + path.MatchRelative().AtParent().AtName("resource_group"), + }...), + objectvalidator.ConflictsWith(path.Expressions{ + path.MatchRelative().AtParent().AtName("storage_account"), + }...), + objectvalidator.ConflictsWith(path.Expressions{ + path.MatchRelative().AtParent().AtName("container"), + }...), + objectvalidator.ConflictsWith(path.Expressions{ + path.MatchRelative().AtParent().AtName("master_image"), + }...), + }, + } +} + +func (GalleryImageModel) GetAttributes() map[string]schema.Attribute { + return GalleryImageModel{}.GetSchema().Attributes +} + type AzureMasterImageModel struct { - ResourceGroup types.String `tfsdk:"resource_group"` - SharedSubscription types.String `tfsdk:"shared_subscription"` - MasterImage types.String `tfsdk:"master_image"` - StorageAccount types.String `tfsdk:"storage_account"` - Container types.String `tfsdk:"container"` - GalleryImage *GalleryImageModel `tfsdk:"gallery_image"` + ResourceGroup types.String `tfsdk:"resource_group"` + SharedSubscription types.String `tfsdk:"shared_subscription"` + MasterImage types.String `tfsdk:"master_image"` + StorageAccount types.String `tfsdk:"storage_account"` + Container types.String `tfsdk:"container"` + GalleryImage types.Object `tfsdk:"gallery_image"` +} + +func (AzureMasterImageModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Details of the Azure Image to use for creating machines.", + Required: true, + Attributes: map[string]schema.Attribute{ + "resource_group": schema.StringAttribute{ + Description: "The Azure Resource Group where the image VHD / managed disk / snapshot for creating machines is located.", + Required: true, + }, + "shared_subscription": schema.StringAttribute{ + Description: "The Azure Subscription ID where the image VHD / managed disk / snapshot for creating machines is located. Only required if the image is not in the same subscription of the hypervisor.", + Optional: true, + }, + "master_image": schema.StringAttribute{ + Description: "The name of the virtual machine snapshot or VM template that will be used. This identifies the hard disk to be used and the default values for the memory and processors. Omit this field if you want to use gallery_image.", + Optional: true, + }, + "storage_account": schema.StringAttribute{ + Description: "The Azure Storage Account where the image VHD for creating machines is located. Only applicable to Azure VHD image blob.", + Optional: true, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.Expressions{ + path.MatchRelative().AtParent().AtName("container"), + }...), + stringvalidator.AlsoRequires(path.Expressions{ + path.MatchRelative().AtParent().AtName("resource_group"), + }...), + }, + }, + "container": schema.StringAttribute{ + Description: "The Azure Storage Account Container where the image VHD for creating machines is located. Only applicable to Azure VHD image blob.", + Optional: true, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.Expressions{ + path.MatchRelative().AtParent().AtName("storage_account"), + }...), + stringvalidator.AlsoRequires(path.Expressions{ + path.MatchRelative().AtParent().AtName("resource_group"), + }...), + }, + }, + "gallery_image": GalleryImageModel{}.GetSchema(), + }, + } +} + +func (AzureMasterImageModel) GetAttributes() map[string]schema.Attribute { + return AzureMasterImageModel{}.GetSchema().Attributes } type AzureMachineProfileModel struct { @@ -93,6 +527,58 @@ type AzureMachineProfileModel struct { MachineProfileResourceGroup types.String `tfsdk:"machine_profile_resource_group"` } +func (AzureMachineProfileModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "The name of the virtual machine or template spec that will be used to identify the default value for the tags, virtual machine size, boot diagnostics, host cache property of OS disk, accelerated networking and availability zone." + "
" + + "Required when identity_type is set to `AzureAD`", + Optional: true, + Attributes: map[string]schema.Attribute{ + "machine_profile_vm_name": schema.StringAttribute{ + Description: "The name of the machine profile virtual machine.", + Optional: true, + }, + "machine_profile_template_spec_name": schema.StringAttribute{ + Description: "The name of the machine profile template spec.", + Optional: true, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.Expressions{ + path.MatchRelative().AtParent().AtName("machine_profile_template_spec_version"), + }...), + stringvalidator.ExactlyOneOf(path.Expressions{ + path.MatchRelative().AtParent().AtName("machine_profile_vm_name"), + }...), + }, + }, + "machine_profile_template_spec_version": schema.StringAttribute{ + Description: "The version of the machine profile template spec.", + Optional: true, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.Expressions{ + path.MatchRelative().AtParent().AtName("machine_profile_template_spec_name"), + }...), + }, + }, + "machine_profile_resource_group": schema.StringAttribute{ + Description: "The name of the resource group where the machine profile VM or template spec is located.", + Required: true, + }, + }, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.RequiresReplaceIf( + func(_ context.Context, req planmodifier.ObjectRequest, resp *objectplanmodifier.RequiresReplaceIfFuncResponse) { + resp.RequiresReplace = req.ConfigValue.IsNull() != req.StateValue.IsNull() + }, + "Force replace when machine_profile is added or removed. Update is allowed only if previously set.", + "Force replace when machine_profile is added or removed. Update is allowed only if previously set.", + ), + }, + } +} + +func (AzureMachineProfileModel) GetAttributes() map[string]schema.Attribute { + return AzureMachineProfileModel{}.GetSchema().Attributes +} + // WritebackCacheModel maps the write back cacheconfiguration schema data. type AzureWritebackCacheModel struct { PersistWBC types.Bool `tfsdk:"persist_wbc"` @@ -104,11 +590,89 @@ type AzureWritebackCacheModel struct { WriteBackCacheMemorySizeMB types.Int64 `tfsdk:"writeback_cache_memory_size_mb"` } +func (AzureWritebackCacheModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Write-back Cache config. Leave this empty to disable Write-back Cache. Write-back Cache requires Machine image with Write-back Cache plugin installed.", + Optional: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.RequiresReplace(), + }, + Attributes: map[string]schema.Attribute{ + "persist_wbc": schema.BoolAttribute{ + Description: "Persist Write-back Cache", + Required: true, + }, + "wbc_disk_storage_type": schema.StringAttribute{ + Description: "Type of naming scheme. Choose between Numeric and Alphabetic.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "StandardSSD_LRS", + "Standard_LRS", + "Premium_LRS", + ), + }, + }, + "persist_os_disk": schema.BoolAttribute{ + Description: "Persist the OS disk when power cycling the non-persistent provisioned virtual machine.", + Required: true, + }, + "persist_vm": schema.BoolAttribute{ + Description: "Persist the non-persistent provisioned virtual machine in Azure environments when power cycling. This property only applies when the PersistOsDisk property is set to True.", + Required: true, + }, + "storage_cost_saving": schema.BoolAttribute{ + Description: "Save storage cost by downgrading the storage type of the disk to Standard HDD when VM shut down.", + Required: true, + }, + "writeback_cache_disk_size_gb": schema.Int64Attribute{ + Description: "The size in GB of any temporary storage disk used by the write back cache.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + }, + "writeback_cache_memory_size_mb": schema.Int64Attribute{ + Description: "The size of the in-memory write back cache in MB.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + }, + }, + } +} + +func (AzureWritebackCacheModel) GetAttributes() map[string]schema.Attribute { + return AzureWritebackCacheModel{}.GetSchema().Attributes +} + type AzureComputeGallerySettings struct { ReplicaRatio types.Int64 `tfsdk:"replica_ratio"` ReplicaMaximum types.Int64 `tfsdk:"replica_maximum"` } +func (AzureComputeGallerySettings) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Use this to place prepared image in Azure Compute Gallery. Required when `storage_type = Azure_Ephemeral_OS_Disk`.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "replica_ratio": schema.Int64Attribute{ + Description: "The ratio of virtual machines to image replicas that you want Azure to keep.", + Required: true, + }, + "replica_maximum": schema.Int64Attribute{ + Description: "The maximum number of image replicas that you want Azure to keep.", + Required: true, + }, + }, + } +} + +func (AzureComputeGallerySettings) GetAttributes() map[string]schema.Attribute { + return AzureComputeGallerySettings{}.GetSchema().Attributes +} + type GcpWritebackCacheModel struct { PersistWBC types.Bool `tfsdk:"persist_wbc"` WBCDiskStorageType types.String `tfsdk:"wbc_disk_storage_type"` @@ -117,23 +681,234 @@ type GcpWritebackCacheModel struct { WriteBackCacheMemorySizeMB types.Int64 `tfsdk:"writeback_cache_memory_size_mb"` } +func (GcpWritebackCacheModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Write-back Cache config. Leave this empty to disable Write-back Cache.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "persist_wbc": schema.BoolAttribute{ + Description: "Persist Write-back Cache", + Required: true, + }, + "wbc_disk_storage_type": schema.StringAttribute{ + Description: "Type of naming scheme. Choose between Numeric and Alphabetic.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "pd-standard", + "pd-balanced", + "pd-ssd", + ), + }, + }, + "persist_os_disk": schema.BoolAttribute{ + Description: "Persist the OS disk when power cycling the non-persistent provisioned virtual machine.", + Required: true, + }, + "writeback_cache_disk_size_gb": schema.Int64Attribute{ + Description: "The size in GB of any temporary storage disk used by the write back cache.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + }, + "writeback_cache_memory_size_mb": schema.Int64Attribute{ + Description: "The size of the in-memory write back cache in MB.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + }, + }, + } +} + +func (GcpWritebackCacheModel) GetAttributes() map[string]schema.Attribute { + return GcpWritebackCacheModel{}.GetSchema().Attributes +} + type XenserverWritebackCacheModel struct { WriteBackCacheDiskSizeGB types.Int64 `tfsdk:"writeback_cache_disk_size_gb"` WriteBackCacheMemorySizeMB types.Int64 `tfsdk:"writeback_cache_memory_size_mb"` } +func (XenserverWritebackCacheModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Write-back Cache config. Leave this empty to disable Write-back Cache.", + Optional: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.RequiresReplace(), + }, + Attributes: map[string]schema.Attribute{ + "writeback_cache_disk_size_gb": schema.Int64Attribute{ + Description: "The size in GB of any temporary storage disk used by the write back cache.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + }, + "writeback_cache_memory_size_mb": schema.Int64Attribute{ + Description: "The size of the in-memory write back cache in MB.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + }, + }, + } +} + +func (XenserverWritebackCacheModel) GetAttributes() map[string]schema.Attribute { + return XenserverWritebackCacheModel{}.GetSchema().Attributes +} + type VsphereWritebackCacheModel struct { WriteBackCacheDiskSizeGB types.Int64 `tfsdk:"writeback_cache_disk_size_gb"` WriteBackCacheMemorySizeMB types.Int64 `tfsdk:"writeback_cache_memory_size_mb"` WriteBackCacheDriveLetter types.String `tfsdk:"writeback_cache_drive_letter"` } +func (VsphereWritebackCacheModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Write-back Cache config. Leave this empty to disable Write-back Cache.", + Optional: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.RequiresReplace(), + }, + Attributes: map[string]schema.Attribute{ + "writeback_cache_disk_size_gb": schema.Int64Attribute{ + Description: "The size in GB of any temporary storage disk used by the write back cache.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + }, + "writeback_cache_memory_size_mb": schema.Int64Attribute{ + Description: "The size of the in-memory write back cache in MB.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(0), + }, + }, + "writeback_cache_drive_letter": schema.StringAttribute{ + Description: "The drive letter assigned for write back cache disk.", + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 1), + }, + }, + }, + } +} + +func (VsphereWritebackCacheModel) GetAttributes() map[string]schema.Attribute { + return VsphereWritebackCacheModel{}.GetSchema().Attributes +} + type AzureDiskEncryptionSetModel struct { DiskEncryptionSetName types.String `tfsdk:"disk_encryption_set_name"` DiskEncryptionSetResourceGroup types.String `tfsdk:"disk_encryption_set_resource_group"` } -func (mc *AzureMachineConfigModel) RefreshProperties(catalog citrixorchestration.MachineCatalogDetailResponseModel) { +func (AzureDiskEncryptionSetModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "The configuration for Disk Encryption Set (DES). The DES must be in the same subscription and region as your resources. If your master image is encrypted with a DES, use the same DES when creating this machine catalog. When using a DES, if you later disable the key with which the corresponding DES is associated in Azure, you can no longer power on the machines in this catalog or add machines to it.", + Optional: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.RequiresReplace(), + }, + Attributes: map[string]schema.Attribute{ + "disk_encryption_set_name": schema.StringAttribute{ + Description: "The name of the disk encryption set.", + Required: true, + }, + "disk_encryption_set_resource_group": schema.StringAttribute{ + Description: "The name of the resource group in which the disk encryption set resides.", + Required: true, + }, + }, + } +} + +func (AzureDiskEncryptionSetModel) GetAttributes() map[string]schema.Attribute { + return AzureDiskEncryptionSetModel{}.GetSchema().Attributes +} + +type ImageUpdateRebootOptionsModel struct { + RebootDuration types.Int64 `tfsdk:"reboot_duration"` + WarningDuration types.Int64 `tfsdk:"warning_duration"` + WarningMessage types.String `tfsdk:"warning_message"` + WarningRepeatInterval types.Int64 `tfsdk:"warning_repeat_interval"` +} + +func (ImageUpdateRebootOptionsModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "The options for how rebooting is performed for image update. When omitted, image update on the VDAs will be performed on next shutdown.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "reboot_duration": schema.Int64Attribute{ + Description: "Approximate maximum duration over which the reboot cycle runs, in minutes. " + + "Set to `-1` to skip reboot, and perform image update on the VDAs on next shutdown. " + + "Set to `0` to reboot all machines immediately.", + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(-1), + }, + }, + "warning_duration": schema.Int64Attribute{ + Description: "Time in minutes prior to a machine reboot at which a warning message is displayed in all user sessions on that machine. When omitted, no warning about reboot will be displayed in user session.", + Optional: true, + Validators: []validator.Int64{ + int64validator.AtLeast(1), + int64validator.AlsoRequires(path.Expressions{ + path.MatchRelative().AtParent().AtName("warning_message"), + }...), + }, + }, + "warning_message": schema.StringAttribute{ + Description: "Warning message displayed in user sessions on a machine scheduled for a reboot. The optional pattern '%m%' is replaced by the number of minutes until the reboot.", + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "warning_repeat_interval": schema.Int64Attribute{ + Description: "Number of minutes to wait before showing the reboot warning message again.", + Optional: true, + Validators: []validator.Int64{ + int64validator.AtLeast(1), + int64validator.AlsoRequires(path.Expressions{ + path.MatchRelative().AtParent().AtName("warning_duration"), + }...), + }, + }, + }, + } +} + +func (ImageUpdateRebootOptionsModel) GetAttributes() map[string]schema.Attribute { + return ImageUpdateRebootOptionsModel{}.GetSchema().Attributes +} + +func (rebootOptions ImageUpdateRebootOptionsModel) ValidateConfig(diagnostics *diag.Diagnostics) { + rebootDuration := int32(rebootOptions.RebootDuration.ValueInt64()) + if rebootDuration == -1 && !rebootOptions.WarningDuration.IsNull() { + diagnostics.AddAttributeError( + path.Root("warning_duration"), + "Invalid Reboot Warning Duration", + "warning_duration cannot be set when reboot_duration is set to -1.", + ) + } + if !rebootOptions.WarningRepeatInterval.IsNull() && rebootOptions.WarningRepeatInterval.ValueInt64() >= rebootOptions.WarningDuration.ValueInt64() { + diagnostics.AddAttributeError( + path.Root("warning_repeat_interval"), + "Invalid Reboot Warning Repeat Interval", + "warning_repeat_interval must be shorter than warning_duration.", + ) + } +} + +func (mc *AzureMachineConfigModel) RefreshProperties(ctx context.Context, diagnostics *diag.Diagnostics, catalog citrixorchestration.MachineCatalogDetailResponseModel) { // Refresh Service Offering provScheme := catalog.GetProvisioningScheme() if provScheme.GetServiceOffering() != "" { @@ -142,20 +917,7 @@ func (mc *AzureMachineConfigModel) RefreshProperties(catalog citrixorchestration // Refresh Master Image masterImage := provScheme.GetMasterImage() - if mc.AzureMasterImage == nil { - mc.AzureMasterImage = &AzureMasterImageModel{} - } - - if mc.AzureMasterImage.GalleryImage != nil { - /* For Azure Image Gallery image, the XDPath looks like: - * XDHyp:\\HostingUnits\\{resource pool}\\image.folder\\{resource group}.resourcegroup\\{gallery name}.gallery\\{image name}.imagedefinition\\{image version}.imageversion - * The Name property in MasterImage will be image version instead of image definition (name of the image) - */ - mc.AzureMasterImage.GalleryImage.Version = types.StringValue(masterImage.GetName()) - } else { - mc.AzureMasterImage.MasterImage = types.StringValue(masterImage.GetName()) - } - + azureMasterImage := util.ObjectValueToTypedObject[AzureMasterImageModel](ctx, diagnostics, mc.AzureMasterImage) masterImageXdPath := masterImage.GetXDPath() if masterImageXdPath != "" { segments := strings.Split(masterImage.GetXDPath(), "\\") @@ -166,43 +928,60 @@ func (mc *AzureMachineConfigModel) RefreshProperties(catalog citrixorchestration if strings.EqualFold(resourceType, util.VhdResourceType) { // VHD image - mc.AzureMasterImage.Container = types.StringValue(strings.Split(segments[lastIndex-2], ".")[0]) - mc.AzureMasterImage.StorageAccount = types.StringValue(strings.Split(segments[lastIndex-3], ".")[0]) + azureMasterImage.MasterImage = types.StringValue(masterImage.GetName()) + azureMasterImage.Container = types.StringValue(strings.Split(segments[lastIndex-2], ".")[0]) + azureMasterImage.StorageAccount = types.StringValue(strings.Split(segments[lastIndex-3], ".")[0]) } else if strings.EqualFold(resourceType, util.ImageVersionResourceType) { - // Gallery image - if mc.AzureMasterImage.GalleryImage == nil { - mc.AzureMasterImage.GalleryImage = &GalleryImageModel{} - } - mc.AzureMasterImage.GalleryImage.Definition = types.StringValue(strings.Split(segments[lastIndex-2], ".")[0]) - mc.AzureMasterImage.GalleryImage.Gallery = types.StringValue(strings.Split(segments[lastIndex-3], ".")[0]) + /* For Azure Image Gallery image, the XDPath looks like: + * XDHyp:\\HostingUnits\\{resource pool}\\image.folder\\{resource group}.resourcegroup\\{gallery name}.gallery\\{image name}.imagedefinition\\{image version}.imageversion + * The Name property in MasterImage will be image version instead of image definition (name of the image) + */ + azureGalleryImageModel := util.ObjectValueToTypedObject[GalleryImageModel](ctx, diagnostics, azureMasterImage.GalleryImage) + azureGalleryImageModel.Version = types.StringValue(masterImage.GetName()) + azureGalleryImageModel.Definition = types.StringValue(strings.Split(segments[lastIndex-2], ".")[0]) + azureGalleryImageModel.Gallery = types.StringValue(strings.Split(segments[lastIndex-3], ".")[0]) + + azureMasterImage.GalleryImage = util.TypedObjectToObjectValue(ctx, diagnostics, azureGalleryImageModel) + } - mc.AzureMasterImage.ResourceGroup = types.StringValue(strings.Split(segments[lastIndex-4], ".")[0]) + azureMasterImage.ResourceGroup = types.StringValue(strings.Split(segments[lastIndex-4], ".")[0]) } else { // Snapshot or Managed Disk - mc.AzureMasterImage.ResourceGroup = types.StringValue(strings.Split(segments[lastIndex-2], ".")[0]) + azureMasterImage.MasterImage = types.StringValue(masterImage.GetName()) + azureMasterImage.ResourceGroup = types.StringValue(strings.Split(segments[lastIndex-2], ".")[0]) } } + mc.AzureMasterImage = util.TypedObjectToObjectValue(ctx, diagnostics, azureMasterImage) + + // Refresh Master Image Note + currentDiskImage := provScheme.GetCurrentDiskImage() + mc.MasterImageNote = types.StringValue(currentDiskImage.GetMasterImageNote()) + // Refresh Machine Profile if provScheme.MachineProfile != nil { machineProfile := provScheme.GetMachineProfile() machineProfileModel := parseAzureMachineProfileResponseToModel(machineProfile) - mc.MachineProfile = machineProfileModel + mc.MachineProfile = util.TypedObjectToObjectValue(ctx, diagnostics, machineProfileModel) } else { - mc.MachineProfile = nil + if attributesMap, err := util.AttributeMapFromObject(AzureMachineProfileModel{}); err == nil { + mc.MachineProfile = types.ObjectNull(attributesMap) + } else { + diagnostics.AddWarning("Error when creating null AzureMachineProfileModel", err.Error()) + } } // Refresh Writeback Cache wbcDiskSize := provScheme.GetWriteBackCacheDiskSizeGB() wbcMemorySize := provScheme.GetWriteBackCacheMemorySizeMB() if wbcDiskSize != 0 { - if mc.WritebackCache == nil { - mc.WritebackCache = &AzureWritebackCacheModel{} - } - mc.WritebackCache.WriteBackCacheDiskSizeGB = types.Int64Value(int64(provScheme.GetWriteBackCacheDiskSizeGB())) + azureWbcModel := AzureWritebackCacheModel{} + azureWbcModel.WriteBackCacheDiskSizeGB = types.Int64Value(int64(provScheme.GetWriteBackCacheDiskSizeGB())) if wbcMemorySize != 0 { - mc.WritebackCache.WriteBackCacheMemorySizeMB = types.Int64Value(int64(provScheme.GetWriteBackCacheMemorySizeMB())) + azureWbcModel.WriteBackCacheMemorySizeMB = types.Int64Value(int64(provScheme.GetWriteBackCacheMemorySizeMB())) } + + mc.WritebackCache = util.TypedObjectToObjectValue(ctx, diagnostics, azureWbcModel) } if provScheme.GetDeviceManagementType() == citrixorchestration.DEVICEMANAGEMENTTYPE_INTUNE { @@ -216,33 +995,46 @@ func (mc *AzureMachineConfigModel) RefreshProperties(catalog citrixorchestration isLicenseTypeSet := false isDesSet := false isUseSharedImageGallerySet := false + isUseEphemeralOsDiskSet := false for _, stringPair := range customProperties { switch stringPair.GetName() { case "StorageType": - mc.StorageType = types.StringValue(stringPair.GetValue()) + if !isUseEphemeralOsDiskSet { + mc.StorageType = types.StringValue(stringPair.GetValue()) + } case "UseManagedDisks": mc.UseManagedDisks = util.StringToTypeBool(stringPair.GetValue()) case "ResourceGroups": mc.VdaResourceGroup = types.StringValue(stringPair.GetValue()) case "WBCDiskStorageType": - if mc.WritebackCache != nil { - mc.WritebackCache.WBCDiskStorageType = types.StringValue(stringPair.GetValue()) + if !mc.WritebackCache.IsNull() { + azureWbcModel := util.ObjectValueToTypedObject[AzureWritebackCacheModel](ctx, diagnostics, mc.WritebackCache) + azureWbcModel.WBCDiskStorageType = types.StringValue(stringPair.GetValue()) + mc.WritebackCache = util.TypedObjectToObjectValue(ctx, diagnostics, azureWbcModel) } case "PersistWBC": - if mc.WritebackCache != nil { - mc.WritebackCache.PersistWBC = util.StringToTypeBool(stringPair.GetValue()) + if !mc.WritebackCache.IsNull() { + azureWbcModel := util.ObjectValueToTypedObject[AzureWritebackCacheModel](ctx, diagnostics, mc.WritebackCache) + azureWbcModel.PersistWBC = util.StringToTypeBool(stringPair.GetValue()) + mc.WritebackCache = util.TypedObjectToObjectValue(ctx, diagnostics, azureWbcModel) } case "PersistOsDisk": - if mc.WritebackCache != nil { - mc.WritebackCache.PersistOsDisk = util.StringToTypeBool(stringPair.GetValue()) + if !mc.WritebackCache.IsNull() { + azureWbcModel := util.ObjectValueToTypedObject[AzureWritebackCacheModel](ctx, diagnostics, mc.WritebackCache) + azureWbcModel.PersistOsDisk = util.StringToTypeBool(stringPair.GetValue()) + mc.WritebackCache = util.TypedObjectToObjectValue(ctx, diagnostics, azureWbcModel) } case "PersistVm": - if mc.WritebackCache != nil { - mc.WritebackCache.PersistVm = util.StringToTypeBool(stringPair.GetValue()) + if !mc.WritebackCache.IsNull() { + azureWbcModel := util.ObjectValueToTypedObject[AzureWritebackCacheModel](ctx, diagnostics, mc.WritebackCache) + azureWbcModel.PersistVm = util.StringToTypeBool(stringPair.GetValue()) + mc.WritebackCache = util.TypedObjectToObjectValue(ctx, diagnostics, azureWbcModel) } case "StorageTypeAtShutdown": - if mc.WritebackCache != nil { - mc.WritebackCache.StorageCostSaving = types.BoolValue(true) + if !mc.WritebackCache.IsNull() { + azureWbcModel := util.ObjectValueToTypedObject[AzureWritebackCacheModel](ctx, diagnostics, mc.WritebackCache) + azureWbcModel.StorageCostSaving = types.BoolValue(true) + mc.WritebackCache = util.TypedObjectToObjectValue(ctx, diagnostics, azureWbcModel) } case "LicenseType": licenseType := stringPair.GetValue() @@ -258,39 +1050,38 @@ func (mc *AzureMachineConfigModel) RefreshProperties(catalog citrixorchestration desName := desArray[len(desArray)-1] resourceGroupsIndex := slices.Index(desArray, "resourceGroups") resourceGroupName := desArray[resourceGroupsIndex+1] - if mc.DiskEncryptionSet == nil { - mc.DiskEncryptionSet = &AzureDiskEncryptionSetModel{} - } - if !strings.EqualFold(mc.DiskEncryptionSet.DiskEncryptionSetName.ValueString(), desName) { - mc.DiskEncryptionSet.DiskEncryptionSetName = types.StringValue(desName) + diskEncryptionSetModel := util.ObjectValueToTypedObject[AzureDiskEncryptionSetModel](ctx, diagnostics, mc.DiskEncryptionSet) + if !strings.EqualFold(diskEncryptionSetModel.DiskEncryptionSetName.ValueString(), desName) { + diskEncryptionSetModel.DiskEncryptionSetName = types.StringValue(desName) } - if !strings.EqualFold(mc.DiskEncryptionSet.DiskEncryptionSetResourceGroup.ValueString(), resourceGroupName) { - mc.DiskEncryptionSet.DiskEncryptionSetResourceGroup = types.StringValue(resourceGroupName) + if !strings.EqualFold(diskEncryptionSetModel.DiskEncryptionSetResourceGroup.ValueString(), resourceGroupName) { + diskEncryptionSetModel.DiskEncryptionSetResourceGroup = types.StringValue(resourceGroupName) } + + mc.DiskEncryptionSet = util.TypedObjectToObjectValue(ctx, diagnostics, diskEncryptionSetModel) + isDesSet = true case "SharedImageGalleryReplicaRatio": if stringPair.GetValue() != "" { isUseSharedImageGallerySet = true - if mc.UseAzureComputeGallery == nil { - mc.UseAzureComputeGallery = &AzureComputeGallerySettings{} - } + azureComputeGallerySettingsModel := util.ObjectValueToTypedObject[AzureComputeGallerySettings](ctx, diagnostics, mc.UseAzureComputeGallery) replicaRatio, _ := strconv.Atoi(stringPair.GetValue()) - mc.UseAzureComputeGallery.ReplicaRatio = types.Int64Value(int64(replicaRatio)) + azureComputeGallerySettingsModel.ReplicaRatio = types.Int64Value(int64(replicaRatio)) + mc.UseAzureComputeGallery = util.TypedObjectToObjectValue(ctx, diagnostics, azureComputeGallerySettingsModel) } case "SharedImageGalleryReplicaMaximum": if stringPair.GetValue() != "" { isUseSharedImageGallerySet = true - if mc.UseAzureComputeGallery == nil { - mc.UseAzureComputeGallery = &AzureComputeGallerySettings{} - } - + azureComputeGallerySettingsModel := util.ObjectValueToTypedObject[AzureComputeGallerySettings](ctx, diagnostics, mc.UseAzureComputeGallery) replicaMaximum, _ := strconv.Atoi(stringPair.GetValue()) - mc.UseAzureComputeGallery.ReplicaMaximum = types.Int64Value(int64(replicaMaximum)) + azureComputeGallerySettingsModel.ReplicaMaximum = types.Int64Value(int64(replicaMaximum)) + mc.UseAzureComputeGallery = util.TypedObjectToObjectValue(ctx, diagnostics, azureComputeGallerySettingsModel) } case "UseEphemeralOsDisk": if strings.EqualFold(stringPair.GetValue(), "true") { mc.StorageType = types.StringValue(util.AzureEphemeralOSDisk) + isUseEphemeralOsDiskSet = true } default: } @@ -300,16 +1091,24 @@ func (mc *AzureMachineConfigModel) RefreshProperties(catalog citrixorchestration mc.LicenseType = types.StringNull() } - if !isDesSet && mc.DiskEncryptionSet != nil { - mc.DiskEncryptionSet = nil + if !isDesSet && !mc.DiskEncryptionSet.IsNull() { + if attributesMap, err := util.AttributeMapFromObject(AzureDiskEncryptionSetModel{}); err == nil { + mc.DiskEncryptionSet = types.ObjectNull(attributesMap) + } else { + diagnostics.AddWarning("Error when creating null AzureDiskEcryptionSetModel", err.Error()) + } } - if !isUseSharedImageGallerySet && mc.UseAzureComputeGallery != nil { - mc.UseAzureComputeGallery = nil + if !isUseSharedImageGallerySet && !mc.UseAzureComputeGallery.IsNull() { + if attributesMap, err := util.AttributeMapFromObject(AzureComputeGallerySettings{}); err == nil { + mc.UseAzureComputeGallery = types.ObjectNull(attributesMap) + } else { + diagnostics.AddWarning("Error when creating null AzureComputeGallerySettings", err.Error()) + } } } -func (mc *AwsMachineConfigModel) RefreshProperties(catalog citrixorchestration.MachineCatalogDetailResponseModel) { +func (mc *AwsMachineConfigModel) RefreshProperties(ctx context.Context, diagnostics *diag.Diagnostics, catalog citrixorchestration.MachineCatalogDetailResponseModel) { // Refresh Service Offering provScheme := catalog.GetProvisioningScheme() if provScheme.GetServiceOffering() != "" { @@ -324,16 +1123,20 @@ func (mc *AwsMachineConfigModel) RefreshProperties(catalog citrixorchestration.M */ mc.MasterImage = types.StringValue(strings.Split(masterImage.GetName(), " (ami-")[0]) + // Refresh Master Image Note + currentDiskImage := provScheme.GetCurrentDiskImage() + mc.MasterImageNote = types.StringValue(currentDiskImage.GetMasterImageNote()) + // Refresh Security Group securityGroups := provScheme.GetSecurityGroups() - mc.SecurityGroups = util.ConvertPrimitiveStringArrayToBaseStringArray(securityGroups) + mc.SecurityGroups = util.StringArrayToStringList(ctx, diagnostics, securityGroups) // Refresh Tenancy Type tenancyType := provScheme.GetTenancyType() mc.TenancyType = types.StringValue(tenancyType) } -func (mc *GcpMachineConfigModel) RefreshProperties(catalog citrixorchestration.MachineCatalogDetailResponseModel) { +func (mc *GcpMachineConfigModel) RefreshProperties(ctx context.Context, diagnostics *diag.Diagnostics, catalog citrixorchestration.MachineCatalogDetailResponseModel) { provScheme := catalog.GetProvisioningScheme() // Refresh Master Image @@ -357,6 +1160,10 @@ func (mc *GcpMachineConfigModel) RefreshProperties(catalog citrixorchestration.M } } + // Refresh Master Image Note + currentDiskImage := provScheme.GetCurrentDiskImage() + mc.MasterImageNote = types.StringValue(currentDiskImage.GetMasterImageNote()) + // Refresh Machine Profile machineProfile := provScheme.GetMachineProfile() if machineProfileName := machineProfile.GetName(); machineProfileName != "" { @@ -366,16 +1173,20 @@ func (mc *GcpMachineConfigModel) RefreshProperties(catalog citrixorchestration.M // Refresh Writeback Cache wbcDiskSize := provScheme.GetWriteBackCacheDiskSizeGB() wbcMemorySize := provScheme.GetWriteBackCacheMemorySizeMB() + writebackCache := util.ObjectValueToTypedObject[GcpWritebackCacheModel](ctx, diagnostics, mc.WritebackCache) + if wbcDiskSize != 0 { - if mc.WritebackCache == nil { - mc.WritebackCache = &GcpWritebackCacheModel{} + if mc.WritebackCache.IsNull() { + writebackCache = GcpWritebackCacheModel{} } - mc.WritebackCache.WriteBackCacheDiskSizeGB = types.Int64Value(int64(provScheme.GetWriteBackCacheDiskSizeGB())) + writebackCache.WriteBackCacheDiskSizeGB = types.Int64Value(int64(provScheme.GetWriteBackCacheDiskSizeGB())) if wbcMemorySize != 0 { - mc.WritebackCache.WriteBackCacheMemorySizeMB = types.Int64Value(int64(provScheme.GetWriteBackCacheMemorySizeMB())) + writebackCache.WriteBackCacheMemorySizeMB = types.Int64Value(int64(provScheme.GetWriteBackCacheMemorySizeMB())) } } + mc.WritebackCache = util.TypedObjectToObjectValue(ctx, diagnostics, writebackCache) + writebackCache = util.ObjectValueToTypedObject[GcpWritebackCacheModel](ctx, diagnostics, mc.WritebackCache) //Refresh custom properties customProperties := provScheme.GetCustomProperties() for _, stringPair := range customProperties { @@ -383,23 +1194,24 @@ func (mc *GcpMachineConfigModel) RefreshProperties(catalog citrixorchestration.M case "StorageType": mc.StorageType = types.StringValue(stringPair.GetValue()) case "WBCDiskStorageType": - if mc.WritebackCache != nil { - mc.WritebackCache.WBCDiskStorageType = types.StringValue(stringPair.GetValue()) + if !mc.WritebackCache.IsNull() { + writebackCache.WBCDiskStorageType = types.StringValue(stringPair.GetValue()) } case "PersistWBC": - if mc.WritebackCache != nil { - mc.WritebackCache.PersistWBC = util.StringToTypeBool(stringPair.GetValue()) + if !mc.WritebackCache.IsNull() { + writebackCache.PersistWBC = util.StringToTypeBool(stringPair.GetValue()) } case "PersistOsDisk": - if mc.WritebackCache != nil { - mc.WritebackCache.PersistOsDisk = util.StringToTypeBool(stringPair.GetValue()) + if !mc.WritebackCache.IsNull() { + writebackCache.PersistOsDisk = util.StringToTypeBool(stringPair.GetValue()) } default: } } + mc.WritebackCache = util.TypedObjectToObjectValue(ctx, diagnostics, writebackCache) } -func (mc *VsphereMachineConfigModel) RefreshProperties(catalog citrixorchestration.MachineCatalogDetailResponseModel) { +func (mc *VsphereMachineConfigModel) RefreshProperties(ctx context.Context, diagnostics *diag.Diagnostics, catalog citrixorchestration.MachineCatalogDetailResponseModel) { provScheme := catalog.GetProvisioningScheme() // Refresh Master Image @@ -407,6 +1219,10 @@ func (mc *VsphereMachineConfigModel) RefreshProperties(catalog citrixorchestrati mc.MasterImageVm = types.StringValue(masterImage) mc.ImageSnapshot = types.StringValue(imageSnapshot) + // Refresh Master Image Note + currentDiskImage := provScheme.GetCurrentDiskImage() + mc.MasterImageNote = types.StringValue(currentDiskImage.GetMasterImageNote()) + // Refresh Memory mc.MemoryMB = types.Int64Value(int64(provScheme.GetMemoryMB())) mc.CpuCount = types.Int64Value(int64(provScheme.GetCpuCount())) @@ -414,21 +1230,23 @@ func (mc *VsphereMachineConfigModel) RefreshProperties(catalog citrixorchestrati // Refresh Writeback Cache wbcDiskSize := provScheme.GetWriteBackCacheDiskSizeGB() wbcMemorySize := provScheme.GetWriteBackCacheMemorySizeMB() + writebackCache := util.ObjectValueToTypedObject[VsphereWritebackCacheModel](ctx, diagnostics, mc.WritebackCache) if wbcDiskSize != 0 { - if mc.WritebackCache == nil { - mc.WritebackCache = &VsphereWritebackCacheModel{} + if mc.WritebackCache.IsNull() { + writebackCache = VsphereWritebackCacheModel{} } - mc.WritebackCache.WriteBackCacheDiskSizeGB = types.Int64Value(int64(provScheme.GetWriteBackCacheDiskSizeGB())) + writebackCache.WriteBackCacheDiskSizeGB = types.Int64Value(int64(provScheme.GetWriteBackCacheDiskSizeGB())) if wbcMemorySize != 0 { - mc.WritebackCache.WriteBackCacheMemorySizeMB = types.Int64Value(int64(provScheme.GetWriteBackCacheMemorySizeMB())) + writebackCache.WriteBackCacheMemorySizeMB = types.Int64Value(int64(provScheme.GetWriteBackCacheMemorySizeMB())) } if provScheme.GetWriteBackCacheDriveLetter() != "" { - mc.WritebackCache.WriteBackCacheDriveLetter = types.StringValue(provScheme.GetWriteBackCacheDriveLetter()) + writebackCache.WriteBackCacheDriveLetter = types.StringValue(provScheme.GetWriteBackCacheDriveLetter()) } } + mc.WritebackCache = util.TypedObjectToObjectValue(ctx, diagnostics, writebackCache) } -func (mc *XenserverMachineConfigModel) RefreshProperties(catalog citrixorchestration.MachineCatalogDetailResponseModel) { +func (mc *XenserverMachineConfigModel) RefreshProperties(ctx context.Context, diagnostics *diag.Diagnostics, catalog citrixorchestration.MachineCatalogDetailResponseModel) { // Refresh Service Offering provScheme := catalog.GetProvisioningScheme() mc.CpuCount = types.Int64Value(int64(provScheme.GetCpuCount())) @@ -438,18 +1256,24 @@ func (mc *XenserverMachineConfigModel) RefreshProperties(catalog citrixorchestra mc.MasterImageVm = types.StringValue(masterImage) mc.ImageSnapshot = types.StringValue(imageSnapshot) + // Refresh Master Image Note + currentDiskImage := provScheme.GetCurrentDiskImage() + mc.MasterImageNote = types.StringValue(currentDiskImage.GetMasterImageNote()) + // Refresh Writeback Cache wbcDiskSize := provScheme.GetWriteBackCacheDiskSizeGB() wbcMemorySize := provScheme.GetWriteBackCacheMemorySizeMB() + writebackCache := util.ObjectValueToTypedObject[XenserverWritebackCacheModel](ctx, diagnostics, mc.WritebackCache) if wbcDiskSize != 0 { - if mc.WritebackCache == nil { - mc.WritebackCache = &XenserverWritebackCacheModel{} + if mc.WritebackCache.IsNull() { + writebackCache = XenserverWritebackCacheModel{} } - mc.WritebackCache.WriteBackCacheDiskSizeGB = types.Int64Value(int64(provScheme.GetWriteBackCacheDiskSizeGB())) + writebackCache.WriteBackCacheDiskSizeGB = types.Int64Value(int64(provScheme.GetWriteBackCacheDiskSizeGB())) if wbcMemorySize != 0 { - mc.WritebackCache.WriteBackCacheMemorySizeMB = types.Int64Value(int64(provScheme.GetWriteBackCacheMemorySizeMB())) + writebackCache.WriteBackCacheMemorySizeMB = types.Int64Value(int64(provScheme.GetWriteBackCacheMemorySizeMB())) } } + mc.WritebackCache = util.TypedObjectToObjectValue(ctx, diagnostics, writebackCache) } func (mc *NutanixMachineConfigModel) RefreshProperties(catalog citrixorchestration.MachineCatalogDetailResponseModel) { @@ -459,6 +1283,10 @@ func (mc *NutanixMachineConfigModel) RefreshProperties(catalog citrixorchestrati masterImage := provScheme.GetMasterImage() mc.MasterImage = types.StringValue(masterImage.GetName()) + // Refresh Master Image Note + currentDiskImage := provScheme.GetCurrentDiskImage() + mc.MasterImageNote = types.StringValue(currentDiskImage.GetMasterImageNote()) + // Refresh Memory mc.MemoryMB = types.Int64Value(int64(provScheme.GetMemoryMB())) mc.CpuCount = types.Int64Value(int64(provScheme.GetCpuCount())) @@ -471,8 +1299,7 @@ func parseAzureMachineProfileResponseToModel(machineProfileResponse citrixorches if machineProfileName := machineProfileResponse.GetName(); machineProfileName != "" { machineProfileSegments := strings.Split(machineProfileResponse.GetXDPath(), "\\") lastIndex := len(machineProfileSegments) - 1 - resourceType := strings.Split(machineProfileSegments[lastIndex], ".")[1] - if strings.EqualFold(resourceType, "templatespecversion") { + if strings.HasSuffix(machineProfileSegments[lastIndex], "templatespecversion") { machineProfileModel.MachineProfileTemplateSpecVersion = types.StringValue(machineProfileName) templateSpecIndex := slices.IndexFunc(machineProfileSegments, func(machineProfileSegment string) bool { diff --git a/internal/daas/policies/policy_set_resource.go b/internal/daas/policies/policy_set_resource.go index 480c0c2..c15c03a 100644 --- a/internal/daas/policies/policy_set_resource.go +++ b/internal/daas/policies/policy_set_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package policies @@ -7,8 +7,7 @@ import ( "encoding/json" "fmt" "net/http" - "regexp" - "sort" + "slices" "strconv" "strings" @@ -16,17 +15,9 @@ import ( citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" "github.com/citrix/terraform-provider-citrix/internal/util" - "github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault" - "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" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" ) @@ -72,40 +63,27 @@ func (r *policySetResource) ModifyPlan(ctx context.Context, req resource.ModifyP return } - serverValue := "" - if r.client.AuthConfig.OnPremises { - serverValue = r.client.ApiClient.GetConfig().Host - } else { - serverValue = fmt.Sprintf("%s.xendesktop.net", r.client.ClientConfig.CustomerId) - } - // Validate DDC Version isDdcVersionSupported := util.CheckProductVersion(r.client, &resp.Diagnostics, 118, 7, 41, "policy set") if !isDdcVersionSupported { return } - allScopeContained := false - for _, scope := range plan.Scopes { - if strings.EqualFold(scope.ValueString(), "All") { - allScopeContained = true - break + if !plan.Scopes.IsNull() { + if slices.Contains(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes), util.AllScopeId) { + resp.Diagnostics.AddError( + "Error "+operation+" Policy Set", + fmt.Sprintf("Id `%s` for Scope `All` should not be added to the policy set scopes.", util.AllScopeId), + ) } } - if !allScopeContained { - plan.Scopes = append(plan.Scopes, types.StringValue("All")) - } - sort.Slice(plan.Scopes, func(i, j int) bool { - return plan.Scopes[i].ValueString() < plan.Scopes[j].ValueString() - }) + plannedPolicies := util.ObjectListToTypedArray[PolicyModel](ctx, &resp.Diagnostics, plan.Policies) - for policyIndex, policy := range plan.Policies { - sort.Slice(policy.PolicySettings, func(i, j int) bool { - return policy.PolicySettings[i].Name.ValueString() < policy.PolicySettings[j].Name.ValueString() - }) + for _, policy := range plannedPolicies { + policySettings := util.ObjectListToTypedArray[PolicySettingModel](ctx, &resp.Diagnostics, policy.PolicySettings) - for _, setting := range policy.PolicySettings { + for _, setting := range policySettings { if strings.EqualFold(setting.Value.ValueString(), "true") || strings.EqualFold(setting.Value.ValueString(), "1") || strings.EqualFold(setting.Value.ValueString(), "false") || @@ -116,18 +94,8 @@ func (r *policySetResource) ModifyPlan(ctx context.Context, req resource.ModifyP ) } } - - sort.Slice(policy.PolicyFilters, func(i, j int) bool { - return policy.PolicyFilters[i].Type.ValueString() < policy.PolicyFilters[j].Type.ValueString() - }) - - for filterIndex, filter := range policy.PolicyFilters { - if filter.Data.Uuid.ValueString() != "" && - filter.Data.Server.ValueString() == "" { - plan.Policies[policyIndex].PolicyFilters[filterIndex].Data.Server = types.StringValue(serverValue) - } - } } + plan.Policies = util.TypedArrayToObjectList[PolicyModel](ctx, &resp.Diagnostics, plannedPolicies) // Set state to fully populated data diags = resp.Plan.Set(ctx, plan) @@ -153,178 +121,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 = schema.Schema{ - Description: "Manages a policy set and the policies within it. The order of the policies specified in this resource reflect the policy priority. This feature will be officially supported for On-Premises with DDC version 2402 and above and will be made available for Cloud soon. For detailed information about policy settings and filters, please refer to [this document](https://github.com/citrix/terraform-provider-citrix/blob/main/internal/daas/policies/policy_set_resource.md).", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the policy set.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the policy set.", - Required: true, - }, - "type": schema.StringAttribute{ - Description: "Type of the policy set. Type can be one of `SitePolicies`, `DeliveryGroupPolicies`, `SiteTemplates`, or `CustomTemplates`.", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf([]string{ - "SitePolicies", - "DeliveryGroupPolicies", - "SiteTemplates", - "CustomTemplates"}...), - }, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "description": schema.StringAttribute{ - Description: "Description of the policy set.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString(""), - }, - "scopes": schema.ListAttribute{ - ElementType: types.StringType, - Description: "The names of the scopes for the policy set to apply on.", - Optional: true, - Computed: true, - Default: listdefault.StaticValue(types.ListNull(types.StringType)), - }, - "policies": schema.ListNestedAttribute{ - Description: "Ordered list of policies. The order of policies in the list determines the priority of the policies.", - Required: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "name": schema.StringAttribute{ - Description: "Name of the policy.", - Required: true, - }, - "description": schema.StringAttribute{ - Description: "Description of the policy.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString(""), - }, - "enabled": schema.BoolAttribute{ - Description: "Indicate whether the policy is being enabled.", - Required: true, - }, - "policy_settings": schema.ListNestedAttribute{ - Description: "Set of policy settings.", - Required: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "name": schema.StringAttribute{ - Description: "Name of the policy setting name.", - Required: true, - }, - "use_default": schema.BoolAttribute{ - Description: "Indicate whether using default value for the policy setting.", - Required: true, - }, - "value": schema.StringAttribute{ - Description: "Value of the policy setting.", - Optional: true, - Computed: true, - Validators: []validator.String{ - stringvalidator.ExactlyOneOf( - path.MatchRelative().AtParent().AtName("enabled"), - path.MatchRelative().AtParent().AtName("value")), - }, - }, - "enabled": schema.BoolAttribute{ - Description: "Whether of the policy setting has enabled or allowed value.", - Optional: true, - Computed: true, - Validators: []validator.Bool{ - boolvalidator.ExactlyOneOf( - path.MatchRelative().AtParent().AtName("enabled"), - path.MatchRelative().AtParent().AtName("value")), - }, - }, - }, - }, - }, - "policy_filters": schema.ListNestedAttribute{ - Description: "Set of policy filters.", - Required: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "type": schema.StringAttribute{ - Description: "Type of the policy filter. Type can be one of `AccessControl`, `BranchRepeater`, `ClientIP`, `ClientName`, `DesktopGroup`, `DesktopKind`, `OU`, `User`, and `DesktopTag`", - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf([]string{ - "AccessControl", - "BranchRepeater", - "ClientIP", - "ClientName", - "DesktopGroup", - "DesktopKind", - "OU", - "User", - "DesktopTag"}...), - }, - }, - "data": schema.SingleNestedAttribute{ - Description: "Data of the policy filter.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "server": schema.StringAttribute{ - Description: "Server address for the policy filter data.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString(""), - }, - "uuid": schema.StringAttribute{ - Description: "Resource UUID for the policy filter data.", - Optional: true, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with UUID in GUID format."), - }, - }, - "connection": schema.StringAttribute{ - Description: "Gateway connection for the policy filter data.", - Optional: true, - }, - "condition": schema.StringAttribute{ - Description: "Gateway condition for the policy filter data.", - Optional: true, - }, - "gateway": schema.StringAttribute{ - Description: "Gateway for the policy filter data.", - Optional: true, - }, - "value": schema.StringAttribute{ - Description: "Va;ie for the policy filter data.", - Optional: true, - }, - }, - }, - "enabled": schema.BoolAttribute{ - Description: "Indicate whether the policy is being enabled.", - Required: true, - }, - "allowed": schema.BoolAttribute{ - Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", - Required: true, - }, - }, - }, - }, - }, - }, - }, - "is_assigned": schema.BoolAttribute{ - Description: "Indicate whether the policy set is being assigned to delivery groups.", - Computed: true, - }, - }, - } + resp.Schema = GetSchema() } // Create implements resource.Resource. @@ -359,7 +156,12 @@ func (r *policySetResource) Create(ctx context.Context, req resource.CreateReque createPolicySetRequestBody.SetDescription(plan.Description.ValueString()) createPolicySetRequestBody.SetPolicySetType(plan.Type.ValueString()) - createPolicySetRequestBody.SetScopes(util.ConvertBaseStringArrayToPrimitiveStringArray(plan.Scopes)) + // Use scope names instead of IDs for create request to support 2311 + plannedScopeNames, err := util.FetchScopeNamesByIds(ctx, resp.Diagnostics, r.client, util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) + if err != nil { + return + } + createPolicySetRequestBody.SetScopes(plannedScopeNames) createPolicySetRequest := r.client.ApiClient.GpoDAAS.GpoCreateGpoPolicySet(ctx) createPolicySetRequest = createPolicySetRequest.PolicySetRequest(*createPolicySetRequestBody) @@ -375,8 +177,9 @@ func (r *policySetResource) Create(ctx context.Context, req resource.CreateReque return } + plannedPolicies := util.ObjectListToTypedArray[PolicyModel](ctx, &resp.Diagnostics, plan.Policies) // Create new policies - batchRequestModel, err := constructCreatePolicyBatchRequestModel(plan.Policies, policySetResponse.GetPolicySetGuid(), policySetResponse.GetName(), r.client, resp.Diagnostics) + batchRequestModel, err := constructCreatePolicyBatchRequestModel(ctx, &resp.Diagnostics, r.client, plannedPolicies, policySetResponse.GetPolicySetGuid(), policySetResponse.GetName()) if err != nil { return } @@ -390,8 +193,8 @@ func (r *policySetResource) Create(ctx context.Context, req resource.CreateReque ) } - if successfulJobs < len(plan.Policies) { - errMsg := fmt.Sprintf("An error occurred while adding policies to the Policy Set. %d of %d policies were added to the Policy Set.", successfulJobs, len(plan.Policies)) + if successfulJobs < len(plan.Policies.Elements()) { + errMsg := fmt.Sprintf("An error occurred while adding policies to the Policy Set. %d of %d policies were added to the Policy Set.", successfulJobs, len(plan.Policies.Elements())) resp.Diagnostics.AddError( "Error adding Policies to Policy Set "+policySetResponse.GetName(), "TransactionId: "+txId+ @@ -407,7 +210,8 @@ func (r *policySetResource) Create(ctx context.Context, req resource.CreateReque if len(policySet.Policies) > 0 { // Update Policy Priority - policyPriorityRequest := constructPolicyPriorityRequest(ctx, r.client, policySet, plan.Policies) + plannedPolicies = util.ObjectListToTypedArray[PolicyModel](ctx, &resp.Diagnostics, plan.Policies) + policyPriorityRequest := constructPolicyPriorityRequest(ctx, r.client, policySet, plannedPolicies) // Update policy priorities in the Policy Set policyPriorityResponse, httpResp, err := citrixdaasclient.AddRequestData(policyPriorityRequest, r.client).Execute() if err != nil || !policyPriorityResponse { @@ -430,10 +234,13 @@ func (r *policySetResource) Create(ctx context.Context, req resource.CreateReque return } - util.RefreshList(plan.Scopes, policySet.Scopes) + policySetScopes, err := util.FetchScopeIdsByNames(ctx, resp.Diagnostics, r.client, policySet.GetScopes()) + if err != nil { + return + } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(policySet, policies) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, policySet, policies, policySetScopes) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -465,9 +272,12 @@ func (r *policySetResource) Read(ctx context.Context, req resource.ReadRequest, return } - util.RefreshList(state.Scopes, policySet.Scopes) + policySetScopes, err := util.FetchScopeIdsByNames(ctx, resp.Diagnostics, r.client, policySet.GetScopes()) + if err != nil { + return + } - state = state.RefreshPropertyValues(policySet, policies) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, policySet, policies, policySetScopes) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -577,7 +387,8 @@ func (r *policySetResource) Update(ctx context.Context, req resource.UpdateReque } // Create all the policies, settings, and filters in the plan - createPoliciesBatchRequestModel, err := constructCreatePolicyBatchRequestModel(plan.Policies, plan.Id.ValueString(), plan.Name.ValueString(), r.client, resp.Diagnostics) + plannedPolicies := util.ObjectListToTypedArray[PolicyModel](ctx, &resp.Diagnostics, plan.Policies) + createPoliciesBatchRequestModel, err := constructCreatePolicyBatchRequestModel(ctx, &resp.Diagnostics, r.client, plannedPolicies, plan.Id.ValueString(), plan.Name.ValueString()) if err != nil { return } @@ -610,7 +421,8 @@ func (r *policySetResource) Update(ctx context.Context, req resource.UpdateReque } if len(policySet.Policies) > 0 { - policyPriorityRequest := constructPolicyPriorityRequest(ctx, r.client, policySet, plan.Policies) + plannedPolicies = util.ObjectListToTypedArray[PolicyModel](ctx, &resp.Diagnostics, plan.Policies) + policyPriorityRequest := constructPolicyPriorityRequest(ctx, r.client, policySet, plannedPolicies) // Update policy priorities in the Policy Set policyPriorityResponse, httpResp, err := citrixdaasclient.AddRequestData(policyPriorityRequest, r.client).Execute() if err != nil || !policyPriorityResponse { @@ -628,11 +440,7 @@ func (r *policySetResource) Update(ctx context.Context, req resource.UpdateReque var editPolicySetRequestBody = &citrixorchestration.PolicySetRequest{} editPolicySetRequestBody.SetName(policySetName) editPolicySetRequestBody.SetDescription(plan.Description.ValueString()) - scopeIds, err := fetchScopeIdsByNames(ctx, r.client, resp.Diagnostics, plan.Scopes) - if err != nil { - return - } - editPolicySetRequestBody.SetScopes(util.ConvertBaseStringArrayToPrimitiveStringArray(scopeIds)) + editPolicySetRequestBody.SetScopes(util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.Scopes)) editPolicySetRequest := r.client.ApiClient.GpoDAAS.GpoUpdateGpoPolicySet(ctx, policySetId) editPolicySetRequest = editPolicySetRequest.PolicySetRequest(*editPolicySetRequestBody) @@ -659,10 +467,13 @@ func (r *policySetResource) Update(ctx context.Context, req resource.UpdateReque return } - util.RefreshList(plan.Scopes, policySet.Scopes) + policySetScopes, err := util.FetchScopeIdsByNames(ctx, resp.Diagnostics, r.client, policySet.GetScopes()) + if err != nil { + return + } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(policySet, policies) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, policySet, policies, policySetScopes) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -869,7 +680,7 @@ func generateBatchApiHeaders(client *citrixdaasclient.CitrixDaasClient) ([]citri return headers, httpResp, err } -func constructCreatePolicyBatchRequestModel(policiesToCreate []PolicyModel, policySetGuid string, policySetName string, client *citrixdaasclient.CitrixDaasClient, diagnostic diag.Diagnostics) (citrixorchestration.BatchRequestModel, error) { +func constructCreatePolicyBatchRequestModel(ctx context.Context, diags *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, policiesToCreate []PolicyModel, policySetGuid string, policySetName string) (citrixorchestration.BatchRequestModel, error) { batchRequestItems := []citrixorchestration.BatchRequestItemModel{} var batchRequestModel citrixorchestration.BatchRequestModel @@ -877,10 +688,11 @@ func constructCreatePolicyBatchRequestModel(policiesToCreate []PolicyModel, poli var createPolicyRequest = citrixorchestration.PolicyRequest{} createPolicyRequest.SetName(policyToCreate.Name.ValueString()) createPolicyRequest.SetDescription(policyToCreate.Description.ValueString()) - createPolicyRequest.SetIsEnabled(policyToCreate.IsEnabled.ValueBool()) + createPolicyRequest.SetIsEnabled(policyToCreate.Enabled.ValueBool()) // Add Policy Settings policySettings := []citrixorchestration.SettingRequest{} - for _, policySetting := range policyToCreate.PolicySettings { + policySettingsToCreate := util.ObjectListToTypedArray[PolicySettingModel](ctx, diags, policyToCreate.PolicySettings) + for _, policySetting := range policySettingsToCreate { settingRequest := citrixorchestration.SettingRequest{} settingRequest.SetSettingName(policySetting.Name.ValueString()) settingRequest.SetUseDefault(policySetting.UseDefault.ValueBool()) @@ -898,39 +710,15 @@ func constructCreatePolicyBatchRequestModel(policiesToCreate []PolicyModel, poli createPolicyRequest.SetSettings(policySettings) // Add Policy Filters - policyFilters := []citrixorchestration.FilterRequest{} - for _, policyFilter := range policyToCreate.PolicyFilters { - filterRequest := citrixorchestration.FilterRequest{} - filterRequest.SetFilterType(policyFilter.Type.ValueString()) - if policyFilter.Data.Value.ValueString() != "" { - filterRequest.SetFilterData(policyFilter.Data.Value.ValueString()) - } else { - policyFilterDataClientModel := PolicyFilterDataClientModel{ - Server: policyFilter.Data.Server.ValueString(), - Uuid: policyFilter.Data.Uuid.ValueString(), - Connection: policyFilter.Data.Connection.ValueString(), - Condition: policyFilter.Data.Condition.ValueString(), - Gateway: policyFilter.Data.Gateway.ValueString(), - } - policyFilterDataJson, err := json.Marshal(policyFilterDataClientModel) - if err != nil { - diagnostic.AddError( - "Error adding Policy Filter "+policyToCreate.Name.ValueString()+" to Policy Set "+policySetName, - "An unexpected error occurred: "+err.Error(), - ) - return batchRequestModel, err - } - filterRequest.SetFilterData(string(policyFilterDataJson)) - } - filterRequest.SetIsAllowed(policyFilter.IsAllowed.ValueBool()) - filterRequest.SetIsEnabled(policyFilter.IsEnabled.ValueBool()) - policyFilters = append(policyFilters, filterRequest) + policyFilters, err := constructPolicyFilterRequests(ctx, diags, client, policyToCreate) + if err != nil { + return batchRequestModel, err } createPolicyRequest.SetFilters(policyFilters) createPolicyRequestBodyString, err := util.ConvertToString(createPolicyRequest) if err != nil { - diagnostic.AddError( + diags.AddError( "Error adding Policy "+policyToCreate.Name.ValueString()+" to Policy Set "+policySetName, "An unexpected error occurred: "+err.Error(), ) @@ -939,7 +727,7 @@ func constructCreatePolicyBatchRequestModel(policiesToCreate []PolicyModel, poli batchApiHeaders, httpResp, err := generateBatchApiHeaders(client) if err != nil { - diagnostic.AddError( + diags.AddError( "Error deleting policy from policy set "+policySetName, "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ "\nCould not delete policies within the policy set to be updated, unexpected error: "+util.ReadClientError(err), @@ -962,6 +750,174 @@ func constructCreatePolicyBatchRequestModel(policiesToCreate []PolicyModel, poli return batchRequestModel, nil } +func constructPolicyFilterRequests(ctx context.Context, diags *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, policy PolicyModel) ([]citrixorchestration.FilterRequest, error) { + filterRequests := []citrixorchestration.FilterRequest{} + + serverValue := "" + if client.AuthConfig.OnPremises || !client.AuthConfig.ApiGateway { + serverValue = client.ApiClient.GetConfig().Host + } else { + serverValue = fmt.Sprintf("%s.xendesktop.net", client.ClientConfig.CustomerId) + } + + if !policy.AccessControlFilters.IsNull() && len(policy.AccessControlFilters.Elements()) > 0 { + accessControlFilters := util.ObjectListToTypedArray[AccessControlFilterModel](ctx, diags, policy.AccessControlFilters) + for _, accessControlFilter := range accessControlFilters { + filterRequest := citrixorchestration.FilterRequest{} + filterRequest.SetFilterType("AccessControl") + + policyFilterDataClientModel := PolicyFilterGatewayDataClientModel{ + Connection: accessControlFilter.Connection, + Condition: accessControlFilter.Condition, + Gateway: accessControlFilter.Gateway, + } + + policyFilterDataJson, err := json.Marshal(policyFilterDataClientModel) + if err != nil { + diags.AddError( + "Error adding Access Control Policy Filter to Policy Set. ", + "An unexpected error occurred: "+err.Error(), + ) + return filterRequests, err + } + filterRequest.SetFilterData(string(policyFilterDataJson)) + filterRequest.SetIsAllowed(accessControlFilter.Allowed.ValueBool()) + filterRequest.SetIsEnabled(accessControlFilter.Enabled.ValueBool()) + filterRequests = append(filterRequests, filterRequest) + } + } + + if !policy.BranchRepeaterFilter.IsNull() { + branchRepeaterFilter := util.ObjectValueToTypedObject[BranchRepeaterFilterModel](ctx, diags, policy.BranchRepeaterFilter) + branchnRepeaterFilterRequest := citrixorchestration.FilterRequest{} + branchnRepeaterFilterRequest.SetFilterType("BranchRepeater") + branchnRepeaterFilterRequest.SetIsAllowed(branchRepeaterFilter.Allowed.ValueBool()) + branchnRepeaterFilterRequest.SetIsEnabled(branchRepeaterFilter.Enabled.ValueBool()) + filterRequests = append(filterRequests, branchnRepeaterFilterRequest) + } + + if !policy.ClientIPFilters.IsNull() && len(policy.ClientIPFilters.Elements()) > 0 { + clientIpFilters := util.ObjectListToTypedArray[ClientIPFilterModel](ctx, diags, policy.ClientIPFilters) + for _, clientIpFilter := range clientIpFilters { + filterRequest := citrixorchestration.FilterRequest{} + filterRequest.SetFilterType("ClientIP") + + filterRequest.SetFilterData(clientIpFilter.IpAddress.ValueString()) + filterRequest.SetIsAllowed(clientIpFilter.Allowed.ValueBool()) + filterRequest.SetIsEnabled(clientIpFilter.Enabled.ValueBool()) + filterRequests = append(filterRequests, filterRequest) + } + } + + if !policy.ClientNameFilters.IsNull() && len(policy.ClientNameFilters.Elements()) > 0 { + clientNameFilters := util.ObjectListToTypedArray[ClientNameFilterModel](ctx, diags, policy.ClientNameFilters) + for _, clientName := range clientNameFilters { + filterRequest := citrixorchestration.FilterRequest{} + filterRequest.SetFilterType("ClientName") + + filterRequest.SetFilterData(clientName.ClientName.ValueString()) + filterRequest.SetIsAllowed(clientName.Allowed.ValueBool()) + filterRequest.SetIsEnabled(clientName.Enabled.ValueBool()) + filterRequests = append(filterRequests, filterRequest) + } + } + + if !policy.DeliveryGroupFilters.IsNull() && len(policy.DeliveryGroupFilters.Elements()) > 0 { + deliveryGroupFilters := util.ObjectListToTypedArray[DeliveryGroupFilterModel](ctx, diags, policy.DeliveryGroupFilters) + for _, deliveryGroupFilter := range deliveryGroupFilters { + filterRequest := citrixorchestration.FilterRequest{} + filterRequest.SetFilterType("DesktopGroup") + + policyFilterDataClientModel := PolicyFilterUuidDataClientModel{ + Uuid: deliveryGroupFilter.DeliveryGroupId.ValueString(), + Server: serverValue, + } + + policyFilterDataJson, err := json.Marshal(policyFilterDataClientModel) + if err != nil { + diags.AddError( + "Error adding Access Control Policy Filter to Policy Set. ", + "An unexpected error occurred: "+err.Error(), + ) + return filterRequests, err + } + + filterRequest.SetFilterData(string(policyFilterDataJson)) + filterRequest.SetIsAllowed(deliveryGroupFilter.Allowed.ValueBool()) + filterRequest.SetIsEnabled(deliveryGroupFilter.Enabled.ValueBool()) + filterRequests = append(filterRequests, filterRequest) + } + } + + if !policy.DeliveryGroupTypeFilters.IsNull() && len(policy.DeliveryGroupTypeFilters.Elements()) > 0 { + deliveryGroupTypeFilters := util.ObjectListToTypedArray[DeliveryGroupTypeFilterModel](ctx, diags, policy.DeliveryGroupTypeFilters) + for _, deliveryGroupTypeFilter := range deliveryGroupTypeFilters { + filterRequest := citrixorchestration.FilterRequest{} + filterRequest.SetFilterType("DesktopKind") + + filterRequest.SetFilterData(deliveryGroupTypeFilter.DeliveryGroupType.ValueString()) + filterRequest.SetIsAllowed(deliveryGroupTypeFilter.Allowed.ValueBool()) + filterRequest.SetIsEnabled(deliveryGroupTypeFilter.Enabled.ValueBool()) + filterRequests = append(filterRequests, filterRequest) + } + } + + if !policy.TagFilters.IsNull() && len(policy.TagFilters.Elements()) > 0 { + tagFilters := util.ObjectListToTypedArray[TagFilterModel](ctx, diags, policy.TagFilters) + for _, tagFilter := range tagFilters { + filterRequest := citrixorchestration.FilterRequest{} + filterRequest.SetFilterType("DesktopTag") + + policyFilterDataClientModel := PolicyFilterUuidDataClientModel{ + Uuid: tagFilter.Tag.ValueString(), + Server: serverValue, + } + + policyFilterDataJson, err := json.Marshal(policyFilterDataClientModel) + if err != nil { + diags.AddError( + "Error adding Access Control Policy Filter to Policy Set. ", + "An unexpected error occurred: "+err.Error(), + ) + return filterRequests, err + } + + filterRequest.SetFilterData(string(policyFilterDataJson)) + filterRequest.SetIsAllowed(tagFilter.Allowed.ValueBool()) + filterRequest.SetIsEnabled(tagFilter.Enabled.ValueBool()) + filterRequests = append(filterRequests, filterRequest) + } + } + + if !policy.OuFilters.IsNull() && len(policy.OuFilters.Elements()) > 0 { + ouFilters := util.ObjectListToTypedArray[OuFilterModel](ctx, diags, policy.OuFilters) + for _, ouFilter := range ouFilters { + filterRequest := citrixorchestration.FilterRequest{} + filterRequest.SetFilterType("OU") + + filterRequest.SetFilterData(ouFilter.Ou.ValueString()) + filterRequest.SetIsAllowed(ouFilter.Allowed.ValueBool()) + filterRequest.SetIsEnabled(ouFilter.Enabled.ValueBool()) + filterRequests = append(filterRequests, filterRequest) + } + } + + if !policy.UserFilters.IsNull() && len(policy.UserFilters.Elements()) > 0 { + userFilters := util.ObjectListToTypedArray[UserFilterModel](ctx, diags, policy.UserFilters) + for _, userFilter := range userFilters { + filterRequest := citrixorchestration.FilterRequest{} + filterRequest.SetFilterType("User") + + filterRequest.SetFilterData(userFilter.UserSid.ValueString()) + filterRequest.SetIsAllowed(userFilter.Allowed.ValueBool()) + filterRequest.SetIsEnabled(userFilter.Enabled.ValueBool()) + filterRequests = append(filterRequests, filterRequest) + } + } + + return filterRequests, nil +} + func constructPolicyPriorityRequest(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, policySet *citrixorchestration.PolicySetResponse, planedPolicies []PolicyModel) citrixorchestration.ApiGpoRankGpoPoliciesRequest { // 1. Construct map of policy name: policy id // 2. Construct array of policy id based on the policy name order @@ -983,29 +939,3 @@ func constructPolicyPriorityRequest(ctx context.Context, client *citrixdaasclien createPolicyPriorityRequest = createPolicyPriorityRequest.RequestBody(util.ConvertBaseStringArrayToPrimitiveStringArray(policyPriority)) return createPolicyPriorityRequest } - -func fetchScopeIdsByNames(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, diagnostics diag.Diagnostics, scopeNames []types.String) ([]types.String, error) { - getAdminScopesRequest := client.ApiClient.AdminAPIsDAAS.AdminGetAdminScopes(ctx) - // Create new Policy Set - getScopesResponse, httpResp, err := citrixdaasclient.AddRequestData(getAdminScopesRequest, client).Execute() - if err != nil || getScopesResponse == nil { - diagnostics.AddError( - "Error fetch scope ids from names", - "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ - "\nError message: "+util.ReadClientError(err), - ) - return nil, err - } - - scopeNameIdMap := map[string]types.String{} - for _, scope := range getScopesResponse.Items { - scopeNameIdMap[scope.GetName()] = types.StringValue(scope.GetId()) - } - - scopeIds := []types.String{} - for _, scopeName := range scopeNames { - scopeIds = append(scopeIds, scopeNameIdMap[scopeName.ValueString()]) - } - - return scopeIds, nil -} diff --git a/internal/daas/policies/policy_set_resource.md b/internal/daas/policies/policy_set_resource.md index 304f00f..8a95a46 100644 --- a/internal/daas/policies/policy_set_resource.md +++ b/internal/daas/policies/policy_set_resource.md @@ -10,7 +10,7 @@ resource "citrix_policy_set" "example-policy-set" { name = "Policy Set Name" description = "Policy Set Description" type = "DeliveryGroupPolicies" - scopes = [ "All", "citrix_admin_scope.example-admin-scope.name" ] + scopes = [ "citrix_admin_scope.example-admin-scope.id" ] policies = [ { name = "Name of the Policy with Priority 0" @@ -23,13 +23,9 @@ resource "citrix_policy_set" "example-policy-set" { use_default = false }, ] - policy_filters = [ + delivery_group_filters = [ { - type = "DesktopGroup" - data = { - server = "10.0.0.1" - uuid = citrix_delivery_group.example-delivery-group.id - } + delivery_group_id = citrix_delivery_group.example-delivery-group.id enabled = true allowed = true }, @@ -40,7 +36,6 @@ resource "citrix_policy_set" "example-policy-set" { description = "Policy in the example policy set with priority 1" enabled = false policy_settings = [] - policy_filters = [] } ] } @@ -55,21 +50,29 @@ resource "citrix_policy_set" "example-policy-set" { ### Access Control Filter Type: `AccessControl` -Filter Data: +Example: ``` # With Citrix Gateway -data = { - connection = "WithAccessGateway" - condition = {Access Condition} // Wildcard `*` is allowed - gateway = {Gateway farm name} // Wildcard `*` is allowed -} +access_control_filters = [ + { + enabled = true + allowed = true + connection = "WithAccessGateway" + condition = {Access Condition} // Wildcard `*` is allowed + gateway = {Gateway farm name} // Wildcard `*` is allowed + } +] # Without Citrix Gateway -data = { - connection = "WithoutAccessGateway" - condition = "*" - gateway = "*" -} +access_control_filters = [ + { + enabled = true + allowed = true + connection = "WithoutAccessGateway" + condition = "*" + gateway = "*" + } +] ``` ### Citrix SD-WAN @@ -79,43 +82,55 @@ Filter Data should not be specified When `allowed` is set to `true`, this means policy is applied to `Connections with Citrix SD-WAN`. When it is set to `false`, this means policy is applied to `Connections without Citrix SD-WAN`. +Example: +``` +branch_repeater_filter = { + enabled = true + allowed = true +} +``` + ### Client IP Address Filter Type: `ClientIP` -Filter Data: +Example: ``` -data = { - value = "{IP address to be filtered}" -} +client_ip_filters = [ + { + enabled = true + allowed = true + ip_address = "{IP address to be filtered}" + } +] ``` ### Client Name Filter Type: `Client Name` -Filter Data: +Example: ``` -data = { - value = "{Name of the client}" -} +client_name_filters = [ + { + enabled = true + allowed = true + client_name = "{Name of the client to be filtered}" + } +] ``` ### Delivery Group Filter Type: `DesktopGroup` -Filter Data Template: +Example: ``` -# OnPrem -data = { - server = "{IP of the DDC}" - uuid = {Id of the Delivery Group} -} - -# Cloud -data = { - server = "{Customer ID}.xendesktop.net" - uuid = {Id of the Delivery Group} -} +delivery_group_filters = [ + { + enabled = true + allowed = true + delivery_group_id = "{ID of the delivery group to be filtered}" + } +] ``` ### Delivery Group Type @@ -129,29 +144,30 @@ Private Application | `PrivateApp` Shared Desktop | `Shared` Shared Application | `SharedApp` -Filter Data Template: +Example: ``` -data = { - value = "{Filter Data}" -} +delivery_group_type_filters = [ + { + enabled = true + allowed = true + delivery_group_type = "{Type of the delivery group to be filtered}" + } +] ``` ### Organizational Unit (OU) Filter Type: `OU` -Filter Data: -``` -data = { - value = "{OU Path}" -} -``` - Example: ``` -data = { - value = "CN=Computers,DC=ctx-ad,DC=local" -} +ou_filters = [ + { + enabled = true + allowed = true + ou = "{Path of the oranizational unit to be filtered}" + } +] ``` ### User or Group @@ -161,22 +177,29 @@ Filter Data: Sid of the user or group Filter Data Example: `S-1-5-21-4235287923-3346439331-1564732298-1103` +Example: +``` +user_filters = [ + { + enabled = true + allowed = true + sid = "{SID of the user or user group to be filtered}" + } +] +``` + ### Tag Filter Type: `DesktopTag` -Filter Data Template: +Example: ``` -# OnPrem -data = { - server = "{IP of the DDC}" - uuid = {Id of the Tag} -} - -# Cloud -data = { - server = "{Customer ID}.xendesktop.net" - uuid = {Id of the Tag} -} +tag_filters = [ + { + enabled = true + allowed = true + tag = "{ID of the tag to be filtered}" + } +] ``` ## Available Policy Settings @@ -393,7 +416,6 @@ Related Settings: Allowed URLs to be redirected to VDA Allowed URLs to be redirected to Client - ``` Setting Name: `AllowBidirectionalContentRedirection` @@ -8142,7 +8164,6 @@ Examples: Tags for watermark settings can be used with other watermark policies. Unsupported tags will be shown as regular text. Watermark text should not exceed 25 characters. - ``` Setting Name: `WatermarkCustomText` @@ -8180,21 +8201,39 @@ Setting Value: ### WebSockets port number Description: ``` +TCP port number for incoming WebSockets connections. ``` Setting Name: `WebSocketsPort` -Setting Value: `{Port Number}` +Setting Value: `{TCP Port Number}` ### WebSockets trusted origin server list Description: ``` -TCP port number for incoming WebSockets connections. +Comma-separated list of trusted origin servers expressed as URLs with the option of using wildcards. + +It is usually the address of the Receiver for Web site. Only Websockets connections originating from one of these addresses will be accepted by XenApp. The generic syntax for this address is:://:/ The protocols should be HTTP or HTTPS. Port is an optional variable and if it is not specified, ports 80 and 443 are used for HTTP and HTTPS respectively. Wildcard characters can also be used to extend this syntax. If this field contains just a '*', it indicates that connections from all origin servers will be accepted. + +Examples +https://abc.domain.com:8080/ +http://abc.def.domain.com:8080/ +https://hostname.domain.com:8081/ +https://*.trusteddomain.com/ +'*' is not treated as wild card character if used with IP. +'http://10.105.*.*' is an invalid trusted origin as per current design. ``` Setting Name: `WSTrustedOriginServerList` -Setting Value: `{TCP Port Number}` +Setting Value: `https://abc.domain.com:8080/` + +Related Settings: +``` +WebSockets connections + +WebSockets port number +``` ### WIA redirection Description: diff --git a/internal/daas/policies/policy_set_resource_model.go b/internal/daas/policies/policy_set_resource_model.go index 9bb3b79..964e21a 100644 --- a/internal/daas/policies/policy_set_resource_model.go +++ b/internal/daas/policies/policy_set_resource_model.go @@ -1,14 +1,29 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package policies import ( + "context" "encoding/json" + "regexp" "sort" "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/boolvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "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/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -19,49 +34,537 @@ type PolicySettingModel struct { Enabled types.Bool `tfsdk:"enabled"` } -type PolicyFilterModel struct { - Type types.String `tfsdk:"type"` - Data PolicyFilterDataModel `tfsdk:"data"` - IsAllowed types.Bool `tfsdk:"allowed"` - IsEnabled types.Bool `tfsdk:"enabled"` +func (PolicySettingModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "Name of the policy setting name.", + Required: true, + }, + "use_default": schema.BoolAttribute{ + Description: "Indicate whether using default value for the policy setting.", + Required: true, + }, + "value": schema.StringAttribute{ + Description: "Value of the policy setting.", + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf( + path.MatchRelative().AtParent().AtName("enabled"), + path.MatchRelative().AtParent().AtName("value")), + }, + }, + "enabled": schema.BoolAttribute{ + Description: "Whether of the policy setting has enabled or allowed value.", + Optional: true, + Computed: true, + Validators: []validator.Bool{ + boolvalidator.ExactlyOneOf( + path.MatchRelative().AtParent().AtName("enabled"), + path.MatchRelative().AtParent().AtName("value")), + }, + }, + }, + } } -type PolicyFilterDataModel struct { - Server types.String `tfsdk:"server"` - Uuid types.String `tfsdk:"uuid"` - Connection types.String `tfsdk:"connection"` - Condition types.String `tfsdk:"condition"` - Gateway types.String `tfsdk:"gateway"` - Value types.String `tfsdk:"value"` +func (PolicySettingModel) GetAttributes() map[string]schema.Attribute { + return PolicySettingModel{}.GetSchema().Attributes +} + +type PolicyFilterUuidDataClientModel struct { + Server string `json:"server,omitempty"` + Uuid string `json:"uuid,omitempty"` +} + +type PolicyFilterGatewayDataClientModel struct { + Connection string `json:"connection,omitempty"` + Condition string `json:"condition,omitempty"` + Gateway string `json:"gateway,omitempty"` +} + +type AccessControlFilterModel struct { + Allowed types.Bool `tfsdk:"allowed"` + Enabled types.Bool `tfsdk:"enabled"` + Connection string `json:"Connection"` + Condition string `json:"Condition"` + Gateway string `json:"Gateway"` +} + +func (AccessControlFilterModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Required: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Required: true, + }, + "connection": schema.StringAttribute{ + Description: "Gateway connection for the policy filter.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf([]string{ + "WithAccessGateway", + "WithoutAccessGateway"}...), + }, + }, + "condition": schema.StringAttribute{ + Description: "Gateway condition for the policy filter.", + Required: true, + }, + "gateway": schema.StringAttribute{ + Description: "Gateway for the policy filter.", + Required: true, + }, + }, + } +} + +func (AccessControlFilterModel) GetAttributes() map[string]schema.Attribute { + return AccessControlFilterModel{}.GetSchema().Attributes +} + +type BranchRepeaterFilterModel struct { + Allowed types.Bool `tfsdk:"allowed"` + Enabled types.Bool `tfsdk:"enabled"` +} + +func (BranchRepeaterFilterModel) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Set of policy filters.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Required: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Required: true, + }, + }, + } +} + +func (BranchRepeaterFilterModel) GetAttributes() map[string]schema.Attribute { + return BranchRepeaterFilterModel{}.GetSchema().Attributes +} + +type ClientIPFilterModel struct { + Allowed types.Bool `tfsdk:"allowed"` + Enabled types.Bool `tfsdk:"enabled"` + IpAddress types.String `tfsdk:"ip_address"` +} + +func (ClientIPFilterModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Required: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Required: true, + }, + "ip_address": schema.StringAttribute{ + Description: "IP Address of the client to be filtered.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.IPv4Regex), "must be a valid IPv4 address without protocol (http:// or https://) and port number"), + }, + }, + }, + } +} + +func (ClientIPFilterModel) GetAttributes() map[string]schema.Attribute { + return ClientIPFilterModel{}.GetSchema().Attributes +} + +type ClientNameFilterModel struct { + Allowed types.Bool `tfsdk:"allowed"` + Enabled types.Bool `tfsdk:"enabled"` + ClientName types.String `tfsdk:"client_name"` +} + +func (ClientNameFilterModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Required: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Required: true, + }, + "client_name": schema.StringAttribute{ + Description: "Name of the client to be filtered.", + Required: true, + }, + }, + } +} + +func (ClientNameFilterModel) GetAttributes() map[string]schema.Attribute { + return ClientNameFilterModel{}.GetSchema().Attributes +} + +type DeliveryGroupFilterModel struct { + Allowed types.Bool `tfsdk:"allowed"` + Enabled types.Bool `tfsdk:"enabled"` + DeliveryGroupId types.String `tfsdk:"delivery_group_id"` +} + +func (DeliveryGroupFilterModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Required: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Required: true, + }, + "delivery_group_id": schema.StringAttribute{ + Description: "Id of the delivery group to be filtered.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + }, + }, + }, + } +} + +func (DeliveryGroupFilterModel) GetAttributes() map[string]schema.Attribute { + return DeliveryGroupFilterModel{}.GetSchema().Attributes +} + +type DeliveryGroupTypeFilterModel struct { + Allowed types.Bool `tfsdk:"allowed"` + Enabled types.Bool `tfsdk:"enabled"` + DeliveryGroupType types.String `tfsdk:"delivery_group_type"` +} + +func (DeliveryGroupTypeFilterModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Required: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Required: true, + }, + "delivery_group_type": schema.StringAttribute{ + Description: "Type of the delivery groups to be filtered.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf([]string{ + "Private", + "PrivateApp", + "Shared", + "SharedApp"}...), + }, + }, + }, + } +} + +func (DeliveryGroupTypeFilterModel) GetAttributes() map[string]schema.Attribute { + return DeliveryGroupTypeFilterModel{}.GetSchema().Attributes } -type PolicyFilterDataClientModel struct { - Server string `json:"server,omitempty"` - Uuid string `json:"uuid,omitempty"` - Connection string `json:"Connection,omitempty"` - Condition string `json:"Condition,omitempty"` - Gateway string `json:"Gateway,omitempty"` +type OuFilterModel struct { + Allowed types.Bool `tfsdk:"allowed"` + Enabled types.Bool `tfsdk:"enabled"` + Ou types.String `tfsdk:"ou"` +} + +func (OuFilterModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Required: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Required: true, + }, + "ou": schema.StringAttribute{ + Description: "Organizational Unit to be filtered.", + Required: true, + }, + }, + } +} + +func (OuFilterModel) GetAttributes() map[string]schema.Attribute { + return OuFilterModel{}.GetSchema().Attributes +} + +type UserFilterModel struct { + Allowed types.Bool `tfsdk:"allowed"` + Enabled types.Bool `tfsdk:"enabled"` + UserSid types.String `tfsdk:"sid"` +} + +func (UserFilterModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Required: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Required: true, + }, + "sid": schema.StringAttribute{ + Description: "SID of the user or user group to be filtered.", + Required: true, + }, + }, + } +} + +func (UserFilterModel) GetAttributes() map[string]schema.Attribute { + return UserFilterModel{}.GetSchema().Attributes +} + +type TagFilterModel struct { + Allowed types.Bool `tfsdk:"allowed"` + Enabled types.Bool `tfsdk:"enabled"` + Tag types.String `tfsdk:"tag"` +} + +func (TagFilterModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the filter is being enabled.", + Required: true, + }, + "allowed": schema.BoolAttribute{ + Description: "Indicate the filtered policy is allowed or denied if the filter condition is met.", + Required: true, + }, + "tag": schema.StringAttribute{ + Description: "Tag to be filtered.", + Required: true, + }, + }, + } +} + +func (TagFilterModel) GetAttributes() map[string]schema.Attribute { + return TagFilterModel{}.GetSchema().Attributes } type PolicyModel struct { - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - IsEnabled types.Bool `tfsdk:"enabled"` - PolicySettings []PolicySettingModel `tfsdk:"policy_settings"` - PolicyFilters []PolicyFilterModel `tfsdk:"policy_filters"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Enabled types.Bool `tfsdk:"enabled"` + PolicySettings types.List `tfsdk:"policy_settings"` // []PolicySettingModel + AccessControlFilters types.List `tfsdk:"access_control_filters"` // []AccessControlFilterModel + BranchRepeaterFilter types.Object `tfsdk:"branch_repeater_filter"` // BranchRepeaterFilterModel + ClientIPFilters types.List `tfsdk:"client_ip_filters"` // []ClientIPFilterModel + ClientNameFilters types.List `tfsdk:"client_name_filters"` // []ClientNameFilterModel + DeliveryGroupFilters types.List `tfsdk:"delivery_group_filters"` // []DeliveryGroupFilterModel + DeliveryGroupTypeFilters types.List `tfsdk:"delivery_group_type_filters"` // []DeliveryGroupTypeFilterModel + OuFilters types.List `tfsdk:"ou_filters"` // []OuFilterModel + UserFilters types.List `tfsdk:"user_filters"` // []UserFilterModel + TagFilters types.List `tfsdk:"tag_filters"` // []TagFilterModel +} + +func (PolicyModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "Name of the policy.", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the policy.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "enabled": schema.BoolAttribute{ + Description: "Indicate whether the policy is being enabled.", + Required: true, + }, + "policy_settings": schema.ListNestedAttribute{ + Description: "Set of policy settings.", + Required: true, + NestedObject: PolicySettingModel{}.GetSchema(), + }, + "access_control_filters": schema.ListNestedAttribute{ + Description: "Access control policy filters.", + Optional: true, + Computed: true, + NestedObject: AccessControlFilterModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "branch_repeater_filter": BranchRepeaterFilterModel{}.GetSchema(), + "client_ip_filters": schema.ListNestedAttribute{ + Description: "Client ip policy filters.", + Optional: true, + Computed: true, + NestedObject: ClientIPFilterModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "client_name_filters": schema.ListNestedAttribute{ + Description: "Client name policy filters.", + Optional: true, + Computed: true, + NestedObject: ClientNameFilterModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "delivery_group_filters": schema.ListNestedAttribute{ + Description: "Delivery group policy filters.", + Optional: true, + Computed: true, + NestedObject: DeliveryGroupFilterModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "delivery_group_type_filters": schema.ListNestedAttribute{ + Description: "Delivery group type policy filters.", + Optional: true, + Computed: true, + NestedObject: DeliveryGroupTypeFilterModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "ou_filters": schema.ListNestedAttribute{ + Description: "Organizational unit policy filters.", + Optional: true, + Computed: true, + NestedObject: OuFilterModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "user_filters": schema.ListNestedAttribute{ + Description: "User policy filters.", + Optional: true, + Computed: true, + NestedObject: UserFilterModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "tag_filters": schema.ListNestedAttribute{ + Description: "Tag policy filters.", + Optional: true, + Computed: true, + NestedObject: TagFilterModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + }, + } +} + +func (PolicyModel) GetAttributes() map[string]schema.Attribute { + return PolicyModel{}.GetSchema().Attributes } type PolicySetResourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Type types.String `tfsdk:"type"` - Description types.String `tfsdk:"description"` - Scopes []types.String `tfsdk:"scopes"` - IsAssigned types.Bool `tfsdk:"is_assigned"` - Policies []PolicyModel `tfsdk:"policies"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Type types.String `tfsdk:"type"` + Description types.String `tfsdk:"description"` + Scopes types.Set `tfsdk:"scopes"` // []types.Set + IsAssigned types.Bool `tfsdk:"assigned"` + Policies types.List `tfsdk:"policies"` // []PolicyModel } -func (r PolicySetResourceModel) RefreshPropertyValues(policySet *citrixorchestration.PolicySetResponse, policies *citrixorchestration.CollectionEnvelopeOfPolicyResponse) PolicySetResourceModel { +func GetSchema() schema.Schema { + return schema.Schema{ + Description: "Manages a policy set and the policies within it. The order of the policies specified in this resource reflect the policy priority. This feature will be officially supported for On-Premises with DDC version 2402 and above and will be made available for Cloud soon. For detailed information about policy settings and filters, please refer to [this document](https://github.com/citrix/terraform-provider-citrix/blob/main/internal/daas/policies/policy_set_resource.md).", // TODO: Update this cooment when policy set is available for cloud + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the policy set.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the policy set.", + Required: true, + }, + "type": schema.StringAttribute{ + Description: "Type of the policy set. Type can be one of `SitePolicies`, `DeliveryGroupPolicies`, `SiteTemplates`, or `CustomTemplates`.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("DeliveryGroupPolicies"), + Validators: []validator.String{ + stringvalidator.OneOf([]string{ + "SitePolicies", + "DeliveryGroupPolicies", + "SiteTemplates", + "CustomTemplates"}...), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "description": schema.StringAttribute{ + Description: "Description of the policy set.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The IDs of the scopes for the policy set to be a part of.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + validator.String( + stringvalidator.RegexMatches(regexp.MustCompile(util.GuidRegex), "must be specified with ID in GUID format"), + ), + ), + }, + }, + "policies": schema.ListNestedAttribute{ + Description: "Ordered list of policies. The order of policies in the list determines the priority of the policies.", + Required: true, + NestedObject: PolicyModel{}.GetSchema(), + }, + "assigned": schema.BoolAttribute{ + Description: "Indicate whether the policy set is being assigned to delivery groups.", + Computed: true, + }, + }, + } +} + +func (r PolicySetResourceModel) RefreshPropertyValues(ctx context.Context, diags *diag.Diagnostics, policySet *citrixorchestration.PolicySetResponse, policies *citrixorchestration.CollectionEnvelopeOfPolicyResponse, policySetScopes []string) PolicySetResourceModel { // Set required values r.Id = types.StringValue(policySet.GetPolicySetGuid()) r.Name = types.StringValue(policySet.GetName()) @@ -74,43 +577,29 @@ func (r PolicySetResourceModel) RefreshPropertyValues(policySet *citrixorchestra r.Description = types.StringNull() } - if policySet.GetScopes() != nil && len(policySet.GetScopes()) != 0 { - r.Scopes = util.ConvertPrimitiveStringArrayToBaseStringArray(policySet.GetScopes()) - - scopeContainsAll := false - - for _, scope := range r.Scopes { - if strings.EqualFold(scope.ValueString(), "all") { - scopeContainsAll = true - break - } - } - - if !scopeContainsAll { - r.Scopes = append(r.Scopes, types.StringValue("All")) + updatedPolicySetScopes := []string{} + for _, scopeId := range policySetScopes { + if !strings.EqualFold(scopeId, util.AllScopeId) { + updatedPolicySetScopes = append(updatedPolicySetScopes, scopeId) } - - sort.Slice(r.Scopes, func(i, j int) bool { - return r.Scopes[i].ValueString() < r.Scopes[j].ValueString() - }) - } else { - r.Scopes = []types.String{types.StringValue("All")} } + r.Scopes = util.StringArrayToStringSet(ctx, diags, updatedPolicySetScopes) + if policies != nil && policies.Items != nil { policyItems := policies.Items sort.Slice(policyItems, func(i, j int) bool { return policyItems[i].GetPriority() < policyItems[j].GetPriority() }) - r.Policies = []PolicyModel{} + refreshedPolicies := []PolicyModel{} for _, policy := range policyItems { policyModel := PolicyModel{ Name: types.StringValue(policy.GetPolicyName()), Description: types.StringValue(policy.GetDescription()), - IsEnabled: types.BoolValue(policy.GetIsEnabled()), + Enabled: types.BoolValue(policy.GetIsEnabled()), } - policyModel.PolicySettings = []PolicySettingModel{} + refreshedPolicySettings := []PolicySettingModel{} if policy.GetSettings() != nil && len(policy.GetSettings()) != 0 { for _, setting := range policy.GetSettings() { policySetting := PolicySettingModel{ @@ -129,55 +618,117 @@ func (r PolicySetResourceModel) RefreshPropertyValues(policySet *citrixorchestra policySetting.Value = settingValue } - policyModel.PolicySettings = append(policyModel.PolicySettings, policySetting) + refreshedPolicySettings = append(refreshedPolicySettings, policySetting) } } - sort.Slice(policyModel.PolicySettings, func(i, j int) bool { - return policyModel.PolicySettings[i].Name.ValueString() < policyModel.PolicySettings[j].Name.ValueString() + sort.Slice(refreshedPolicySettings, func(i, j int) bool { + return refreshedPolicySettings[i].Name.ValueString() < refreshedPolicySettings[j].Name.ValueString() }) - policyModel.PolicyFilters = []PolicyFilterModel{} + policyModel.PolicySettings = util.TypedArrayToObjectList[PolicySettingModel](ctx, diags, refreshedPolicySettings) + + var accessControlFilters []AccessControlFilterModel + + attributes, _ := util.AttributeMapFromObject(BranchRepeaterFilterModel{}) + policyModel.BranchRepeaterFilter = types.ObjectNull(attributes) + + var clientIpFilters []ClientIPFilterModel + var clientNameFilters []ClientNameFilterModel + var desktopGroupFilters []DeliveryGroupFilterModel + var desktopKindFilters []DeliveryGroupTypeFilterModel + var desktopTagFilters []TagFilterModel + var ouFilters []OuFilterModel + var userFilters []UserFilterModel if policy.GetFilters() != nil && len(policy.GetFilters()) != 0 { for _, filter := range policy.GetFilters() { - var filterData PolicyFilterDataClientModel - filterDataModel := PolicyFilterDataModel{} - err := json.Unmarshal([]byte(filter.GetFilterData()), &filterData) - if err != nil { - filterDataModel.Value = types.StringValue(filter.GetFilterData()) - } else { - if filterData.Server != "" { - filterDataModel.Server = types.StringValue(filterData.Server) - } - if filterData.Uuid != "" { - filterDataModel.Uuid = types.StringValue(filterData.Uuid) - } - if filterData.Connection != "" { - filterDataModel.Connection = types.StringValue(filterData.Connection) - } - if filterData.Condition != "" { - filterDataModel.Condition = types.StringValue(filterData.Condition) - } - if filterData.Gateway != "" { - filterDataModel.Gateway = types.StringValue(filterData.Gateway) - } - } - policyModel.PolicyFilters = append(policyModel.PolicyFilters, PolicyFilterModel{ - Type: types.StringValue(filter.GetFilterType()), - IsAllowed: types.BoolValue(filter.GetIsAllowed()), - IsEnabled: types.BoolValue(filter.GetIsEnabled()), - Data: filterDataModel, - }) + var uuidFilterData PolicyFilterUuidDataClientModel + _ = json.Unmarshal([]byte(filter.GetFilterData()), &uuidFilterData) + + var gatewayFilterData PolicyFilterGatewayDataClientModel + _ = json.Unmarshal([]byte(filter.GetFilterData()), &uuidFilterData) + + filterType := filter.GetFilterType() + switch filterType { + case "AccessControl": + accessControlFilters = append(accessControlFilters, AccessControlFilterModel{ + Allowed: types.BoolValue(filter.GetIsAllowed()), + Enabled: types.BoolValue(filter.GetIsEnabled()), + Connection: gatewayFilterData.Connection, + Condition: gatewayFilterData.Condition, + Gateway: gatewayFilterData.Gateway, + }) + case "BranchRepeater": + policyModel.BranchRepeaterFilter = util.TypedObjectToObjectValue(ctx, diags, BranchRepeaterFilterModel{ + Allowed: types.BoolValue(filter.GetIsAllowed()), + Enabled: types.BoolValue(filter.GetIsEnabled()), + }) + case "ClientIP": + clientIpFilters = append(clientIpFilters, ClientIPFilterModel{ + Allowed: types.BoolValue(filter.GetIsAllowed()), + Enabled: types.BoolValue(filter.GetIsEnabled()), + IpAddress: types.StringValue(filter.GetFilterData()), + }) + case "ClientName": + clientNameFilters = append(clientNameFilters, ClientNameFilterModel{ + Allowed: types.BoolValue(filter.GetIsAllowed()), + Enabled: types.BoolValue(filter.GetIsEnabled()), + ClientName: types.StringValue(filter.GetFilterData()), + }) + case "DesktopGroup": + desktopGroupFilters = append(desktopGroupFilters, DeliveryGroupFilterModel{ + Allowed: types.BoolValue(filter.GetIsAllowed()), + Enabled: types.BoolValue(filter.GetIsEnabled()), + DeliveryGroupId: types.StringValue(uuidFilterData.Uuid), + }) + case "DesktopKind": + desktopKindFilters = append(desktopKindFilters, DeliveryGroupTypeFilterModel{ + Allowed: types.BoolValue(filter.GetIsAllowed()), + Enabled: types.BoolValue(filter.GetIsEnabled()), + DeliveryGroupType: types.StringValue(filter.GetFilterData()), + }) + case "DesktopTag": + desktopTagFilters = append(desktopTagFilters, TagFilterModel{ + Allowed: types.BoolValue(filter.GetIsAllowed()), + Enabled: types.BoolValue(filter.GetIsEnabled()), + Tag: types.StringValue(uuidFilterData.Uuid), + }) + case "OU": + ouFilters = append(ouFilters, OuFilterModel{ + Allowed: types.BoolValue(filter.GetIsAllowed()), + Enabled: types.BoolValue(filter.GetIsEnabled()), + Ou: types.StringValue(filter.GetFilterData()), + }) + case "User": + userFilters = append(userFilters, UserFilterModel{ + Allowed: types.BoolValue(filter.GetIsAllowed()), + Enabled: types.BoolValue(filter.GetIsEnabled()), + UserSid: types.StringValue(filter.GetFilterData()), + }) + } } } + policyModel.AccessControlFilters = util.TypedArrayToObjectList[AccessControlFilterModel](ctx, diags, accessControlFilters) + policyModel.ClientIPFilters = util.TypedArrayToObjectList[ClientIPFilterModel](ctx, diags, clientIpFilters) + policyModel.ClientNameFilters = util.TypedArrayToObjectList[ClientNameFilterModel](ctx, diags, clientNameFilters) + policyModel.DeliveryGroupFilters = util.TypedArrayToObjectList[DeliveryGroupFilterModel](ctx, diags, desktopGroupFilters) + policyModel.DeliveryGroupTypeFilters = util.TypedArrayToObjectList[DeliveryGroupTypeFilterModel](ctx, diags, desktopKindFilters) + policyModel.TagFilters = util.TypedArrayToObjectList[TagFilterModel](ctx, diags, desktopTagFilters) + policyModel.OuFilters = util.TypedArrayToObjectList[OuFilterModel](ctx, diags, ouFilters) + policyModel.UserFilters = util.TypedArrayToObjectList[UserFilterModel](ctx, diags, userFilters) - sort.Slice(policyModel.PolicyFilters, func(i, j int) bool { - return policyModel.PolicyFilters[i].Type.ValueString() < policyModel.PolicyFilters[j].Type.ValueString() - }) - - r.Policies = append(r.Policies, policyModel) + refreshedPolicies = append(refreshedPolicies, policyModel) } + updatedPolicies := util.TypedArrayToObjectList[PolicyModel](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()) + } + + r.Policies = types.ListNull(types.ObjectType{AttrTypes: attributesMap}) } r.IsAssigned = types.BoolValue(policySet.GetIsAssigned()) diff --git a/internal/daas/resource_locations/resource_locations_resource.go b/internal/daas/resource_locations/resource_locations_resource.go index 045e767..e6ca2ef 100644 --- a/internal/daas/resource_locations/resource_locations_resource.go +++ b/internal/daas/resource_locations/resource_locations_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package resource_locations diff --git a/internal/daas/resource_locations/resource_locations_resource_model.go b/internal/daas/resource_locations/resource_locations_resource_model.go index 734ae6c..c729009 100644 --- a/internal/daas/resource_locations/resource_locations_resource_model.go +++ b/internal/daas/resource_locations/resource_locations_resource_model.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package resource_locations diff --git a/internal/daas/storefront_server/storefront_server_resource.go b/internal/daas/storefront_server/storefront_server_resource.go index bd1146f..7b8b173 100644 --- a/internal/daas/storefront_server/storefront_server_resource.go +++ b/internal/daas/storefront_server/storefront_server_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package storefront_server @@ -10,13 +10,11 @@ import ( 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/path" "github.com/hashicorp/terraform-plugin-framework/resource" "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" ) // Ensure the implementation satisfies the expected interfaces. @@ -60,9 +58,6 @@ func (r *storeFrontServerResource) Schema(_ context.Context, _ resource.SchemaRe "description": schema.StringAttribute{ Description: "Description of the StoreFront server.", Required: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, }, "url": schema.StringAttribute{ Description: "URL for connecting to the StoreFront server.", diff --git a/internal/daas/storefront_server/storefront_server_resource_model.go b/internal/daas/storefront_server/storefront_server_resource_model.go index 5002366..b755c78 100644 --- a/internal/daas/storefront_server/storefront_server_resource_model.go +++ b/internal/daas/storefront_server/storefront_server_resource_model.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package storefront_server diff --git a/internal/daas/vda/vda_data_source.go b/internal/daas/vda/vda_data_source.go index a18c15b..598edc9 100644 --- a/internal/daas/vda/vda_data_source.go +++ b/internal/daas/vda/vda_data_source.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package vda diff --git a/internal/daas/vda/vda_data_source_model.go b/internal/daas/vda/vda_data_source_model.go index 28d8a89..b9d47a5 100644 --- a/internal/daas/vda/vda_data_source_model.go +++ b/internal/daas/vda/vda_data_source_model.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package vda diff --git a/internal/daas/zone/zone_resource.go b/internal/daas/zone/zone_resource.go index 1750868..1c9ff6b 100644 --- a/internal/daas/zone/zone_resource.go +++ b/internal/daas/zone/zone_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package zone @@ -10,14 +10,9 @@ import ( citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" "github.com/citrix/terraform-provider-citrix/internal/util" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "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/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" ) // Ensure the implementation satisfies the expected interfaces. @@ -45,45 +40,7 @@ func (r *zoneResource) Metadata(_ context.Context, req resource.MetadataRequest, // Schema defines the schema for the resource. func (r *zoneResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Manages a zone.\nFor cloud DDC, Zones and Cloud Connectors are managed only by Citrix Cloud. Ensure you have a resource location manually created and connectors deployed in it. You may then apply or import the zone using the zone Id.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "GUID identifier of the zone.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Description: "Name of the zone.\nFor Cloud DDC, ensure this matches the name of the existing zone that needs to be used.", - Required: true, - }, - "description": schema.StringAttribute{ - Description: "Description of the zone.\nFor Cloud DDC, ensure this matches the description of the existing zone that needs to be used.", - Optional: true, - }, - "metadata": schema.ListNestedAttribute{ - Description: "Metadata of the zone. Cannot be modified in DaaS cloud.", - Optional: true, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "name": schema.StringAttribute{ - Description: "Metadata name.", - Required: true, - }, - "value": schema.StringAttribute{ - Description: "Metadata value.", - Required: true, - }, - }, - }, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - }, - }, - } + resp.Schema = GetSchema() } // Configure adds the provider configured client to the resource. @@ -115,7 +72,7 @@ func (r *zoneResource) Create(ctx context.Context, req resource.CreateRequest, r if err == nil && zone != nil { // zone exists. Add it to the state file - plan = plan.RefreshPropertyValues(zone, false) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, zone, false) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) @@ -133,8 +90,9 @@ func (r *zoneResource) Create(ctx context.Context, req resource.CreateRequest, r var body citrixorchestration.CreateZoneRequestModel body.SetName(plan.Name.ValueString()) body.SetDescription(plan.Description.ValueString()) - if plan.Metadata != nil { - metadata := util.ParseNameValueStringPairToClientModel(plan.Metadata) + if !plan.Metadata.IsNull() { + terraformModel := util.ObjectListToTypedArray[util.NameValueStringPairModel](ctx, &resp.Diagnostics, plan.Metadata) + metadata := util.ParseNameValueStringPairToClientModel(terraformModel) body.SetMetadata(metadata) } @@ -159,7 +117,7 @@ func (r *zoneResource) Create(ctx context.Context, req resource.CreateRequest, r } // Map response body to schema and populate Computed attribute values - plan = plan.RefreshPropertyValues(zone, r.client.AuthConfig.OnPremises) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, zone, r.client.AuthConfig.OnPremises) // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -187,7 +145,7 @@ func (r *zoneResource) Read(ctx context.Context, req resource.ReadRequest, resp return } - state = state.RefreshPropertyValues(zone, r.client.AuthConfig.OnPremises) + state = state.RefreshPropertyValues(ctx, &resp.Diagnostics, zone, r.client.AuthConfig.OnPremises) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -222,8 +180,9 @@ func (r *zoneResource) Update(ctx context.Context, req resource.UpdateRequest, r editZoneRequestBody.SetName(plan.Name.ValueString()) editZoneRequestBody.SetDescription(plan.Description.ValueString()) - if plan.Metadata != nil { - metadata := util.ParseNameValueStringPairToClientModel(plan.Metadata) + if !plan.Metadata.IsNull() { + terraformModel := util.ObjectListToTypedArray[util.NameValueStringPairModel](ctx, &resp.Diagnostics, plan.Metadata) + metadata := util.ParseNameValueStringPairToClientModel(terraformModel) editZoneRequestBody.SetMetadata(metadata) } @@ -246,7 +205,7 @@ func (r *zoneResource) Update(ctx context.Context, req resource.UpdateRequest, r } // Update resource state with updated property values - plan = plan.RefreshPropertyValues(updatedZone, r.client.AuthConfig.OnPremises) + plan = plan.RefreshPropertyValues(ctx, &resp.Diagnostics, updatedZone, r.client.AuthConfig.OnPremises) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) diff --git a/internal/daas/zone/zone_resource_model.go b/internal/daas/zone/zone_resource_model.go index 5c7290f..b67de71 100644 --- a/internal/daas/zone/zone_resource_model.go +++ b/internal/daas/zone/zone_resource_model.go @@ -1,39 +1,76 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package zone import ( + "context" + citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "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/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) // ZoneResourceModel maps the resource schema data. type ZoneResourceModel struct { - Id types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - Metadata []util.NameValueStringPairModel `tfsdk:"metadata"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Metadata types.List `tfsdk:"metadata"` // []utils.NameValueStringPairModel +} + +func GetSchema() schema.Schema { + return schema.Schema{ + Description: "Manages a zone.\nFor cloud DDC, Zones and Cloud Connectors are managed only by Citrix Cloud. Ensure you have a resource location manually created and connectors deployed in it. You may then apply or import the zone using the zone Id.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "GUID identifier of the zone.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the zone.\nFor Cloud DDC, ensure this matches the name of the existing zone that needs to be used.", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the zone.\nFor Cloud DDC, ensure this matches the description of the existing zone that needs to be used.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "metadata": schema.ListNestedAttribute{ + Description: "Metadata of the zone. Cannot be modified in DaaS cloud.", + Optional: true, + NestedObject: util.NameValueStringPairModel{}.GetSchema(), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + }, + } } -func (r ZoneResourceModel) RefreshPropertyValues(zone *citrixorchestration.ZoneDetailResponseModel, onpremises bool) ZoneResourceModel { +func (r ZoneResourceModel) RefreshPropertyValues(ctx context.Context, diags *diag.Diagnostics, zone *citrixorchestration.ZoneDetailResponseModel, onpremises bool) ZoneResourceModel { // Overwrite zone with refreshed state r.Id = types.StringValue(zone.GetId()) r.Name = types.StringValue(zone.GetName()) + r.Description = types.StringValue(zone.GetDescription()) // Set optional values - if zone.GetDescription() != "" { - r.Description = types.StringValue(zone.GetDescription()) - } else { - r.Description = types.StringNull() - } - metadata := zone.GetMetadata() - if onpremises && (r.Metadata != nil || len(metadata) > 0) { + if onpremises && (!r.Metadata.IsNull() || len(metadata) > 0) { // Cloud customers cannot modify Zone metadata because of CC zone syncing // On-Premises customers can have either nil value for metadata, or provide an empty array - r.Metadata = util.ParseNameValueStringPairToPluginModel(metadata) + r.Metadata = util.TypedArrayToObjectList[util.NameValueStringPairModel](ctx, diags, util.ParseNameValueStringPairToPluginModel(metadata)) } return r diff --git a/internal/examples/resources/citrix_admin_scope/resource.tf b/internal/examples/resources/citrix_admin_scope/resource.tf index ba453d0..4aeb219 100644 --- a/internal/examples/resources/citrix_admin_scope/resource.tf +++ b/internal/examples/resources/citrix_admin_scope/resource.tf @@ -1,14 +1,4 @@ resource "citrix_admin_scope" "example-admin-scope" { name = "example-admin-scope" description = "Example admin scope for delivery group and machine catalog" - scoped_objects = [ - { - object_type = "DeliveryGroup", - object = "" - }, - { - object_type = "MachineCatalog", - object = "" - } - ] } \ No newline at end of file diff --git a/internal/examples/resources/citrix_application/resource.tf b/internal/examples/resources/citrix_application/resource.tf index 1af0156..7fd9537 100644 --- a/internal/examples/resources/citrix_application/resource.tf +++ b/internal/examples/resources/citrix_application/resource.tf @@ -9,4 +9,6 @@ resource "citrix_application" "example-application" { working_directory = "" } delivery_groups = [citrix_delivery_group.example-delivery-group.id] + icon = citrix_application_icon.example-application-icon.id + limit_visibility_to_users = ["example\\user1"] } diff --git a/internal/examples/resources/citrix_application_group/import.sh b/internal/examples/resources/citrix_application_group/import.sh new file mode 100644 index 0000000..79f93fc --- /dev/null +++ b/internal/examples/resources/citrix_application_group/import.sh @@ -0,0 +1,2 @@ +# Application group can be imported by specifying the GUID +terraform import citrix_application_group.example-application-group b620d505-0d0d-43b1-8c94-5cb21c5ab40d \ No newline at end of file diff --git a/internal/examples/resources/citrix_application_group/resource.tf b/internal/examples/resources/citrix_application_group/resource.tf new file mode 100644 index 0000000..bbb59fb --- /dev/null +++ b/internal/examples/resources/citrix_application_group/resource.tf @@ -0,0 +1,8 @@ +resource "citrix_application_group" "example-application-group" { + name = "example-name" + description = "example-description" + included_users = ["user@text.com"] + delivery_groups = [citrix_delivery_group.example-delivery-group.id, citrix_delivery_group.example-delivery-group-2.id] +} + + diff --git a/internal/examples/resources/citrix_application_icon/import.sh b/internal/examples/resources/citrix_application_icon/import.sh new file mode 100644 index 0000000..449a596 --- /dev/null +++ b/internal/examples/resources/citrix_application_icon/import.sh @@ -0,0 +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 diff --git a/internal/examples/resources/citrix_application_icon/resource.tf b/internal/examples/resources/citrix_application_icon/resource.tf new file mode 100644 index 0000000..e12f5ce --- /dev/null +++ b/internal/examples/resources/citrix_application_icon/resource.tf @@ -0,0 +1,7 @@ +resource "citrix_application_icon" "example-application-icon" { + raw_data = "example-raw-data" +} + +# You can use the following PowerShell commands to convert an .ico file to base64: +# $pic = Get-Content 'fileName.ico' -Encoding Byte +# $picBase64 = [System.Convert]::ToBase64String($pic) diff --git a/internal/examples/resources/citrix_delivery_group/resource.tf b/internal/examples/resources/citrix_delivery_group/resource.tf index 0ed243b..9d47017 100644 --- a/internal/examples/resources/citrix_delivery_group/resource.tf +++ b/internal/examples/resources/citrix_delivery_group/resource.tf @@ -9,7 +9,7 @@ resource "citrix_delivery_group" "example-delivery-group" { desktops = [ { published_name = "Example Desktop" - description = "Desription for example desktop" + description = "Description for example desktop" restricted_access_users = { allow_list = [ "user1@example.com" @@ -98,7 +98,6 @@ resource "citrix_delivery_group" "example-delivery-group" { } } ] - policy_set_id = citrix_policy_set.example-policy-set.id minimum_functional_level = "L7_20" } \ 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 acf196d..234c82c 100644 --- a/internal/examples/resources/citrix_machine_catalog/resource.tf +++ b/internal/examples/resources/citrix_machine_catalog/resource.tf @@ -4,10 +4,7 @@ resource "citrix_machine_catalog" "example-azure-mtsession" { zone = "" allocation_type = "Random" session_support = "MultiSession" - is_power_managed = true - is_remote_pc = false provisioning_type = "MCS" - minimum_functional_level = "L7_20" provisioning_scheme = { hypervisor = citrix_azure_hypervisor.example-azure-hypervisor.id hypervisor_resource_pool = citrix_azure_hypervisor_resource_pool.example-azure-hypervisor-resource-pool.id @@ -22,11 +19,19 @@ resource "citrix_machine_catalog" "example-azure-mtsession" { storage_type = "Standard_LRS" use_managed_disks = true service_offering = "Standard_D2_v2" - azure_machine_config = { - resource_group = "" - storage_account = "" - container = "" - master_image = "" + 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 = var.azure_resource_group + master_image = var.azure_master_image + + # For Azure image gallery + # gallery_image = { + # gallery = var.azure_gallery_name + # definition = var.azure_gallery_image_definition + # version = var.azure_gallery_image_version + # } } writeback_cache = { wbc_disk_storage_type = "pd-standard" @@ -38,12 +43,6 @@ resource "citrix_machine_catalog" "example-azure-mtsession" { storage_cost_saving = true } } - network_mapping = [ - { - network_device = "0" - network = "" - } - ] availability_zones = "1,2,..." number_of_total_machines = 1 machine_account_creation_rules ={ @@ -59,8 +58,6 @@ resource "citrix_machine_catalog" "example-aws-mtsession" { zone = "" allocation_type = "Random" session_support = "MultiSession" - is_power_managed = true - is_remote_pc = false provisioning_type = "MCS" provisioning_scheme = { hypervisor = citrix_aws_hypervisor.example-aws-hypervisor.id @@ -81,12 +78,6 @@ resource "citrix_machine_catalog" "example-aws-mtsession" { ] tenancy_type = "Shared" } - network_mapping = [ - { - network_device = "0" - network = "10.0.128.0/20" - } - ] number_of_total_machines = 1 machine_account_creation_rules ={ naming_scheme = "aws-multi-##" @@ -101,8 +92,6 @@ resource "citrix_machine_catalog" "example-gcp-mtsession" { zone = "" allocation_type = "Random" session_support = "MultiSession" - is_power_managed = true - is_remote_pc = false provisioning_type = "MCS" provisioning_scheme = { hypervisor = citrix_gcp_hypervisor.example-gcp-hypervisor.id @@ -115,7 +104,6 @@ resource "citrix_machine_catalog" "example-gcp-mtsession" { service_account_password = "" } gcp_machine_config = { - machine_profile = "" master_image = "" machine_snapshot = "" @@ -140,29 +128,29 @@ resource "citrix_machine_catalog" "example-gcp-mtsession" { resource "citrix_machine_catalog" "example-vsphere-mtsession" { name = "example-vsphere-mtsession" description = "Example multi-session catalog on Vsphere hypervisor" - provisioning_type = "MCS" + zone = "" allocation_type = "Random" session_support = "MultiSession" - zone = "" + provisioning_type = "MCS" provisioning_scheme = { - identity_type = "ActiveDirectory" - number_of_total_machines = 1 - machine_account_creation_rules = { - naming_scheme = "catalog-##" - naming_scheme_type = "Numeric" - } hypervisor = citrix_vsphere_hypervisor.vsphere-hypervisor-1.id hypervisor_resource_pool = citrix_vsphere_hypervisor_resource_pool.vsphere-hypervisor-rp-1.id + identity_type = "ActiveDirectory" + machine_domain_identity = { + domain = "" + service_account = "" + service_account_password = "" + } vsphere_machine_config = { master_image_vm = "" image_snapshot = "///..." cpu_count = 2 memory_mb = 4096 } - machine_domain_identity = { - domain = "" - service_account = "" - service_account_password = "" + number_of_total_machines = 1 + machine_account_creation_rules = { + naming_scheme = "catalog-##" + naming_scheme_type = "Numeric" } } } @@ -170,29 +158,29 @@ resource "citrix_machine_catalog" "example-vsphere-mtsession" { resource "citrix_machine_catalog" "example-xenserver-mtsession" { name = "example-xenserver-mtsession" description = "Example multi-session catalog on XenServer hypervisor" - provisioning_type = "MCS" + zone = "" allocation_type = "Random" session_support = "MultiSession" - zone = "" + provisioning_type = "MCS" provisioning_scheme = { - identity_type = "ActiveDirectory" - number_of_total_machines = 1 - machine_account_creation_rules = { - naming_scheme = "catalog-##" - naming_scheme_type = "Numeric" - } hypervisor = citrix_xenserver_hypervisor.xenserver-hypervisor-1.id hypervisor_resource_pool = citrix_xenserver_hypervisor_resource_pool.xenserver-hypervisor-rp-1.id + identity_type = "ActiveDirectory" + machine_domain_identity = { + domain = "" + service_account = "" + service_account_password = "" + } xenserver_machine_config = { master_image_vm = "" image_snapshot = "///..." cpu_count = 2 memory_mb = 4096 } - machine_domain_identity = { - domain = "" - service_account = "" - service_account_password = "" + number_of_total_machines = 1 + machine_account_creation_rules = { + naming_scheme = "catalog-##" + naming_scheme_type = "Numeric" } } } @@ -200,19 +188,19 @@ resource "citrix_machine_catalog" "example-xenserver-mtsession" { resource "citrix_machine_catalog" "example-nutanix-mtsession" { name = "example-nutanix-mtsession" description = "Example multi-session catalog on Nutanix hypervisor" - provisioning_type = "MCS" + zone = citrix_zone.example-zone.id allocation_type = "Random" session_support = "MultiSession" - zone = citrix_zone.example-zone.id + provisioning_type = "MCS" provisioning_scheme = { - identity_type = "ActiveDirectory" - number_of_total_machines = 1 - machine_account_creation_rules = { - naming_scheme = "catalog-##" - naming_scheme_type = "Numeric" - } hypervisor = citrix_nutanix_hypervisor.example-nutanix-hypervisor.id hypervisor_resource_pool = citrix_nutanix_hypervisor_resource_pool.example-nutanix-rp.id + identity_type = "ActiveDirectory" + machine_domain_identity = { + domain = "" + service_account = "" + service_account_password = "" + } nutanix_machine_config = { container = "" master_image = "" @@ -220,10 +208,10 @@ resource "citrix_machine_catalog" "example-nutanix-mtsession" { memory_mb = 4096 cores_per_cpu_count = 2 } - machine_domain_identity = { - domain = "" - service_account = "" - service_account_password = "" + number_of_total_machines = 1 + machine_account_creation_rules = { + naming_scheme = "catalog-##" + naming_scheme_type = "Numeric" } } } @@ -244,7 +232,8 @@ resource "citrix_machine_catalog" "example-manual-power-managed-mtsession" { { region = "East US" resource_group_name = "machine-resource-group-name" - machine_name = "Domain\\MachineName" + machine_account = "DOMAIN\\MachineName" + machine_name = "MachineName" } ] } @@ -264,10 +253,10 @@ resource "citrix_machine_catalog" "example-manual-non-power-managed-mtsession" { { machines = [ { - machine_name = "Domain\\MachineName1" + machine_account = "DOMAIN\\MachineName1" }, { - machine_name = "Domain\\MachineName2" + machine_account = "DOMAIN\\MachineName2" } ] } @@ -287,10 +276,10 @@ resource "citrix_machine_catalog" "example-remote-pc" { { machines = [ { - machine_name = "Domain\\MachineName1" + machine_account = "DOMAIN\\MachineName1" }, { - machine_name = "Domain\\MachineName2" + machine_account = "DOMAIN\\MachineName2" } ] } @@ -320,10 +309,18 @@ resource "citrix_machine_catalog" "example-non-domain-joined-azure-mcs" { use_managed_disks = true service_offering = "Standard_D2_v2" azure_master_image = { - resource_group = "" - storage_account = "" - container = "" - 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 = var.azure_resource_group + master_image = var.azure_master_image + + # For Azure image gallery + # gallery_image = { + # gallery = var.azure_gallery_name + # definition = var.azure_gallery_image_definition + # version = var.azure_gallery_image_version + # } } writeback_cache = { wbc_disk_storage_type = "pd-standard" diff --git a/internal/examples/resources/citrix_policy_set/resource.tf b/internal/examples/resources/citrix_policy_set/resource.tf index 086cd2c..9e36e32 100644 --- a/internal/examples/resources/citrix_policy_set/resource.tf +++ b/internal/examples/resources/citrix_policy_set/resource.tf @@ -2,7 +2,7 @@ resource "citrix_policy_set" "example-policy-set" { name = "example-policy-set" description = "This is an example policy set description" type = "DeliveryGroupPolicies" - scopes = [ "All", citrix_admin_scope.example-admin-scope.name ] + scopes = [ citrix_admin_scope.example-admin-scope.id ] policies = [ { name = "test-policy-with-priority-0" @@ -15,13 +15,64 @@ resource "citrix_policy_set" "example-policy-set" { use_default = false }, ] - policy_filters = [ + access_control_filters = [ { - type = "DesktopGroup" - data = { - server = "0.0.0.0" - uuid = citrix_delivery_group.example-delivery-group.id - } + connection = "WithAccessGateway" + condition = "*" + gateway = "*" + enabled = true + allowed = true + }, + ] + branch_repeater_filter = { + enabled = true + allowed = true + }, + client_ip_filters = [ + { + ip_address = "10.0.0.1" + enabled = true + allowed = true + } + ] + client_name_filters = [ + { + client_name = "Example Client Name" + enabled = true + allowed = true + } + ] + delivery_group_filters = [ + { + delivery_group_id = citrix_delivery_group.example-delivery-group.id + enabled = true + allowed = true + }, + ] + delivery_group_type_filters = [ + { + delivery_group_type = "Private" + enabled = true + allowed = true + }, + ] + ou_filters = [ + { + ou = "{Path of the oranizational unit to be filtered}" + enabled = true + allowed = true + }, + ] + user_filters = [ + { + sid = "{SID of the user or user group to be filtered}" + enabled = true + allowed = true + }, + ] + tag_filters = [ + { + tag = "{ID of the tag to be filtered}" enabled = true allowed = true }, @@ -32,7 +83,6 @@ resource "citrix_policy_set" "example-policy-set" { description = "Test policy in the example policy set with priority 1" enabled = false policy_settings = [] - policy_filters = [] } ] } diff --git a/internal/examples/resources/citrix_stf_user_farm_mapping/import.sh b/internal/examples/resources/citrix_stf_user_farm_mapping/import.sh new file mode 100644 index 0000000..0e7c0af --- /dev/null +++ b/internal/examples/resources/citrix_stf_user_farm_mapping/import.sh @@ -0,0 +1,2 @@ +# StoreFront UserFarmMapping can be imported with the Store Virtual Path and UserFarmMapping Name +terraform import citrix_stf_store_service.example-stf-store-service "/Citrix/Store","Example UserFarmMapping" diff --git a/internal/examples/resources/citrix_stf_user_farm_mapping/resource.tf b/internal/examples/resources/citrix_stf_user_farm_mapping/resource.tf new file mode 100644 index 0000000..7311d5d --- /dev/null +++ b/internal/examples/resources/citrix_stf_user_farm_mapping/resource.tf @@ -0,0 +1,31 @@ +resource "citrix_stf_user_farm_mapping" "example-stf-user-farm-mapping" { + name = "Example STFUserFarmMapping" + store_virtual_path = citrix_stf_storeservice.example-stf-store-service.virtual_path + group_members = [ + { + group_name = "TestGroup1" + account_sid = "{First Account Sid}" + }, + { + group_name = "TestGroup2" + account_sid = "{Second Account Sid}" + } + ] + equivalent_farm_sets = [ + { + name = "EU1", + aggregation_group_name = "EU1Users" + primary_farms = ["Primary"] + backup_farms = ["Backup"] + load_balance_mode = "LoadBalanced" + farms_are_identical = true + }, + { + name = "EU2", + aggregation_group_name = "EU2Users" + primary_farms = ["Secondary"] + load_balance_mode = "Failover" + farms_are_identical = false + } + ] +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 5011464..360f69c 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package provider @@ -14,6 +14,7 @@ import ( "net/url" "os" "path/filepath" + "slices" "strings" "syscall" "time" @@ -28,6 +29,7 @@ import ( "github.com/citrix/terraform-provider-citrix/internal/daas/vda" "github.com/citrix/terraform-provider-citrix/internal/storefront/stf_authentication" "github.com/citrix/terraform-provider-citrix/internal/storefront/stf_deployment" + "github.com/citrix/terraform-provider-citrix/internal/storefront/stf_multi_site" "github.com/citrix/terraform-provider-citrix/internal/storefront/stf_store" "github.com/citrix/terraform-provider-citrix/internal/storefront/stf_webreceiver" @@ -133,14 +135,14 @@ func (p *citrixProvider) Schema(_ context.Context, _ provider.SchemaRequest, res }, "client_id": schema.StringAttribute{ Description: "Client Id for Citrix DaaS service authentication. " + "
" + - "For Citrix On-Premises customers: Use this to specify Domain Admin Username. " + "
" + + "For Citrix On-Premises customers: Use this to specify a DDC administrator username. " + "
" + "For Citrix Cloud customers: Use this to specify Cloud API Key Client Id." + "
" + "Can be set via Environment Variable **CITRIX_CLIENT_ID**.", Optional: true, }, "client_secret": schema.StringAttribute{ Description: "Client Secret for Citrix DaaS service authentication. " + "
" + - "For Citrix on-premises customers: Use this to specify Domain Admin Password. " + "
" + + "For Citrix on-premises customers: Use this to specify a DDC administrator password. " + "
" + "For Citrix Cloud customers: Use this to specify Cloud API Key Client Secret." + "
" + "Can be set via Environment Variable **CITRIX_CLIENT_SECRET**.", Optional: true, @@ -168,13 +170,13 @@ func (p *citrixProvider) Schema(_ context.Context, _ provider.SchemaRequest, res "ad_admin_username": schema.StringAttribute{ Description: "Active Directory Admin Username to connect to storefront server " + "
" + "Only applicable for Citrix on-premises customers. Use this to specify AD admin username " + "
" + - "Can be set via Environment Variable **SF_AD_ADMAIN_USERNAME**.", + "Can be set via Environment Variable **SF_AD_ADMIN_USERNAME**.", Required: true, }, "ad_admin_password": schema.StringAttribute{ Description: "Active Directory Admin Password to connect to storefront server " + "
" + "Only applicable for Citrix on-premises customers. Use this to specify AD admin password" + "
" + - "Can be set via Environment Variable **SF_AD_ADMAIN_PASSWORD**.", + "Can be set via Environment Variable **SF_AD_ADMIN_PASSWORD**.", Required: true, }, }, @@ -208,8 +210,9 @@ func middlewareAuthFunc(authClient *citrixclient.CitrixDaasClient, r *http.Reque }) } -type RegistryResponse struct { - Version string `json:"version"` +type registryResponse struct { + Version string `json:"version"` + Versions []string `json:"versions"` } func getVersionFromTerraformRegistry() (string, error) { @@ -226,12 +229,22 @@ func getVersionFromTerraformRegistry() (string, error) { if err != nil { return "", err } - registryResp := RegistryResponse{} + registryResp := registryResponse{} err = json.Unmarshal(body, ®istryResp) if err != nil { return "", err } + // find the last stable version + // the versions are returned in order from oldest to newest so reverse it first + slices.Reverse(registryResp.Versions) + for _, ver := range registryResp.Versions { + if semver.Prerelease("v"+ver) == "" { + return ver, nil + } + } + + // if no stable version found just return the latest version return registryResp.Version, nil } @@ -351,8 +364,8 @@ func (p *citrixProvider) Configure(ctx context.Context, req provider.ConfigureRe clientSecret := os.Getenv("CITRIX_CLIENT_SECRET") disableSslVerification := strings.EqualFold(os.Getenv("CITRIX_DISABLE_SSL_VERIFICATION"), "true") storefront_computer_name := os.Getenv("SF_COMPUTER_NAME") - storefront_ad_admin_username := os.Getenv("SF_AD_ADMAIN_USERNAME") - storefront_ad_admin_password := os.Getenv("SF_AD_ADMAIN_PASSWORD") + storefront_ad_admin_username := os.Getenv("SF_AD_ADMIN_USERNAME") + storefront_ad_admin_password := os.Getenv("SF_AD_ADMIN_PASSWORD") if !config.Hostname.IsNull() { hostname = config.Hostname.ValueString() @@ -525,7 +538,7 @@ func (p *citrixProvider) Configure(ctx context.Context, req provider.ConfigureRe "Invalid credential in provider config", "Make sure client_id and client_secret is correct in provider config. ", ) - } else if httpResp.StatusCode == 503 { + } else if httpResp.StatusCode >= 500 { if onPremises { resp.Diagnostics.AddError( "Citrix DaaS service unavailable", @@ -592,7 +605,7 @@ func (p *citrixProvider) Configure(ctx context.Context, req provider.ConfigureRe return } - //Set Storefront Client + // Set StoreFront Client client = citrixclient.NewStoreFrontClient(ctx, storefront_computer_name, storefront_ad_admin_username, storefront_ad_admin_password, client) // Make the Citrix API client available during DataSource and Resource @@ -632,6 +645,8 @@ func (p *citrixProvider) Resources(_ context.Context) []func() resource.Resource storefront_server.NewStoreFrontServerResource, application.NewApplicationResource, application.NewApplicationFolderResource, + application.NewApplicationGroupResource, + application.NewApplicationIconResource, admin_scope.NewAdminScopeResource, admin_role.NewAdminRoleResource, policies.NewPolicySetResource, @@ -643,6 +658,7 @@ func (p *citrixProvider) Resources(_ context.Context) []func() resource.Resource stf_authentication.NewSTFAuthenticationServiceResource, stf_store.NewSTFStoreServiceResource, stf_webreceiver.NewSTFWebReceiverResource, + stf_multi_site.NewSTFUserFarmMappingResource, // Add resource here } } diff --git a/internal/storefront/stf_authentication/stf_authentication_service_resource.go b/internal/storefront/stf_authentication/stf_authentication_service_resource.go index 1013dc5..45583f4 100644 --- a/internal/storefront/stf_authentication/stf_authentication_service_resource.go +++ b/internal/storefront/stf_authentication/stf_authentication_service_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package stf_authentication @@ -46,7 +46,7 @@ func (r *stfAuthenticationServiceResource) Metadata(_ context.Context, req resou // Schema defines the schema for the resource. func (r *stfAuthenticationServiceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: "Storefront Authentication Service.", + Description: "StoreFront Authentication Service.", Attributes: map[string]schema.Attribute{ "site_id": schema.StringAttribute{ Description: "The IIS site to configure the authentication service for. Defaults to `1`.", @@ -106,7 +106,7 @@ func (r *stfAuthenticationServiceResource) Create(ctx context.Context, req resou siteIdInt, err := strconv.ParseInt(plan.SiteId.ValueString(), 10, 64) if err != nil { resp.Diagnostics.AddError( - "Error updating Storefront Authentication Service ", + "Error updating StoreFront Authentication Service ", "\nError message: "+err.Error(), ) return @@ -122,7 +122,7 @@ func (r *stfAuthenticationServiceResource) Create(ctx context.Context, req resou authenticationServiceDetail, err := addAuthenticationServiceRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error adding Storefront Authentication Service", + "Error adding StoreFront Authentication Service", fmt.Sprintf("Error Message: %s", err.Error()), ) return @@ -200,7 +200,7 @@ func (r *stfAuthenticationServiceResource) Update(ctx context.Context, req resou siteIdInt, err := strconv.ParseInt(state.SiteId.ValueString(), 10, 64) if err != nil { resp.Diagnostics.AddError( - "Error updating Storefront Authentication Service ", + "Error updating StoreFront Authentication Service ", "\nError message: "+err.Error(), ) return @@ -216,7 +216,7 @@ func (r *stfAuthenticationServiceResource) Update(ctx context.Context, req resou err := removeAuthenticationServiceRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error updating Storefront Authentication Service ", + "Error updating StoreFront Authentication Service ", "\nError message: "+err.Error(), ) return @@ -228,7 +228,7 @@ func (r *stfAuthenticationServiceResource) Update(ctx context.Context, req resou siteIdInt, err := strconv.ParseInt(plan.SiteId.ValueString(), 10, 64) if err != nil { resp.Diagnostics.AddError( - "Error updating Storefront Authentication Service ", + "Error updating StoreFront Authentication Service ", "\nError message: "+err.Error(), ) return @@ -243,7 +243,7 @@ func (r *stfAuthenticationServiceResource) Update(ctx context.Context, req resou authenticationServiceDetail, err := addAuthenticationServiceRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error adding Storefront Authentication Service", + "Error adding StoreFront Authentication Service", fmt.Sprintf("Error Message: %s", err.Error()), ) return @@ -277,7 +277,7 @@ func (r *stfAuthenticationServiceResource) Delete(ctx context.Context, req resou siteIdInt, err := strconv.ParseInt(state.SiteId.ValueString(), 10, 64) if err != nil { resp.Diagnostics.AddError( - "Error removing Storefront Authentication Service ", + "Error removing StoreFront Authentication Service ", "\nError message: "+err.Error(), ) return @@ -293,7 +293,7 @@ func (r *stfAuthenticationServiceResource) Delete(ctx context.Context, req resou err := removeAuthenticationServiceRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error removing Storefront Authentication Service ", + "Error removing StoreFront Authentication Service ", "\nError message: "+err.Error(), ) return @@ -335,7 +335,7 @@ func getSTFAuthenticationService(ctx context.Context, client *citrixdaasclient.C siteIdInt, err := strconv.ParseInt(state.SiteId.ValueString(), 10, 64) if err != nil { diagnostics.AddError( - "Error fetching state of Storefront Authentication Service ", + "Error fetching state of StoreFront Authentication Service ", "Error message: "+err.Error(), ) return nil, err @@ -356,7 +356,7 @@ func getSTFAuthenticationService(ctx context.Context, client *citrixdaasclient.C return nil, nil } diagnostics.AddError( - "Error fetching state of Storefront Authentication Service ", + "Error fetching state of StoreFront Authentication Service ", "Error message: "+err.Error(), ) return &STFAuthenticationService, err diff --git a/internal/storefront/stf_authentication/stf_authentication_service_resource_model.go b/internal/storefront/stf_authentication/stf_authentication_service_resource_model.go index aafdc33..1bc12a0 100644 --- a/internal/storefront/stf_authentication/stf_authentication_service_resource_model.go +++ b/internal/storefront/stf_authentication/stf_authentication_service_resource_model.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package stf_authentication diff --git a/internal/storefront/stf_deployment/stf_deployment_resource.go b/internal/storefront/stf_deployment/stf_deployment_resource.go index 458a4f1..2d4b78d 100644 --- a/internal/storefront/stf_deployment/stf_deployment_resource.go +++ b/internal/storefront/stf_deployment/stf_deployment_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package stf_deployment import ( @@ -43,10 +43,10 @@ func (r *stfDeploymentResource) Metadata(_ context.Context, req resource.Metadat // Schema defines the schema for the resource. func (r *stfDeploymentResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: "Storefront Deployment.", + Description: "StoreFront Deployment.", Attributes: map[string]schema.Attribute{ "site_id": schema.StringAttribute{ - Description: "The IIS site id of the Storefront deployment. Defaults to 1.", + Description: "The IIS site id of the StoreFront deployment. Defaults to 1.", Optional: true, Computed: true, Default: stringdefault.StaticString("1"), @@ -89,7 +89,7 @@ func (r *stfDeploymentResource) Create(ctx context.Context, req resource.CreateR siteIdInt, err := strconv.ParseInt(plan.SiteId.ValueString(), 10, 64) if err != nil { resp.Diagnostics.AddError( - "Error creating Storefront Deployment ", + "Error creating StoreFront Deployment ", "\nError message: "+err.Error(), ) return @@ -104,7 +104,7 @@ func (r *stfDeploymentResource) Create(ctx context.Context, req resource.CreateR DeploymentDetail, err := createDeploymentRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error creating Storefront Deployment", + "Error creating StoreFront Deployment", "TransactionId: ", ) return @@ -172,7 +172,7 @@ func (r *stfDeploymentResource) Update(ctx context.Context, req resource.UpdateR siteIdInt, err := strconv.ParseInt(plan.SiteId.ValueString(), 10, 64) if err != nil { resp.Diagnostics.AddError( - "Error fetching state of Storefront Authentication Service ", + "Error fetching state of StoreFront Authentication Service ", "Error message: "+err.Error(), ) return @@ -184,7 +184,7 @@ func (r *stfDeploymentResource) Update(ctx context.Context, req resource.UpdateR _, err = editDeploymentRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error updating Storefront Deployment ", + "Error updating StoreFront Deployment ", "\nError message: "+err.Error(), ) } @@ -222,7 +222,7 @@ func (r *stfDeploymentResource) Delete(ctx context.Context, req resource.DeleteR siteIdInt, err := strconv.ParseInt(state.SiteId.ValueString(), 10, 64) if err != nil { resp.Diagnostics.AddError( - "Error deleting Storefront Deployment ", + "Error deleting StoreFront Deployment ", "Error message: "+err.Error(), ) return @@ -235,7 +235,7 @@ func (r *stfDeploymentResource) Delete(ctx context.Context, req resource.DeleteR _, err := deleteDeploymentRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error deleting Storefront Deployment ", + "Error deleting StoreFront Deployment ", "\nError message: "+err.Error(), ) return @@ -254,7 +254,7 @@ func getSTFDeployment(ctx context.Context, client *citrixdaasclient.CitrixDaasCl siteIdInt, err := strconv.ParseInt(*siteId, 10, 64) if err != nil { diagnostics.AddError( - "Error fetching state of Storefront Deployment ", + "Error fetching state of StoreFront Deployment ", "Error message: "+err.Error(), ) return nil, err diff --git a/internal/storefront/stf_deployment/stf_deployment_resource_model.go b/internal/storefront/stf_deployment/stf_deployment_resource_model.go index 4fda333..5271c1f 100644 --- a/internal/storefront/stf_deployment/stf_deployment_resource_model.go +++ b/internal/storefront/stf_deployment/stf_deployment_resource_model.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package stf_deployment diff --git a/internal/storefront/stf_multi_site/stf_user_farm_mapping_resource.go b/internal/storefront/stf_multi_site/stf_user_farm_mapping_resource.go new file mode 100644 index 0000000..a260f4c --- /dev/null +++ b/internal/storefront/stf_multi_site/stf_user_farm_mapping_resource.go @@ -0,0 +1,259 @@ +// Copyright © 2024. Citrix Systems, Inc. +package stf_multi_site + +import ( + "context" + "fmt" + "strings" + + citrixstorefront "github.com/citrix/citrix-daas-rest-go/citrixstorefront/models" + 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" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &stfUserFarmMappingResource{} + _ resource.ResourceWithConfigure = &stfUserFarmMappingResource{} + _ resource.ResourceWithImportState = &stfUserFarmMappingResource{} +) + +// stfUserFarmMappingResource is a helper function to simplify the provider implementation. +func NewSTFUserFarmMappingResource() resource.Resource { + return &stfUserFarmMappingResource{} +} + +// stfUserFarmMappingResource is the resource implementation. +type stfUserFarmMappingResource struct { + client *citrixdaasclient.CitrixDaasClient +} + +// Metadata returns the resource type name. +func (r *stfUserFarmMappingResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_stf_user_farm_mapping" +} + +// Configure adds the provider configured client to the resource. +func (r *stfUserFarmMappingResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*citrixdaasclient.CitrixDaasClient) +} + +// Create implements resource.Resource. +func (r *stfUserFarmMappingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + // Retrieve values from plan + var plan STFUserFarmMappingResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Create and Get new STF UserFarmMapping + storeVirtualPath := plan.VirtualPath.ValueString() + storeVirtualPathNullableString := citrixstorefront.NewNullableString(&storeVirtualPath) + + userFarmMappingName := plan.Name.ValueString() + userFarmMappingNameNullableString := citrixstorefront.NewNullableString(&userFarmMappingName) + + groupMembers := BuildSTFUserFarmMappingGroupList(ctx, &resp.Diagnostics, plan.GroupMembers) + equivalentFarmSets := BuildSTFEquivalentFarmSetRequestModelList(ctx, &resp.Diagnostics, plan.EquivalentFarmSets) + + getRequest := r.client.StorefrontClient.MultiSiteSF.STFMultiSiteGetUserFarmMapping(ctx, *storeVirtualPathNullableString, *userFarmMappingNameNullableString) + _, err := getRequest.Execute() + if err == nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Failed to create StoreFront UserFarmMapping `%s`", userFarmMappingName), + fmt.Sprintf("StoreFront UserFarmMapping with name `%s` already exists", userFarmMappingName), + ) + return + } else if !strings.EqualFold(err.Error(), util.NOT_EXIST) { + resp.Diagnostics.AddError( + fmt.Sprintf("Error verify the existence of StoreFront UserFarmMapping `%s`", userFarmMappingName), + fmt.Sprintf("Error Message: %s", err.Error()), + ) + return + } + + createdNewSTFUserFarmMapping, err := CreateAndGetNewSTFUserFarmMapping(ctx, &resp.Diagnostics, r.client, storeVirtualPath, userFarmMappingName, equivalentFarmSets, groupMembers) + if err != nil { + return + } + + plan.RefreshPropertyValues(ctx, &resp.Diagnostics, createdNewSTFUserFarmMapping) + + // Set state to fully populated data + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read implements resource.ResourceWithConfigure. +func (r *stfUserFarmMappingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + // Get current state + var state STFUserFarmMappingResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + storeVirtualPath := state.VirtualPath.ValueString() + storeVirtualPathNullableString := citrixstorefront.NewNullableString(&storeVirtualPath) + + userFarmMappingName := state.Name.ValueString() + userFarmMappingNameNullableString := citrixstorefront.NewNullableString(&userFarmMappingName) + + getRequest := r.client.StorefrontClient.MultiSiteSF.STFMultiSiteGetUserFarmMapping(ctx, *storeVirtualPathNullableString, *userFarmMappingNameNullableString) + getResult, err := getRequest.Execute() + if err != nil { + if strings.EqualFold(err.Error(), util.NOT_EXIST) { + resp.Diagnostics.AddWarning( + "UserFarmMapping not found", + "UserFarmMapping Service was not found and will be removed from the state file. An apply action will result in the creation of a new resource.", + ) + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( + "Error fetch details of StoreFront UserFarmMapping", + fmt.Sprintf("Error Message: %s", err.Error()), + ) + return + } + + state.RefreshPropertyValues(ctx, &resp.Diagnostics, getResult) + + // Set state to fully populated data + diags = resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Update implements resource.Resource. +func (r *stfUserFarmMappingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +} + +// Delete implements resource.ResourceWithConfigure. +func (r *stfUserFarmMappingResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + // Retrieve values from state + var state STFUserFarmMappingResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + storeVirtualPath := state.VirtualPath.ValueString() + storeVirtualPathNullableString := citrixstorefront.NewNullableString(&storeVirtualPath) + + userFarmMappingName := state.Name.ValueString() + userFarmMappingNameNullableString := citrixstorefront.NewNullableString(&userFarmMappingName) + + // Delete existing STF UserFarmMapping + deleteRequest := r.client.StorefrontClient.MultiSiteSF.STFMultiSiteRemoveUserFarmMapping(ctx, *storeVirtualPathNullableString, *userFarmMappingNameNullableString) + _, err := deleteRequest.Execute() + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error deleting StoreFront UserFarmMapping `%s` with virtual path `%s`", userFarmMappingName, storeVirtualPath), + "\nError message: "+err.Error(), + ) + return + } +} + +// ImportState implements resource.ResourceWithImportState. +func (r *stfUserFarmMappingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + defer util.PanicHandler(&resp.Diagnostics) + + idSegments := strings.SplitN(req.ID, ",", 2) + + if (len(idSegments) != 2) || (idSegments[0] == "" || idSegments[1] == "") { + resp.Diagnostics.AddError( + "Invalid Import Identifier", + fmt.Sprintf("Expected format: `store_virtual_path,name`, got: `%q`", req.ID), + ) + return + } + + // Retrieve import ID and save to id attribute + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("store_virtual_path"), idSegments[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idSegments[1])...) +} + +func BuildSTFUserFarmMappingGroupList(ctx context.Context, diagnostics *diag.Diagnostics, plannedGroupMembers types.List) []citrixstorefront.STFUserFarmMappingGroup { + groupMembersInput := util.ObjectListToTypedArray[UserFarmMappingGroup](ctx, diagnostics, plannedGroupMembers) + groupMembers := []citrixstorefront.STFUserFarmMappingGroup{} + + for _, groupMemberInput := range groupMembersInput { + groupMember := citrixstorefront.STFUserFarmMappingGroup{} + groupMember.SetGroupName(groupMemberInput.GroupName.ValueString()) + groupMember.SetAccountSid(groupMemberInput.AccountSid.ValueString()) + + groupMembers = append(groupMembers, groupMember) + } + + return groupMembers +} + +func BuildSTFEquivalentFarmSetRequestModelList(ctx context.Context, diagnostics *diag.Diagnostics, plannedEquivalentFarmSets types.List) []citrixstorefront.STFEquivalentFarmSetRequestModel { + equivalentFarmSetsInput := util.ObjectListToTypedArray[EquivalentFarmSet](ctx, diagnostics, plannedEquivalentFarmSets) + equivalentFarmSets := []citrixstorefront.STFEquivalentFarmSetRequestModel{} + + for _, equivalentFarmSetInput := range equivalentFarmSetsInput { + equivalentFarmSet := citrixstorefront.STFEquivalentFarmSetRequestModel{} + equivalentFarmSet.SetName(equivalentFarmSetInput.Name.ValueString()) + equivalentFarmSet.SetAggregationGroupName(equivalentFarmSetInput.AggregationGroupName.ValueString()) + equivalentFarmSet.SetFarmsAreIdentical(equivalentFarmSetInput.FarmsAreIdentical.ValueBool()) + equivalentFarmSet.SetLoadBalanceMode(equivalentFarmSetInput.LoadBalanceMode.ValueString()) + equivalentFarmSet.SetPrimaryFarms(util.StringListToStringArray(ctx, diagnostics, equivalentFarmSetInput.PrimaryFarms)) + equivalentFarmSet.SetBackupFarms(util.StringListToStringArray(ctx, diagnostics, equivalentFarmSetInput.BackupFarms)) + + equivalentFarmSets = append(equivalentFarmSets, equivalentFarmSet) + } + + return equivalentFarmSets +} + +func CreateAndGetNewSTFUserFarmMapping(ctx context.Context, diagnostics *diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, virtualPath string, name string, equivalentFarmSets []citrixstorefront.STFEquivalentFarmSetRequestModel, groupMembers []citrixstorefront.STFUserFarmMappingGroup) (citrixstorefront.STFUserFarmMappingResponseModel, error) { + virtualPathNullableString := citrixstorefront.NewNullableString(&virtualPath) + nameNullableString := citrixstorefront.NewNullableString(&name) + addRequest := client.StorefrontClient.MultiSiteSF.STFMultiSiteAddUserFarmMapping(ctx, *virtualPathNullableString, *nameNullableString, equivalentFarmSets, groupMembers) + _, err := addRequest.Execute() + if err != nil { + diagnostics.AddError( + "Error adding StoreFront UserFarmMapping", + fmt.Sprintf("Error Message: %s", err.Error()), + ) + return citrixstorefront.STFUserFarmMappingResponseModel{}, err + } + + getRequest := client.StorefrontClient.MultiSiteSF.STFMultiSiteGetUserFarmMapping(ctx, *virtualPathNullableString, *nameNullableString) + getResult, err := getRequest.Execute() + if err != nil { + diagnostics.AddError( + "Error fetch details of StoreFront UserFarmMapping", + fmt.Sprintf("Error Message: %s", err.Error()), + ) + return citrixstorefront.STFUserFarmMappingResponseModel{}, err + } + return getResult, nil +} 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 new file mode 100644 index 0000000..0c886e9 --- /dev/null +++ b/internal/storefront/stf_multi_site/stf_user_farm_mapping_resource_model.go @@ -0,0 +1,235 @@ +// Copyright © 2024. Citrix Systems, Inc. +package stf_multi_site + +import ( + "context" + "regexp" + + citrixstorefront "github.com/citrix/citrix-daas-rest-go/citrixstorefront/models" + "github.com/citrix/terraform-provider-citrix/internal/util" + + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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/listdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "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" +) + +// ensure UserFarmMappingGroup implements RefreshableListItemWithAttributes +var _ util.RefreshableListItemWithAttributes[citrixstorefront.STFGroupMemberResponseModel] = UserFarmMappingGroup{} + +type UserFarmMappingGroup struct { + GroupName types.String `tfsdk:"group_name"` + AccountSid types.String `tfsdk:"account_sid"` +} + +func (UserFarmMappingGroup) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "group_name": schema.StringAttribute{ + Description: "A display only group name.", + Required: true, + Validators: []validator.String{ + stringvalidator.NoneOfCaseInsensitive("everyone"), + }, + }, + "account_sid": schema.StringAttribute{ + Description: "Sid of the account.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(util.ActiveDirectorySidRegex), "must be in Active Directory SID format"), + }, + }, + }, + } +} + +func (UserFarmMappingGroup) GetAttributes() map[string]schema.Attribute { + return UserFarmMappingGroup{}.GetSchema().Attributes +} + +func (r UserFarmMappingGroup) GetKey() string { + return r.GroupName.ValueString() +} + +func (r UserFarmMappingGroup) RefreshListItem(_ context.Context, _ *diag.Diagnostics, item citrixstorefront.STFGroupMemberResponseModel) util.ModelWithAttributes { + // Implement the logic to refresh the list item based on the item + groupName := types.StringValue(*item.GroupName.Get()) + r.GroupName = groupName + + accountSid := types.StringValue(*item.AccountSid.Get()) + r.AccountSid = accountSid + + return r +} + +// ensure EquivalentFarmSet implements RefreshableListItemWithAttributes +var _ util.RefreshableListItemWithAttributes[citrixstorefront.STFFarmSetResponseModel] = EquivalentFarmSet{} + +type EquivalentFarmSet struct { + Name types.String `tfsdk:"name"` + AggregationGroupName types.String `tfsdk:"aggregation_group_name"` + PrimaryFarms types.List `tfsdk:"primary_farms"` // List[string] + BackupFarms types.List `tfsdk:"backup_farms"` // List[string] + LoadBalanceMode types.String `tfsdk:"load_balance_mode"` // Failover or LoadBalanced + FarmsAreIdentical types.Bool `tfsdk:"farms_are_identical"` +} + +func (EquivalentFarmSet) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "The unique Name used to identify the EquivalentFarmSet.", + Required: true, + }, + "aggregation_group_name": schema.StringAttribute{ + Description: "The AggregationGroupName used to de-duplicate applications and desktops that are available on multiple EquivalentFarmSets. Where multiple EquivalentFarmSets are defined the AggregationGroup will prevent the user seeing the application multiple times if it exists in both places.", + Required: true, + }, + "primary_farms": schema.ListAttribute{ + ElementType: types.StringType, + Description: "The PrimaryFarms. The farm names should match those defined in the Store service.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "backup_farms": schema.ListAttribute{ + ElementType: types.StringType, + Description: "The BackupFarms. The farm names should match those defined in the Store Service.", + Optional: true, + Computed: true, + Default: listdefault.StaticValue(types.ListValueMust(types.StringType, []attr.Value{})), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + "load_balance_mode": schema.StringAttribute{ + Description: "The load balance mode, either `Failover` or `LoadBalanced`.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "Failover", + "LoadBalanced", + ), + }, + }, + "farms_are_identical": schema.BoolAttribute{ + Description: "Whether the PrimaryFarms in the EquivalentFarmSet all publish identical resources. Set to true if all resources are identical on all primary farms. Set to false if the deployment has some unique resources per farm. Default to `false`.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + }, + } +} + +func (EquivalentFarmSet) GetAttributes() map[string]schema.Attribute { + return EquivalentFarmSet{}.GetSchema().Attributes +} + +func (r EquivalentFarmSet) GetKey() string { + return r.Name.ValueString() +} + +func (r EquivalentFarmSet) RefreshListItem(ctx context.Context, diagnostics *diag.Diagnostics, item citrixstorefront.STFFarmSetResponseModel) util.ModelWithAttributes { + // Implement the logic to refresh the list item based on the item + name := types.StringValue(*item.Name.Get()) + r.Name = name + + aggregationGroupName := types.StringValue(*item.AggregationGroupName.Get()) + r.AggregationGroupName = aggregationGroupName + + primaryFarms := util.RefreshListValues(ctx, diagnostics, r.PrimaryFarms, item.PrimaryFarms) + r.PrimaryFarms = primaryFarms + + backupFarms := util.RefreshListValues(ctx, diagnostics, r.BackupFarms, item.BackupFarms) + r.BackupFarms = backupFarms + + LoadBalanceMode := types.StringValue(*item.LoadBalanceMode.Get()) + r.LoadBalanceMode = LoadBalanceMode + + farmsAreIdentical := types.BoolValue(*item.FarmsAreIdentical.Get()) + r.FarmsAreIdentical = farmsAreIdentical + + return r +} + +type STFUserFarmMappingResourceModel struct { + VirtualPath types.String `tfsdk:"store_virtual_path"` + Name types.String `tfsdk:"name"` + GroupMembers types.List `tfsdk:"group_members"` // List of UserFarmMappingGroup + EquivalentFarmSets types.List `tfsdk:"equivalent_farm_sets"` // List of EquivalentFarmSets +} + +// Schema implements resource.Resource. +func (r *stfUserFarmMappingResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "StoreFront User Farm Mapping Resource", + Attributes: map[string]schema.Attribute{ + "store_virtual_path": schema.StringAttribute{ + Description: "The IIS VirtualPath at which the Store is configured to be accessed by Receivers.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + Description: "The unique name used to identify the UserFarmMapping.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "group_members": schema.ListNestedAttribute{ + Description: "The Windows groups to which the UserFarmMapping will apply. Not specifying this field will assign all users to the UserFarmMapping.", + Optional: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, + NestedObject: UserFarmMappingGroup{}.GetSchema(), + }, + "equivalent_farm_sets": schema.ListNestedAttribute{ + Description: "Configurations of the EquivalentFarmSets that will be assigned to the UserFarmMapping.", + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, + NestedObject: EquivalentFarmSet{}.GetSchema(), + }, + }, + } +} + +// Map response body to schema and populate Computed attribute values +func (r *STFUserFarmMappingResourceModel) RefreshPropertyValues(ctx context.Context, diagnostics *diag.Diagnostics, result citrixstorefront.STFUserFarmMappingResponseModel) { + // Implement the logic to refresh the property values based on the result + r.Name = types.StringValue(*result.Name.Get()) + r.VirtualPath = types.StringValue(*result.VirtualPath.Get()) + + updatedGroupMembers := util.TypedArrayToObjectList[UserFarmMappingGroup](ctx, diagnostics, []UserFarmMappingGroup{}) + if result.GroupMembers != nil && len(result.GroupMembers) > 0 { + updatedGroupMembers = util.RefreshListValueProperties[UserFarmMappingGroup, citrixstorefront.STFGroupMemberResponseModel](ctx, diagnostics, r.GroupMembers, result.GroupMembers, util.GetSTFGroupMemberKey) + } + r.GroupMembers = updatedGroupMembers + + updatedEquivalentFarmSets := util.TypedArrayToObjectList[EquivalentFarmSet](ctx, diagnostics, []EquivalentFarmSet{}) + if result.FarmSets != nil && len(result.FarmSets) > 0 { + updatedEquivalentFarmSets = util.RefreshListValueProperties[EquivalentFarmSet, citrixstorefront.STFFarmSetResponseModel](ctx, diagnostics, r.EquivalentFarmSets, result.FarmSets, util.GetSTFFarmSetKey) + } + r.EquivalentFarmSets = updatedEquivalentFarmSets +} diff --git a/internal/storefront/stf_store/stf_store_service_resource.go b/internal/storefront/stf_store/stf_store_service_resource.go index dc71e55..9bd6679 100644 --- a/internal/storefront/stf_store/stf_store_service_resource.go +++ b/internal/storefront/stf_store/stf_store_service_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package stf_store import ( @@ -14,14 +14,6 @@ import ( "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/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" - "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" - "github.com/hashicorp/terraform-plugin-framework/types" ) // Ensure the implementation satisfies the expected interfaces. @@ -46,91 +38,6 @@ func (r *stfStoreServiceResource) Metadata(_ context.Context, req resource.Metad resp.TypeName = req.ProviderTypeName + "_stf_store_service" } -// Schema defines the schema for the resource. -func (r *stfStoreServiceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Storefront StoreService.", - Attributes: map[string]schema.Attribute{ - "site_id": schema.StringAttribute{ - Description: "The IIS site id of the Storefront storeservice. Defaults to 1.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString("1"), - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "virtual_path": schema.StringAttribute{ - Description: "The IIS VirtualPath at which the Store will be configured to be accessed by Receivers.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "friendly_name": schema.StringAttribute{ - Description: "The friendly name of the Store", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "authentication_service": schema.StringAttribute{ - Description: "The StoreFront Authentication Service to use for authenticating users.", - Optional: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "anonymous": schema.BoolAttribute{ - Description: "Whether the Store is anonymous. Anonymous Store not requiring authentication.", - Optional: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - }, - "load_balance": schema.BoolAttribute{ - Description: "Whether the Store is load balanced.", - Optional: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - }, - "farm_config": schema.SingleNestedAttribute{ - Description: "Farm configuration for the Store.", - Optional: true, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.RequiresReplace(), - }, - Attributes: map[string]schema.Attribute{ - "farm_name": schema.StringAttribute{ - Description: "The name of the Farm.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "farm_type": schema.StringAttribute{ - Description: "The type of the Farm.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "servers": schema.ListAttribute{ - ElementType: types.StringType, - Description: "The list of servers in the Farm.", - Required: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.RequiresReplace(), - }, - }, - }, - }, - }, - } -} - // Configure adds the provider configured client to the resource. func (r *stfStoreServiceResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { if req.ProviderData == nil { @@ -158,7 +65,7 @@ func (r *stfStoreServiceResource) Create(ctx context.Context, req resource.Creat siteIdInt, err := strconv.ParseInt(plan.SiteId.ValueString(), 10, 64) if err != nil { resp.Diagnostics.AddError( - "Error creating Storefront StoreService ", + "Error creating StoreFront StoreService ", "\nError message: "+err.Error(), ) return @@ -178,10 +85,12 @@ func (r *stfStoreServiceResource) Create(ctx context.Context, req resource.Creat body.SetLoadBalance(plan.LoadBalance.ValueBool()) } - if plan.FarmConfig != nil { - body.SetFarmName(plan.FarmConfig.FarmName.ValueString()) - body.SetFarmType(plan.FarmConfig.FarmType.ValueString()) - body.SetServers(util.ConvertBaseStringArrayToPrimitiveStringArray(plan.FarmConfig.Servers)) + if !plan.FarmConfig.IsNull() { + farmConfig := util.ObjectValueToTypedObject[FarmConfig](ctx, &resp.Diagnostics, plan.FarmConfig) + body.SetFarmName(farmConfig.FarmName.ValueString()) + body.SetFarmType(farmConfig.FarmType.ValueString()) + farmServers := util.StringListToStringArray(ctx, &resp.Diagnostics, farmConfig.Servers) + body.SetServers(farmServers) } createStoreServiceRequest := r.client.StorefrontClient.StoreSF.STFStoreCreateSTFStore(ctx, body) @@ -190,7 +99,7 @@ func (r *stfStoreServiceResource) Create(ctx context.Context, req resource.Creat StoreServiceDetail, err := createStoreServiceRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error creating Storefront StoreService", + "Error creating StoreFront StoreService", "TransactionId: ", ) return @@ -260,7 +169,7 @@ func (r *stfStoreServiceResource) Update(ctx context.Context, req resource.Updat _, err = editStoreServiceRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error updating Storefront StoreService ", + "Error updating StoreFront StoreService ", "\nError message: "+err.Error(), ) } @@ -303,7 +212,7 @@ func (r *stfStoreServiceResource) Delete(ctx context.Context, req resource.Delet _, err := deleteStoreServiceRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error deleting Storefront StoreService ", + "Error deleting StoreFront StoreService ", "\nError message: "+err.Error(), ) return @@ -344,7 +253,7 @@ func getSTFStoreService(ctx context.Context, client *citrixdaasclient.CitrixDaas siteIdInt, err := strconv.ParseInt(*siteId, 10, 64) if err != nil { diagnostics.AddError( - "Error fetching state of Storefront StoreService ", + "Error fetching state of StoreFront StoreService ", "Error message: "+err.Error(), ) return nil, err diff --git a/internal/storefront/stf_store/stf_store_service_resource_model.go b/internal/storefront/stf_store/stf_store_service_resource_model.go index 929dcbd..807864e 100644 --- a/internal/storefront/stf_store/stf_store_service_resource_model.go +++ b/internal/storefront/stf_store/stf_store_service_resource_model.go @@ -1,15 +1,68 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package stf_store import ( + "context" "strconv" citrixstorefront "github.com/citrix/citrix-daas-rest-go/citrixstorefront/models" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" + "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" "github.com/hashicorp/terraform-plugin-framework/types" ) +type FarmConfig struct { + FarmName types.String `tfsdk:"farm_name"` + FarmType types.String `tfsdk:"farm_type"` + Servers types.List `tfsdk:"servers"` // []types.String +} + +func (FarmConfig) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Farm configuration for the Store.", + Optional: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.RequiresReplace(), + }, + Attributes: map[string]schema.Attribute{ + "farm_name": schema.StringAttribute{ + Description: "The name of the Farm.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "farm_type": schema.StringAttribute{ + Description: "The type of the Farm.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "servers": schema.ListAttribute{ + ElementType: types.StringType, + Description: "The list of servers in the Farm.", + Required: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (FarmConfig) GetAttributes() map[string]schema.Attribute { + return FarmConfig{}.GetSchema().Attributes +} + // SFStoreServiceResourceModel maps the resource schema data. type STFStoreServiceResourceModel struct { VirtualPath types.String `tfsdk:"virtual_path"` @@ -18,13 +71,61 @@ type STFStoreServiceResourceModel struct { AuthenticationService types.String `tfsdk:"authentication_service"` Anonymous types.Bool `tfsdk:"anonymous"` LoadBalance types.Bool `tfsdk:"load_balance"` - FarmConfig *FarmConfig `tfsdk:"farm_config"` + FarmConfig types.Object `tfsdk:"farm_config"` // FarmConfig } -type FarmConfig struct { - FarmName types.String `tfsdk:"farm_name"` - FarmType types.String `tfsdk:"farm_type"` - Servers []types.String `tfsdk:"servers"` +func (*stfStoreServiceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "StoreFront StoreService.", + Attributes: map[string]schema.Attribute{ + "site_id": schema.StringAttribute{ + Description: "The IIS site id of the StoreFront storeservice. Defaults to 1.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("1"), + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "virtual_path": schema.StringAttribute{ + Description: "The IIS VirtualPath at which the Store will be configured to be accessed by Receivers.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "friendly_name": schema.StringAttribute{ + Description: "The friendly name of the Store", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "authentication_service": schema.StringAttribute{ + Description: "The StoreFront Authentication Service to use for authenticating users.", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "anonymous": schema.BoolAttribute{ + Description: "Whether the Store is anonymous. Anonymous Store not requiring authentication.", + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "load_balance": schema.BoolAttribute{ + Description: "Whether the Store is load balanced.", + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "farm_config": FarmConfig{}.GetSchema(), + }, + } } func (r *STFStoreServiceResourceModel) RefreshPropertyValues(storeservice *citrixstorefront.STFStoreDetailModel) { diff --git a/internal/storefront/stf_webreceiver/stf_webreceiver_resource.go b/internal/storefront/stf_webreceiver/stf_webreceiver_resource.go index a9224f5..4ad66f8 100644 --- a/internal/storefront/stf_webreceiver/stf_webreceiver_resource.go +++ b/internal/storefront/stf_webreceiver/stf_webreceiver_resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package stf_webreceiver import ( @@ -14,13 +14,6 @@ import ( "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/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault" - "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" - "github.com/hashicorp/terraform-plugin-framework/types" ) // Ensure the implementation satisfies the expected interfaces. @@ -45,129 +38,6 @@ func (r *stfWebReceiverResource) Metadata(_ context.Context, req resource.Metada resp.TypeName = req.ProviderTypeName + "_stf_webreceiver_service" } -// Schema defines the schema for the resource. -func (r *stfWebReceiverResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Storefront WebReceiver.", - Attributes: map[string]schema.Attribute{ - "site_id": schema.StringAttribute{ - Description: "The IIS site id of the Storefront webreceiver. Defaults to 1.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString("1"), - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "virtual_path": schema.StringAttribute{ - Description: "The IIS VirtualPath at which the WebReceiver will be configured to be accessed by Receivers.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "friendly_name": schema.StringAttribute{ - Description: "The friendly name of the WebReceiver", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "store_service": schema.StringAttribute{ - Description: "The StoreFront Store Service linked to the WebReceiver.", - Optional: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "authentication_methods": schema.ListAttribute{ - ElementType: types.StringType, - Description: "The authentication methods supported by the WebReceiver.", - Optional: true, - Computed: true, - Default: listdefault.StaticValue(types.ListNull(types.StringType)), - }, - "plugin_assistant": schema.SingleNestedAttribute{ - Description: "Pluin Assistant configuration for the WebReceiver.", - Optional: true, - Attributes: map[string]schema.Attribute{ - "enabled": schema.BoolAttribute{ - Description: "Enable the Plugin Assistant.", - Optional: true, - Computed: true, - Default: booldefault.StaticBool(true), - }, - "upgrade_at_login": schema.BoolAttribute{ - Description: "Prompt to upgrade older clients.", - Optional: true, - Computed: true, - Default: booldefault.StaticBool(false), - }, - "show_after_login": schema.BoolAttribute{ - Description: "Show Plugin Assistant after the user logs in.", - Optional: true, - Computed: true, - Default: booldefault.StaticBool(false), - }, - "win32_path": schema.StringAttribute{ - Description: "Path to the Windows Receiver.", - Optional: true, - }, - "macos_path": schema.StringAttribute{ - Description: "Path to the MacOS Receiver.", - Optional: true, - }, - "macos_minimum_supported_version": schema.StringAttribute{ - Description: "Minimum version of the MacOS supported.", - Optional: true, - }, - "html5_single_tab_launch": schema.BoolAttribute{ - Description: "Launch Html5 Receiver in the same browser tab.", - Optional: true, - }, - "html5_enabled": schema.StringAttribute{ - Description: "Method of deploying and using the Html5 Receiver.", - Optional: true, - Computed: true, - Default: stringdefault.StaticString("Off"), - }, - "html5_platforms": schema.StringAttribute{ - Description: "The supported Html5 platforms.", - Optional: true, - }, - "html5_preferences": schema.StringAttribute{ - Description: "Html5 Receiver preferences.", - Optional: true, - }, - "html5_chrome_app_origins": schema.StringAttribute{ - Description: "The Html5 Chrome Application Origins settings.", - Optional: true, - }, - "html5_chrome_app_preferences": schema.StringAttribute{ - Description: "The Html5 Chrome Application preferences.", - Optional: true, - }, - "protocol_handler_enabled": schema.BoolAttribute{ - Description: "Enable the Receiver Protocol Handler.", - Optional: true, - Computed: true, - Default: booldefault.StaticBool(true), - }, - "protocol_handler_platforms": schema.StringAttribute{ - Description: "The supported Protocol Handler platforms.", - Optional: true, - }, - "protocol_handler_skip_double_hop_check_when_disabled": schema.BoolAttribute{ - Description: "Skip the Protocol Handle double hop check.", - Optional: true, - }, - }, - }, - }, - } -} - // Configure adds the provider configured client to the resource. func (r *stfWebReceiverResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { if req.ProviderData == nil { @@ -194,7 +64,7 @@ func (r *stfWebReceiverResource) Create(ctx context.Context, req resource.Create siteIdInt, err := strconv.ParseInt(plan.SiteId.ValueString(), 10, 64) if err != nil { resp.Diagnostics.AddError( - "Error creating Storefront WebReceiver ", + "Error creating StoreFront WebReceiver ", "\nError message: "+err.Error(), ) return @@ -208,23 +78,24 @@ func (r *stfWebReceiverResource) Create(ctx context.Context, req resource.Create WebReceiverDetail, err := createWebReceiverRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error creating Storefront WebReceiver", + "Error creating StoreFront WebReceiver", "TransactionId: ", ) return } // Create the authentication methods Body - if plan.AuthenticationMethods != nil { + if !plan.AuthenticationMethods.IsNull() { var authMethodCreateBody citrixstorefront.UpdateSTFWebReceiverAuthenticationMethodsRequestModel authMethodCreateBody.SetWebReceiverService("(Get-STFWebReceiverService -VirtualPath " + plan.VirtualPath.ValueString() + " -SiteId " + plan.SiteId.ValueString() + " )") - authMethodCreateBody.SetAuthenticationMethods(util.ConvertBaseStringArrayToPrimitiveStringArray(plan.AuthenticationMethods)) + authMethods := util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.AuthenticationMethods) + authMethodCreateBody.SetAuthenticationMethods(authMethods) creatAuthProtocolRequest := r.client.StorefrontClient.WebReceiverSF.STFWebReceiverSetSTFWebReceiverAuthenticationMethods(ctx, authMethodCreateBody) // Create new STF WebReceiver Authentication Methods _, err = creatAuthProtocolRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error creating Storefront WebReceiver Authentication Methods", + "Error creating StoreFront WebReceiver Authentication Methods", "TransactionId: ", ) return @@ -232,30 +103,32 @@ func (r *stfWebReceiverResource) Create(ctx context.Context, req resource.Create } // Create the Plugin Assistant - if plan.PluginAssistant != nil { + if !plan.PluginAssistant.IsNull() { var pluginAssistantBody citrixstorefront.UpdateSTFWebReceiverPluginAssistantRequestModel pluginAssistantBody.SetWebReceiverService("(Get-STFWebReceiverService -VirtualPath " + plan.VirtualPath.ValueString() + " -SiteId " + plan.SiteId.ValueString() + " )") - pluginAssistantBody.SetEnabled(plan.PluginAssistant.Enabled.ValueBool()) - pluginAssistantBody.SetUpgradeAtLogin(plan.PluginAssistant.UpgradeAtLogin.ValueBool()) - pluginAssistantBody.SetShowAfterLogin(plan.PluginAssistant.ShowAfterLogin.ValueBool()) - pluginAssistantBody.SetWin32Path(plan.PluginAssistant.Win32Path.ValueString()) - pluginAssistantBody.SetMacOSPath(plan.PluginAssistant.MacOSPath.ValueString()) - pluginAssistantBody.SetMacOSMinimumSupportedVersion(plan.PluginAssistant.MacOSMinimumSupportedVersion.ValueString()) - pluginAssistantBody.SetHtml5SingleTabLaunch(plan.PluginAssistant.Html5SingleTabLaunch.ValueBool()) - pluginAssistantBody.SetHtml5Enabled(plan.PluginAssistant.Html5Enabled.ValueString()) - pluginAssistantBody.SetHtml5Platforms(plan.PluginAssistant.Html5Platforms.ValueString()) - pluginAssistantBody.SetHtml5Preferences(plan.PluginAssistant.Html5Preferences.ValueString()) - pluginAssistantBody.SetHtml5ChromeAppOrigins(plan.PluginAssistant.Html5ChromeAppOrigins.ValueString()) - pluginAssistantBody.SetHtml5ChromeAppPreferences(plan.PluginAssistant.Html5ChromeAppPreferences.ValueString()) - pluginAssistantBody.SetProtocolHandlerEnabled(plan.PluginAssistant.ProtocolHandlerEnabled.ValueBool()) - pluginAssistantBody.SetProtocolHandlerPlatforms(plan.PluginAssistant.ProtocolHandlerPlatforms.ValueString()) - pluginAssistantBody.SetProtocolHandlerSkipDoubleHopCheckWhenDisabled(plan.PluginAssistant.ProtocolHandlerSkipDoubleHopCheckWhenDisabled.ValueBool()) + + plannedPluginAssistant := util.ObjectValueToTypedObject[PluginAssistant](ctx, &resp.Diagnostics, plan.PluginAssistant) + pluginAssistantBody.SetEnabled(plannedPluginAssistant.Enabled.ValueBool()) + pluginAssistantBody.SetUpgradeAtLogin(plannedPluginAssistant.UpgradeAtLogin.ValueBool()) + pluginAssistantBody.SetShowAfterLogin(plannedPluginAssistant.ShowAfterLogin.ValueBool()) + pluginAssistantBody.SetWin32Path(plannedPluginAssistant.Win32Path.ValueString()) + pluginAssistantBody.SetMacOSPath(plannedPluginAssistant.MacOSPath.ValueString()) + pluginAssistantBody.SetMacOSMinimumSupportedVersion(plannedPluginAssistant.MacOSMinimumSupportedVersion.ValueString()) + pluginAssistantBody.SetHtml5SingleTabLaunch(plannedPluginAssistant.Html5SingleTabLaunch.ValueBool()) + pluginAssistantBody.SetHtml5Enabled(plannedPluginAssistant.Html5Enabled.ValueString()) + pluginAssistantBody.SetHtml5Platforms(plannedPluginAssistant.Html5Platforms.ValueString()) + pluginAssistantBody.SetHtml5Preferences(plannedPluginAssistant.Html5Preferences.ValueString()) + pluginAssistantBody.SetHtml5ChromeAppOrigins(plannedPluginAssistant.Html5ChromeAppOrigins.ValueString()) + pluginAssistantBody.SetHtml5ChromeAppPreferences(plannedPluginAssistant.Html5ChromeAppPreferences.ValueString()) + pluginAssistantBody.SetProtocolHandlerEnabled(plannedPluginAssistant.ProtocolHandlerEnabled.ValueBool()) + pluginAssistantBody.SetProtocolHandlerPlatforms(plannedPluginAssistant.ProtocolHandlerPlatforms.ValueString()) + pluginAssistantBody.SetProtocolHandlerSkipDoubleHopCheckWhenDisabled(plannedPluginAssistant.ProtocolHandlerSkipDoubleHopCheckWhenDisabled.ValueBool()) pluginAssistantRequest := r.client.StorefrontClient.WebReceiverSF.STFWebReceiverPluginAssistantUpdate(ctx, pluginAssistantBody) // Create new STF WebReceiver Plugin Assistant _, err = pluginAssistantRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error creating Storefront WebReceiver Plugin Assistant", + "Error creating StoreFront WebReceiver Plugin Assistant", "TransactionId: ", ) return @@ -264,35 +137,36 @@ func (r *stfWebReceiverResource) Create(ctx context.Context, req resource.Create } // Refresh the authentication methods - if plan.AuthenticationMethods != nil { + if !plan.AuthenticationMethods.IsNull() { var authMethodGetBody citrixstorefront.GetSTFWebReceiverAuthenticationMethodsRequestModel authMethodGetBody.SetWebReceiverService("(Get-STFWebReceiverService -VirtualPath " + plan.VirtualPath.ValueString() + " -SiteId " + plan.SiteId.ValueString() + " )") getAuthProtocolRequest := r.client.StorefrontClient.WebReceiverSF.STFWebReceiverGetSTFWebReceiverAuthenticationMethods(ctx, authMethodGetBody) authMethoResult, err := getAuthProtocolRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error fetching Storefront WebReceiver Authentication Methods", + "Error fetching StoreFront WebReceiver Authentication Methods", "TransactionId: ", ) return } - util.RefreshList(plan.AuthenticationMethods, authMethoResult.Methods) + + plan.AuthenticationMethods = util.StringArrayToStringSet(ctx, &resp.Diagnostics, authMethoResult.Methods) } //Refresh Plugin Assistant - if plan.PluginAssistant != nil { + if !plan.PluginAssistant.IsNull() { var pluginAssistantGetBody citrixstorefront.GetSTFWebReceiverPluginAssistantRequestModel pluginAssistantGetBody.SetWebReceiverService("(Get-STFWebReceiverService -VirtualPath " + plan.VirtualPath.ValueString() + " -SiteId " + plan.SiteId.ValueString() + " )") getPlugInAssistantRequest := r.client.StorefrontClient.WebReceiverSF.STFWebReceiverPluginAssistantGet(ctx, pluginAssistantGetBody) assistant, err := getPlugInAssistantRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error fetching Storefront WebReceiver Plugin Assistant", + "Error fetching StoreFront WebReceiver Plugin Assistant", "TransactionId: ", ) return } - plan.RefreshPlugInAssistant(&assistant) + plan.RefreshPlugInAssistant(ctx, &resp.Diagnostics, &assistant) } // Map response body to schema and populate Computed attribute values @@ -324,19 +198,19 @@ func (r *stfWebReceiverResource) Read(ctx context.Context, req resource.ReadRequ } //Refresh Plugin Assistant - if state.PluginAssistant != nil { + if !state.PluginAssistant.IsNull() { var pluginAssistantGetBody citrixstorefront.GetSTFWebReceiverPluginAssistantRequestModel pluginAssistantGetBody.SetWebReceiverService("(Get-STFWebReceiverService -VirtualPath " + state.VirtualPath.ValueString() + " -SiteId " + state.SiteId.ValueString() + " )") getPlugInAssistantRequest := r.client.StorefrontClient.WebReceiverSF.STFWebReceiverPluginAssistantGet(ctx, pluginAssistantGetBody) assistant, err := getPlugInAssistantRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error fetching Storefront WebReceiver Plugin Assistant", + "Error fetching StoreFront WebReceiver Plugin Assistant", "TransactionId: ", ) return } - state.RefreshPlugInAssistant(&assistant) + state.RefreshPlugInAssistant(ctx, &resp.Diagnostics, &assistant) } state.RefreshPropertyValues(STFWebReceiver) @@ -370,16 +244,17 @@ func (r *stfWebReceiverResource) Update(ctx context.Context, req resource.Update } // Update the Auth Methods - if plan.AuthenticationMethods != nil { + if !plan.AuthenticationMethods.IsNull() { var authMethodCreateBody citrixstorefront.UpdateSTFWebReceiverAuthenticationMethodsRequestModel authMethodCreateBody.SetWebReceiverService("(Get-STFWebReceiverService -VirtualPath " + plan.VirtualPath.ValueString() + " -SiteId " + plan.SiteId.ValueString() + " )") - authMethodCreateBody.SetAuthenticationMethods(util.ConvertBaseStringArrayToPrimitiveStringArray(plan.AuthenticationMethods)) + authMethods := util.StringSetToStringArray(ctx, &resp.Diagnostics, plan.AuthenticationMethods) + authMethodCreateBody.SetAuthenticationMethods(authMethods) creatAuthProtocolRequest := r.client.StorefrontClient.WebReceiverSF.STFWebReceiverSetSTFWebReceiverAuthenticationMethods(ctx, authMethodCreateBody) // Create new STF WebReceiver Authentication Methods _, err := creatAuthProtocolRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error creating Storefront WebReceiver Authentication Methods", + "Error creating StoreFront WebReceiver Authentication Methods", "TransactionId: ", ) return @@ -387,30 +262,32 @@ func (r *stfWebReceiverResource) Update(ctx context.Context, req resource.Update } // update the Plugin Assistant - if plan.PluginAssistant != nil { + if !plan.PluginAssistant.IsNull() { var pluginAssistantBody citrixstorefront.UpdateSTFWebReceiverPluginAssistantRequestModel pluginAssistantBody.SetWebReceiverService("(Get-STFWebReceiverService -VirtualPath " + plan.VirtualPath.ValueString() + " -SiteId " + plan.SiteId.ValueString() + " )") - pluginAssistantBody.SetEnabled(plan.PluginAssistant.Enabled.ValueBool()) - pluginAssistantBody.SetUpgradeAtLogin(plan.PluginAssistant.UpgradeAtLogin.ValueBool()) - pluginAssistantBody.SetShowAfterLogin(plan.PluginAssistant.ShowAfterLogin.ValueBool()) - pluginAssistantBody.SetWin32Path(plan.PluginAssistant.Win32Path.ValueString()) - pluginAssistantBody.SetMacOSPath(plan.PluginAssistant.MacOSPath.ValueString()) - pluginAssistantBody.SetMacOSMinimumSupportedVersion(plan.PluginAssistant.MacOSMinimumSupportedVersion.ValueString()) - pluginAssistantBody.SetHtml5SingleTabLaunch(plan.PluginAssistant.Html5SingleTabLaunch.ValueBool()) - pluginAssistantBody.SetHtml5Enabled(plan.PluginAssistant.Html5Enabled.ValueString()) - pluginAssistantBody.SetHtml5Platforms(plan.PluginAssistant.Html5Platforms.ValueString()) - pluginAssistantBody.SetHtml5Preferences(plan.PluginAssistant.Html5Preferences.ValueString()) - pluginAssistantBody.SetHtml5ChromeAppOrigins(plan.PluginAssistant.Html5ChromeAppOrigins.ValueString()) - pluginAssistantBody.SetHtml5ChromeAppPreferences(plan.PluginAssistant.Html5ChromeAppPreferences.ValueString()) - pluginAssistantBody.SetProtocolHandlerEnabled(plan.PluginAssistant.ProtocolHandlerEnabled.ValueBool()) - pluginAssistantBody.SetProtocolHandlerPlatforms(plan.PluginAssistant.ProtocolHandlerPlatforms.ValueString()) - pluginAssistantBody.SetProtocolHandlerSkipDoubleHopCheckWhenDisabled(plan.PluginAssistant.ProtocolHandlerSkipDoubleHopCheckWhenDisabled.ValueBool()) + + plannedPluginAssistant := util.ObjectValueToTypedObject[PluginAssistant](ctx, &resp.Diagnostics, plan.PluginAssistant) + pluginAssistantBody.SetEnabled(plannedPluginAssistant.Enabled.ValueBool()) + pluginAssistantBody.SetUpgradeAtLogin(plannedPluginAssistant.UpgradeAtLogin.ValueBool()) + pluginAssistantBody.SetShowAfterLogin(plannedPluginAssistant.ShowAfterLogin.ValueBool()) + pluginAssistantBody.SetWin32Path(plannedPluginAssistant.Win32Path.ValueString()) + pluginAssistantBody.SetMacOSPath(plannedPluginAssistant.MacOSPath.ValueString()) + pluginAssistantBody.SetMacOSMinimumSupportedVersion(plannedPluginAssistant.MacOSMinimumSupportedVersion.ValueString()) + pluginAssistantBody.SetHtml5SingleTabLaunch(plannedPluginAssistant.Html5SingleTabLaunch.ValueBool()) + pluginAssistantBody.SetHtml5Enabled(plannedPluginAssistant.Html5Enabled.ValueString()) + pluginAssistantBody.SetHtml5Platforms(plannedPluginAssistant.Html5Platforms.ValueString()) + pluginAssistantBody.SetHtml5Preferences(plannedPluginAssistant.Html5Preferences.ValueString()) + pluginAssistantBody.SetHtml5ChromeAppOrigins(plannedPluginAssistant.Html5ChromeAppOrigins.ValueString()) + pluginAssistantBody.SetHtml5ChromeAppPreferences(plannedPluginAssistant.Html5ChromeAppPreferences.ValueString()) + pluginAssistantBody.SetProtocolHandlerEnabled(plannedPluginAssistant.ProtocolHandlerEnabled.ValueBool()) + pluginAssistantBody.SetProtocolHandlerPlatforms(plannedPluginAssistant.ProtocolHandlerPlatforms.ValueString()) + pluginAssistantBody.SetProtocolHandlerSkipDoubleHopCheckWhenDisabled(plannedPluginAssistant.ProtocolHandlerSkipDoubleHopCheckWhenDisabled.ValueBool()) pluginAssistantRequest := r.client.StorefrontClient.WebReceiverSF.STFWebReceiverPluginAssistantUpdate(ctx, pluginAssistantBody) // Create new STF WebReceiver Plugin Assistant _, err := pluginAssistantRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error creating Storefront WebReceiver Plugin Assistant", + "Error creating StoreFront WebReceiver Plugin Assistant", "TransactionId: ", ) return @@ -447,7 +324,7 @@ func (r *stfWebReceiverResource) Delete(ctx context.Context, req resource.Delete _, err := deleteWebReceiverRequest.Execute() if err != nil { resp.Diagnostics.AddError( - "Error deleting Storefront WebReceiver ", + "Error deleting StoreFront WebReceiver ", "\nError message: "+err.Error(), ) return @@ -488,7 +365,7 @@ func getSTFWebReceiver(ctx context.Context, client *citrixdaasclient.CitrixDaasC siteIdInt, err := strconv.ParseInt(state.SiteId.ValueString(), 10, 64) if err != nil { diagnostics.AddError( - "Error fetching state of Storefront WebReceiver ", + "Error fetching state of StoreFront WebReceiver ", "Error message: "+err.Error(), ) return nil, err diff --git a/internal/storefront/stf_webreceiver/stf_webreceiver_resource_model.go b/internal/storefront/stf_webreceiver/stf_webreceiver_resource_model.go index e39f370..6d7bdc2 100644 --- a/internal/storefront/stf_webreceiver/stf_webreceiver_resource_model.go +++ b/internal/storefront/stf_webreceiver/stf_webreceiver_resource_model.go @@ -1,25 +1,25 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package stf_webreceiver import ( + "context" "strconv" citrixstorefront "github.com/citrix/citrix-daas-rest-go/citrixstorefront/models" + "github.com/citrix/terraform-provider-citrix/internal/util" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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/setdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" ) -// SFWebReceiverResourceModel maps the resource schema data. -type STFWebReceiverResourceModel struct { - VirtualPath types.String `tfsdk:"virtual_path"` - SiteId types.String `tfsdk:"site_id"` - FriendlyName types.String `tfsdk:"friendly_name"` - StoreService types.String `tfsdk:"store_service"` - PluginAssistant *PluginAssistant `tfsdk:"plugin_assistant"` - AuthenticationMethods []types.String `tfsdk:"authentication_methods"` -} - type PluginAssistant struct { Enabled types.Bool `tfsdk:"enabled"` //Enable Receiver client detection. UpgradeAtLogin types.Bool `tfsdk:"upgrade_at_login"` //Prompt to upgrade older clients. @@ -38,6 +38,147 @@ type PluginAssistant struct { ProtocolHandlerSkipDoubleHopCheckWhenDisabled types.Bool `tfsdk:"protocol_handler_skip_double_hop_check_when_disabled"` //Skip the Protocol Handle double hop check. } +func (PluginAssistant) GetSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Description: "Pluin Assistant configuration for the WebReceiver.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Description: "Enable the Plugin Assistant.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "upgrade_at_login": schema.BoolAttribute{ + Description: "Prompt to upgrade older clients.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "show_after_login": schema.BoolAttribute{ + Description: "Show Plugin Assistant after the user logs in.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "win32_path": schema.StringAttribute{ + Description: "Path to the Windows Receiver.", + Optional: true, + }, + "macos_path": schema.StringAttribute{ + Description: "Path to the MacOS Receiver.", + Optional: true, + }, + "macos_minimum_supported_version": schema.StringAttribute{ + Description: "Minimum version of the MacOS supported.", + Optional: true, + }, + "html5_single_tab_launch": schema.BoolAttribute{ + Description: "Launch Html5 Receiver in the same browser tab.", + Optional: true, + }, + "html5_enabled": schema.StringAttribute{ + Description: "Method of deploying and using the Html5 Receiver.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("Off"), + }, + "html5_platforms": schema.StringAttribute{ + Description: "The supported Html5 platforms.", + Optional: true, + }, + "html5_preferences": schema.StringAttribute{ + Description: "Html5 Receiver preferences.", + Optional: true, + }, + "html5_chrome_app_origins": schema.StringAttribute{ + Description: "The Html5 Chrome Application Origins settings.", + Optional: true, + }, + "html5_chrome_app_preferences": schema.StringAttribute{ + Description: "The Html5 Chrome Application preferences.", + Optional: true, + }, + "protocol_handler_enabled": schema.BoolAttribute{ + Description: "Enable the Receiver Protocol Handler.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "protocol_handler_platforms": schema.StringAttribute{ + Description: "The supported Protocol Handler platforms.", + Optional: true, + }, + "protocol_handler_skip_double_hop_check_when_disabled": schema.BoolAttribute{ + Description: "Skip the Protocol Handle double hop check.", + Optional: true, + }, + }, + } +} + +func (PluginAssistant) GetAttributes() map[string]schema.Attribute { + return PluginAssistant{}.GetSchema().Attributes +} + +// SFWebReceiverResourceModel maps the resource schema data. +type STFWebReceiverResourceModel struct { + VirtualPath types.String `tfsdk:"virtual_path"` + SiteId types.String `tfsdk:"site_id"` + FriendlyName types.String `tfsdk:"friendly_name"` + StoreService types.String `tfsdk:"store_service"` + PluginAssistant types.Object `tfsdk:"plugin_assistant"` // PluginAssistant + AuthenticationMethods types.Set `tfsdk:"authentication_methods"` // Set[string] +} + +// Schema defines the schema for the resource. +func (r *stfWebReceiverResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "StoreFront WebReceiver.", + Attributes: map[string]schema.Attribute{ + "site_id": schema.StringAttribute{ + Description: "The IIS site id of the StoreFront webreceiver. Defaults to 1.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("1"), + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "virtual_path": schema.StringAttribute{ + Description: "The IIS VirtualPath at which the WebReceiver will be configured to be accessed by Receivers.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "friendly_name": schema.StringAttribute{ + Description: "The friendly name of the WebReceiver", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "store_service": schema.StringAttribute{ + Description: "The StoreFront Store Service linked to the WebReceiver.", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "authentication_methods": schema.SetAttribute{ + ElementType: types.StringType, + Description: "The authentication methods supported by the WebReceiver.", + Optional: true, + Computed: true, + Default: setdefault.StaticValue(types.SetNull(types.StringType)), + }, + "plugin_assistant": PluginAssistant{}.GetSchema(), + }, + } +} + func (r *STFWebReceiverResourceModel) RefreshPropertyValues(webreceiver *citrixstorefront.STFWebReceiverDetailModel) { // Overwrite SFWebReceiverResourceModel with refreshed state r.VirtualPath = types.StringValue(*webreceiver.VirtualPath.Get()) @@ -46,49 +187,52 @@ func (r *STFWebReceiverResourceModel) RefreshPropertyValues(webreceiver *citrixs } -func (r *STFWebReceiverResourceModel) RefreshPlugInAssistant(assistant *citrixstorefront.WebReceiverPluginAssistantModel) { +func (r *STFWebReceiverResourceModel) RefreshPlugInAssistant(ctx context.Context, diagnostics *diag.Diagnostics, assistant *citrixstorefront.WebReceiverPluginAssistantModel) { // Overwrite SFWebReceiverResourceModel with refreshed state - r.PluginAssistant.Enabled = types.BoolValue(*assistant.Enabled.Get()) - r.PluginAssistant.UpgradeAtLogin = types.BoolValue(*assistant.UpgradeAtLogin.Get()) - r.PluginAssistant.ShowAfterLogin = types.BoolValue(*assistant.ShowAfterLogin.Get()) - if !r.PluginAssistant.Win32Path.IsNull() { - r.PluginAssistant.Win32Path = types.StringValue(*assistant.Win32.Path.Get()) + refreshedPluginAssistant := util.ObjectValueToTypedObject[PluginAssistant](ctx, diagnostics, r.PluginAssistant) + refreshedPluginAssistant.Enabled = types.BoolValue(*assistant.Enabled.Get()) + refreshedPluginAssistant.UpgradeAtLogin = types.BoolValue(*assistant.UpgradeAtLogin.Get()) + refreshedPluginAssistant.ShowAfterLogin = types.BoolValue(*assistant.ShowAfterLogin.Get()) + if !refreshedPluginAssistant.Win32Path.IsNull() { + refreshedPluginAssistant.Win32Path = types.StringValue(*assistant.Win32.Path.Get()) } - if !r.PluginAssistant.MacOSPath.IsNull() { - r.PluginAssistant.MacOSPath = types.StringValue(*assistant.MacOS.Path.Get()) + if !refreshedPluginAssistant.MacOSPath.IsNull() { + refreshedPluginAssistant.MacOSPath = types.StringValue(*assistant.MacOS.Path.Get()) } - if !r.PluginAssistant.MacOSMinimumSupportedVersion.IsNull() { - r.PluginAssistant.MacOSMinimumSupportedVersion = types.StringValue(*assistant.MacOS.MinimumSupportedVersion.Get()) + if !refreshedPluginAssistant.MacOSMinimumSupportedVersion.IsNull() { + refreshedPluginAssistant.MacOSMinimumSupportedVersion = types.StringValue(*assistant.MacOS.MinimumSupportedVersion.Get()) } - if !r.PluginAssistant.Html5SingleTabLaunch.IsNull() { - r.PluginAssistant.Html5SingleTabLaunch = types.BoolValue(*assistant.HTML5.SingleTabLaunch.Get()) + if !refreshedPluginAssistant.Html5SingleTabLaunch.IsNull() { + refreshedPluginAssistant.Html5SingleTabLaunch = types.BoolValue(*assistant.HTML5.SingleTabLaunch.Get()) } - if !r.PluginAssistant.Html5Enabled.IsNull() { + if !refreshedPluginAssistant.Html5Enabled.IsNull() { if *assistant.HTML5.Enabled.Get() == 0 { - r.PluginAssistant.Html5Enabled = types.StringValue("Off") + refreshedPluginAssistant.Html5Enabled = types.StringValue("Off") } else if *assistant.HTML5.Enabled.Get() == 1 { - r.PluginAssistant.Html5Enabled = types.StringValue("Always") + refreshedPluginAssistant.Html5Enabled = types.StringValue("Always") } else { - r.PluginAssistant.Html5Enabled = types.StringValue("Fallback") + refreshedPluginAssistant.Html5Enabled = types.StringValue("Fallback") } } - if !r.PluginAssistant.Html5Platforms.IsNull() { - r.PluginAssistant.Html5Platforms = types.StringValue(*assistant.HTML5.Platforms.Get()) + if !refreshedPluginAssistant.Html5Platforms.IsNull() { + refreshedPluginAssistant.Html5Platforms = types.StringValue(*assistant.HTML5.Platforms.Get()) } - if !r.PluginAssistant.Html5Preferences.IsNull() { - r.PluginAssistant.Html5Preferences = types.StringValue(*assistant.HTML5.Preferences.Get()) + if !refreshedPluginAssistant.Html5Preferences.IsNull() { + refreshedPluginAssistant.Html5Preferences = types.StringValue(*assistant.HTML5.Preferences.Get()) } - if !r.PluginAssistant.Html5ChromeAppOrigins.IsNull() { - r.PluginAssistant.Html5ChromeAppOrigins = types.StringValue(*assistant.HTML5.ChromeAppOrigins.Get()) + if !refreshedPluginAssistant.Html5ChromeAppOrigins.IsNull() { + refreshedPluginAssistant.Html5ChromeAppOrigins = types.StringValue(*assistant.HTML5.ChromeAppOrigins.Get()) } - if !r.PluginAssistant.Html5ChromeAppPreferences.IsNull() { - r.PluginAssistant.Html5ChromeAppPreferences = types.StringValue(*assistant.HTML5.ChromeAppPreferences.Get()) + if !refreshedPluginAssistant.Html5ChromeAppPreferences.IsNull() { + refreshedPluginAssistant.Html5ChromeAppPreferences = types.StringValue(*assistant.HTML5.ChromeAppPreferences.Get()) } - if !r.PluginAssistant.ProtocolHandlerEnabled.IsNull() { - r.PluginAssistant.ProtocolHandlerEnabled = types.BoolValue(*assistant.ProtocolHandler.Enabled.Get()) + if !refreshedPluginAssistant.ProtocolHandlerEnabled.IsNull() { + refreshedPluginAssistant.ProtocolHandlerEnabled = types.BoolValue(*assistant.ProtocolHandler.Enabled.Get()) } - if !r.PluginAssistant.ProtocolHandlerPlatforms.IsNull() { - r.PluginAssistant.ProtocolHandlerPlatforms = types.StringValue(*assistant.ProtocolHandler.Platforms.Get()) + if !refreshedPluginAssistant.ProtocolHandlerPlatforms.IsNull() { + refreshedPluginAssistant.ProtocolHandlerPlatforms = types.StringValue(*assistant.ProtocolHandler.Platforms.Get()) } + refreshedPluginAssistantObject := util.TypedObjectToObjectValue(ctx, diagnostics, refreshedPluginAssistant) + r.PluginAssistant = refreshedPluginAssistantObject } diff --git a/internal/test/admin_role_resource_test.go b/internal/test/admin_role_resource_test.go index c33399b..8dd2883 100644 --- a/internal/test/admin_role_resource_test.go +++ b/internal/test/admin_role_resource_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test @@ -40,8 +40,8 @@ func TestAdminRoleResource(t *testing.T) { resource.TestCheckResourceAttr("citrix_admin_role.test_role", "can_launch_monitor", "true"), // Verify the permissions list resource.TestCheckResourceAttr("citrix_admin_role.test_role", "permissions.#", "2"), - resource.TestCheckResourceAttr("citrix_admin_role.test_role", "permissions.0", "Director_DismissAlerts"), - resource.TestCheckResourceAttr("citrix_admin_role.test_role", "permissions.1", "DesktopGroup_AddApplicationGroup"), + resource.TestCheckTypeSetElemAttr("citrix_admin_role.test_role", "permissions.*", "Director_DismissAlerts"), + resource.TestCheckTypeSetElemAttr("citrix_admin_role.test_role", "permissions.*", "DesktopGroup_AddApplicationGroup"), ), }, // ImportState testing @@ -67,9 +67,9 @@ func TestAdminRoleResource(t *testing.T) { resource.TestCheckResourceAttr("citrix_admin_role.test_role", "can_launch_monitor", "true"), // Verify the permissions list resource.TestCheckResourceAttr("citrix_admin_role.test_role", "permissions.#", "3"), - resource.TestCheckResourceAttr("citrix_admin_role.test_role", "permissions.0", "Director_DismissAlerts"), - resource.TestCheckResourceAttr("citrix_admin_role.test_role", "permissions.1", "ApplicationGroup_AddScope"), - resource.TestCheckResourceAttr("citrix_admin_role.test_role", "permissions.2", "AppLib_AddPackage"), + resource.TestCheckTypeSetElemAttr("citrix_admin_role.test_role", "permissions.*", "Director_DismissAlerts"), + resource.TestCheckTypeSetElemAttr("citrix_admin_role.test_role", "permissions.*", "ApplicationGroup_AddScope"), + resource.TestCheckTypeSetElemAttr("citrix_admin_role.test_role", "permissions.*", "AppLib_AddPackage"), ), }, // Delete testing automatically occurs in TestCase diff --git a/internal/test/admin_scope_data_source_test.go b/internal/test/admin_scope_data_source_test.go index 523ca16..df7b577 100644 --- a/internal/test/admin_scope_data_source_test.go +++ b/internal/test/admin_scope_data_source_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test diff --git a/internal/test/admin_scope_resource_test.go b/internal/test/admin_scope_resource_test.go index 0ed76ad..2ee32a7 100644 --- a/internal/test/admin_scope_resource_test.go +++ b/internal/test/admin_scope_resource_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test @@ -20,8 +20,6 @@ func TestAdminScopeResourcePreCheck(t *testing.T) { func TestAdminScopeResource(t *testing.T) { name := os.Getenv("TEST_ADMIN_SCOPE_NAME") - catalogName := os.Getenv("TEST_MC_NAME") - dgName := os.Getenv("TEST_DG_NAME") resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, PreCheck: func() { @@ -49,11 +47,6 @@ func TestAdminScopeResource(t *testing.T) { resource.TestCheckResourceAttr("citrix_admin_scope.test_scope", "name", name), // Verify the description of the admin scope resource.TestCheckResourceAttr("citrix_admin_scope.test_scope", "description", "test scope created via terraform"), - // Verify number of scoped objects - resource.TestCheckResourceAttr("citrix_admin_scope.test_scope", "scoped_objects.#", "1"), - // Verify the scoped objects data - resource.TestCheckResourceAttr("citrix_admin_scope.test_scope", "scoped_objects.0.object_type", "DeliveryGroup"), - resource.TestCheckResourceAttr("citrix_admin_scope.test_scope", "scoped_objects.0.object", dgName), ), }, // ImportState testing @@ -81,13 +74,6 @@ func TestAdminScopeResource(t *testing.T) { resource.TestCheckResourceAttr("citrix_admin_scope.test_scope", "name", fmt.Sprintf("%s-updated", name)), // Verify the description of the admin scope resource.TestCheckResourceAttr("citrix_admin_scope.test_scope", "description", "Updated description for test scope"), - // Verify number of scoped objects - resource.TestCheckResourceAttr("citrix_admin_scope.test_scope", "scoped_objects.#", "2"), - // Verify the scoped objects data - resource.TestCheckResourceAttr("citrix_admin_scope.test_scope", "scoped_objects.0.object_type", "DeliveryGroup"), - resource.TestCheckResourceAttr("citrix_admin_scope.test_scope", "scoped_objects.0.object", dgName), - resource.TestCheckResourceAttr("citrix_admin_scope.test_scope", "scoped_objects.1.object_type", "MachineCatalog"), - resource.TestCheckResourceAttr("citrix_admin_scope.test_scope", "scoped_objects.1.object", catalogName), ), }, // Delete testing automatically occurs in TestCase @@ -100,28 +86,12 @@ var ( resource "citrix_admin_scope" "test_scope" { name = "%s" description = "test scope created via terraform" - scoped_objects = [ - { - object_type = "DeliveryGroup", - object = citrix_delivery_group.testDeliveryGroup.name - } - ] } ` adminScopeTestResource_updated = ` resource "citrix_admin_scope" "test_scope" { name = "%s-updated" description = "Updated description for test scope" - scoped_objects = [ - { - object_type = "DeliveryGroup", - object = citrix_delivery_group.testDeliveryGroup.name - }, - { - object_type = "MachineCatalog", - object = citrix_machine_catalog.testMachineCatalog.name - } - ] } ` ) diff --git a/internal/test/admin_user_resource_test.go b/internal/test/admin_user_resource_test.go index cc71695..d1478ac 100644 --- a/internal/test/admin_user_resource_test.go +++ b/internal/test/admin_user_resource_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test diff --git a/internal/test/application_group_resource_test.go b/internal/test/application_group_resource_test.go new file mode 100644 index 0000000..4f0202f --- /dev/null +++ b/internal/test/application_group_resource_test.go @@ -0,0 +1,105 @@ +package test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// TestApplicationGroupResourcePreCheck validates the necessary env variable exist +// in the testing environment +func TestApplicationGroupResourcePreCheck(t *testing.T) { + if v := os.Getenv("TEST_APP_GROUP_NAME"); v == "" { + t.Fatal("TEST_APP_GROUP_NAME must be set for acceptance tests") + } +} + +func TestApplicationGroupResource(t *testing.T) { + name := os.Getenv("TEST_APP_GROUP_NAME") + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: func() { + TestProviderPreCheck(t) + TestHypervisorPreCheck_Azure(t) + TestHypervisorResourcePoolPreCheck_Azure(t) + TestMachineCatalogPreCheck_Azure(t) + TestDeliveryGroupPreCheck(t) + TestApplicationFolderPreCheck(t) + TestApplicationGroupResourcePreCheck(t) + }, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: composeTestResourceTf( + BuildApplicationGroupResource(t, testApplicationGroupResource), + BuildDeliveryGroupResource(t, testDeliveryGroupResources), + BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_updated, "", "ActiveDirectory"), + BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_testResource_azure), + BuildHypervisorResourceAzure(t, hypervisor_testResources), + BuildZoneResource(t, zone_testResource, os.Getenv("TEST_ZONE_NAME_AZURE")), + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify name of application + resource.TestCheckResourceAttr("citrix_application_group.testApplicationGroup", "name", name), + // Verify description of application + resource.TestCheckResourceAttr("citrix_application_group.testApplicationGroup", "description", "ApplicationGroup for testing"), + // Verify the number of delivery groups + resource.TestCheckResourceAttr("citrix_application_group.testApplicationGroup", "delivery_groups.#", "1"), + ), + }, + // ImportState testing + { + ResourceName: "citrix_application_group.testApplicationGroup", + ImportState: true, + ImportStateVerify: true, + // The last_updated attribute does not exist in the Orchestration + // API, therefore there is no value for it during import. + ImportStateVerifyIgnore: []string{"delivery_groups", "installed_app_properties"}, + }, + // Update and Read testing + { + Config: composeTestResourceTf( + BuildApplicationGroupResource(t, testApplicationGroupResource_updated), + BuildApplicationFolderResource(t, testApplicationFolderResource_updated), + BuildDeliveryGroupResource(t, testDeliveryGroupResources), + BuildPolicySetResourceWithoutDeliveryGroup(t), + BuildMachineCatalogResourceAzure(t, machinecatalog_testResources_azure_updated, "", "ActiveDirectory"), + BuildHypervisorResourcePoolResourceAzure(t, hypervisor_resource_pool_testResource_azure), + BuildHypervisorResourceAzure(t, hypervisor_testResources), + BuildZoneResource(t, zone_testResource, os.Getenv("TEST_ZONE_NAME_AZURE")), + ), + Check: resource.ComposeAggregateTestCheckFunc( + // Verify name of application + resource.TestCheckResourceAttr("citrix_application_group.testApplicationGroup", "name", fmt.Sprintf("%s-updated", name)), + // Verify description of application + resource.TestCheckResourceAttr("citrix_application_group.testApplicationGroup", "description", "ApplicationGroup for testing updated"), + ), + }, + // Delete testing + }, + }) +} + +var ( + testApplicationGroupResource = ` +resource "citrix_application_group" "testApplicationGroup" { + name = "%s" + description = "ApplicationGroup for testing" + delivery_groups = [citrix_delivery_group.testDeliveryGroup.id] + +}` + testApplicationGroupResource_updated = ` +resource "citrix_application_group" "testApplicationGroup" { + name = "%s-updated" + description = "ApplicationGroup for testing updated" + delivery_groups = [citrix_delivery_group.testDeliveryGroup.id] + +}` +) + +func BuildApplicationGroupResource(t *testing.T, applicationResource string) string { + name := os.Getenv("TEST_APP_GROUP_NAME") + return fmt.Sprintf(applicationResource, name) +} diff --git a/internal/test/azure_mcs_suite_test.go b/internal/test/azure_mcs_suite_test.go index 8e63db3..397f431 100644 --- a/internal/test/azure_mcs_suite_test.go +++ b/internal/test/azure_mcs_suite_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test @@ -485,9 +485,7 @@ func TestAzureMcs(t *testing.T) { // Verify type of the policy set resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "type", "DeliveryGroupPolicies"), // Verify the number of scopes of the policy set - resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.#", "1"), - // Verify the scopes of the policy set - resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.0", "All"), + resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.#", "0"), // Verify the number of policies in the policy set resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "policies.#", "2"), // Verify name of the first policy in the policy set @@ -600,9 +598,7 @@ func TestAzureMcs(t *testing.T) { // Verify type of the policy set resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "type", "DeliveryGroupPolicies"), // Verify the number of scopes of the policy set - resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.#", "1"), - // Verify the scopes of the policy set - resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.0", "All"), + resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.#", "0"), // Verify the number of policies in the policy set resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "policies.#", "1"), // Verify name of the second policy in the policy set diff --git a/internal/test/common.go b/internal/test/common.go index 2682d01..34bef1d 100644 --- a/internal/test/common.go +++ b/internal/test/common.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test diff --git a/internal/test/delivery_group_test.go b/internal/test/delivery_group_test.go index ebd4aad..f862974 100644 --- a/internal/test/delivery_group_test.go +++ b/internal/test/delivery_group_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test @@ -366,7 +366,6 @@ resource "citrix_delivery_group" "testDeliveryGroup" { resource "citrix_policy_set" "testPolicySetWithoutDG" { name = "%s" description = "Test policy set description updated" - scopes = [ "All" ] type = "DeliveryGroupPolicies" policies = [ { @@ -380,8 +379,6 @@ resource "citrix_policy_set" "testPolicySetWithoutDG" { use_default = false }, ] - policy_filters = [ - ] } ] } diff --git a/internal/test/gac_settings_resource_test.go b/internal/test/gac_settings_resource_test.go index 6e74ebf..acae609 100644 --- a/internal/test/gac_settings_resource_test.go +++ b/internal/test/gac_settings_resource_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test diff --git a/internal/test/hypervisor_resource_pool_test.go b/internal/test/hypervisor_resource_pool_test.go index bfabd9f..3d913c8 100644 --- a/internal/test/hypervisor_resource_pool_test.go +++ b/internal/test/hypervisor_resource_pool_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test diff --git a/internal/test/hypervisor_resource_test.go b/internal/test/hypervisor_resource_test.go index 9cda420..b37d0ab 100644 --- a/internal/test/hypervisor_resource_test.go +++ b/internal/test/hypervisor_resource_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test diff --git a/internal/test/machine_catalog_resource_test.go b/internal/test/machine_catalog_resource_test.go index f8c63ba..6aae5d5 100644 --- a/internal/test/machine_catalog_resource_test.go +++ b/internal/test/machine_catalog_resource_test.go @@ -1,10 +1,11 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test import ( "fmt" "os" + "strings" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -75,7 +76,7 @@ func TestActiveDirectoryMachineCatalogResourceAzure(t *testing.T) { // Verify machine catalog identity type resource.TestCheckResourceAttr("citrix_machine_catalog.testMachineCatalog-AD", "provisioning_scheme.identity_type", "ActiveDirectory"), // Verify nic network - resource.TestCheckResourceAttr("citrix_machine_catalog.testMachineCatalog-AD", "provisioning_scheme.network_mapping.network", os.Getenv("TEST_MC_SUBNET")), + resource.TestCheckResourceAttr("citrix_machine_catalog.testMachineCatalog-AD", "provisioning_scheme.network_mapping.0.network", os.Getenv("TEST_MC_SUBNET")), ), }, // ImportState testing @@ -160,7 +161,7 @@ func TestHybridAzureADMachineCatalogResourceAzure(t *testing.T) { // Verify domain admin username resource.TestCheckResourceAttr("citrix_machine_catalog.testMachineCatalog-HybAAD", "provisioning_scheme.machine_domain_identity.service_account", os.Getenv("TEST_MC_SERVICE_ACCOUNT")), // Verify nic network - resource.TestCheckResourceAttr("citrix_machine_catalog.testMachineCatalog-HybAAD", "provisioning_scheme.network_mapping.network", os.Getenv("TEST_MC_SUBNET")), + resource.TestCheckResourceAttr("citrix_machine_catalog.testMachineCatalog-HybAAD", "provisioning_scheme.network_mapping.0.network", os.Getenv("TEST_MC_SUBNET")), ), }, // ImportState testing @@ -259,7 +260,7 @@ func TestAzureADMachineCatalogResourceAzure(t *testing.T) { // Verify machine catalog identity type resource.TestCheckResourceAttr("citrix_machine_catalog.testMachineCatalog-AAD", "provisioning_scheme.identity_type", "AzureAD"), // Verify nic network - resource.TestCheckResourceAttr("citrix_machine_catalog.testMachineCatalog-AAD", "provisioning_scheme.network_mapping.network", os.Getenv("TEST_MC_SUBNET")), + resource.TestCheckResourceAttr("citrix_machine_catalog.testMachineCatalog-AAD", "provisioning_scheme.network_mapping.0.network", os.Getenv("TEST_MC_SUBNET")), ), }, // ImportState testing @@ -1141,7 +1142,7 @@ func TestMachineCatalogResource_Manual_Power_Managed_Nutanix(t *testing.T) { func TestMachineCatalogPreCheck_Manual_Power_Managed_AWS_EC2(t *testing.T) { if v := os.Getenv("TEST_MC_NAME_MANUAL_AWS_EC2"); v == "" { - t.Fatal("TEST_MC_NAME_MANUAL must be set for acceptance tests") + t.Fatal("TEST_MC_NAME_MANUAL_AWS_EC2 must be set for acceptance tests") } if v := os.Getenv("TEST_MC_ALLOCATION_TYPE_MANUAL_POWER_MANAGED"); v == "" { t.Fatal("TEST_MC_ALLOCATION_TYPE_MANUAL_POWER_MANAGED must be set for acceptance tests") @@ -1354,7 +1355,7 @@ resource "citrix_machine_catalog" "testMachineCatalog%s" { ] number_of_total_machines = 1 machine_account_creation_rules ={ - naming_scheme = "test-machine-##" + naming_scheme = "%s" naming_scheme_type ="Numeric" } } @@ -1405,10 +1406,10 @@ resource "citrix_machine_catalog" "testMachineCatalog%s" { network = "%s" } ] - availability_zones = "1,3" + availability_zones = ["1","3"] number_of_total_machines = 2 machine_account_creation_rules ={ - naming_scheme = "test-machine-##" + naming_scheme = "%s" naming_scheme_type ="Numeric" } } @@ -1459,10 +1460,10 @@ resource "citrix_machine_catalog" "testMachineCatalog%s" { network = "%s" } ] - availability_zones = "1,3" + availability_zones = ["1","3"] number_of_total_machines = 1 machine_account_creation_rules ={ - naming_scheme = "test-machine-##" + naming_scheme = "%s" naming_scheme_type ="Numeric" } } @@ -1512,7 +1513,7 @@ resource "citrix_machine_catalog" "testMachineCatalog%s" { ] number_of_total_machines = 1 machine_account_creation_rules ={ - naming_scheme = "test-machine-##" + naming_scheme = "%s" naming_scheme_type ="Numeric" } } @@ -1561,10 +1562,10 @@ resource "citrix_machine_catalog" "testMachineCatalog%s" { network = "%s" } ] - availability_zones = "1,3" + availability_zones = ["1","3"] number_of_total_machines = 2 machine_account_creation_rules ={ - naming_scheme = "test-machine-##" + naming_scheme = "%s" naming_scheme_type ="Numeric" } } @@ -1605,7 +1606,7 @@ resource "citrix_machine_catalog" "testMachineCatalog%s" { } number_of_total_machines = 1 machine_account_creation_rules ={ - naming_scheme = "test-machine-##" + naming_scheme = "%s" naming_scheme_type ="Numeric" } } @@ -1644,10 +1645,10 @@ resource "citrix_machine_catalog" "testMachineCatalog%s" { storage_cost_saving = true } } - availability_zones = "1,3" + availability_zones = ["1","3"] number_of_total_machines = 2 machine_account_creation_rules ={ - naming_scheme = "test-machine-##" + naming_scheme = "%s" naming_scheme_type ="Numeric" } } @@ -1679,7 +1680,7 @@ resource "citrix_machine_catalog" "testMachineCatalog%s" { machine_snapshot = "%s" } number_of_total_machines = 1 - availability_zones = "%s" + availability_zones = %s machine_account_creation_rules ={ naming_scheme = "test-machine-##" naming_scheme_type ="Numeric" @@ -1713,7 +1714,7 @@ resource "citrix_machine_catalog" "testMachineCatalog%s" { machine_snapshot = "%s" } number_of_total_machines = 2 - availability_zones = "%s" + availability_zones = %s machine_account_creation_rules ={ naming_scheme = "test-machine-##" naming_scheme_type ="Numeric" @@ -2197,11 +2198,14 @@ resource "citrix_machine_catalog" "testMachineCatalog%s" { func BuildMachineCatalogResourceAzure(t *testing.T, machineResource, catalogNameSuffix, identityType string) string { name := os.Getenv("TEST_MC_NAME") + namingScheme := "vda-##" if identityType == "HybridAzureAD" { name += "-HybAAD" + namingScheme += "-HybAAD" } if identityType == "AzureAD" { name += "-AAD" + namingScheme += "-AAD" } service_account := os.Getenv("TEST_MC_SERVICE_ACCOUNT") service_account_pass := os.Getenv("TEST_MC_SERVICE_ACCOUNT_PASS") @@ -2218,7 +2222,7 @@ func BuildMachineCatalogResourceAzure(t *testing.T, machineResource, catalogName //machine account domain := os.Getenv("TEST_MC_DOMAIN") - return fmt.Sprintf(machineResource, catalogNameSuffix, name, identityType, domain, service_account, service_account_pass, service_offering, resource_group, storage_account, container, master_image, subnet) + return fmt.Sprintf(machineResource, catalogNameSuffix, name, identityType, domain, service_account, service_account_pass, service_offering, resource_group, storage_account, container, master_image, subnet, namingScheme) } func BuildMachineCatalogResourceAzureAd(t *testing.T, machineResource string) string { @@ -2229,6 +2233,7 @@ func BuildMachineCatalogResourceAzureAd(t *testing.T, machineResource string) st storage_account := os.Getenv("TEST_MC_IMAGE_STORAGE_ACCOUNT") container := os.Getenv("TEST_MC_IMAGE_CONTAINER") subnet := os.Getenv("TEST_MC_SUBNET") + namingScheme := "vda-##-AAD" machine_profile_vm_name := os.Getenv("TEST_MC_MACHINE_PROFILE_VM_NAME") machine_profile_resource_group := os.Getenv("TEST_MC_MACHINE_PROFILE_RESOURCE_GROUP") @@ -2236,7 +2241,7 @@ func BuildMachineCatalogResourceAzureAd(t *testing.T, machineResource string) st master_image = os.Getenv("TEST_MC_MASTER_IMAGE_UPDATED") } - return fmt.Sprintf(machineResource, "-AAD", name, service_offering, resource_group, storage_account, container, master_image, machine_profile_vm_name, machine_profile_resource_group, subnet) + return fmt.Sprintf(machineResource, "-AAD", name, service_offering, resource_group, storage_account, container, master_image, machine_profile_vm_name, machine_profile_resource_group, subnet, namingScheme) } func BuildMachineCatalogResourceWorkgroup(t *testing.T, machineResource string) string { @@ -2246,12 +2251,13 @@ func BuildMachineCatalogResourceWorkgroup(t *testing.T, machineResource string) resource_group := os.Getenv("TEST_MC_IMAGE_RESOUCE_GROUP") storage_account := os.Getenv("TEST_MC_IMAGE_STORAGE_ACCOUNT") container := os.Getenv("TEST_MC_IMAGE_CONTAINER") + namingScheme := "vda-##-WG" if machineResource == machinecatalog_testResources_workgroup_updated { master_image = os.Getenv("TEST_MC_MASTER_IMAGE_UPDATED") } - return fmt.Sprintf(machineResource, "-WG", name, service_offering, resource_group, storage_account, container, master_image) + return fmt.Sprintf(machineResource, "-WG", name, service_offering, resource_group, storage_account, container, master_image, namingScheme) } func BuildMachineCatalogResourceGCP(t *testing.T, machineResource string) string { @@ -2260,7 +2266,8 @@ func BuildMachineCatalogResourceGCP(t *testing.T, machineResource string) string service_account := os.Getenv("TEST_MC_SERVICE_ACCOUNT_GCP") service_account_pass := os.Getenv("TEST_MC_SERVICE_ACCOUNT_PASS_GCP") storage_type := os.Getenv("TEST_MC_STORAGE_TYPE_GCP") - availability_zones := os.Getenv("TEST_MC_AVAILABILITY_ZONES_GCP") + availability_zones_list := strings.Split(os.Getenv("TEST_MC_AVAILABILITY_ZONES_GCP"), ",") + availability_zones := "[\"" + strings.Join(availability_zones_list, "\",\"") + "\"]" // ["1","3"] machine_profile := os.Getenv("TEST_MC_MACHINE_PROFILE_GCP") master_image := os.Getenv("TEST_MC_MASTER_IMAGE_GCP") machine_snapshot := os.Getenv("TEST_MC_MACHINE_SNAPSHOT_GCP") diff --git a/internal/test/policy_set_resource_test.go b/internal/test/policy_set_resource_test.go index 42e0167..9809f46 100644 --- a/internal/test/policy_set_resource_test.go +++ b/internal/test/policy_set_resource_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test @@ -15,7 +15,6 @@ var ( resource "citrix_policy_set" "testPolicySet" { name = "%s-1" description = "Test policy set description" - scopes = [ "All" ] type = "DeliveryGroupPolicies" policies = [ { @@ -34,13 +33,9 @@ resource "citrix_policy_set" "testPolicySet" { use_default = false } ] - policy_filters = [ + delivery_group_filters = [ { - type = "DesktopGroup" - data = { - server = "%s" - uuid = citrix_delivery_group.testDeliveryGroup.id - } + delivery_group_id = citrix_delivery_group.testDeliveryGroup.id enabled = true allowed = true }, @@ -57,7 +52,6 @@ resource "citrix_policy_set" "testPolicySet" { use_default = false }, ] - policy_filters = [] } ] } @@ -67,7 +61,6 @@ resource "citrix_policy_set" "testPolicySet" { resource "citrix_policy_set" "testPolicySet" { name = "%s-2" description = "Test policy set description" - scopes = [ "All" ] type = "DeliveryGroupPolicies" policies = [ { @@ -81,7 +74,6 @@ resource "citrix_policy_set" "testPolicySet" { use_default = false }, ] - policy_filters = [] }, { name = "first-test-policy" @@ -94,13 +86,9 @@ resource "citrix_policy_set" "testPolicySet" { use_default = false }, ] - policy_filters = [ + delivery_group_filters = [ { - type = "DesktopGroup" - data = { - server = "%s" - uuid = citrix_delivery_group.testDeliveryGroup.id - } + delivery_group_id = citrix_delivery_group.testDeliveryGroup.id enabled = true allowed = true }, @@ -114,7 +102,6 @@ resource "citrix_policy_set" "testPolicySet" { resource "citrix_policy_set" "testPolicySet" { name = "%s-3" description = "Test policy set description updated" - scopes = [ "All" ] type = "DeliveryGroupPolicies" policies = [ { @@ -128,13 +115,9 @@ resource "citrix_policy_set" "testPolicySet" { use_default = false }, ] - policy_filters = [ + delivery_group_filters = [ { - type = "DesktopGroup" - data = { - server = "%s" - uuid = citrix_delivery_group.testDeliveryGroup.id - } + delivery_group_id = citrix_delivery_group.testDeliveryGroup.id enabled = true allowed = true }, @@ -149,17 +132,12 @@ func TestPolicySetResourcePreCheck(t *testing.T) { if v := os.Getenv("TEST_POLICY_SET_NAME"); v == "" { t.Fatal("TEST_POLICY_SET_NAME must be set for acceptance tests") } - - if v := os.Getenv("CITRIX_DDC_HOST_NAME"); v == "" { - t.Fatal("CITRIX_DDC_HOST_NAME must be set for acceptance tests") - } } func BuildPolicySetResource(t *testing.T, policySet string) string { policySetName := os.Getenv("TEST_POLICY_SET_NAME") - ddcServerHostName := os.Getenv("CITRIX_DDC_HOST_NAME") - return fmt.Sprintf(policySet, policySetName, ddcServerHostName) + return fmt.Sprintf(policySet, policySetName) } func TestPolicySetResource(t *testing.T) { @@ -193,9 +171,7 @@ func TestPolicySetResource(t *testing.T) { // Verify type of the policy set resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "type", "DeliveryGroupPolicies"), // Verify the number of scopes of the policy set - resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.#", "1"), - // Verify the scopes of the policy set - resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.0", "All"), + resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.#", "0"), // Verify the number of policies in the policy set resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "policies.#", "2"), // Verify name of the first policy in the policy set @@ -229,9 +205,7 @@ func TestPolicySetResource(t *testing.T) { // Verify type of the policy set resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "type", "DeliveryGroupPolicies"), // Verify the number of scopes of the policy set - resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.#", "1"), - // Verify the scopes of the policy set - resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.0", "All"), + resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.#", "0"), // Verify the number of policies in the policy set resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "policies.#", "2"), // Verify name of the first policy in the policy set @@ -268,9 +242,7 @@ func TestPolicySetResource(t *testing.T) { // Verify type of the policy set resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "type", "DeliveryGroupPolicies"), // Verify the number of scopes of the policy set - resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.#", "1"), - // Verify the scopes of the policy set - resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.0", "All"), + resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "scopes.#", "0"), // Verify the number of policies in the policy set resource.TestCheckResourceAttr("citrix_policy_set.testPolicySet", "policies.#", "1"), // Verify name of the second policy in the policy set diff --git a/internal/test/provider_test.go b/internal/test/provider_test.go index d98fc6c..1406401 100644 --- a/internal/test/provider_test.go +++ b/internal/test/provider_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test diff --git a/internal/test/resource_locations_resource_test.go b/internal/test/resource_locations_resource_test.go index 13004ba..8812331 100644 --- a/internal/test/resource_locations_resource_test.go +++ b/internal/test/resource_locations_resource_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test diff --git a/internal/test/stf_deployment_resource_test.go b/internal/test/stf_deployment_resource_test.go index 0b633fc..4ab38b4 100644 --- a/internal/test/stf_deployment_resource_test.go +++ b/internal/test/stf_deployment_resource_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test diff --git a/internal/test/stf_store_service_resource_test.go b/internal/test/stf_store_service_resource_test.go index 94447fa..a1aca1d 100644 --- a/internal/test/stf_store_service_resource_test.go +++ b/internal/test/stf_store_service_resource_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test diff --git a/internal/test/stf_user_farm_mapping_resource_test.go b/internal/test/stf_user_farm_mapping_resource_test.go new file mode 100644 index 0000000..52b7866 --- /dev/null +++ b/internal/test/stf_user_farm_mapping_resource_test.go @@ -0,0 +1,233 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestSTFUserFarmMappingResourcePreCheck(t *testing.T) { + if v := os.Getenv("TEST_STF_USER_FARM_MAPPING_NAME"); v == "" { + t.Fatal("TEST_STF_USER_FARM_MAPPING_NAME must be set for acceptance tests") + } + + if v := os.Getenv("TEST_STF_USER_FARM_MAPPING_USER1_SID"); v == "" { + t.Fatal("TEST_STF_USER_FARM_MAPPING_USER1_SID must be set for acceptance tests") + } + + if v := os.Getenv("TEST_STF_USER_FARM_MAPPING_USER2_SID"); v == "" { + t.Fatal("TEST_STF_USER_FARM_MAPPING_USER2_SID must be set for acceptance tests") + } + + if v := os.Getenv("TEST_STF_PRIMARY_FARM_NAME"); v == "" { + t.Fatal("TEST_STF_PRIMARY_FARM_NAME must be set for acceptance tests") + } + + if v := os.Getenv("TEST_STF_SECONDARY_FARM_NAME"); v == "" { + t.Fatal("TEST_STF_SECONDARY_FARM_NAME must be set for acceptance tests") + } + + if v := os.Getenv("TEST_STF_BACKUP_FARM_NAME"); v == "" { + t.Fatal("TEST_STF_BACKUP_FARM_NAME must be set for acceptance tests") + } +} + +func TestSTFUserFarmMappingResource(t *testing.T) { + virtualPath := os.Getenv("TEST_STF_Store_Virtual_Path") + name := os.Getenv("TEST_STF_USER_FARM_MAPPING_NAME") + user1Sid := os.Getenv("TEST_STF_USER_FARM_MAPPING_USER1_SID") + user2Sid := os.Getenv("TEST_STF_USER_FARM_MAPPING_USER2_SID") + primaryFarmName := os.Getenv("TEST_STF_PRIMARY_FARM_NAME") + secondaryFarmName := os.Getenv("TEST_STF_SECONDARY_FARM_NAME") + backupFarmName := os.Getenv("TEST_STF_BACKUP_FARM_NAME") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: func() { + TestProviderPreCheck(t) + TestSTFStoreServicePreCheck(t) + TestSTFUserFarmMappingResourcePreCheck(t) + }, + Steps: []resource.TestStep{ + + // Create and Read testing + { + Config: BuildSTFUserFarmMappingResource(t, testSTFUserFarmMappingResources), + + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "name", name), + // Verify store_virtual_path of STF UserFarmMapping Resource + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "store_virtual_path", virtualPath), + // Verify group_members for STF UserFarmMapping Resource + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "group_members.#", "2"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "group_members.0.group_name", "TestGroup1"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "group_members.0.account_sid", user1Sid), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "group_members.1.group_name", "TestGroup2"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "group_members.1.account_sid", user2Sid), + // Verify equivalent_farm_sets of STF UserFarmMapping Resource + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.#", "2"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.name", "TestEFS1"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.aggregation_group_name", "EFS1Group"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.load_balance_mode", "LoadBalanced"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.farms_are_identical", "false"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.primary_farms.#", "1"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.primary_farms.0", primaryFarmName), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.backup_farms.#", "0"), + + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.name", "TestEFS2"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.aggregation_group_name", "EFS2Group"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.load_balance_mode", "Failover"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.farms_are_identical", "true"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.primary_farms.#", "1"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.primary_farms.0", secondaryFarmName), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.backup_farms.#", "1"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.backup_farms.0", backupFarmName), + ), + }, + // ImportState testing + { + ResourceName: "citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "name", + ImportStateIdFunc: generateImportStateId_STFUserFarmMappingResource, + ImportStateVerifyIgnore: []string{"last_updated"}, + }, + + // Update testing for STF WebReceiver Service + { + Config: BuildSTFUserFarmMappingResource(t, testSTFUserFarmMappingResources_updated), + + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "name", fmt.Sprintf("%s-updated", name)), + // Verify store_virtual_path of STF UserFarmMapping Resource + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "store_virtual_path", virtualPath), + // Verify group_members for STF UserFarmMapping Resource + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "group_members.#", "2"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "group_members.0.group_name", "TestGroup2"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "group_members.0.account_sid", user1Sid), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "group_members.1.group_name", "TestGroup1"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "group_members.1.account_sid", user2Sid), + // Verify equivalent_farm_sets of STF UserFarmMapping Resource + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.#", "2"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.name", "TestEFS2"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.aggregation_group_name", "EFS2Group"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.load_balance_mode", "Failover"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.farms_are_identical", "true"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.primary_farms.#", "1"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.primary_farms.0", primaryFarmName), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.0.backup_farms.#", "0"), + + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.name", "TestEFS1"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.aggregation_group_name", "EFS1Group"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.load_balance_mode", "LoadBalanced"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.farms_are_identical", "false"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.primary_farms.#", "1"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.primary_farms.0", secondaryFarmName), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.backup_farms.#", "1"), + resource.TestCheckResourceAttr("citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource", "equivalent_farm_sets.1.backup_farms.0", backupFarmName), + ), + }, + }, + }) +} + +func BuildSTFUserFarmMappingResource(t *testing.T, userFarmMappingResource string) string { + name := os.Getenv("TEST_STF_USER_FARM_MAPPING_NAME") + user1Sid := os.Getenv("TEST_STF_USER_FARM_MAPPING_USER1_SID") + user2Sid := os.Getenv("TEST_STF_USER_FARM_MAPPING_USER2_SID") + primaryFarmName := os.Getenv("TEST_STF_PRIMARY_FARM_NAME") + secondaryFarmName := os.Getenv("TEST_STF_SECONDARY_FARM_NAME") + backupFarmName := os.Getenv("TEST_STF_BACKUP_FARM_NAME") + + return BuildSTFStoreServiceResource(t, testSTFStoreServiceResources) + fmt.Sprintf(userFarmMappingResource, name, user1Sid, user2Sid, primaryFarmName, secondaryFarmName, backupFarmName) +} + +func generateImportStateId_STFUserFarmMappingResource(state *terraform.State) (string, error) { + resourceName := "citrix_stf_user_farm_mapping.testSTFUserFarmMappingResource" + var rawState map[string]string + for _, m := range state.Modules { + if len(m.Resources) > 0 { + if v, ok := m.Resources[resourceName]; ok { + rawState = v.Primary.Attributes + } + } + } + + return fmt.Sprintf("%s,%s", rawState["store_virtual_path"], rawState["name"]), nil +} + +var ( + testSTFUserFarmMappingResources = ` + resource "citrix_stf_user_farm_mapping" "testSTFUserFarmMappingResource" { + name = "%s" + store_virtual_path = citrix_stf_store_service.testSTFStoreService.virtual_path + group_members = [ + { + group_name = "TestGroup1" + account_sid = "%s" + }, + { + group_name = "TestGroup2" + account_sid = "%s" + } + ] + equivalent_farm_sets = [ + { + name = "TestEFS1", + aggregation_group_name = "EFS1Group" + primary_farms = ["%s"] + load_balance_mode = "LoadBalanced" + farms_are_identical = false + }, + { + name = "TestEFS2", + aggregation_group_name = "EFS2Group" + primary_farms = ["%s"] + backup_farms = ["%s"] + load_balance_mode = "Failover" + farms_are_identical = true + } + ] + } + ` + + testSTFUserFarmMappingResources_updated = ` + resource "citrix_stf_user_farm_mapping" "testSTFUserFarmMappingResource" { + name = "%s-updated" + store_virtual_path = citrix_stf_store_service.testSTFStoreService.virtual_path + group_members = [ + { + group_name = "TestGroup2" + account_sid = "%s" + }, + { + group_name = "TestGroup1" + account_sid = "%s" + } + ] + equivalent_farm_sets = [ + { + name = "TestEFS2", + aggregation_group_name = "EFS2Group" + primary_farms = ["%s"] + load_balance_mode = "Failover" + farms_are_identical = true + }, + { + name = "TestEFS1", + aggregation_group_name = "EFS1Group" + primary_farms = ["%s"] + backup_farms = ["%s"] + load_balance_mode = "LoadBalanced" + farms_are_identical = false + } + ] + } + ` +) diff --git a/internal/test/stf_webreceiver_service_resource_test.go b/internal/test/stf_webreceiver_service_resource_test.go index 848cc77..a8be352 100644 --- a/internal/test/stf_webreceiver_service_resource_test.go +++ b/internal/test/stf_webreceiver_service_resource_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test @@ -11,8 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" ) -// testAccPreCheck validates the necessary test API keys exist -// in the testing environment +// TestSTFWebReceiverServicePreCheck validates the necessary test API keys exist in the testing environment func TestSTFWebReceiverServicePreCheck(t *testing.T) { if v := os.Getenv("TEST_STF_SITE_ID"); v == "" { diff --git a/internal/test/zone_resource_test.go b/internal/test/zone_resource_test.go index fac068b..6eb7b8c 100644 --- a/internal/test/zone_resource_test.go +++ b/internal/test/zone_resource_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package test diff --git a/internal/util/common.go b/internal/util/common.go index 49392e2..427fe38 100644 --- a/internal/util/common.go +++ b/internal/util/common.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package util @@ -10,18 +10,22 @@ import ( "io/ioutil" "net/http" "reflect" + "regexp" "runtime" "runtime/debug" + "slices" "strconv" "strings" citrixorchestration "github.com/citrix/citrix-daas-rest-go/citrixorchestration" + citrixstorefront "github.com/citrix/citrix-daas-rest-go/citrixstorefront/models" citrixdaasclient "github.com/citrix/citrix-daas-rest-go/client" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -36,9 +40,14 @@ const SamRegex string = `^[a-zA-Z][a-zA-Z0-9\- ]{0,61}[a-zA-Z0-9]\\\w[\w\.\- ]+$ // UPN const UpnRegex string = `^[^@]+@\b(([a-zA-Z0-9-_]){1,63}\.)+[a-zA-Z]{2,63}$` +const SamAndUpnRegex string = `^[a-zA-Z][a-zA-Z0-9\- ]{0,61}[a-zA-Z0-9]\\\w[\w\.\- ]+$|^[^@]+@\b(([a-zA-Z0-9-_]){1,63}\.)+[a-zA-Z]{2,63}$` + // GUID const GuidRegex string = `^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$` +// GUID +const StoreFrontServerIdRegex string = `^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{13}[}]?$` + // IPv4 const IPv4Regex string = `^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$` @@ -63,9 +72,18 @@ const SslThumbprintRegex string = `^([0-9a-fA-F]{40}|[0-9a-fA-F]{64})$` // AWS EC2 Instance Type const AwsEc2InstanceTypeRegex string = `^[a-z0-9]{1,15}\.[a-z0-9]{1,15}$` +// Active Directory Sid +const ActiveDirectorySidRegex string = `^S-1-[0-59]-\d{2}-\d{8,10}-\d{8,10}-\d{8,10}-[1-9]\d{3}$` + // NOT_EXIST error code const NOT_EXIST string = "NOT_EXIST" +// ID of the All Scope +const AllScopeId string = "00000000-0000-0000-0000-000000000000" + +// ID of the Citrix Managed Users Scope +const CtxManagedScopeId string = "f71a1148-7030-467a-a6d3-4a6bcf6a6532" + // Resource Types const ImageVersionResourceType string = "ImageVersion" const RegionResourceType string = "Region" @@ -101,6 +119,25 @@ type NameValueStringPairModel struct { Value types.String `tfsdk:"value"` } +func (r NameValueStringPairModel) GetSchema() schema.NestedAttributeObject { + return schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "Metadata name.", + Required: true, + }, + "value": schema.StringAttribute{ + Description: "Metadata value.", + Required: true, + }, + }, + } +} + +func (NameValueStringPairModel) GetAttributes() map[string]schema.Attribute { + return NameValueStringPairModel{}.GetSchema().Attributes +} + // // Helper function to parse an array of name value pairs in terraform model to an array of name value pairs in client model // @@ -194,83 +231,58 @@ func ReadClientError(err error) string { } // -// Helper function to convert array of terraform strings to array of golang primitive strings -// -// Array of terraform stringsArray of golang primitive strings -// Array of golang primitive strings -func ConvertBaseStringArrayToPrimitiveStringArray(v []types.String) []string { - res := []string{} - for _, stringVal := range v { - res = append(res, stringVal.ValueString()) - } - - return res -} - -// -// Helper function to convert array of golang primitive strings to array of terraform strings +// Helper function to serialize any struct value into a string // -// Array of golang primitive strings -// Array of terraform strings -func ConvertPrimitiveStringArrayToBaseStringArray(v []string) []types.String { - res := []types.String{} - for _, stringVal := range v { - res = append(res, types.StringValue(stringVal)) +// Input struct value +// Serialized string value of the struct +func ConvertToString(model any) (string, error) { + body, err := json.Marshal(model) + if err != nil { + return "", err } - return res + return string(body), nil } // -// Helper function to convert array of golang primitive interface to array of terraform strings +// Extract Ids from a list of objects // -// Array of golang primitive interface -// Array of terraform strings -func ConvertPrimitiveInterfaceArrayToBaseStringArray(v []interface{}) ([]types.String, string) { - res := []types.String{} - for _, val := range v { - switch stringVal := val.(type) { - case string: - res = append(res, types.StringValue(stringVal)) - default: - return nil, "At this time, only string values are supported in arrays." +// Input list of objects +// List of Ids extracted from input list +func GetIdsForOrchestrationObjects[objType any](slice []objType) []string { + val := reflect.ValueOf(slice) + ids := []string{} + + for i := 0; i < val.Len(); i++ { + elem := val.Index(i) + if elem.Kind() == reflect.Ptr { + elem = elem.Elem() + } + idField := elem.FieldByName("Id") + if !idField.IsValid() || idField.Kind() != reflect.String { + continue } + ids = append(ids, idField.String()) } - return res, "" + return ids } // -// Helper function to convert terraform bool value to string +// Extract Ids from a list of scope objects // -// Boolean value in terraform bool -// Boolean value in string -func TypeBoolToString(from types.Bool) string { - return strconv.FormatBool(from.ValueBool()) -} - -// -// Helper function to convert string to terraform boolean value -// -// Boolean value in string -// Boolean value in terraform types.Bool -func StringToTypeBool(from string) types.Bool { - result, _ := strconv.ParseBool(from) - return types.BoolValue(result) -} - -// -// Helper function to serialize any struct value into a string -// -// Input struct value -// Serialized string value of the struct -func ConvertToString(model any) (string, error) { - body, err := json.Marshal(model) - if err != nil { - return "", err +// Input list of objects +// List of Ids extracted from input list +func GetIdsForScopeObjects[objType any](slice []objType) []string { + ids := GetIdsForOrchestrationObjects(slice) + filteredIds := []string{} + + for _, id := range ids { + if id != AllScopeId && id != CtxManagedScopeId { + filteredIds = append(filteredIds, id) + } } - - return string(body), nil + return filteredIds } // @@ -408,6 +420,47 @@ func ProcessAsyncJobResponse(ctx context.Context, client *citrixdaasclient.Citri return nil } +// Represents a list item which supports being refreshed from a client model +type RefreshableListItemWithAttributes[clientType any] interface { + // Gets the key to compare the item with the client model + GetKey() string + + // Refreshes the item with the client model and returns the updated item + RefreshListItem(context.Context, *diag.Diagnostics, clientType) ModelWithAttributes + + // Has to implement the ModelWithAttributes interface for conversion back to a Terraform model + ModelWithAttributes +} + +// These functions are used by RefreshListProperties +func GetOrchestrationRebootScheduleKey(r citrixorchestration.RebootScheduleResponseModel) string { + return r.GetName() +} + +func GetOrchestrationDesktopKey(r citrixorchestration.DesktopResponseModel) string { + return r.GetPublishedName() +} + +func GetOrchestrationHypervisorStorageKey(remote citrixorchestration.HypervisorStorageResourceResponseModel) string { + return remote.GetName() +} + +func GetOrchestrationNetworkMappingKey(remote citrixorchestration.NetworkMapResponseModel) string { + return remote.GetDeviceId() +} + +func GetOrchestrationRemotePcOuKey(remote citrixorchestration.RemotePCEnrollmentScopeResponseModel) string { + return remote.GetOU() +} + +func GetSTFGroupMemberKey(remote citrixstorefront.STFGroupMemberResponseModel) string { + return *remote.GroupName.Get() +} + +func GetSTFFarmSetKey(remote citrixstorefront.STFFarmSetResponseModel) string { + return *remote.Name.Get() +} + // // Helper function for calculating the new state of a list of nested attribute, while // keeping the order of the elements in the array intact, and adds missing elements @@ -415,12 +468,16 @@ func ProcessAsyncJobResponse(ctx context.Context, client *citrixdaasclient.Citri // Can be used for refreshing all list nested attributes. // // State values in Terraform model -// Name of the identifier field in Terraform model // Remote values in client model -// Name of the identifier field in client model -// Name of the refresh properties function defined in the terraform model -// Array in Terraform model for new state -func RefreshListProperties[tfType any, clientType any](state []tfType, tfId string, remote []clientType, clientId string, refreshFunc string) []tfType { +// Function to get the Id from the client model +// Terraform list for new state +func RefreshListValueProperties[tfType RefreshableListItemWithAttributes[clientType], clientType any](ctx context.Context, diagnostics *diag.Diagnostics, state types.List, remote []clientType, getClientKey func(clientType) string) types.List { + unwrappedList := ObjectListToTypedArray[tfType](ctx, diagnostics, state) + refreshedList := refreshListProperties[tfType, clientType](ctx, diagnostics, unwrappedList, remote, getClientKey) + return TypedArrayToObjectList[tfType](ctx, diagnostics, refreshedList) +} + +func refreshListProperties[tfType RefreshableListItemWithAttributes[clientType], clientType any](ctx context.Context, diagnostics *diag.Diagnostics, state []tfType, remote []clientType, getClientKey func(clientType) string) []tfType { if len(remote) == 0 { return nil } @@ -430,56 +487,57 @@ func RefreshListProperties[tfType any, clientType any](state []tfType, tfId stri } stateItems := map[string]int{} - for index, item := range state { - value := reflect.ValueOf(&item).Elem() - id := value.FieldByName(tfId).Interface().(basetypes.StringValue) - stateItems[id.ValueString()] = index + for index, tfItem := range state { + stateItems[tfItem.GetKey()] = index } - var tfItem tfType - tfStruct := reflect.TypeOf(tfItem) - - method, _ := tfStruct.MethodByName(refreshFunc) newState := state - var id string - for _, item := range remote { - value := reflect.ValueOf(&item).Elem() - valueType := value.FieldByName(clientId).Type() - if valueType == reflect.TypeOf(citrixorchestration.NullableString{}) { - idNullable := value.FieldByName(clientId).Interface().(citrixorchestration.NullableString) - if idNullable.IsSet() { - id = *idNullable.Get() - } - } else { - id = value.FieldByName(clientId).Interface().(string) - } - index, exists := stateItems[id] - requestValue := reflect.ValueOf(item) + for _, clientItem := range remote { + clientKey := getClientKey(clientItem) + index, exists := stateItems[clientKey] if exists { - newStateItemReflectValue := method.Func.Call([]reflect.Value{reflect.ValueOf(state[index]), requestValue})[0] - newState[index] = newStateItemReflectValue.Interface().(tfType) + tfItem := state[index] + newState[index] = tfItem.RefreshListItem(ctx, diagnostics, clientItem).(tfType) } else { - tfStructItem := reflect.New(tfStruct).Elem() - newStateItemReflectValue := method.Func.Call([]reflect.Value{tfStructItem, requestValue})[0] - newState = append(newState, newStateItemReflectValue.Interface().(tfType)) + var tfStructItem tfType + if attributeMap, err := AttributeMapFromObject(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) + newState = append(newState, newStateItem) + } else { + diagnostics.AddWarning("Error when creating empty "+reflect.TypeOf(tfStructItem).String(), err.Error()) + } } - stateItems[id] = -1 // Mark as visited. The ones not visited should be removed. + stateItems[clientKey] = -1 // Mark as visited. The ones not visited should be removed. } result := []tfType{} - for _, item := range newState { - value := reflect.ValueOf(&item).Elem() - id := value.FieldByName(tfId).Interface().(basetypes.StringValue) - - if stateItems[id.ValueString()] == -1 { - result = append(result, item) // if visited, include. Not visited ones will not be included. + for _, tfItem := range newState { + if stateItems[tfItem.GetKey()] == -1 { + result = append(result, tfItem) // if visited, include. Not visited ones will not be included. } } return result } +// +// Helper function for calculating the new state of a list of strings, while +// keeping the order of the elements in the array intact, and adds missing elements +// from remote to state. +// Can be used for refreshing all list of strings. +// +// State values in Terraform model +// Remote values in client model +// Array in Terraform model for new state +func RefreshListValues(ctx context.Context, diagnostics *diag.Diagnostics, state types.List, remote []string) types.List { + unwrappedList := StringListToStringArray(ctx, diagnostics, state) + refreshedList := RefreshList(unwrappedList, remote) + return StringArrayToStringList(ctx, diagnostics, refreshedList) +} + // // Helper function for calculating the new state of a list of strings, while // keeping the order of the elements in the array intact, and adds missing elements @@ -488,24 +546,24 @@ func RefreshListProperties[tfType any, clientType any](state []tfType, tfId stri // // List of values in state // List of values in remote -func RefreshList(state []types.String, remote []string) []types.String { +func RefreshList(state []string, remote []string) []string { stateItems := map[string]bool{} for _, item := range state { - stateItems[strings.ToLower(item.ValueString())] = false // not visited + stateItems[strings.ToLower(item)] = false // not visited } for _, item := range remote { itemInLowerCase := strings.ToLower(item) _, exists := stateItems[itemInLowerCase] if !exists { - state = append(state, types.StringValue(item)) + state = append(state, item) } stateItems[itemInLowerCase] = true } - result := []types.String{} + result := []string{} for _, item := range state { - if stateItems[strings.ToLower(item.ValueString())] { + if stateItems[strings.ToLower(item)] { result = append(result, item) } } @@ -608,3 +666,197 @@ func CheckProductVersion(client *citrixdaasclient.CitrixDaasClient, diagnostic * return true } + +// +// Helper function to refresh user list. +// +func RefreshUsersList(ctx context.Context, diags *diag.Diagnostics, usersSet types.Set, usersInRemote []citrixorchestration.IdentityUserResponseModel) types.Set { + samNamesMap := map[string]int{} + upnMap := map[string]int{} + + for index, userInRemote := range usersInRemote { + userSamName := userInRemote.GetSamName() + userPrincipalName := userInRemote.GetPrincipalName() + if userSamName != "" { + samNamesMap[strings.ToLower(userSamName)] = index + } + if userPrincipalName != "" { + upnMap[strings.ToLower(userPrincipalName)] = index + } + } + + res := []string{} + users := StringSetToStringArray(ctx, diags, usersSet) + for _, user := range users { + samRegex, _ := regexp.Compile(SamRegex) + if samRegex.MatchString(user) { + index, exists := samNamesMap[strings.ToLower(user)] + if !exists { + continue + } + res = append(res, user) + samNamesMap[strings.ToLower(user)] = -1 + if index != -1 { + userPrincipalName := usersInRemote[index].GetPrincipalName() + _, exists = upnMap[strings.ToLower(userPrincipalName)] + if exists { + upnMap[strings.ToLower(userPrincipalName)] = -1 + } + } + + continue + } + + upnRegex, _ := regexp.Compile(UpnRegex) + if upnRegex.MatchString(user) { + index, exists := upnMap[strings.ToLower(user)] + if !exists { + continue + } + res = append(res, user) + upnMap[strings.ToLower(user)] = -1 + if index != -1 { + samName := usersInRemote[index].GetSamName() + _, exists = samNamesMap[strings.ToLower(samName)] + if exists { + samNamesMap[strings.ToLower(samName)] = -1 + } + } + } + } + + for samName, index := range samNamesMap { + if index != -1 { // Users that are only in remote + res = append(res, samName) + } + } + + return StringArrayToStringSet(ctx, diags, res) +} + +// +// Helper function to fetch scope ids from scope names +// +func FetchScopeIdsByNames(ctx context.Context, diagnostics diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, scopeNames []string) ([]string, error) { + getAdminScopesRequest := client.ApiClient.AdminAPIsDAAS.AdminGetAdminScopes(ctx) + // get all the scopes + getScopesResponse, httpResp, err := citrixdaasclient.AddRequestData(getAdminScopesRequest, client).Execute() + if err != nil || getScopesResponse == nil { + diagnostics.AddError( + "Error fetch scope ids from names", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+ReadClientError(err), + ) + return nil, err + } + + scopeNameIdMap := map[string]string{} + for _, scope := range getScopesResponse.Items { + scopeNameIdMap[scope.GetName()] = scope.GetId() + } + + scopeIds := []string{} + for _, scopeName := range scopeNames { + scopeIds = append(scopeIds, scopeNameIdMap[scopeName]) + } + + return scopeIds, nil +} + +// +// Helper function to fetch scope names from scope ids +// +func FetchScopeNamesByIds(ctx context.Context, diagnostics diag.Diagnostics, client *citrixdaasclient.CitrixDaasClient, scopeIds []string) ([]string, error) { + getAdminScopesRequest := client.ApiClient.AdminAPIsDAAS.AdminGetAdminScopes(ctx) + // Create new Policy Set + getScopesResponse, httpResp, err := citrixdaasclient.AddRequestData(getAdminScopesRequest, client).Execute() + if err != nil || getScopesResponse == nil { + diagnostics.AddError( + "Error fetch scope names from ids", + "TransactionId: "+citrixdaasclient.GetTransactionIdFromHttpResponse(httpResp)+ + "\nError message: "+ReadClientError(err), + ) + return nil, err + } + + scopeIdNameMap := map[string]types.String{} + for _, scope := range getScopesResponse.Items { + scopeIdNameMap[scope.GetId()] = types.StringValue(scope.GetName()) + } + + scopeNames := []string{} + for _, scopeId := range scopeIds { + scopeNames = append(scopeNames, scopeIdNameMap[scopeId].ValueString()) + } + + return scopeNames, nil +} + +func GetUsersUsingIdentity(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, users []string) ([]citrixorchestration.IdentityUserResponseModel, *http.Response, error) { + allUsersFromIdentity := []citrixorchestration.IdentityUserResponseModel{} + + getIncludedUsersRequest := client.ApiClient.IdentityAPIsDAAS.IdentityGetUsers(ctx) + getIncludedUsersRequest = getIncludedUsersRequest.User(users).UserType(citrixorchestration.IDENTITYUSERTYPE_ALL) + adUsers, httpResp, err := citrixdaasclient.AddRequestData(getIncludedUsersRequest, client).Execute() + + if err != nil { + return allUsersFromIdentity, httpResp, err + } + + allUsersFromIdentity = append(allUsersFromIdentity, adUsers.GetItems()...) + + if len(allUsersFromIdentity) < len(users) { + getIncludedUsersRequest = getIncludedUsersRequest.User(users).UserType(citrixorchestration.IDENTITYUSERTYPE_ALL).Provider(citrixorchestration.IDENTITYPROVIDERTYPE_ALL) + azureAdUsers, httpResp, err := citrixdaasclient.AddRequestData(getIncludedUsersRequest, client).Execute() + + if err != nil { + return allUsersFromIdentity, httpResp, err + } + + allUsersFromIdentity = append(allUsersFromIdentity, azureAdUsers.GetItems()...) + } + + err = VerifyIdentityUserListCompleteness(users, allUsersFromIdentity) + + if err != nil { + return allUsersFromIdentity, httpResp, err + } + + return allUsersFromIdentity, httpResp, nil +} + +func GetUserIdsUsingIdentity(ctx context.Context, client *citrixdaasclient.CitrixDaasClient, users []string) ([]string, *http.Response, error) { + userIds := []string{} + allUsersFromIdentity, httpResp, err := GetUsersUsingIdentity(ctx, client, users) + if err != nil { + return userIds, httpResp, err + } + + for _, user := range allUsersFromIdentity { + id := user.GetOid() // Azure AD users + if id == "" { + id = user.GetSid() // For AD users, OID is empty, use SID + } + userIds = append(userIds, id) + } + + return userIds, httpResp, nil +} + +func VerifyIdentityUserListCompleteness(inputUserNames []string, remoteUsers []citrixorchestration.IdentityUserResponseModel) error { + missingUsers := []string{} + for _, includedUser := range inputUserNames { + userIndex := slices.IndexFunc(remoteUsers, func(i citrixorchestration.IdentityUserResponseModel) bool { + return strings.EqualFold(includedUser, i.GetSamName()) || strings.EqualFold(includedUser, i.GetPrincipalName()) + }) + if userIndex == -1 { + missingUsers = append(missingUsers, includedUser) + } + } + + if len(missingUsers) > 0 { + return fmt.Errorf("The following users could not be found: " + strings.Join(missingUsers, ", ")) + } + + return nil +} diff --git a/internal/util/resource.go b/internal/util/resource.go index 6d2f947..47af6f1 100644 --- a/internal/util/resource.go +++ b/internal/util/resource.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package util diff --git a/internal/util/types.go b/internal/util/types.go new file mode 100644 index 0000000..9f1aae5 --- /dev/null +++ b/internal/util/types.go @@ -0,0 +1,456 @@ +// Copyright © 2024. Citrix Systems, Inc. + +package util + +import ( + "context" + "fmt" + "reflect" + "strconv" + "sync" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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 +} + +// Store the attribute map for each model type so we don't have to regenerate it every time +var attributeMapCache sync.Map + +// Store the default object for each object type so we don't have to regenerate it every time +var defaultObjectCache sync.Map + +// +// Helper function to convert a model to a map of attribute types. Used when converting back to a types.Object +// +// Model to convert, must implement the ModelWithSchema interface +// Map of attribute types +func AttributeMapFromObject(m ModelWithAttributes) (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()) + if err != nil { + return nil, err + } + attributeMapCache.Store(keyName, attributeMap) + return attributeMap, nil +} + +// +// Helper function to convert a 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) { + var attributeTypes = map[string]attr.Type{} + for attributeName, attribute := range s { + attrib, err := attributeToTerraformType(attribute) + if err != nil { + return nil, err + } + attributeTypes[attributeName] = attrib + } + 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) { + switch attrib := attribute.(type) { + case schema.StringAttribute: + return types.StringType, nil + case schema.BoolAttribute: + return types.BoolType, nil + case schema.NumberAttribute: + return types.NumberType, nil + case schema.Int64Attribute: + return types.Int64Type, nil + case schema.Float64Attribute: + return types.Float64Type, nil + case schema.ListAttribute: + return types.ListType{ElemType: attrib.ElementType}, nil + case schema.ListNestedAttribute: + // list of object, recurse + nestedAttributes, err := attributeMapFromSchema(attrib.NestedObject.Attributes) + if err != nil { + return nil, err + } + return types.ListType{ElemType: types.ObjectType{AttrTypes: nestedAttributes}}, nil + case schema.ObjectAttribute: + return attrib.GetType(), nil + case schema.SingleNestedAttribute: + // object, recurse + nestedAttributes, err := attributeMapFromSchema(attrib.Attributes) + if err != nil { + return nil, err + } + return types.ObjectType{AttrTypes: nestedAttributes}, nil + case schema.SetAttribute: + return types.SetType{ElemType: attrib.ElementType}, nil + case schema.SetNestedAttribute: + // set of object, recurse + nestedAttributes, err := attributeMapFromSchema(attrib.NestedObject.Attributes) + if err != nil { + return nil, err + } + return types.SetType{ElemType: types.ObjectType{AttrTypes: nestedAttributes}}, nil + case schema.MapAttribute: + return types.MapType{ElemType: attrib.ElementType}, nil + case schema.MapNestedAttribute: + // map of object, recurse + nestedAttributes, err := attributeMapFromSchema(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) +} + +// Helper function to get and cache the default object including populating nested types.List and types.Object so they aren't nil +func defaultObjectFromObjectValue[objTyp any](ctx context.Context, v types.Object) objTyp { + var temp objTyp + keyName := reflect.TypeOf(temp).String() + if defaultObject, ok := defaultObjectCache.Load(keyName); ok { + return defaultObject.(objTyp) + } + + // not doing an extra sync/double checked lock because generating the default object is pretty quick + // Use reflect to build a top level map from tfsdk:field_name to the reflect field value + attributeByTag := map[string]reflect.Value{} + val := reflect.ValueOf(&temp).Elem() + for i := 0; i < val.NumField(); i++ { + field := val.Type().Field(i) + if tag, ok := field.Tag.Lookup("tfsdk"); ok { + attributeByTag[tag] = val.Field(i) + } + } + + m := v.AttributeTypes(ctx) + for attributeName, attributeVal := range m { + if reflectAttribute, ok := attributeByTag[attributeName]; ok { + // If this attribute is a nested attribute, use the reflect field to create a new null/unknown with the proper attributeMap + // If this isn't done the framework will return errors like "Value Conversion Error, Expected framework type from provider logic ... Received framework type from provider logic: types._____[]" + if attributeVal, ok := attributeVal.(types.ObjectType); ok { + attributeMap := attributeVal.AttributeTypes() + if v.IsNull() { + reflectAttribute.Set(reflect.ValueOf(types.ObjectNull(attributeMap))) + } else { + reflectAttribute.Set(reflect.ValueOf(types.ObjectUnknown(attributeMap))) + } + } + if attributeVal, ok := attributeVal.(types.ListType); ok { + elemType := attributeVal.ElementType() + if v.IsNull() { + reflectAttribute.Set(reflect.ValueOf(types.ListNull(elemType))) + } else { + reflectAttribute.Set(reflect.ValueOf(types.ListUnknown(elemType))) + } + } + if attributeVal, ok := attributeVal.(types.SetType); ok { + elemType := attributeVal.ElementType() + if v.IsNull() { + reflectAttribute.Set(reflect.ValueOf(types.SetNull(elemType))) + } else { + reflectAttribute.Set(reflect.ValueOf(types.SetUnknown(elemType))) + } + } + if attributeVal, ok := attributeVal.(types.MapType); ok { + elemType := attributeVal.ElementType() + if v.IsNull() { + reflectAttribute.Set(reflect.ValueOf(types.MapNull(elemType))) + } else { + reflectAttribute.Set(reflect.ValueOf(types.MapUnknown(elemType))) + } + } + } + } + defaultObjectCache.Store(keyName, temp) + return temp +} + +// +// Helper function to convert a native terraform object to a golang object of the specified type. +// Use TypedObjectToObjectValue to go the other way. +// +// context +// Any issues will be appended to these diagnostics +// Object in the native terraform types.Object wrapper +// Object of the specified type +func ObjectValueToTypedObject[objTyp any](ctx context.Context, diagnostics *diag.Diagnostics, v types.Object) objTyp { + temp := defaultObjectFromObjectValue[objTyp](ctx, v) + if v.IsNull() || v.IsUnknown() { + return temp + } + + diags := v.As(ctx, &temp, basetypes.ObjectAsOptions{}) + if diags != nil { + diagnostics.Append(diags...) + } + return temp +} + +// +// 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 TypedObjectToObjectValue(ctx context.Context, diagnostics *diag.Diagnostics, v ModelWithAttributes) types.Object { + attributesMap, err := AttributeMapFromObject(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 native terraform list of objects to a golang slice of the specified type +// Use TypedArrayToObjectList to go the other way. +// +// context +// Any issues will be appended to these diagnostics +// List of object in the native terraform types.List wrapper +// Array of the specified type +func ObjectListToTypedArray[objTyp any](ctx context.Context, diagnostics *diag.Diagnostics, v types.List) []objTyp { + res := make([]types.Object, 0, len(v.Elements())) + if v.IsNull() || v.IsUnknown() { + return nil + } + + // convert to slice of TF type + diags := v.ElementsAs(ctx, &res, false) + if diags != nil { + diagnostics.Append(diags...) + return nil + } + + // convert to slice of real objects + arr := make([]objTyp, 0, len(res)) + for _, val := range res { + arr = append(arr, ObjectValueToTypedObject[objTyp](ctx, diagnostics, val)) + } + return arr +} + +// +// 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 TypedArrayToObjectList[objTyp ModelWithAttributes](ctx context.Context, diagnostics *diag.Diagnostics, v []objTyp) types.List { + var t objTyp + attributesMap, err := AttributeMapFromObject(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, TypedObjectToObjectValue(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 terraform list of terraform strings to array of golang primitive strings. +// Use StringArrayToStringList to go the other way. +// +// List of terraform strings +// Array of golang primitive strings +func StringListToStringArray(ctx context.Context, diagnostics *diag.Diagnostics, v types.List) []string { + res := make([]types.String, 0, len(v.Elements())) + + if v.IsNull() || v.IsUnknown() { + return nil + } + // convert to slice of TF type + diags := v.ElementsAs(ctx, &res, false) + if diags != nil { + diagnostics.Append(diags...) + return nil + } + + arr := []string{} + for _, stringVal := range res { + arr = append(arr, stringVal.ValueString()) + } + + return arr +} + +// +// Helper function to convert a golang slice of string to a native terraform list of strings. +// Use StringListToStringArray to go the other way. +// +// Any issues will be appended to these diagnostics +// Slice of strings +// types.List +func StringArrayToStringList(ctx context.Context, diagnostics *diag.Diagnostics, v []string) types.List { + if v == nil { + return types.ListNull(types.StringType) + } + + res := make([]types.String, 0, len(v)) + for _, val := range v { + res = append(res, basetypes.NewStringValue(val)) + } + list, diags := types.ListValueFrom(ctx, types.StringType, res) + if diags != nil { + diagnostics.Append(diags...) + return types.ListNull(types.StringType) + } + return list +} + +// +// Helper function to convert a terraform set of terraform strings to array of golang primitive strings. +// Use StringArrayToStringSet to go the other way. +// +// Set of terraform strings +// Array of golang primitive strings +func StringSetToStringArray(ctx context.Context, diagnostics *diag.Diagnostics, v types.Set) []string { + res := make([]types.String, 0, len(v.Elements())) + + if v.IsNull() || v.IsUnknown() { + return nil + } + // convert to slice of TF type + diags := v.ElementsAs(ctx, &res, false) + if diags != nil { + diagnostics.Append(diags...) + return nil + } + + arr := []string{} + for _, stringVal := range res { + arr = append(arr, stringVal.ValueString()) + } + + return arr +} + +// +// Helper function to convert a golang slice of string to a native terraform set of strings. +// Use StringSetToStringArray to go the other way. +// +// Any issues will be appended to these diagnostics +// Slice of strings +// types.Set +func StringArrayToStringSet(ctx context.Context, diagnostics *diag.Diagnostics, v []string) types.Set { + if v == nil { + return types.SetNull(types.StringType) + } + + res := make([]types.String, 0, len(v)) + for _, val := range v { + res = append(res, basetypes.NewStringValue(val)) + } + set, diags := types.SetValueFrom(ctx, types.StringType, res) + if diags != nil { + diagnostics.Append(diags...) + return types.SetNull(types.StringType) + } + return set +} + +// +// Helper function to convert array of terraform strings to array of golang primitive strings +// Deprecated: Remove after we fully move to types.List +// +// Array of terraform stringsArray of golang primitive strings +// Array of golang primitive strings +func ConvertBaseStringArrayToPrimitiveStringArray(v []types.String) []string { + res := []string{} + for _, stringVal := range v { + res = append(res, stringVal.ValueString()) + } + + return res +} + +// +// Helper function to convert array of golang primitive strings to array of terraform strings +// Deprecated: Remove after we fully move to types.List +// +// Array of golang primitive strings +// Array of terraform strings +func ConvertPrimitiveStringArrayToBaseStringArray(v []string) []types.String { + res := []types.String{} + for _, stringVal := range v { + res = append(res, types.StringValue(stringVal)) + } + + return res +} + +// +// Helper function to convert array of golang primitive interface to array of terraform strings +// Deprecated: Remove after we fully move to types.List +// +// Array of golang primitive interface +// Array of terraform strings +func ConvertPrimitiveInterfaceArrayToBaseStringArray(v []interface{}) ([]types.String, string) { + res := []types.String{} + for _, val := range v { + switch stringVal := val.(type) { + case string: + res = append(res, types.StringValue(stringVal)) + default: + return nil, "At this time, only string values are supported in arrays." + } + } + + return res, "" +} + +// +// Helper function to convert terraform bool value to string +// +// Boolean value in terraform bool +// Boolean value in string +func TypeBoolToString(from types.Bool) string { + return strconv.FormatBool(from.ValueBool()) +} + +// +// Helper function to convert string to terraform boolean value +// +// Boolean value in string +// Boolean value in terraform types.Bool +func StringToTypeBool(from string) types.Bool { + result, _ := strconv.ParseBool(from) + return types.BoolValue(result) +} diff --git a/internal/validators/also_requires_on_values_string_validator.go b/internal/validators/also_requires_on_values_string_validator.go index 0979925..e38db96 100644 --- a/internal/validators/also_requires_on_values_string_validator.go +++ b/internal/validators/also_requires_on_values_string_validator.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package validators diff --git a/internal/validators/also_requires_on_values_string_validator_example_test.go b/internal/validators/also_requires_on_values_string_validator_example_test.go index 37548b2..67c07c2 100644 --- a/internal/validators/also_requires_on_values_string_validator_example_test.go +++ b/internal/validators/also_requires_on_values_string_validator_example_test.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package validators diff --git a/main.go b/main.go index b29fda8..458ce75 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. package main diff --git a/scripts/onboarding-helper/README.md b/scripts/onboarding-helper/README.md index 461c160..4a8334b 100644 --- a/scripts/onboarding-helper/README.md +++ b/scripts/onboarding-helper/README.md @@ -44,10 +44,10 @@ This automation script is designed to onboard an existing site to Terraform. It - `CustomerId`: - For Citrix Cloud customers **only** (Required): Your Citrix Cloud customer ID. This is only applicable for Citrix Cloud customers. - `ClientId`: Your client ID for Citrix DaaS service authentication. - - For Citrix On-Premises customers: Use this to specify Domain Admin Username. + - For Citrix On-Premises customers: Use this to specify a DDC administrator username. - For Citrix Cloud customers: Use this to specify Cloud API Key Client Id. - `ClientSecret`: Your client secret for Citrix DaaS service authentication. - - For Citrix on-premises customers: Use this to specify Domain Admin Password. + - For Citrix on-premises customers: Use this to specify a DDC administrator password. - For Citrix Cloud customers: Use this to specify Cloud API Key Client Secret. - `DomainFqdn`: Your client secret for Citrix DaaS service authentication. - For Citrix on-premises customers **only** (Required): Use this to specify Domain FQDN. diff --git a/scripts/onboarding-helper/terraform-onboarding.ps1 b/scripts/onboarding-helper/terraform-onboarding.ps1 index 372ec5f..15c5cee 100644 --- a/scripts/onboarding-helper/terraform-onboarding.ps1 +++ b/scripts/onboarding-helper/terraform-onboarding.ps1 @@ -14,10 +14,12 @@ Currently this script is still in TechPreview .Parameter ClientId The Client Id for Citrix DaaS service authentication. + For Citrix on-premises customers: Use this to specify a DDC administrator username. + For Citrix Cloud customers: Use this to specify Cloud API Key Client Id. .Parameter ClientSecret The Client Secret for Citrix DaaS service authentication. - For Citrix on-premises customers: Use this to specify Domain Admin Password. + For Citrix on-premises customers: Use this to specify a DDC administrator password. For Citrix Cloud customers: Use this to specify Cloud API Key Client Secret. .Parameter Hostname @@ -468,8 +470,8 @@ function RemoveComputedProperties { $pathRegex = '(\s+)path\s*=\s*".*\\\\.*"' $content = $content -replace $pathRegex, "" - # Remove is_assigned property from application since it is computed - $isAssignedRegex = "(\s+)is_assigned(\s+)= (\S+)" + # Remove assigned property from application since it is computed + $isAssignedRegex = "(\s+)assigned(\s+)= (\S+)" $content = $content -replace $isAssignedRegex, "" return $content diff --git a/settings.cloud.example.json b/settings.cloud.example.json index f272c18..3df66db 100644 --- a/settings.cloud.example.json +++ b/settings.cloud.example.json @@ -213,6 +213,8 @@ "TEST_APP_NAME" :"app-test", // Application Folder Go Tests "TEST_APP_FOLDER_NAME" :"app-folder", + // Application Group Go Tests + "TEST_APP_GROUP_NAME" :"app-group", // Admin role env variable "TEST_ROLE_NAME" :"ctx-test-role", @@ -221,7 +223,6 @@ // Policy Set env variable "TEST_POLICY_SET_NAME" :"ctx-test-policy-set", - "CITRIX_DDC_HOST_NAME" : "{customerId}.xendesktop.net", // GAC Go Tests "TEST_SETTINGS_CONFIG_SERVICE_URL" : "{workspace_url_with_port_number}", @@ -232,13 +233,21 @@ // StoreFront env variable "SF_COMPUTER_NAME" : "{storefront_computer_name}", - "SF_AD_ADMAIN_USERNAME" : "{storefront_admin_username}", + "SF_AD_ADMIN_USERNAME" : "{storefront_admin_username}", "SF_AD_ADMIN_PASSWORD" : "{storefront_admin_password}", "TEST_STF_SITE_ID" :"1", "TEST_STF_SITE_ID_UPDATED" :"2", "TEST_STF_Auth_Virtual_Path" : "/Citrix/Auth" , "TEST_STF_Store_Virtual_Path" : "/Citrix/Store", - "TEST_STF_WEBRECEIVER_VIRTUAL_PATH" : "/Citrix/StoreWeb" + "TEST_STF_WEBRECEIVER_VIRTUAL_PATH" : "/Citrix/StoreWeb", + + // StoreFront STFUserFarmMapping Resource env variables + "TEST_STF_USER_FARM_MAPPING_NAME": "Test STFUserFarmMappingName", + "TEST_STF_USER_FARM_MAPPING_USER1_SID": "{Active Directory User 1 Account Sid}", + "TEST_STF_USER_FARM_MAPPING_USER2_SID": "{Active Directory User 2 Account Sid}", + "TEST_STF_PRIMARY_FARM_NAME": "{Primary Farm Name}", + "TEST_STF_SECONDARY_FARM_NAME": "{Secondary Farm Name}", + "TEST_STF_BACKUP_FARM_NAME": "{Backup Farm Name}" }, "go.testTimeout": "30m" diff --git a/settings.onprem.example.json b/settings.onprem.example.json index 9fbc8c4..a716bea 100644 --- a/settings.onprem.example.json +++ b/settings.onprem.example.json @@ -213,7 +213,9 @@ "TEST_APP_NAME" :"app-test", // Application Folder Go Tests "TEST_APP_FOLDER_NAME" :"app-folder", - + // Application Group Go Tests + "TEST_APP_GROUP_NAME" :"app-group", + // Admin role env variable "TEST_ROLE_NAME" :"ctx-test-role", // Admin Scope Go Tests @@ -224,17 +226,24 @@ // Policy Set env variable "TEST_POLICY_SET_NAME" :"ctx-test-policy-set", - "CITRIX_DDC_HOST_NAME" : "{ddcHostname}", // StoreFront env variable "SF_COMPUTER_NAME" : "{storefront_computer_name}", - "SF_AD_ADMAIN_USERNAME" : "{storefront_admin_username}", + "SF_AD_ADMIN_USERNAME" : "{storefront_admin_username}", "SF_AD_ADMIN_PASSWORD" : "{storefront_admin_password}", "TEST_STF_SITE_ID" :"1", "TEST_STF_SITE_ID_UPDATED" :"2", "TEST_STF_Auth_Virtual_Path" : "/Citrix/Auth" , "TEST_STF_Store_Virtual_Path" : "/Citrix/Store", - "TEST_STF_WEBRECEIVER_VIRTUAL_PATH" : "/Citrix/StoreWeb" + "TEST_STF_WEBRECEIVER_VIRTUAL_PATH" : "/Citrix/StoreWeb", + + // StoreFront STFUserFarmMapping Resource env variables + "TEST_STF_USER_FARM_MAPPING_NAME": "Test STFUserFarmMappingName", + "TEST_STF_USER_FARM_MAPPING_USER1_SID": "{Active Directory User 1 Account Sid}", + "TEST_STF_USER_FARM_MAPPING_USER2_SID": "{Active Directory User 2 Account Sid}", + "TEST_STF_PRIMARY_FARM_NAME": "{Primary Farm Name}", + "TEST_STF_SECONDARY_FARM_NAME": "{Secondary Farm Name}", + "TEST_STF_BACKUP_FARM_NAME": "{Backup Farm Name}" }, "go.testTimeout": "30m" } \ No newline at end of file diff --git a/tools/tools.go b/tools/tools.go index dec8333..40acc34 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -1,4 +1,4 @@ -// Copyright © 2023. Citrix Systems, Inc. +// Copyright © 2024. Citrix Systems, Inc. //go:build tools