Overview
This guide aims to provide an overview of using Terraform to create a complete Citrix DaaS Resource Location on Amazon EC2.
At the end of the process, you created:
- A new Citrix Cloud Resource Location (RL) running on Amazon EC2
- 2 Cloud Connector Virtual Machines registered with the Domain and the Resource Location
- A Hypervisor Connection and a Hypervisor Pool pointing to the new Resource Location in Amazon EC2
- A Machine Catalog based on the uploaded Master Image VHD or on an Amazon EC2-based Master Image
- A Delivery Group based on the Machine Catalog with full Autoscale Support
- Example policies and policy scope bound to the Delivery Group
What is Terraform
Terraform is an Infrastructure-as-Code (IaC) tool that defines cloud and on-prem resources in easy-readable configuration files rather than through a GUI.
IaC allows you to build, change, and manage your infrastructure safely and consistently by defining resource configurations.
These configurations can be versioned, reused, and shared and are created in its native declarative configuration language known as HashiCorp Configuration Language (HCL), or optionally using JSON.
Terraform creates and manages resources on Cloud platforms and other services through their application programming interfaces (APIs). Terraform providers are compatible with virtually any platform or service with an accessible API.
More information about Terraform can be found at https://developer.hashicorp.com/terraform/intro.
Installation
HashiCorp distributes Terraform as a binary package. You can also install Terraform using popular package managers. In this example, we use Chocolatey for Windows to deploy Terraform. Chocolatey is a free and open-source package management system for Windows. Install the Terraform package from the CLI.
Installation of Chocolatey
Open a PowerShell shell with Administrative rights and paste the following command:
|
Chocolatey downloads and installs all necessary components automatically:
|
Run choco --v
to check if Chocolatey was installed successfully:
|
Installation of Terraform
After the successful installation of Chocolatey you can install Terraform by running this command on the PowerShell session:
|
Run terraform -version
to check if Terraform installed successfully:
|
The installation of Terraform is now completed.
Terraform - Basics and Commands
Terraform Block
The terraform {}
block contains Terraform settings, including the required providers to provision your infrastructure. Terraform installs providers from the Terraform Registry.
Providers
The provider block configures the specified provider. A provider is a plug-in that Terraform uses to create and manage your resources. Providing multiple provider blocks in the Terraform configuration enables managing resources from different providers.
Resources
Resource blocks define the components of the infrastructure - physical, virtual, or logical. These blocks contain arguments to configure the resource. The provider's reference lists the required and optional arguments for each resource.
The core Terraform workflow consists of three stages
Write
:
You define resources that are deployed, altered, or deleted.
Plan
:
Terraform creates an execution plan describing the infrastructure that it creates, updates, or destroys based on the existing infrastructure and your configuration.
Apply
:
On approval, Terraform does the proposed operations in the correct order, respecting any resource dependencies.
Terraform does not only add complete configurations, it also allows you to change previously added configurations.
For example, changing the DNS servers of an NIC of an Amazon EC2 VM does not require redeploying the whole configuration - Terraform only alters the needed resources.
Terraform Provider for Citrix
Citrix has developed a custom Terraform provider for automating Citrix product deployments and configurations.
Note:
The Citrix Terraform provider is currently in Tech Preview!
This guide is updated when the provider has reached RTM.
Using Terraform with Citrix' provider, you can manage your Citrix products via Infrastructure as Code. Terraform is giving you higher efficiency and consistency on infrastructure management and better reusability on infrastructure configuration.
The provider defines individual units of infrastructure and currently supports both Citrix Virtual Apps and Desktops
and Citrix Desktop as a Service
solutions.
You can automate the creation of a site setup including host connections, machine catalogs, and delivery groups.
You can deploy resources in Amazon EC2, AWS, and GCP in addition to supported on-premises Hypervisors.
Note:
All information about the Citrix Terraform provider can be found on GitHub: https://github.com/citrix/terraform-provider-citrix
Terraform expects to be invoked from a working directory that contains configuration files written in the Terraform language.
Terraform uses configuration content from this directory and also uses the directory to store settings, cached plug-ins and modules, and state data.
A working directory must be initialized before Terraform can do any operations in it.
Initialize the working directory by using the command:
|
The provider defines how Terraform can interact with the underlying API. Configurations must declare which providers they require so Terraform can install and use them.
Terraform CLI finds and installs providers when initializing a working directory. It can automatically download providers from a Terraform registry, or load them from a local mirror or cache.
Example: Terraform configuration files - provider.tf
The file provider.tf
contains the information on the target site where to apply the configuration.
Depending on whether it is a Citrix Cloud site or a Citrix On-Premises site, the provider needs to be configured differently:
|
A guide for the creation of a secure API client can be found in Citrix Developer Docs and is shown later as well.
|
Example - Schema used for the Provider configuration
-
client_id
(String): Client Id for Citrix DaaS service authentication
For Citrix On-Premises customers: Use this variable to specify theDomain-Admin Username
.
For Citrix Cloud customers: Use this variable to specifyCloud API Key Client ID
.
Can be set via the Environment Variable CITRIX_CLIENT_ID. -
client_secret
(String, Sensitive): Client Secret for Citrix DaaS service authentication
For Citrix On-Premises customers: Use this variable to specify theDomain-Admin Password
.
For Citrix Cloud customers: Use this variable to specifyCloud API Key Client Secret
.
Can be set via the Environment Variable CITRIX_CLIENT_SECRET.| -
customer_id
(String): Citrix Cloud customer ID
Only applicable for Citrix Cloud customers.
Can be set via the 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 do not need this option. Set totrue
to skip SSL verification only when the target DDC does not have a valid SSL certificate issued by a trusted CA.
When set totrue
, please make sure that your provider config is set for a known DDChostname
.
It is recommended to configure a valid certificate for the target DDC.
Can be set via the 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
.
Can be set via the Environment Variable CITRIX_ENVIRONMENT.| -
hostname
(String) | Hostname/base URL of Citrix DaaS service
For Citrix On-Premises customers (Required): Use this variable to specify the Delivery Controllerhostname
.
For Citrix Cloud customers (Optional): Use this variable to override the Citrix DaaS servicehostname
.
Can be set via the Environment Variable CITRIX_HOSTNAME.|
Note:
The Citrix Terraform provider is currently in Tech Preview!
This guide is updated when the provider has reached RTM.
Deploying a Citrix Cloud Resource location on Amazon EC2 using Terraform
Overview
This guide aims to showcase the possibility of creating a complete Citrix Cloud Resource Location on Amazon EC2 using Terraform. We want to reduce manual interventions to the absolute minimum.
All Terraform configuration files can be found late on GitHub - we update this guide when the GitHub repository is ready.
In this guide we use an existing domain and will not deploy a new domain - for further instructions for deploying a new domain refer to the guide Citrix DaaS and Terraform - Automatic Deployment of a Resource Location on Microsoft Azure. Note that this guide will be reworked soon!
The AD deployment used for this guide consists of a Hub-and-Spoke model - each Resource Location running on a Hypervisor/Hyperscaler is connected to the main Domain Controller by using IPSec-based Site-to-Site VPNs. Each Resource Location has its own sub-domain.
NOTE: For easier reading and understanding, we did not use any
loop
-constructs orcount
keywords - each resource is created explicitly.
The Terraform flow is split into different parts:
The Terraform flow is split into different parts:
-
Part One - this part can be run on any computer where Terraform is installed :
-
Creating the initially needed Resources on Amazon EC2:
- Creating all needed IAM roles on Amazon EC2
- Creating all needed IAM Instance profiles on Amazon EC2
- Creating all needed IAM policies on Amazon EC2
- Creating all needed Secret Manager configurations on Amazon EC2
- Creating all needed DHCP configurations on Amazon EC2
- Creating a Windows Server 2022-based Master Image VM used for deploying the Machine Catalog in step 3
- Creating two Windows Server 2022-based VMs used as Cloud Connector VMs in step 2
- Creating a Windows Server 2022-based VM acting as a Administrative workstation for running the Terraform steps 2 and 3 - this is necessary because of using WinRM for further configuration and deployment in steps 2 and 3!
- Creating all necessary scripts for joining the VMs to the existing sub-domain
- Putting the VMs into the existing sub-domain
-
Creating the initially needed Resources on Amazon EC2:
NOTE: The Master Image needs to be configured before it can be deployed - Terraform will configure a blank, Domain-joined VM! The Administrative VM must have Terraform installed - use the Chocolately Guidance provided previously for installation
-
Part Two - this part can only be run on the previously created Administrative VM as the deployment of steps 2 and 3 relies heavily on WinRM:
-
Configuring the three previously created Virtual Machines on Amazon EC2:
- Installing the needed software on the CCs
- Installing the needed software on the Admin-VM
-
Creating the necessary Resources in Citrix Cloud:
- Creating a Resource Location in Citrix Cloud
- Configuring the 2 CCs as Cloud Connectors
- Registering the 2 CCs in the newly created Resource Location
-
-
Part Three:
-
Creating the Machine Catalog and Delivery Group in Citrix Cloud:
- Retrieving the Site- and Zone-ID of the Resource Location
- Creating a dedicated Hypervisor Connection to Amazon EC2
- Creating a dedicated Hypervisor Resource Pool
- Creating a Machine Catalog (MC) in the newly created Resource Location
- Creating a Delivery Group (DG) based on the MC in the newly created Resource Location
-
Creating the Machine Catalog and Delivery Group in Citrix Cloud:
NOTE: Make sure that all Terraform-related VMs can communicate using WinRM.
If the Admin-VM is not able to connect to the CCs using WinRM, the deployment fails.
Various configuration guides for WinRM can be found on the Internet.
Determine if WinRM connections/communications are functioning
We strongly recommend a quick check to determine the communication before starting the Terraform scripts.
Open a PowerShell console and type the following command:
|
A short Terraform script also checks if the communication via WinRM between the Admin-VM and in this example, the CC1-VM is working as intended:
locals {
#### Test the WinRM communication
#### Need to invoke PowerShell as Domain User as the provisioner does not allow to be run in a Domain Users-context
TerraformTestWinRMScript = <<-EOT
$PSUsername = '${var.Provisioner_DomainAdmin-Username}'
$PSPassword = '${var.Provisioner_DomainAdmin-Password}'
$PSSecurePassword = ConvertTo-SecureString $PSPassword -AsPlainText -Force
$PSCredential = New-Object System.Management.Automation.PSCredential ($PSUsername, $PSSecurePassword)
Invoke-Command -ComputerName localhost -Credential $PSCredential -ScriptBlock {
$FileNameForData = 'C:\temp\xdinst\Processes.txt'
If (Test-Path $FileNameForData) {Remove-Item -Path $FileNameForData -Force}
Get-Process | Out-File -FilePath 'C:\temp\xdinst\Processes.txt'
}
EOT
}
#### Write script into local data-directory
resource "local_file" "WriteWinRMTestScriptIntoDataDirectory" {
filename = "${path.module}/data/Terraform-Test-WinRM.ps1"
content = local.TerraformTestWinRMScript
}
resource "null_resource" "CreateTestScriptOnCC1" {
connection {
type = var.Provisioner_Type
user = var.Provisioner_Admin-Username
password = var.Provisioner_Admin-Password
host = var.Provisioner_CC1-IP
timeout = var.Provisioner_Timeout
}
provisioner "file" {
source = "${path.module}/data/Terraform-Test-WinRM.ps1"
destination = "C:/temp/xdinst/Terraform-Test-WinRM.ps1"
}
provisioner "remote-exec" {
inline = [
"powershell -File 'C:/temp/xdinst/Terraform-Test-WinRM.ps1'"
]
}
}
|
If you can see in the Terraform console something like...:
|
...then you can be sure that the provisioning using WinRM is working as intended!
Configuration using variables
All needed configuration settings are stored in the corresponding Variables which need to be set. Some Configuration settings are propagated throughout the whole Terraform configuration...
You need to start each of the 3 modules manually using the Terraform workflow terraform init
, terraform plan
, and terraform apply
in the corresponding module directory. Terraform then completes the necessary configuration steps of the corresponding module.
NOTE: Each module must be completed successfully before the next module can be started!
File System structure
Root-Directory
Module 1: _CConAWS-Creation
:
Filename | Purpose |
---|---|
_CConAWS-Creation-Create.tf
|
Resource configuration and primary flow definition |
_CConAWS-Creation-Create-variables.tf
|
Definition of Variables |
_CConAWS-Creation-Create.auto.tfvars.json
|
Setting the values of the Variables |
_CConAWS-Creation-Provider.tf
|
Provider definition and configuration |
_CConAWS-Creation-Provider-variables.tf
|
Definition of Variables |
_CConAWS-Creation-Provider.auto.tfvars.json
|
Setting the values of the Variables |
Add-EC2InstanceToDomainAdminVM.ps1
|
Powershell-Script for joining the Admin-VM to the Domain |
Add-EC2InstanceToDomainCC1.ps1
|
Powershell-Script for joining the CC1-VM to the Domain |
Add-EC2InstanceToDomainCC2.ps1
|
Powershell-Script for joining the CC2-VM to the Domain |
Add-EC2InstanceToDomainWMI.ps1
|
Powershell-Script for joining the CC2-VM to the Domain |
DATA-Directory
|
Place to put files to upload using file provisioning |
(NOT RECOMMENDED - see later explanation |
Module 2: _CConAWS-Install
:
Filename | Purpose |
---|---|
_CCOnAWS-Install-CreatePreReqs.tf
|
Resource configuration and primary flow definition |
_CCOnAWS-Install-CreatePreReqs-variables.tf
|
Definition of Variables |
_CCOnAWS-Install-CreatePreReqs.auto.tfvars.json
|
Setting the values of the Variables |
_CConAWS-Install-Provider.tf
|
Provider definition and configuration |
_CConAWS-Install-Provider-variables.tf
|
Definition of Variables |
_CConAWS-Install-Provider.auto.tfvars.json
|
Setting the values of the Variables |
GetSiteID.ps1
|
PowerShell script to make a REST-API call to determine the CC-Site-ID |
GetZoneID.ps1
|
PowerShell script to make a REST-API call to determine the CC-Zone-ID |
DATA/InstallPreReqsOnAVM1.ps1
|
PowerShell script to deploy needed pre-requisites on the Admin-VM |
DATA/InstallPreReqsOnAVM2.ps1
|
PowerShell script to deploy needed pre-requisites on the Admin-VM |
DATA/InstallPreReqsOnCC.ps1
|
PowerShell script to deploy needed pre-requisites on the Admin-VM |
Module 3: _CConAWS-CCStuff
:
Filename | Purpose |
---|---|
_CCOnAWS-CCStuff-CreateCCEntities.tf
|
Resource configuration and primary flow definition |
_CCOnAWS-CCStuff-CreateCCEntities-variables.tf
|
Definition of Variables |
_CCOnAWS-CCStuff-CreateCCEntities.auto.tfvars.json
|
Setting the values of the Variables |
_CConAWS-CCStuff-Provider.tf
|
Provider definition and configuration |
_CConAWS-CCStuff-Provider-variables.tf
|
Definition of Variables |
_CConAWS-CCStuff-Provider.auto.tfvars.json
|
Setting the values of the Variables |
NOTE: All Terraform-related directories and files (
.terraform
,-terrafrom.lock.hcl
,terraform.tfstate
,terraform.tfstate
) must not be changed or deleted - doing so might break the deployment!
Change the settings in the .json
files according to your needs.
The following prerequisites are needed before setting the corresponding settings or running the Terraform workflow to ensure a smooth and error-free build.
Prerequisites
Installing AWS.Tools for PowerShell and AWS CLI
In this guide, we use AWS CLI and PowerShell cmdlets to determine further needed information.
Further information about AWS CLI and the installation/configuration can be found at AWS Command Line Interface, further information about the PowerShell cmdlets for AWS can be found at Installing the AWS Tools for PowerShell on Windows.
|
Existing Amazon EC2 entities
We anticipate that the following resources are existing and are already configured on Amazon EC2:
- A working tenant
- All needed rights for the IAM user on the tenant
- A VPC
- At least one subnet in the VPC
- A security group configured for allowing inbound connections from the subnet and partially from the Internet: WinRM-HTTP, WinRM-HTTPS, UDP, DNS (UDP and TCP), ICMP (for testing purposes), HTTP, HTTPS, TCP (for testing purposes), RDP. No blocking rules for outbound connections should be in place
- An access key with its secret (see a description how to create the key later on)
We can get the needed information about the VPC by using PowerShell:
|
Note the VpcId
and put it into the corresponding .auto.tvars.json
file.
We can get the needed information about one or more subnets also by using PowerShell:
|
Note the SubnetID
of the Availability Zone that you want to use and put it into the corresponding .auto.tvars.json
file.
Getting the region in Amazon EC2 where the resources will be deployed
The currently configured default region can be found by using for example AWS CLI - open a PowerShell window and type:
|
Write down the Location
as we need to assign it to variables.
Getting the available AMI Image-IDs from Amazon EC2
We want to automatically deploy the virtual machines necessary for the DC and the CCs - so we need detailed configuration settings:
- Set the credentials needed for allowing PowerShell to access the EC2 tenant:
|
- Get the available AMI images:
|
Note the value of the AMI that you want to use - for example ami-0ad8b6fa068e0299a
.
Getting the available VM sizes from Amazon EC2
We need to determine the available VM sizes. A PowerShell script helps us to list the available instance types on Amazon EC2:
|
We need to filter the results to narrow down usable instances - we want to use instances with max. 4 vCPUs and max. 8 GB RAM:
|
Note the Instancetype
parameter of the Amazon EC2 Resource Location that you want to use. We use t3.medium
and t3.large
.
Be sure to use the Citrix Terraform Provider version 0.5.3 or higher or we need to pass the Instance type to the Terraform provider in a different format (examples):
Amazon EC2 syntax: | Needed format: |
---|---|
t3.nano | T3 Nano Instance |
t3.small | T3 Small Instance |
t3.medium | T3 Medium Instance |
t3.large | T3 Large Instance |
e2.small | E2 Small Instance |
e2.medium | E2 Medium Instance |
Caution: Be sure that your subscription has no quota limitation on the chosen VM type and you have enough resources on Amazon EC2 to create all the Virtual Machines planned to put into the Machine Catalog by checking quotas - otherwise the creation of the Machine Catalog fails if there are not enough compute resources available! The AWS Quota code for Standard On-Demand instances is
L-1216C47A
.
Example: Checking the current quotas for the instances (N-family) which we plan to use in this guide:
|
The output of the cmdlet shows that we should have enough available resources on the instance level.
The quotas can be increased using the Amazon EC2 Console or PowerShell.
More information about increasing vCPU quotas can be found here: Amazon Elastic Compute Cloud (Amazon EC2) quotas.
Further Software Components for Configuration and Deployment
The Terraform deployment needs actual versions of the following software components:
-
Citrix Cloud Connector Installer:
cwcconnector.exe
. Download the Citrix Cloud Connector Installer -
Citrix Remote PowerShell SDK Installer:
CitrixPoshSdk.exe
. Download the Citrix Remote PowerShell SDK Installer
These components are required during the workflow. The Terraform engine looks for these files. In this guide we anticipate that the necessary software can be downloaded from a Storage Repository - we use an Azure Storage Blob where all necessary software is uploaded to.
The URIs of the Storage Repository can be set in the corresponding variables:
-
For the Cloud Connector:
"CC_Install_CWCURI":"https://wmwblob.blob.core.windows.net/tfdata/cwcconnector.exe"
-
For the Remote PowerShell SDK:
"CC_Install_RPoSHURI":"https://wmwblob.blob.core.windows.net/tfdata/CitrixPoshSdk.exe"
Creating an Access Key with a Secret for Amazon EC2 Authentication in AWS CLI and/or AWS.Tools for PowerShell
Access Keys are long-term credentials for an IAM user or the root user which are used for Authentication and Authorization in EC2.
They consist of two parts: an Access Key-ID and a Secret Access Key. Both are needed for Authentication and Authorization.
Further information about Access Key management and the configuration can be found in Managing access keys for IAM users.
The needed security information for the IAM Policies is stored in an EC2-Secrets Manager:
Further information about EC2-Secrets Manager and the configuration can be found at AWS Secrets Manager.
Creating a Secure Client in Citrix Cloud
The Secure Client in Citrix Cloud is the same as the Access Key in Amazon EC2. It is used for Authentication.
API clients in Citrix Cloud are always tied to one administrator and one customer. API clients are not visible to other administrators. If you want to access to more than one customer, you must create API clients within each customer.
API clients are automatically restricted to the rights of the administrator that created it. For example, if an administrator is restricted to access only notifications, then the administrator’s API clients have the same restrictions:
- Reducing an administrator’s access also reduces the access of the API clients owned by that administrator
- Removing an administrator’s access also removes the administrator’s API clients.
To create an API client, select the Identity and Access Management option from the menu. If this option does not appear, you do not have adequate permissions to create an API client. Contact your administrator to get the required permissions.
-
Open
Identity and Access Management
in WebStudio:
Creating an API Client in Citrix Cloud -
Click
API Access
,Secure Clients
and put a name in the textbox next to the buttonCreate Client
. After entering a name. clickCreate Client
:
Creating an API Client in Citrix Cloud -
After the Secure Client is created, copy and write down the shown
ID
andSecret
:
Creating an API Client in Citrix Cloud
The Secret is only visible during creation - after closing the window you are not able to get it anymore.
The client-id
and client-secret
fields are needed by the Citrix Terraform provider.
The also needed customer-id
field can be found in your Citrix Cloud details.
Put the values in the corresponding .auto.tvars.json
file:
...
"cc-customerId": "uzXXXXXXXX",
"cc-apikey-clientId": "f4eXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"cc-apikey-clientSecret": "VXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"cc-apikey-type": "client_credentials",
...
|
Creating a Bearer Token in Citrix Cloud
The Bearer Token is needed for the Authorization of some REST-API calls in Citrix Cloud.
As the Citrix provider currently has not implemented all functionalities yet, some REST-API calls are still needed. The Bearer Token authorizes these calls.
It is important to set the URI to call and the required parameters correct: The URI must follow this syntax:
For example [https://api-us.cloud.com/cctrustoauth2/{customerid}/tokens/clients] where {customerid}
is your Customer ID you obtained from the Account Settings page.
If your Customer ID is for example 1234567890
, the URI is [https://api-us.cloud.com/cctrustoauth2/1234567890/tokens/clients]
In this example, we use the Postman application to create a Bearer Token:
-
Paste the correct URI into Postman´s address bar and select POST as the method. Verify the correct settings of the API call.
Creating a Bearer Token using Postman
If everything is set correctly, Postman shows a Response containing a JSON-formatted file containing the Bearer token in the fieldaccess-token
: The token is normally valid for3600
seconds. -
Put the values in the corresponding
.auto.tvars.json
file:..."cc-apikey-type": "client_credentials","cc-apikey-bearer": "CWSAuth bearer=eyJhbGciOiJSUzI1NiIsI...0q0IW7SZFVzeBittWnEwTYOZ7Q
"...
NOTE: In this deployment, Terraform automatically requests a valid Bearer Token using a REST-API call from Citrix Cloud and stores the Bearer Token on the hard disk.
If subsequent needs for having a Bearer Token occur during script progress, Terraform loads the Bearer Token from the hard drive.
You can also use PowerShell to request a Bearer Token - therefore you need a valid Secure Client stored in Citrix Cloud:
|
Module 1: Create the initially needed Resources on Amazon EC2
This module is split into the following configuration parts:
-
Creating the initially needed Resources on Amazon EC2:
- Creating all needed IAM roles on Amazon EC2
- Creating all needed IAM Instance profiles on Amazon EC2
- Creating all needed IAM policies on Amazon EC2
- Creating all needed Secret Manager configurations on Amazon EC2
- Creating all needed DHCP configurations on Amazon EC2
- Creating a Windows Server 2022-based Master Image VM used for deploying the Machine Catalog in step 3
- Creating two Windows Server 2022-based VMs which will be used as Cloud Connector VMs in step 2
- Creating a Windows Server 2022-based VM acting as an Administrative workstation for running the Terraform steps 2 and 3 - this is necessary because of using WinRM for further configuration and deployment in steps 2 and 3!
- Creating all necessary scripts for joining the VMs to the existing sub-domain
- Putting the VMs into the existing subdomain
- Fetching and saving a valid Bearer Token
All these steps are automatically done by Terraform.
NOTE: This first part is separated from the next steps as we assume that there is no Admin-VM installed in the corresponding VPC/Subnet. The Admin-VM is created now in Module 1 and all further modules must be run from it. Working with WinRM calls over the Internet would pose a security threat and would significantly reduce the performance of the Terraform-based deployment. These are the reasons for the split approach!
Please make sure you have configured the variables according to your needs.
The configuration can be started by following the normal Terraform workflow:
terraform init
,
terraform plan
and if no errors occur
terraform apply
|
As no errors occurred, Terraform has completed the creation and partial configuration of the relevant prerequisites on Amazon EC2.
Example of successful creation:
All VMs were successfully created and registered on the Domain Controller:
Now the next step can be started.
Module 2: Install and Configure all Resources in Amazon EC2
IMPORTANT: This module can only be run on the previously created Administrative VM as the deployment of steps 2 and 3 relies heavily on WinRM! Please install Terraform on the Administrative VM before trying to run the scripts!
This module is split into the following configuration parts:
-
Configuring the three previously created Virtual Machines on Amazon EC2:
- Installing the needed software on the CCs
- Installing the needed software on the Admin-VM
-
Creating the necessary Resources in Citrix Cloud:
- Creating a Resource Location in Citrix Cloud
- Configuring the 2 CCs as Cloud Connectors
- Registering the 2 CCs in the newly created Resource Location
NOTE: This module has some complexity as many automated actions and many different entities are created on Amazon EC2 and run on the VMs. It contains a mixture of PowerShell scripts, REST-API calls, and Terraform scripts to achieve a working deployment.
Our provider currently does not support creating a Resource Location on Citrix Cloud. Therefore we use a PowerShell script to create it using a REST-API call.
Please make sure you have configured the variables according to your needs by using the corresponding .auto.tfvars.json
file.
Terraform runs various scripts before creating the configuration of the CCs to determine needed information like the Site-ID, the Zone-ID, and the Resource Location-ID.
These IDs are used in other scripts or files - for example the parameter file for deploying the Cloud Connector needs the Resource Location ID of the Resource Location which Terraform creates automatically. Unfortunately the REST-API provider does not return the ID of the newly created Resource Location, so we need to run PowerShell after the creation of the Resource Location:
Examples of necessary scripts:
At first, Terraform writes the configuration file without the Resource Location ID:
#### Create CWC-Installer configuration file based on variables and save it into Transfer directory
resource "local_file" "CWC-Configuration" {
depends_on = [restapi_object.CreateRL]
content = jsonencode(
{
"customerName" = "${var.CC_CustomerID}",
"clientId" = "${var.CC_APIKey-ClientID}",
"clientSecret" = "${var.CC_APIKey-ClientSecret}",
"resourceLocationId" = "XXXXXXXXXX",
"acceptTermsOfService" = true
}
)
filename = "${var.CC_Install_LogPath}/DATA/cwc.json"
}
|
IMPORTANT
The Terraform script will pause for 600 seconds after creation of the Resource Location! This is because of time constraints in the back-end - the creation of the Zone related to the Resource Location needs some time - we have seen delays up to 8 minutes before the Zone was created. If the script would proceed too fast, the Zone ID readout would fail and the Terraform script would not be able to continue successfully!
After installing further pre-requisites, Terraform runs a PowerShell script to get the needed ID and updates the configuration file for the CWC installer:
#### Create PowerShell file for determining the correct RL-ID
resource "local_file" "InstallPreReqsOnAVM2-ps1" {
content = <<-EOT
#### Need to invoke PowerShell as Domain User as the provisioner does not allow to be run in a Domain Users-context
$PSUsername = '${var.Provisioner_DomainAdmin-Username}'
$PSPassword = '${var.Provisioner_DomainAdmin-Password}'
$PSSecurePassword = ConvertTo-SecureString $PSPassword -AsPlainText -Force
$PSCredential = New-Object System.Management.Automation.PSCredential ($PSUsername, $PSSecurePassword)
Invoke-Command -ComputerName localhost -Credential $PSCredential -ScriptBlock {
$path = "${var.CC_Install_LogPath}"
# Correct the Resource Location ID in cwc.json file
$requestUri = "https://api-eu.cloud.com/resourcelocations"
$headers = @{ "Accept"="application/json"; "Authorization" = "${var.CC_APIKey-Bearer}"; "Citrix-CustomerId" = "${var.CC_CustomerID}"}
$response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers | Convertto-Json
$RLs = ConvertFrom-Json $response
$RLFiltered = $RLs.items | Where-Object name -in "${var.CC_RestRLName}"
Add-Content ${var.CC_Install_LogPath}/log.txt $RLFiltered
$RLID = $RLFiltered.id
$OrigContent = Get-Content ${var.CC_Install_LogPath}/DATA/cwc.json
Add-Content ${var.CC_Install_LogPath}/log.txt $RLID
Add-Content ${var.CC_Install_LogPath}/log.txt $OrigContent
$CorrContent = $OrigCOntent.Replace('XXXXXXXXXX', $RLID) | Out-File -FilePath ${var.CC_Install_LogPath}/DATA/cwc.json
Add-Content ${var.CC_Install_LogPath}/DATA/GetRLID.txt $RLID
Add-Content ${var.CC_Install_LogPath}/log.txt "`ncwc.json corrected."
Add-Content ${var.CC_Install_LogPath}/log.txt "`nScript completed."
}
EOT
filename = "${path.module}/DATA/InstallPreReqsOnAVM2.ps1"
}
|
The Terraform configuration contains some idle time slots to make sure that background operations on Amazon EC2 or on the VMs can be completed before the next configuration steps occur.
We have seen different elapsed configuration times related to different loads on the Amazon EC2 systems!
Before running Terraform, we cannot see the Resource Location:
The configuration can be started by following the normal Terraform workflow:
terraform init
,
terraform plan
and if no errors occur
terraform apply
|
This configuration completes the creation and configuration of all initial resources:
- Installing the needed software on the CCs
- Creating a Resource Location in Citrix Cloud
- Configuring the 2 CCs as Cloud Connectors
- Registering the 2 CCs in the newly created Resource Location
After successful runs of all needed scripts we can see the new Resource Location in Citrix Cloud and the 2 Cloud Connectors bound to the Resource Location:
The environment is now ready to deploy a Machine Catalog and a Delivery Group using Module 3.
Module 3: Create all Resources in Amazon EC2 and Citrix Cloud
This module is split into the following configuration parts:
- Creating a Hypervisor Connection to Amazon EC2 and a Hypervisor Pool
- Creating a Machine Catalog (MC) in the newly created Resource Location
- Creating a Delivery Group (DG) based on the MC in the newly created Resource Location
- Deploying some example policies using Terraform
The Terraform configuration contains some idle time slots to make sure that background operations on Amazon EC2 or the VMs can be completed before the next configuration steps occur.
We have seen different elapsed configuration times related to different loads on the Amazon EC2 systems! Before Terraform can create the Hypervisor Connection and the Hypervisor Pool, Terraform needs to retrieve the Site-ID and Zone-ID of the newly created Resource Location. As the Citrix Terraform Provider currently has no Cloud-level functionalities implemented, Terraform needs PowerShell scripts to retrieve the IDs. It created the necessary scripts with all the needed variables, saved the scripts, and ran them in Module 2.
NOTE: The Citrix Remote PowerShell SDK must be installed on the machine running Terraform as Terraform relies on the SDK!
After retrieving the IDs, Terraform configures a Hypervisor Connection to Amazon EC2 and a Hypervisor Resource Pool associated with the Hypervisor Connection. When these prerequisites are completed, the Machine Catalog is created. After the successful creation of the Hypervisor Connection, the Hypervisor Resource Pool, and the Machine Catalog the last step of the deployment process starts - the creation of the Delivery Group.
The Terraform configuration assumes that all machines in the created Machine Catalog are used in the Delivery Group and that Autoscale will be configured for this Delivery Group.
More information about Autoscale can be found here: https://docs.citrix.com/en-us/tech-zone/learn/tech-briefs/autoscale.html
NOTE: This module has some complexity as many automated actions and many different entities are created. The entities can be configured using the corresponding
.auto.tfvars.json
file.
The deployment of Citrix Policies is a new feature that was built-in in version 0.5.2.
We need to know the internal policy name as localized policy names and descriptions are not usable.
Therefore we need to use a PowerShell script to determine all internal names - some pre-requisites are necessary for the script to work.
You can need any machine but the Cloud Connectors!
- Install the Citrix Supportability Pack
- Install the Citrix Group Policy Management - scroll down to Group Policy
|
Using these names allows the deployment of policies by using the Terraform provider.
NOTE: Currently it is only supported to deploy policies using Terraform on-premises with CVAD 2402 or higher. Trying to deploy policies on a DaaS-based environment does not work yet, as the needed back-end configurations are not generally available yet. We update this guide when the Cloud back-end has all modifications rolled out.
Caution:
Be sure that your subscription has no quota limitation on the chosen VM type and you have enough resources on Amazon EC2 to create all the Virtual Machines planned to put into the Machine Catalog by checking quotas - otherwise the creation of the Machine Catalog will fail if there are not enough compute resources available!
Before running Terraform no Terraform-related entities are available:
The configuration can be started by following the normal Terraform workflow:
terraform init
,
terraform plan
and if no errors occur
terraform apply
|
This configuration completes the full deployment of a Citrix Cloud Resource Location in Microsoft Amazon EC2.
The environment created by Terraform is now ready for usage. All entities are in place:
The Resource Location:
The Hypervisor Connection and the Hypervisor Pool:
The Machine Catalog:
The Worker VM in the Machine Catalog:
The Delivery Group:
The AutoScale settings of the Delivery Group:
The Desktop in the Library:
The Desktop in the Library:
Connection to the Worker VM´s Desktop:
Appendix
Examples of the Terraform scripts
Module 1: CConAWS-Creation
These are the Terraform configuration files for Module 1 (excerpts):
_CCOnAWS-Creation-Provider.tf
# Terraform deployment of Citrix DaaS on Amazon AWS EC2
## Definition of all required Terraform providers
terraform {
required_version = ">= 1.4.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.4.0"
}
restapi = {
source = "Mastercard/restapi"
version = "1.18.2"
}
citrix = {
source = "citrix/citrix"
version = ">=0.5.3"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "${var.AWSEC2_Region}"
access_key = "${var.AWSEC2_AccessKey}"
secret_key = "${var.AWSEC2_AccessKeySecret}"
}
# Configure the Citrix Provider
provider "citrix" {
customer_id = "${var.CC_CustomerID}"
client_id = "${var.CC_APIKey-ClientID}"
client_secret = "${var.CC_APIKey-ClientSecret}"
}
# Configure the REST-API provider
provider "restapi" {
alias = "restapi_rl"
uri = "${var.CC_RestAPIURI}"
create_method = "POST"
write_returns_object = true
debug = true
headers = {
"Content-Type" = "application/json",
"Citrix-CustomerId" = "${var.CC_CustomerID}",
"Accept" = "application/json",
"Authorization" = "${var.CC_APIKey-Bearer}"
}
}
|
_CCOnAWS-Creation-Create.tf
# Terraform deployment of Citrix DaaS on Amazon AWS EC2
## Creation of all required VMs - two Cloud Connectors and one Worker Master image
locals {
}
### Create needed IAM roles
#### IAM EC2 Policy with Assume Role
data "aws_iam_policy_document" "ec2_assume_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
#### Create EC2 IAM Role
resource "aws_iam_role" "ec2_iam_role" {
name = "ec2-iam-role"
path = "/"
assume_role_policy = data.aws_iam_policy_document.ec2_assume_role.json
}
#### Create EC2 IAM Instance Profile
resource "aws_iam_instance_profile" "ec2_profile" {
name = "ec2-profile"
role = aws_iam_role.ec2_iam_role.name
}
#### Attach Policies to Instance Role
resource "aws_iam_policy_attachment" "ec2_attach1" {
name = "ec2-iam-attachment"
roles = [aws_iam_role.ec2_iam_role.id]
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_policy_attachment" "ec2_attach2" {
name = "ec2-iam-attachment"
roles = [aws_iam_role.ec2_iam_role.id]
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
}
#### Create Secret Manager IAM Policy
resource "aws_iam_policy" "secret_manager_ec2_policy" {
name = "secret-manager-ec2-policy"
description = "Secret Manager EC2 policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"secretsmanager:*"
]
Effect = "Allow"
Resource = "*"
},
]
})
}
#### Attach Secret Manager Policies to Instance Role
resource "aws_iam_policy_attachment" "api_secret_manager_ec2_attach" {
name = "secret-manager-ec2-attachment"
roles = [aws_iam_role.ec2_iam_role.id]
policy_arn = aws_iam_policy.secret_manager_ec2_policy.arn
}
data "template_file" "Add-EC2InstanceToDomainScriptCC1" {
template = file("${path.module}/Add-EC2InstanceToDomainCC1.ps1")
vars = {
ad_secret_id = "AD/SA/DomainJoin"
ad_domain = "aws.the-austrian.citrix.guy.at"
}
}
data "template_file" "Add-EC2InstanceToDomainScriptCC2" {
template = file("${path.module}/Add-EC2InstanceToDomainCC2.ps1")
vars = {
ad_secret_id = "AD/SA/DomainJoin"
ad_domain = "aws.the-austrian.citrix.guy.at"
}
}
data "template_file" "Add-EC2InstanceToDomainScriptWMI" {
template = file("${path.module}/Add-EC2InstanceToDomainWMI.ps1")
vars = {
ad_secret_id = "AD/SA/DomainJoin"
ad_domain = "aws.the-austrian.citrix.guy.at"
}
}
data "template_file" "Add-EC2InstanceToDomainScriptAdminVM" {
template = file("${path.module}/Add-EC2InstanceToDomainAdminVM.ps1")
vars = {
ad_secret_id = "AD/SA/DomainJoin"
ad_domain = "aws.the-austrian.citrix.guy.at"
}
}
#### Create DHCP settings
resource "aws_vpc_dhcp_options" "vpc-dhcp-options" {
depends_on = [ data.template_file.Add-EC2InstanceToDomainScriptCC1 ]
domain_name_servers = [ var.AWSEC2_DC-IP ]
}
resource "aws_vpc_dhcp_options_association" "dns_resolver" {
vpc_id = var.AWSEC2_VPC-ID
dhcp_options_id = aws_vpc_dhcp_options.vpc-dhcp-options.id
}
### Create CC1-VM
resource "aws_instance" "CC1" {
depends_on = [ data.template_file.Add-EC2InstanceToDomainScriptCC1 ]
ami = var.AWSEC2_AMI
subnet_id = var.AWSEC2_Subnet-ID
private_ip = var.AWSEC2_PrivIP-CC1
instance_type = var.AWSEC2_Instance-Type
key_name = var.AWSEC2_AMI-KeyPairName
vpc_security_group_ids = [ var.AWSEC2_SecurityGroup ]
tags = {
Name = var.AWSEC2_InstanceName-CC1
}
user_data = data.template_file.Add-EC2InstanceToDomainScriptCC1.rendered
iam_instance_profile = aws_iam_instance_profile.ec2_profile.id
}
### Create CC2-VM
resource "aws_instance" "CC2" {
depends_on = [ data.template_file.Add-EC2InstanceToDomainScriptCC2 ]
ami = var.AWSEC2_AMI
subnet_id = var.AWSEC2_Subnet-ID
private_ip = var.AWSEC2_PrivIP-CC2
instance_type = var.AWSEC2_Instance-Type
key_name = var.AWSEC2_AMI-KeyPairName
vpc_security_group_ids = [ var.AWSEC2_SecurityGroup ]
tags = {
Name = var.AWSEC2_InstanceName-CC2
}
user_data = data.template_file.Add-EC2InstanceToDomainScriptCC2.rendered
iam_instance_profile = aws_iam_instance_profile.ec2_profile.id
}
### Create Admin-VM
resource "aws_instance" "AdminVM" {
depends_on = [ data.template_file.Add-EC2InstanceToDomainScriptAdminVM ]
ami = var.AWSEC2_AMI
subnet_id = var.AWSEC2_Subnet-ID
private_ip = var.AWSEC2_PrivIP-AdminVM
instance_type = var.AWSEC2_Instance-Type-Worker
key_name = var.AWSEC2_AMI-KeyPairName
vpc_security_group_ids = [ var.AWSEC2_SecurityGroup ]
tags = {
Name = var.AWSEC2_InstanceName-AdminVM
}
user_data = data.template_file.Add-EC2InstanceToDomainScriptAdminVM.rendered
iam_instance_profile = aws_iam_instance_profile.ec2_profile.id
}
### Create WMI-VM
resource "aws_instance" "WMI" {
depends_on = [ data.template_file.Add-EC2InstanceToDomainScriptWMI ]
ami = var.AWSEC2_AMI
subnet_id = var.AWSEC2_Subnet-ID
private_ip = var.AWSEC2_PrivIP-WMI
instance_type = var.AWSEC2_Instance-Type
key_name = var.AWSEC2_AMI-KeyPairName
vpc_security_group_ids = [ var.AWSEC2_SecurityGroup ]
tags = {
Name = var.AWSEC2_InstanceName-WMI
}
user_data = data.template_file.Add-EC2InstanceToDomainScriptWMI.rendered
iam_instance_profile = aws_iam_instance_profile.ec2_profile.id
}
|
_CCOnAWS-Creation-GetBearerToken.tf
### Create PowerShell file for retrieving the Bearer Token
resource "local_file" "GetBearerToken" {
content = <<-EOT
asnp Citrix*
$key= "${var.CC_APIKey-ClientID}"
$secret= "${var.CC_APIKey-ClientSecret}"
$customer= "${var.CC_CustomerID}"
$XDStoredCredentials = Set-XDCredentials -StoreAs default -ProfileType CloudApi -CustomerId $customer -APIKey $key -SecretKey $secret
$auth = Get-XDAuthentication
$BT = $GLOBAL:XDAuthToken | Out-File "${path.module}/GetBT.txt"
EOT
filename = "${path.module}/GetBT.ps1"
}
### Running GetBearertoken-Script to retrieve the Bearer Token
resource "terraform_data" "GetBT" {
depends_on = [ local_file.GetBearerToken ]
provisioner "local-exec" {
command = "${path.module}/GetBT.ps1"
interpreter = ["PowerShell", "-File"]
}
}
### Retrieving the Bearer Token
data "local_file" "Retrieve_BT" {
depends_on = [ terraform_data.GetBT ]
filename = "${path.module}/GetBT.txt"
}
output "terraform_data_BR_Read" {
value = data.local_file.Retrieve_BT.content
}
|
Module 2: CConAWS-Install
These are the Terraform configuration files for Module 2 (excerpts):
_CCOnAWS-Install-CreatePreReqs.tf
# Terraform deployment of Citrix DaaS on Amazon AWS EC2
## Creating a dedicated Resource Location on Citrix Cloud
resource "random_uuid" "IDforCCRL" {
}
### Create local directory
resource "local_file" "Log" {
content = "Directory created."
filename = "${var.CC_Install_LogPath}/log.txt"
}
resource "local_file" "LogData" {
depends_on = [ local_file.Log ]
content = "Directory created."
filename = "${var.CC_Install_LogPath}/DATA/log.txt"
}
### Create PowerShell command to be run on the CC machines
locals {
randomuuid = random_uuid.IDforCCRL.result
}
### Create a dedicated Resource Location in Citrix Cloud
resource "restapi_object" "CreateRL" {
depends_on = [ local_file.log ]
provider = restapi.restapi_rl
path="/resourcelocations"
data = jsonencode(
{
"id" = "${local.randomuuid}",
"name" = "${var.CC_RestRLName}",
"internalOnly" = false,
"timeZone" = "GMT Standard Time",
"readOnly" = false
}
)
}
### Create PowerShell files with configuration and next steps and save it into Transfer directory
#### Create CWC-Installer configuration file based on variables and save it into Transfer directory
resource "local_file" "CWC-Configuration" {
depends_on = [restapi_object.CreateRL]
content = jsonencode(
{
"customerName" = "${var.CC_CustomerID}",
"clientId" = "${var.CC_APIKey-ClientID}",
"clientSecret" = "${var.CC_APIKey-ClientSecret}",
"resourceLocationId" = "XXXXXXXXXX",
"acceptTermsOfService" = true
}
)
filename = "${var.CC_Install_LogPath}/DATA/cwc.json"
}
#### Wait 5 mins after RL creation to settle Zone creation
resource "time_sleep" "wait_300_seconds" {
create_duration = "300s"
}
### Create PowerShell file for determining the SiteID
resource "local_file" "GetSiteIDScript" {
depends_on = [restapi_object.CreateRL, time_sleep.wait_300_seconds]
content = <<-EOT
$requestUri = "https://api-eu.cloud.com/cvad/manage/me"
$headers = @{ "Accept"="application/json"; "Authorization" = "${var.CC_APIKey-Bearer}"; "Citrix-CustomerId" = "${var.CC_CustomerID}" }
$response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers | Select-Object Customers
$responsetojson = $response | Convertto-Json -Depth 3
$responsekorr = $responsetojson -replace("null","""empty""")
$responsefromjson = $responsekorr | Convertfrom-json
$SitesObj=$responsefromjson.Customers[0].Sites[0]
$Export1 = $SitesObj -replace("@{Id=","")
$SplittedString = $Export1.Split(";")
$SiteID= $SplittedString[0]
$PathCompl = "${var.CC_Install_LogPath}/DATA/GetSiteID.txt"
Set-Content -Path $PathCompl -Value $SiteID
EOT
filename = "${path.module}/GetSiteID.ps1"
}
### Running the SiteID-Script to generate the SiteID
resource "terraform_data" "SiteID" {
depends_on = [ local_file.GetSiteIDScript ]
provisioner "local-exec" {
command = "GetSiteID.ps1"
interpreter = ["PowerShell", "-File"]
}
}
### Retrieving the SiteID
data "local_file" "input_site" {
depends_on = [ terraform_data.SiteID ]
filename = "${var.CC_Install_LogPath}/DATA/GetSiteID.txt"
}
### Create PowerShell file for determining the ZoneID
resource "local_file" "GetZoneIDScript" {
depends_on = [ data.local_file.input_site ]
content = <<-EOT
$requestUri = "https://api-eu.cloud.com/cvad/manage/Zones"
$headers = @{ "Accept"="application/json"; "Authorization" = "${var.CC_APIKey-Bearer}"; "Citrix-CustomerId" = "${var.CC_CustomerID}"; "Citrix-InstanceId" = "${data.local_file.input_site.content}" }
$response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers | Convertto-Json
$responsedejson = $response | ConvertFrom-Json
$ZoneId = $responsedejson.Items | Where-Object { $_.Name -eq "${var.CC_RestRLName}" } | Select-Object id
$Export1 = $ZoneId -replace("@{Id=","")
$ZoneID = $Export1 -replace("}","")
$PathCompl = "${var.CC_Install_LogPath}/DATA/GetZoneID.txt"
Set-Content -Path $PathCompl -Value $ZoneID
EOT
filename = "${path.module}/GetZoneID.ps1"
}
### Running the ZoneID-Script to generate the ZoneID
resource "terraform_data" "ZoneID" {
depends_on = [ local_file.GetZoneIDScript ]
provisioner "local-exec" {
command = "GetZoneID.ps1"
interpreter = ["PowerShell", "-File"]
}
}
#### Create PowerShell file for installing the Citrix Cloud Connector - we need to determine the correct RL-ID
resource "local_file" "InstallPreReqsOnCC-ps1" {
content = <<-EOT
#### Need to invoke PowerShell as Domain User as the provisioner does not allow to be run in a Domain Users-context
$PSUsername = '${var.Provisioner_DomainAdmin-Username}'
$PSPassword = '${var.Provisioner_DomainAdmin-Password}'
$PSSecurePassword = ConvertTo-SecureString $PSPassword -AsPlainText -Force
$PSCredential = New-Object System.Management.Automation.PSCredential ($PSUsername, $PSSecurePassword)
Invoke-Command -ComputerName localhost -Credential $PSCredential -ScriptBlock {
$path = "${var.CC_Install_LogPath}"
If(!(test-path -PathType container $path))
{
New-Item -ItemType Directory -Path $path
}
Add-Content ${var.CC_Install_LogPath}/log.txt "`nScript started."
# Download the Citrix Cloud Connector-Software to CC
Invoke-WebRequest ${var.CC_Install_CWCURI} -OutFile '${var.CC_Install_LogPath}/DATA/CWCConnector.exe'
# Install Citrix Cloud Controller based on the cwc.json configuration file
Add-Content ${var.CC_Install_LogPath}/log.txt "`nInstalling Cloud Connector."
Start-Process -Filepath "${var.CC_Install_LogPath}/DATA/CWCConnector.exe" -ArgumentList "/q /ParametersFilePath:${var.CC_Install_LogPath}/DATA/cwc.json"
Add-Content ${var.CC_Install_LogPath}/log.txt "`nInstalled Cloud Connector."
Restart-Computer -Force -Timeout 1800
}
EOT
filename = "${path.module}/DATA/InstallPreReqsOnCC.ps1"
}
#### Create PowerShell file for installing the Citrix Remote PoSH SDK on AVM - we need to determine the correct RL-ID
resource "local_file" "InstallPreReqsOnAVM1-ps1" {
content = <<-EOT
#### Need to invoke PowerShell as Domain User as the provisioner does not allow to be run in a Domain Users-context
$PSUsername = '${var.Provisioner_DomainAdmin-Username}'
$PSPassword = '${var.Provisioner_DomainAdmin-Password}'
$PSSecurePassword = ConvertTo-SecureString $PSPassword -AsPlainText -Force
$PSCredential = New-Object System.Management.Automation.PSCredential ($PSUsername, $PSSecurePassword)
Invoke-Command -ComputerName localhost -Credential $PSCredential -ScriptBlock {
$path = "${var.CC_Install_LogPath}"
If(!(test-path -PathType container $path))
{
New-Item -ItemType Directory -Path $path
}
Add-Content ${var.CC_Install_LogPath}/log.txt "`nScript started."
# Download Citrix Remote PowerShell SDK
Invoke-WebRequest '${var.CC_Install_RPoSHURI}' -OutFile '${var.CC_Install_LogPath}/DATA/CitrixPoshSdk.exe'
Add-Content ${var.CC_Install_LogPath}/log.txt "`nPowerShell SDK downloaded."
# Install Citrix Remote PowerShell SDK
Start-Process -Filepath "${var.CC_Install_LogPath}/DATA/CitrixPoshSdk.exe" -ArgumentList "-quiet"
Add-Content ${var.CC_Install_LogPath}/log.txt "`nPowerShell SDK installed."
# Timeout to settle all processes
Start-Sleep -Seconds 60
Add-Content ${var.CC_Install_LogPath}/log.txt "`nTimeout elapsed."
}
EOT
filename = "${path.module}/DATA/InstallPreReqsOnAVM1.ps1"
}
#### Create PowerShell file for installing the Citrix Remote PoSH SDK on AVM - we need to determine the correct RL-ID
resource "local_file" "InstallPreReqsOnAVM2-ps1" {
content = <<-EOT
#### Need to invoke PowerShell as Domain User as the provisioner does not allow to be run in a Domain Users-context
$PSUsername = '${var.Provisioner_DomainAdmin-Username}'
$PSPassword = '${var.Provisioner_DomainAdmin-Password}'
$PSSecurePassword = ConvertTo-SecureString $PSPassword -AsPlainText -Force
$PSCredential = New-Object System.Management.Automation.PSCredential ($PSUsername, $PSSecurePassword)
Invoke-Command -ComputerName localhost -Credential $PSCredential -ScriptBlock {
$path = "${var.CC_Install_LogPath}"
# Correct the Resource Location ID in cwc.json file
$requestUri = "https://api-eu.cloud.com/resourcelocations"
$headers = @{ "Accept"="application/json"; "Authorization" = "${var.CC_APIKey-Bearer}"; "Citrix-CustomerId" = "${var.CC_CustomerID}"}
$response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers | Convertto-Json
$RLs = ConvertFrom-Json $response
$RLFiltered = $RLs.items | Where-Object name -in "${var.CC_RestRLName}"
Add-Content ${var.CC_Install_LogPath}/log.txt $RLFiltered
$RLID = $RLFiltered.id
$OrigContent = Get-Content ${var.CC_Install_LogPath}/DATA/cwc.json
Add-Content ${var.CC_Install_LogPath}/log.txt $RLID
Add-Content ${var.CC_Install_LogPath}/log.txt $OrigContent
$CorrContent = $OrigCOntent.Replace('XXXXXXXXXX', $RLID) | Out-File -FilePath ${var.CC_Install_LogPath}/DATA/cwc.json
Add-Content ${var.CC_Install_LogPath}/DATA/GetRLID.txt $RLID
Add-Content ${var.CC_Install_LogPath}/log.txt "`ncwc.json corrected."
Add-Content ${var.CC_Install_LogPath}/log.txt "`nScript completed."
}
EOT
filename = "${path.module}/DATA/InstallPreReqsOnAVM2.ps1"
}
### Upload required components to AVM
#### Set the Provisioner-Connection
resource "null_resource" "UploadRequiredComponentsToAVM" {
depends_on = [ local_file.InstallPreReqsOnAVM1-ps1, local_file.GetSiteIDScript ]
connection {
type = var.Provisioner_Type
user = var.Provisioner_Admin-Username
password = var.Provisioner_Admin-Password
host = var.Provisioner_AVM-IP
timeout = var.Provisioner_Timeout
}
###### Upload PreReqs script to AVM
provisioner "file" {
source = "${path.module}/DATA/InstallPreReqsOnAVM1.ps1"
destination = "${var.CC_Install_LogPath}/DATA/InstallPreReqsOnAVM1.ps1"
}
provisioner "file" {
source = "${path.module}/DATA/InstallPreReqsOnAVM2.ps1"
destination = "${var.CC_Install_LogPath}/DATA/InstallPreReqsOnAVM2.ps1"
}
}
### Call the required scripts on AVM
#### Set the Provisioner-Connection
resource "null_resource" "CallRequiredScriptsOnAVM1" {
depends_on = [ null_resource.UploadRequiredComponentsToAVM ]
connection {
type = var.Provisioner_Type
user = var.Provisioner_Admin-Username
password = var.Provisioner_Admin-Password
host = var.Provisioner_AVM-IP
timeout = var.Provisioner_Timeout
}
###### Execute the PreReqs script on AVM
provisioner "remote-exec" {
inline = [
"powershell -File ${var.CC_Install_LogPath}/DATA/InstallPreReqsOnAVM1.ps1"
]
}
}
resource "null_resource" "CallRequiredScriptsOnAVM2" {
depends_on = [ null_resource.CallRequiredScriptsOnAVM1 ]
connection {
type = var.Provisioner_Type
user = var.Provisioner_Admin-Username
password = var.Provisioner_Admin-Password
host = var.Provisioner_AVM-IP
timeout = var.Provisioner_Timeout
}
###### Execute the PreReqs script on AVM
provisioner "remote-exec" {
inline = [
"powershell -File ${var.CC_Install_LogPath}/DATA/InstallPreReqsOnAVM2.ps1"
]
}
}
##############################################################################################################################
### Upload required components to CC1
#### Set the Provisioner-Connection
resource "null_resource" "UploadRequiredComponentsToCC1" {
depends_on = [ local_file.InstallPreReqsOnCC-ps1, null_resource.CallRequiredScriptsOnAVM2]
connection {
type = var.Provisioner_Type
user = var.Provisioner_Admin-Username
password = var.Provisioner_Admin-Password
host = var.Provisioner_DDC1-IP
timeout = var.Provisioner_Timeout
}
###### Upload Cloud Connector configuration file to CC1
provisioner "file" {
source = "${var.CC_Install_LogPath}/DATA/cwc.json"
destination = "${var.CC_Install_LogPath}/DATA/cwc.json"
}
###### Upload SiteID file to CC1
provisioner "file" {
source = "${var.CC_Install_LogPath}/DATA/GetSiteID.txt"
destination = "${var.CC_Install_LogPath}/DATA/GetSiteID.txt"
}
###### Upload ZoneID file to CC1
provisioner "file" {
source = "${var.CC_Install_LogPath}/DATA/GetZoneID.txt"
destination = "${var.CC_Install_LogPath}/DATA/GetZoneID.txt"
}
###### Upload RLID file to CC1
provisioner "file" {
source = "${var.CC_Install_LogPath}/DATA/GetRLID.txt"
destination = "${var.CC_Install_LogPath}/DATA/GetRLID.txt"
}
###### Upload PreReqs script to CC1
provisioner "file" {
source = "${path.module}/DATA/InstallPreReqsOnCC.ps1"
destination = "${var.CC_Install_LogPath}/DATA/InstallPreReqsOnCC.ps1"
}
}
###### Execute the PreReqs script on CC1
resource "null_resource" "CallRequiredScriptsOnCC1" {
depends_on = [ null_resource.UploadRequiredComponentsToCC1 ]
connection {
type = var.Provisioner_Type
user = var.Provisioner_Admin-Username
password = var.Provisioner_Admin-Password
host = var.Provisioner_AVM-IP
timeout = var.Provisioner_Timeout
}
provisioner "remote-exec" {
inline = [
"powershell -File ${var.CC_Install_LogPath}/DATA/InstallPreReqsOnCC.ps1"
]
}
}
### Upload required components to CC2
#### Set the Provisioner-Connection
resource "null_resource" "UploadRequiredComponentsToCC2" {
depends_on = [ local_file.InstallPreReqsOnCC-ps1,null_resource.CallRequiredScriptsOnAVM2 ]
connection {
type = var.Provisioner_Type
user = var.Provisioner_Admin-Username
password = var.Provisioner_Admin-Password
host = var.Provisioner_DDC2-IP
timeout = var.Provisioner_Timeout
}
###### Upload Cloud Connector configuration file to CC2
provisioner "file" {
source = "${var.CC_Install_LogPath}/DATA/cwc.json"
destination = "${var.CC_Install_LogPath}/DATA/cwc.json"
}
###### Upload SiteID file to CC2
provisioner "file" {
source = "${var.CC_Install_LogPath}/DATA/GetSiteID.txt"
destination = "${var.CC_Install_LogPath}/DATA/GetSiteID.txt"
}
###### Upload ZoneID file to CC2
provisioner "file" {
source = "${var.CC_Install_LogPath}/DATA/GetZoneID.txt"
destination = "${var.CC_Install_LogPath}/DATA/GetZoneID.txt"
}
###### Upload RLID file to CC2
provisioner "file" {
source = "${var.CC_Install_LogPath}/DATA/GetRLID.txt"
destination = "${var.CC_Install_LogPath}/DATA/GetRLID.txt"
}
###### Upload PreReqs script to CC2
provisioner "file" {
source = "${path.module}/DATA/InstallPreReqsOnCC.ps1"
destination = "${var.CC_Install_LogPath}/DATA/InstallPreReqsOnCC.ps1"
}
}
###### Execute the PreReqs script on CC2
resource "null_resource" "CallRequiredScriptsOnCC2" {
depends_on = [ null_resource.UploadRequiredComponentsToCC2 ]
connection {
type = var.Provisioner_Type
user = var.Provisioner_Admin-Username
password = var.Provisioner_Admin-Password
host = var.Provisioner_AVM-IP
timeout = var.Provisioner_Timeout
}
provisioner "remote-exec" {
inline = [
"powershell -File ${var.CC_Install_LogPath}/DATA/InstallPreReqsOnCC.ps1"
]
}
}
|
Module 3: CConAWS-CCStuff
These are the Terraform configuration files for Module 3 (excerpts):
_CCOnAWS-CCStuff-CreateCCEntities.tf
# Terraform deployment of Citrix DaaS on Amazon AWS EC2
## Creating all Citrix Cloud-related entities
### Creating a Hypervisor Connection
#### Retrieving the ZoneID
data "local_file" "LoadZoneID" {
filename = "${var.CC_Install_LogPath}/DATA/GetZoneID.txt"
}
#### Creating the Hypervisor Connection
resource "citrix_aws_hypervisor" "CreateHypervisorConnection" {
depends_on = [ data.local_file.LoadZoneID ]
name = "${var.CC_AWSEC2-HypConn-Name}"
zone = data.local_file.LoadZoneID.content
api_key = "${var.AWSEC2_AccessKey}"
secret_key = "${var.AWSEC2_AccessKeySecret}"
region = "${var.AWSEC2_Region}"
}
### Creating a Hypervisor Resource Pool
#### Retrieving the VPC name based on the VPC ID
data "aws_vpc" "AWSVPC" {
id = "${var.AWSEC2_VPC-ID}"
}
#### Retrieving the Availability Zone
data "aws_vpc" "AWSAZ" {
id = "${var.AWSEC2_VPC-ID}"
}
#### Retrieving the Subnet Mask based on the Subnet ID
data "aws_subnet" "AWSSubnet" {
id = "${var.AWSEC2_Subnet-ID}"
}
#### Create the Hypervisor Resource Pool
resource "citrix_aws_hypervisor_resource_pool" "CreateHypervisorPool" {
depends_on = [ citrix_aws_hypervisor.CreateHypervisorConnection ]
name = "${var.CC_AWSEC2-HypConnPool-Name}"
hypervisor = citrix_aws_hypervisor.CreateHypervisorConnection.id
subnets = [
"${data.aws_subnet.AWSSubnet.cidr_block}",
]
vpc = "${var.AWSEC2_VPC-Name}"
availability_zone = data.aws_subnet.AWSSubnet.availability_zone
}
#### Create AMI from WMI instance
resource "aws_ami_from_instance" "CreateAMIFromWMI" {
name = "TACG-AWS-TF-AMIFromWMI"
source_instance_id = "${var.AWSEC2_AMI-ID}"
timeouts {
create = "45m"
}
}
#### Sleep 60s to let AWS Background processes settle
resource "time_sleep" "wait_60_seconds" {
depends_on = [ citrix_aws_hypervisor_resource_pool.CreateHypervisorPool, aws_ami_from_instance.CreateAMIFromWMI ]
create_duration = "60s"
}
#### Create the Machine Catalog
resource "citrix_machine_catalog" "CreateMCSCatalog" {
depends_on = [ time_sleep.wait_60_seconds ]
name = "${var.CC_AWSEC2-MC-Name}"
description = "${var.CC_AWSEC2-MC-Description}"
allocation_type = "${var.CC_AWSEC2-MC-AllocationType}"
session_support = "${var.CC_AWSEC2-MC-SessionType}"
is_power_managed = true
is_remote_pc = false
provisioning_type = "MCS"
zone = data.local_file.LoadZoneID.content
provisioning_scheme = {
hypervisor = citrix_aws_hypervisor.CreateHypervisorConnection.id
hypervisor_resource_pool = citrix_aws_hypervisor_resource_pool.CreateHypervisorPool.id
identity_type = "${var.CC_AWSEC2-MC-IDPType}"
machine_domain_identity = {
domain = "${var.CC_AWSEC2-MC-Domain}"
#domain_ou = "${var.CC_AWSEC2-MC-DomainOU}"
service_account = "${var.Provisioner_DomainAdmin-Username-UPN}"
service_account_password = "${var.Provisioner_DomainAdmin-Password}"
}
aws_machine_config = {
image_ami = aws_ami_from_instance.CreateAMIFromWMI.id
master_image = aws_ami_from_instance.CreateAMIFromWMI.name
service_offering = "${var.CC_AWSEC2-MC-Service_Offering}"
}
number_of_total_machines = "${var.CC_AWSEC2-MC-Machine_Count}"
network_mapping = {
network_device = "0"
network = "${data.aws_subnet.AWSSubnet.cidr_block}"
}
machine_account_creation_rules = {
naming_scheme = "${var.CC_AWSEC2-MC-Naming_Scheme_Name}"
naming_scheme_type = "${var.CC_AWSEC2-MC-Naming_Scheme_Type}"
}
}
}
#### Sleep 60s to let CC Background processes settle
resource "time_sleep" "wait_60_seconds_1" {
#depends_on = [ citrix_machine_catalog.CreateMCSCatalog ]
create_duration = "60s"
}
#### Create an Example-Policy Set
resource "citrix_policy_set" "SetPolicies" {
count = var.CC_AWSEC2-Policy-IsNotDaaS ? 1 : 0
depends_on = [ time_sleep.wait_60_seconds_1 ]
name = "${var.CC_AWSEC2-Policy-Name}"
description = "${var.CC_AWSEC2-Policy-Description}"
type = "DeliveryGroupPolicies"
scopes = [ "All" ]
policies = [
{
name = "TACG-AWS-TF-Pol1"
description = "Policy to enable use of Universal Printer"
is_enabled = true
policy_settings = [
{
name = "UniversalPrintDriverUsage"
value = "Use universal printing only"
use_default = false
},
]
policy_filters = [
{
type = "DesktopGroup"
is_enabled = true
is_allowed = true
},
]
},
{
name = "TACG-AWS-TF-Pol2"
description = "Policy to enable Client Drive Redirection"
is_enabled = true
policy_settings = [
{
name = "UniversalPrintDriverUsage"
value = "Prohibited"
use_default = false
},
]
policy_filters = [
{
type = "DesktopGroup"
is_enabled = true
is_allowed = true
},
]
}
]
}
#### Sleep 60s to let CC Background processes settle
resource "time_sleep" "Wait_60_Seconds_2" {
depends_on = [ citrix_policy_set.SetPolicies ]
create_duration = "60s"
}
#### Create the Delivery Group based on the Machine Catalog
resource "citrix_delivery_group" "CreateDG" {
depends_on = [ time_sleep.Wait_60_Seconds_2]
name = "${var.CC_AWSEC2-DG-Name}"
associated_machine_catalogs = [
{
#machine_catalog = citrix_machine_catalog.CreateMCSCatalog.id
machine_catalog ="f4e34a11-6e31-421f-8cb4-060bc4a13fef"
machine_count = "${var.CC_AWSEC2-MC-Machine_Count}"
}
]
desktops = [
{
published_name = "${var.CC_AWSEC2-DG-PublishedDesktopName}"
description = "${var.CC_AWSEC2-DG-Description}"
restricted_access_users = {
allow_list = [ "TACG-AWS\\vdaallowed" ]
}
enabled = true
enable_session_roaming = var.CC_AWSEC2-DG-SessionRoaming
}
]
autoscale_settings = {
autoscale_enabled = true
disconnect_peak_idle_session_after_seconds = 300
log_off_peak_disconnected_session_after_seconds = 300
peak_log_off_action = "Nothing"
power_time_schemes = [
{
days_of_week = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday"
]
name = "${var.CC_AWSEC2-DG-AS-Name}"
display_name = "${var.CC_AWSEC2-DG-AS-Name}"
peak_time_ranges = [
"09:00-17:00"
]
pool_size_schedules = [
{
time_range = "09:00-17:00",
pool_size = 1
}
]
pool_using_percentage = false
},
]
}
restricted_access_users = {
allow_list = [ "TACG-AWS\\vdaallowed" ]
}
reboot_schedules = [
{
name = "TACG-AWS-Reboot Schedule"
reboot_schedule_enabled = true
frequency = "Weekly"
frequency_factor = 1
days_in_week = [
"Sunday",
]
start_time = "02:00"
start_date = "2024-01-01"
reboot_duration_minutes = 0
ignore_maintenance_mode = true
natural_reboot_schedule = false
}
]
policy_set_id = citrix_policy_set.SetPolicies.id
}
|
There are no comments to display.
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now