diff --git a/.taskcat.yml b/.taskcat.yml index c4b6629..eca7e44 100644 --- a/.taskcat.yml +++ b/.taskcat.yml @@ -41,28 +41,14 @@ tests: AvailabilityZones: "$[taskcat_getaz_2]" UiPathFQDN: test.shivansh.dev #testone.uipathmarketplace.net - # three-az-orchestrator: - # template: ./templates/main.template.yaml - # regions: - # - us-east-1 - # parameters: - # AvailabilityZones: "$[taskcat_getaz_3]" - # UiPathFQDN: testtwo.uipathmarketplace.net - - # add-license-orchestrator: - # template: ./templates/main.template.yaml - # regions: - # - us-west-1 - # parameters: - # AvailabilityZones: "$[taskcat_getaz_2]" - # UiPathFQDN: testthree.uipathmarketplace.net - # OrchestratorLicense: "orchlicense" - - # europe-orchestrator: - # template: ./templates/main.template.yaml - # regions: - # - eu-west-1 - # parameters: - # AvailabilityZones: "$[taskcat_getaz_2]" - # UiPathFQDN: testfour.uipathmarketplace.net - # OrchestratorInstanceCount: "3" + existing-vpc: + template: ./templates/uipath-orchestrator.template.yaml + regions: + - us-east-1 + parameters: + AvailabilityZones: "$[taskcat_getaz_2]" + UiPathFQDN: testtwo.shivansh.dev #testone.uipathmarketplace.net + VPCCIDR: 10.0.0.0/16 + VPCID: <> + PrivateSubnetIDs: <> + PublicSubnetIDs: <> diff --git a/README.md b/README.md index e7e1739..4f761b6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ This Quick Start is for users who wants to deploy UiPath Orchestrator on the AWS The [AWS CloudFormation template](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/template?stackName=uipath&templateURL=https://aws-quickstart.s3.amazonaws.com/quickstart-uipath-orchestrator/templates/main.template.yaml) included with the Quick Start automates deploying UiPath Orchestrator on AWS. You can also use the AWS CloudFormation template as a starting point for your own implementation. -![Quick Start architecture for UiPath Orchestrator on AWS](./UIPath-Orchestrator-arch.png) +For deploying into existing VPCs, this [AWS CloudFormation template](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/template?stackName=uipath&templateURL=https://aws-quickstart.s3.amazonaws.com/quickstart-uipath-orchestrator/templates/uipath-orchestrator.template.yaml) can be used as an entrypoint. + +![Quick Start architecture for UiPath Orchestrator on AWS](./architecture.png) As shown in the architecture diagram, the Quick Start sets up the following: diff --git a/architecture.png b/architecture.png new file mode 100644 index 0000000..13a0c3e Binary files /dev/null and b/architecture.png differ diff --git a/scripts/Install-UiPathRobots.ps1 b/scripts/Install-UiPathRobots.ps1 index 95e10c1..1039505 100644 --- a/scripts/Install-UiPathRobots.ps1 +++ b/scripts/Install-UiPathRobots.ps1 @@ -74,25 +74,25 @@ function Main { } Install-RobotWithMSI -msiPath $msiPath -robotInstallType $robotType - if ($robotType -eq "Unattended" -and $cloudName -eq "AWS") { - Add-LocalAdministrator -userName $userName - } - else { - Write-Output "Robot type selected is Attended, skipping adding local administrator." - } if ($orchestratorUrl -and $orchAdmin -and $orchPassword -and $tenant) { Write-Output "orchestratorUrl $orchestratorUrl" Write-Output "orchAdmin $orchAdmin" - Write-Output "orchPassword $orchPassword" Write-Output "tenant $tenant" $websession = Get-UiPathOrchestratorLoginSession Add-UiPathRobotFolder -session $websession -folderName $folderName if ($cloudName -eq "AWS") { - Add-UiPathRobotUser -session $websession -robotUsername "$env:computername\$userName" -userName $userName - Write-Output "Added AWS Robot: $userName" + if ($robotType -eq "Unattended") { + Add-LocalAdministrator -userName $userName + Add-UiPathRobotUser -session $websession -robotUsername "$env:computername\$userName" -userName $userName + Write-Output "Added AWS Robot: $userName" + } + elseif ($robotType -eq "Attended") { + Add-UiPathRobotUser -session $websession -robotUsername "$env:computername\Administrator" -userName $userName + Write-Output "Added AWS Robot: $userName" + } } elseif ($cloudName -eq "Azure") { Add-UiPathRobotUser -session $websession -userName $userName -robotUsername $robotUserName diff --git a/scripts/modules/AzureUtils.psm1 b/scripts/modules/AzureUtils.psm1 new file mode 100644 index 0000000..ebbbfcc --- /dev/null +++ b/scripts/modules/AzureUtils.psm1 @@ -0,0 +1,34 @@ +<# +.Description +Get-AzCloudEnvironment calls the instance metadata service to determine the environment on which the resource is deployed to. +#> +function Get-AzCloudEnvironment { + try { + $tries = 0 + $retryCount = 5 + Write-Host "Getting environment from instance metadata service" + while ($tries -le $retryCount) { + try { + $azEnvironment = (Invoke-RestMethod -Headers @{"Metadata"="true"} -Method GET -Uri "http://169.254.169.254/metadata/instance?api-version=2020-09-01" -TimeoutSec 180).compute.azEnvironment + break + } + catch { + $tries++ + Write-Host "Exception: $_" + if ($tries -gt $retryCount) { + throw $_ + } + else { + Write-Host "Failed to reach instance metadata service. Retrying again in $($tries * 10) seconds" + Start-Sleep ($tries * 10) + } + } + } + } + catch { + Write-Error -Exception $_.Exception -Message "Instance metadata service is unavailable." + throw $_.Exception + } + + return $azEnvironment +} diff --git a/scripts/modules/CleanUpUtils.psm1 b/scripts/modules/CleanUpUtils.psm1 new file mode 100644 index 0000000..61172c9 --- /dev/null +++ b/scripts/modules/CleanUpUtils.psm1 @@ -0,0 +1,38 @@ + +<# +.Description +Remove-OrchArtifacts requires OrchestratorAPIUtils module to be loaded. +Removes the Orchestrator Users and Machine templates that match the -uniqueString parameter +#> +function Remove-OrchArtifacts { + param( + [Parameter(Mandatory = $true)] + [string]$uniqueString, + + [Parameter(Mandatory = $true)] + [ValidateScript( { if (($_ -as [System.URI]).AbsoluteURI -eq $null) { throw "Invalid" } return $true })] + [string]$orchUrl, + + [Parameter(Mandatory = $true)] + [string]$orchAdmin, + + [Parameter(Mandatory = $true)] + [string]$orchPassword, + + [Parameter(Mandatory = $false)] + [string]$tenant = "Default" + + ) + + $robotsUsernames = Get-OrchTenantUsersByString -userString $uniqueString -orchUrl $orchUrl -orchAdmin $orchAdmin -orchPassword $orchPassword -tenant $tenant + + foreach ($robot in $robotsUsernames.value) { + Remove-OrchTenantUsersById -robotId $robot.id -orchUrl $orchUrl -orchAdmin $orchAdmin -orchPassword $orchPassword -tenant $tenant + } + + $robotsMachineTemplates = Get-OrchTenantMachineTemplatesByString -machineString $uniqueString -orchUrl $orchUrl -orchAdmin $orchAdmin -orchPassword $orchPassword -tenant $tenant + + foreach ($machineTemplate in $robotsMachineTemplates.value) { + Remove-OrchTenantMachineTemplateById -machineTemplateId $machineTemplate.id -orchUrl $orchUrl -orchAdmin $orchAdmin -orchPassword $orchPassword -tenant $tenant + } +} diff --git a/scripts/modules/OrchestratorAPIUtils.psm1 b/scripts/modules/OrchestratorAPIUtils.psm1 new file mode 100644 index 0000000..589e184 --- /dev/null +++ b/scripts/modules/OrchestratorAPIUtils.psm1 @@ -0,0 +1,207 @@ +function Get-UiPathOrchestratorLoginSession { + param( + [Parameter(Mandatory = $true)] + [ValidateScript( { if (($_ -as [System.URI]).AbsoluteURI -eq $null) { throw "Invalid" } return $true })] + [string]$orchUrl, + + [Parameter(Mandatory = $true)] + [string]$orchAdmin, + + [Parameter(Mandatory = $true)] + [string]$orchPassword, + + [Parameter(Mandatory = $false)] + [string]$tenant = "Default" + + ) + $dataLogin = @{ + tenancyName = $tenant + usernameOrEmailAddress = $orchAdmin + password = $orchPassword + } | ConvertTo-Json + + $orchUrlLogin = "$orchUrl/api/Account/Authenticate" + + try { + $orchWebResponse = Invoke-RestMethod -Uri $orchUrlLogin ` + -Method Post ` + -Body $dataLogin ` + -ContentType "application/json" ` + -UseBasicParsing ` + -SessionVariable websession + } + catch { + Write-Error "Authentication failed with message: $($_.ErrorDetails.Message)" + Exit 1 + } + $websession.Headers.Add('Authorization', "Bearer " + $orchWebResponse.result) + return $websession +} + +function Get-OrchTenantUsersByString { + param( + [Parameter(Mandatory = $true)] + [string]$userString, + + [Parameter(Mandatory = $true)] + [ValidateScript( { if (($_ -as [System.URI]).AbsoluteURI -eq $null) { throw "Invalid" } return $true })] + [string]$orchUrl, + + [Parameter(Mandatory = $true)] + [string]$orchAdmin, + + [Parameter(Mandatory = $true)] + [string]$orchPassword, + + [Parameter(Mandatory = $false)] + [string]$tenant = "Default" + ) + + $websession = Get-UiPathOrchestratorLoginSession -orchUrl $orchUrl ` + -orchAdmin $orchAdmin ` + -orchPassword $orchPassword ` + -tenant $tenant + + $orchUsersURL = "$orchUrl/odata/Users" + + $robotsUsers = Invoke-RestMethod -Uri "$orchUsersURL`?`$filter=contains(UserName, `'$($userString)`')" ` + -Method Get ` + -UseBasicParsing ` + -WebSession $websession + + return $robotsUsers +} + +function Remove-OrchTenantUsersById { + param( + [Parameter(Mandatory = $true)] + [string]$robotId, + + [Parameter(Mandatory = $true)] + [ValidateScript( { if (($_ -as [System.URI]).AbsoluteURI -eq $null) { throw "Invalid" } return $true })] + [string]$orchUrl, + + [Parameter(Mandatory = $true)] + [string]$orchAdmin, + + [Parameter(Mandatory = $true)] + [string]$orchPassword, + + [Parameter(Mandatory = $false)] + [string]$tenant = "Default" + ) + + $websession = Get-UiPathOrchestratorLoginSession -orchUrl $orchUrl ` + -orchAdmin $orchAdmin ` + -orchPassword $orchPassword ` + -tenant $tenant + + $orchUsersURL = "$orchUrl/odata/Users" + Invoke-RestMethod -Uri "$orchUsersURL($robotId)" ` + -Method Delete ` + -UseBasicParsing ` + -WebSession $websession +} + +function Get-OrchTenantMachineTemplatesByString { + param( + [Parameter(Mandatory = $true)] + [string]$machineString, + + [Parameter(Mandatory = $true)] + [ValidateScript( { if (($_ -as [System.URI]).AbsoluteURI -eq $null) { throw "Invalid" } return $true })] + [string]$orchUrl, + + [Parameter(Mandatory = $true)] + [string]$orchAdmin, + + [Parameter(Mandatory = $true)] + [string]$orchPassword, + + [Parameter(Mandatory = $false)] + [string]$tenant = "Default" + ) + + $websession = Get-UiPathOrchestratorLoginSession -orchUrl $orchUrl ` + -orchAdmin $orchAdmin ` + -orchPassword $orchPassword ` + -tenant $tenant + + $orchMachinesURL = "$orchUrl/odata/Machines" + + $machineTemplates = Invoke-RestMethod -Uri "$orchMachinesURL`?`$filter=contains(Name, `'$($machineString)`')" ` + -Method GET ` + -ContentType "application/json" ` + -UseBasicParsing ` + -WebSession $websession + + return $machineTemplates +} + +function Remove-OrchTenantMachineTemplateById { + param( + [Parameter(Mandatory = $true)] + [string]$machineTemplateId, + + [Parameter(Mandatory = $true)] + [ValidateScript( { if (($_ -as [System.URI]).AbsoluteURI -eq $null) { throw "Invalid" } return $true })] + [string]$orchUrl, + + [Parameter(Mandatory = $true)] + [string]$orchAdmin, + + [Parameter(Mandatory = $true)] + [string]$orchPassword, + + [Parameter(Mandatory = $false)] + [string]$tenant = "Default" + ) + + $websession = Get-UiPathOrchestratorLoginSession -orchUrl $orchUrl ` + -orchAdmin $orchAdmin ` + -orchPassword $orchPassword ` + -tenant $tenant + + $orchUsersURL = "$orchUrl/odata/Machines" + Invoke-RestMethod -Uri "$orchUsersURL($machineTemplateId)" ` + -Method Delete ` + -UseBasicParsing ` + -WebSession $websession +} + +function Remove-ChangePasswordOnFirstLoginPolicy { + param( + + [Parameter(Mandatory = $true)] + [ValidateScript( { if (($_ -as [System.URI]).AbsoluteURI -eq $null) { throw "Invalid" } return $true })] + [string]$orchUrl, + + [Parameter(Mandatory = $true)] + [string]$orchAdmin, + + [Parameter(Mandatory = $true)] + [string]$orchPassword, + + [Parameter(Mandatory = $false)] + [string]$tenant = "host" + ) + + $websession = Get-UiPathOrchestratorLoginSession -orchUrl $orchUrl ` + -orchAdmin $orchAdmin ` + -orchPassword $orchPassword ` + -tenant $tenant + + $orchSettingsURL = "$orchUrl/odata/Settings" + $body = @{ + Name = "Auth.Password.ShouldChangePasswordAfterFirstLogin" + Value = "false" + Scope = "All" + Id = "Auth.Password.ShouldChangePasswordAfterFirstLogin" + } | ConvertTo-Json + Invoke-RestMethod -Uri "$orchSettingsURL('Auth.Password.ShouldChangePasswordAfterFirstLogin')" ` + -Method Put ` + -ContentType "application/json;odata.metadata=minimal;odata.streaming=true" ` + -UseBasicParsing ` + -WebSession $websession ` + -Body $body +} diff --git a/scripts/modules/TestingUtils.psm1 b/scripts/modules/TestingUtils.psm1 new file mode 100644 index 0000000..e3bc59a --- /dev/null +++ b/scripts/modules/TestingUtils.psm1 @@ -0,0 +1,128 @@ +<# +.Description +Test-URLForOK calls an url and expects a 200. If it gets a 200 it exists ok, +if not it will apply the retry policy and throw an error if it still does not get a 200 OK HTTP response. +#> +function Test-URLForOK { + param( + [Parameter(Mandatory = $true)] + [ValidateScript( { if (($_ -as [System.URI]).AbsoluteURI -eq $null) { throw "Invalid" } return $true })] + [string]$url, + + [Parameter(Mandatory = $false)] + [int]$retryCount = 5, + + [Parameter(Mandatory = $false)] + [ValidateSet("Linear", "Exponential")] + [string]$retryPolicy = "Linear" + ) + + try { + $tries = 0 + Write-Host "Getting response from URL: $url" + while ($tries -le $retryCount) { + try { + Invoke-WebRequest -URI $url ` + -Method 'GET' ` + -TimeoutSec 180 ` + -UseBasicParsing + break + } + catch { + $tries++ + Write-Host "Exception: $_" + if ($tries -gt $retryCount) { + throw $_ + } + else { + if ($retryPolicy -eq "Linear") { + Write-Host "Failed to GET $url. Retrying again in 10 seconds" + Start-Sleep 10 + } + if ($retryPolicy -eq "Exponential") { + Write-Host "Failed to GET $url. Retrying again in $($tries * 10) seconds" + Start-Sleep ($tries * 10) + } + } + } + } + + } + catch { + Write-Error -Exception $_.Exception -Message "Exception occured when doing a HTTP GET at $url" + throw $_.Exception + } +} + +<# +.Description +Test-RobotUserIsCreated requires OrchestratorAPIUtils module to be loaded. +It exists with 0 if the tests are correct and with 1 if not. +The main check is if the number of robots that match the robotUniqueString parameter that exists in the orchestrator tenant equals with the expectedRobotsWithUniqueString parameter. +#> +function Test-RobotUserIsCreated { + param( + [Parameter(Mandatory = $true)] + [string]$robotUniqueString, + + [Parameter(Mandatory = $true)] + [string]$expectedRobotsWithUniqueString, #kept string to compare easier since only doing equality (no casting) + + [Parameter(Mandatory = $true)] + [ValidateScript( { if (($_ -as [System.URI]).AbsoluteURI -eq $null) { throw "Invalid" } return $true })] + [string]$orchUrl, + + [Parameter(Mandatory = $true)] + [string]$orchAdmin, + + [Parameter(Mandatory = $true)] + [string]$orchPassword, + + [Parameter(Mandatory = $false)] + [string]$tenant = "Default", + + [Parameter(Mandatory = $false)] + [int]$retryCount = 5, + + [Parameter(Mandatory = $false)] + [ValidateSet("Linear", "Exponential")] + [string]$retryPolicy = "Linear" + + ) + try { + $tries = 0 + while ($tries -le $retryCount) { + try { + + $websession = Get-UiPathOrchestratorLoginSession -orchUrl $orchUrl -orchAdmin $orchAdmin -orchPassword $orchPassword -tenant $tenant + $orchUsers = "$orchUrl/odata/Users" + $result = Invoke-RestMethod -Uri "$orchUsers`?`$filter=contains(UserName, `'$($robotUniqueString)`')" -Method Get -UseBasicParsing -WebSession $websession + + if ($result.'@odata.count' -ne $expectedRobotsWithUniqueString) {Exit 1} + else {return} + } + catch { + $tries++ + Write-Host "Exception: $_" + if ($tries -gt $retryCount) { + throw $_ + } + else { + if ($retryPolicy -eq "Linear") { + Write-Host "Failed to GET $orchUrl. Retrying again in 10 seconds" + Start-Sleep 10 + } + if ($retryPolicy -eq "Exponential") { + Write-Host "Failed to GET $orchUrl. Retrying again in $($tries * 10) seconds" + Start-Sleep ($tries * 10) + } + } + } + } + + } + catch { + Write-Error -Exception $_.Exception -Message "Exception occured when doing a HTTP GET at $orchUrl" + throw $_.Exception + } +} diff --git a/templates/ha.template.yaml b/templates/ha.template.yaml index ac10c6a..bbab6cf 100644 --- a/templates/ha.template.yaml +++ b/templates/ha.template.yaml @@ -151,7 +151,7 @@ Resources: - - '#!/bin/bash' - yum update -y - yum install -y wget - - 'wget http://download.uipath.com/haa/get-haa.sh' + - 'wget https://raw.githubusercontent.com/UiPath/Infrastructure/main/Setup/get-haa.sh' - chmod +x get-haa.sh - !Join - ' ' @@ -189,7 +189,7 @@ Resources: - - '#!/bin/bash' - yum update -y - yum install -y wget - - 'wget http://download.uipath.com/haa/get-haa.sh' + - 'wget https://raw.githubusercontent.com/UiPath/Infrastructure/main/Setup/get-haa.sh' - chmod +x get-haa.sh - !Join - ' ' @@ -224,7 +224,7 @@ Resources: - - '#!/bin/bash' - yum update -y - yum install -y wget - - 'wget http://download.uipath.com/haa/get-haa.sh' + - 'wget https://raw.githubusercontent.com/UiPath/Infrastructure/main/Setup/get-haa.sh' - chmod +x get-haa.sh - !Join - ' ' diff --git a/templates/main.template.yaml b/templates/main.template.yaml index 40811de..191534c 100644 --- a/templates/main.template.yaml +++ b/templates/main.template.yaml @@ -956,127 +956,18 @@ Resources: - !Ref AvailabilityZones NumberOfAZs: !Ref NumberOfAZs VPCCIDR: !Ref VPCCIDR - CreatePrivateSubnets: 'true' - CreateAdditionalPrivateSubnets: 'true' - KeyPairName: !Ref KeyPairName PublicSubnetTag1: !Ref PublicSubnetTag1 PublicSubnetTag2: !Ref PublicSubnetTag2 PublicSubnetTag3: !Ref PublicSubnetTag3 PrivateSubnetATag1: !Ref PrivateSubnetTag1 PrivateSubnetBTag1: !Ref PrivateSubnetTag2 PrivateSubnetBTag2: !Ref PrivateSubnetTag3 - FindAmiLambdaRole: - Type: 'AWS::IAM::Role' - Properties: - Description: >- - IAM Role to be assumed by Lambda functions which find AMI ids - AssumeRolePolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Principal: - Service: - - lambda.amazonaws.com - Action: - - 'sts:AssumeRole' - Path: / - Policies: - - PolicyName: root - PolicyDocument: - Version: 2012-10-17 - Statement: - - Effect: Allow - Action: - - 'ec2:DescribeImages' - Resource: '*' - - Effect: Allow - Action: - - 'logs:CreateLogGroup' - - 'logs:CreateLogStream' - - 'logs:PutLogEvents' - Resource: 'arn:aws:logs:*:*:*' - - Effect: Allow - Action: - - 'xray:PutTraceSegments' - Resource: '*' - FindAMIFunction: - Type: 'AWS::Lambda::Function' - Properties: - Description: Finds a matching AMI Id - Handler: index.handler - Role: !GetAtt FindAmiLambdaRole.Arn - Code: - ZipFile: !Join - - |+ - - - - import boto3 - - import json - - import cfnresponse - - 'def create(properties, physical_id):' - - ' regionName = properties[''RegionName'']' - - ' imageName = properties[''ImageName'']' - - ' architecture = properties[''Architecture'']' - - ' virtualizationType = properties[''VirtualizationType'']' - - ' owners = properties[''Owners'']' - - ' imageId = ''''' - - ' ec2 = boto3.client(''ec2'', regionName)' - - ' images = ec2.describe_images(' - - ' ExecutableUsers=[''all''],' - - ' Filters=[' - - ' { ''Name'': ''name'', ''Values'': [imageName] },' - - ' { ''Name'': ''state'', ''Values'': [''available''] },' - - ' { ''Name'': ''architecture'', ''Values'': [architecture] },' - - ' { ''Name'': ''virtualization-type'', ''Values'': [virtualizationType] }' - - ' ],' - - ' Owners=[owners]' - - ' )[''Images'']' - - ' if len(images) > 0:' - - ' imageId = images[0][''ImageId'']' - - ' print(regionName, imageId)' - - ' returnAttribute = {}' - - ' returnAttribute[''ImageId''] = imageId' - - ' returnAttribute[''Action''] = ''CREATE''' - - ' return cfnresponse.SUCCESS, imageId, returnAttribute' - - 'def update(properties, physical_id):' - - ' imageId = physical_id' - - ' returnAttribute = {}' - - ' returnAttribute[''ImageId''] = imageId' - - ' returnAttribute[''Action''] = ''UPDATE''' - - ' return cfnresponse.SUCCESS, imageId, returnAttribute' - - 'def delete(properties, physical_id):' - - ' imageId = physical_id' - - ' returnAttribute = {}' - - ' returnAttribute[''ImageId''] = imageId' - - ' returnAttribute[''Action''] = ''DELETE''' - - ' return cfnresponse.SUCCESS, imageId, returnAttribute' - - 'def handler(event, context):' - - ' print(''Received event: '' + json.dumps(event))' - - ' status = cfnresponse.FAILED' - - ' new_physical_id = None' - - ' returnAttribute = {}' - - ' try:' - - ' properties = event.get(''ResourceProperties'')' - - ' physical_id = event.get(''PhysicalResourceId'')' - - ' status, new_physical_id, returnAttribute = {' - - ' ''Create'': create,' - - ' ''Update'': update,' - - ' ''Delete'': delete' - - ' }.get(event[''RequestType''], lambda x, y: (cfnresponse.FAILED, None))(properties, physical_id)' - - ' except Exception as e:' - - ' print(''Exception: '' + str(e))' - - ' status = cfnresponse.FAILED' - - ' finally:' - - ' cfnresponse.send(event, context, status, returnAttribute, new_physical_id)' - Runtime: python3.6 - Timeout: 30 - TracingConfig: - Mode: Active - StorageStack: + OrchestratorStack: Type: 'AWS::CloudFormation::Stack' Properties: TemplateURL: !Sub - >- - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/storage.template.yaml + https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/uipath-orchestrator.template.yaml - S3Bucket: !If - UsingDefaultBucket - !Sub '${QSS3BucketName}-${AWS::Region}' @@ -1086,202 +977,56 @@ Resources: - !Ref 'AWS::Region' - !Ref QSS3BucketRegion Parameters: - SubnetId: !GetAtt NetworkStack.Outputs.PrivateSubnet1AID - KeyPairName: !Ref KeyPairName - GatewayName: !Ref GatewayName - GatewayTimezone: !Ref GatewayTimezone - VPCID: !GetAtt NetworkStack.Outputs.VPCID VPCCIDR: !Ref VPCCIDR - RoutingStack: - Type: 'AWS::CloudFormation::Stack' - Properties: - TemplateURL: !Sub - - >- - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/routing.template.yaml - - S3Bucket: !If - - UsingDefaultBucket - - !Sub '${QSS3BucketName}-${AWS::Region}' - - !Ref QSS3BucketName - S3Region: !If - - UsingDefaultBucket - - !Ref 'AWS::Region' - - !Ref QSS3BucketRegion - Parameters: - VPCID: !GetAtt - - NetworkStack - - Outputs.VPCID - VPCCIDR: !Ref VPCCIDR - SubnetIDs: !Join + VPCID: !GetAtt NetworkStack.Outputs.VPCID + KeyPairName: !Ref KeyPairName + PrivateSubnetIDs: !Join + - ',' + - - !GetAtt NetworkStack.Outputs.PrivateSubnet1AID + - !GetAtt NetworkStack.Outputs.PrivateSubnet2AID + - !If [ 3AZCondition, !GetAtt NetworkStack.Outputs.PrivateSubnet3AID, !Ref "AWS::NoValue" ] + PublicSubnetIDs: !Join - ',' - - !GetAtt NetworkStack.Outputs.PublicSubnet1ID - !GetAtt NetworkStack.Outputs.PublicSubnet2ID - - !If [3AZCondition, !GetAtt NetworkStack.Outputs.PublicSubnet3ID, !Ref "AWS::NoValue"] - Fqdn: !Ref UiPathFQDN + - !If [ 3AZCondition, !GetAtt NetworkStack.Outputs.PublicSubnet3ID, !Ref "AWS::NoValue" ] + InstanceType: !Ref InstanceType + GatewayName: !Ref GatewayName + GatewayTimezone: !Ref GatewayTimezone + UiPathFQDN: !Ref UiPathFQDN HostedZoneID: !Ref HostedZoneID - SSLCertificateArn: !GetAtt ConfigureSSLStack.Outputs.ACMCertificate - HAStack: - Type: 'AWS::CloudFormation::Stack' - Properties: - TemplateURL: !Sub - - >- - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/ha.template.yaml - - S3Bucket: !If - - UsingDefaultBucket - - !Sub '${QSS3BucketName}-${AWS::Region}' - - !Ref QSS3BucketName - S3Region: !If - - UsingDefaultBucket - - !Ref 'AWS::Region' - - !Ref QSS3BucketRegion - Parameters: - VPCID: !GetAtt NetworkStack.Outputs.VPCID - VPCCIDR: !Ref VPCCIDR - SubnetIDs: !Join - - ',' - - - !GetAtt NetworkStack.Outputs.PrivateSubnet1AID - - !GetAtt NetworkStack.Outputs.PrivateSubnet2BID - - !If [3AZCondition, !GetAtt NetworkStack.Outputs.PrivateSubnet3AID, !Ref "AWS::NoValue"] - KeyPairName: !Ref KeyPairName HAAUser: !Ref HAAUser HAAPassword: !Ref HAAPassword + HAPort: !Ref HAPort HAALicense: !Ref HAALicense - FindAMIFunctionArn: !GetAtt - - FindAMIFunction - - Arn - DatabaseStack: - Type: 'AWS::CloudFormation::Stack' - Properties: - TemplateURL: !Sub - - >- - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/database.template.yaml - - S3Bucket: !If - - UsingDefaultBucket - - !Sub '${QSS3BucketName}-${AWS::Region}' - - !Ref QSS3BucketName - S3Region: !If - - UsingDefaultBucket - - !Ref 'AWS::Region' - - !Ref QSS3BucketRegion - Parameters: - VPCID: !GetAtt NetworkStack.Outputs.VPCID - VPCCIDR: !Ref VPCCIDR - SubnetIDs: !Join - - ',' - - - !GetAtt NetworkStack.Outputs.PrivateSubnet1AID - - !GetAtt NetworkStack.Outputs.PrivateSubnet2BID - - !If [3AZCondition, !GetAtt NetworkStack.Outputs.PrivateSubnet3AID, !Ref "AWS::NoValue"] RDSDBName: !Ref RDSDBName RDSAllocatedStorage: !Ref RDSAllocatedStorage RDSMaxAllocatedStorage: !Ref RDSMaxAllocatedStorage RDSUsername: !Ref RDSUsername RDSPassword: !Ref RDSPassword - ServerStack: - Type: 'AWS::CloudFormation::Stack' - Properties: - TemplateURL: !Sub - - >- - https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/server.template.yaml - - S3Bucket: !If - - UsingDefaultBucket - - !Sub '${QSS3BucketName}-${AWS::Region}' - - !Ref QSS3BucketName - S3Region: !If - - UsingDefaultBucket - - !Ref 'AWS::Region' - - !Ref QSS3BucketRegion - Parameters: - VPCID: !GetAtt NetworkStack.Outputs.VPCID - VPCCIDR: !Ref VPCCIDR - PrivateSubnetIDs: !Join - - ',' - - - !GetAtt NetworkStack.Outputs.PrivateSubnet1AID - - !GetAtt NetworkStack.Outputs.PrivateSubnet2BID - - !If [3AZCondition, !GetAtt NetworkStack.Outputs.PrivateSubnet3AID, !Ref "AWS::NoValue"] - PublicSubnetIDs: !Join - - ',' - - - !GetAtt NetworkStack.Outputs.PublicSubnet1ID - - !GetAtt NetworkStack.Outputs.PublicSubnet2ID - - !If [3AZCondition, !GetAtt NetworkStack.Outputs.PublicSubnet3ID, !Ref "AWS::NoValue"] - TargetGroupARN: !GetAtt - - RoutingStack - - Outputs.TargetGroupARN - KeyPairName: !Ref KeyPairName - InstanceType: !Ref InstanceType - FindAMIFunctionArn: !GetAtt - - FindAMIFunction - - Arn OrchestratorVersion: !Ref OrchestratorVersion OrchestratorInstanceCount: !Ref OrchestratorInstanceCount OrchestratorLicense: !Ref OrchestratorLicense OrchestratorPassword: !Ref OrchestratorPassword - DatabaseServerName: !GetAtt - - DatabaseStack - - Outputs.RDSDBInstanceEndpointAddress - DatabaseName: !Ref RDSDBName - DatabaseUsername: !Ref RDSUsername - DatabasePassword: !Ref RDSPassword - NugetStorageBucketName: !GetAtt - - StorageStack - - Outputs.NugetStorageBucketName - NugetStorageBucketArn: !GetAtt - - StorageStack - - Outputs.NugetStorageBucketArn - GatewayPrivateIP: !GetAtt - - StorageStack - - Outputs.GatewayVMPrivateIP1 - HAMasterPrivateIP: !GetAtt - - HAStack - - Outputs.HAMasterPrivateIP - HASlave1PrivateIP: !GetAtt - - HAStack - - Outputs.HASlave1PrivateIP - HASlave2PrivateIP: !GetAtt - - HAStack - - Outputs.HASlave2PrivateIP - HAPort: !Ref HAPort - HAAPassword: !Ref HAAPassword QSS3BucketName: !Ref QSS3BucketName - QSS3BucketRegion: !Ref QSS3BucketRegion QSS3KeyPrefix: !Ref QSS3KeyPrefix - Fqdn: !Ref UiPathFQDN - ConfigureSSLStack: - Type: AWS::CloudFormation::Stack - Properties: - TemplateURL: !Sub - - "https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}submodules/quickstart-aws-acm-certificate/templates/quickstart-aws-acm-certificate.template.yml" - - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] - S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] - Parameters: - QSS3BucketName: !Ref QSS3BucketName QSS3BucketRegion: !Ref QSS3BucketRegion - QSS3KeyPrefix: !Sub "${QSS3KeyPrefix}submodules/quickstart-aws-acm-certificate/" - DomainName: !Ref UiPathFQDN - HostedZoneID: !Ref HostedZoneID - Outputs: FQDNLoadBalancer: Description: UiPath Orchestrator Login page Value: !Ref UiPathFQDN BastionHost: - Description: Bastion Host's Public IP address + Description: Bastion Host's Public IP address Value: !GetAtt - - ServerStack - - Outputs.BastionHostPublicIP + - OrchestratorStack + - Outputs.BastionHost HaaNodesIPs: - Description: High Availability Add-on's list of IP addresses - Value: !Join - - ',' - - - !GetAtt - - HAStack - - Outputs.HAMasterPrivateIP - - !GetAtt - - HAStack - - Outputs.HASlave1PrivateIP - - !GetAtt - - HAStack - - Outputs.HASlave2PrivateIP + Description: High Availability Add-on's list of IP addresses + Value: !GetAtt + - OrchestratorStack + - Outputs.HaaNodesIPs NugetStorageS3Bucket: Description: S3 bucket name that contains Orchestrator's Nuget packages Value: !GetAtt - - StorageStack - - Outputs.NugetStorageBucketName + - OrchestratorStack + - Outputs.NugetStorageS3Bucket diff --git a/templates/uipath-orchestrator.template.yaml b/templates/uipath-orchestrator.template.yaml new file mode 100644 index 0000000..9b00b49 --- /dev/null +++ b/templates/uipath-orchestrator.template.yaml @@ -0,0 +1,1161 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: >- + This template creates a Multi-AZ, multi-subnet VPC infrastructure with UIPath + Orchestrator deployed in private Subnets, with an ALB behind a Route53 in + public subnets. (qs-1r2g41245) +Metadata: + QuickStartDocumentation: + EntrypointName: "Launch into an existing VPC" + Order: "1" + 'AWS::CloudFormation::Interface': + ParameterGroups: + - Label: + default: Network Configuration + Parameters: + - VPCCIDR + - VPCID + - PublicSubnetIDs + - PrivateSubnetIDs + - Label: + default: Orchestrator Instance Configuration + Parameters: + - KeyPairName + - InstanceType + - Label: + default: Storage Gateway Configuration + Parameters: + - GatewayName + - GatewayTimezone + - Label: + default: DNS or SSL Configuration + Parameters: + - UiPathFQDN + - HostedZoneID + - Label: + default: High Availability Configuration + Parameters: + - HAAUser + - HAAPassword + - HAPort + - HAALicense + - Label: + default: RDS Database Configuration + Parameters: + - RDSDBName + - RDSAllocatedStorage + - RDSMaxAllocatedStorage + - RDSUsername + - RDSPassword + - Label: + default: Orchestrator Server Configuration + Parameters: + - OrchestratorVersion + - OrchestratorInstanceCount + - OrchestratorLicense + - OrchestratorPassword + - Label: + default: Quickstart Bucket configuration + Parameters: + - QSS3BucketName + - QSS3KeyPrefix + ParameterLabels: + VPCCIDR: + default: VPC CIDR + VPCID: + default: VPC ID + PublicSubnetIDs: + default: VPC CIDR + PrivateSubnetIDs: + default: VPC CIDR + KeyPairName: + default: Instance Key pair + InstanceType: + default: Instance type + GatewayName: + default: Storage Gateway + UiPathFQDN: + default: Hosting FQDN + HostedZoneID: + default: Route 53 Hosted Zone Id + HAAUser: + default: HA Addon Username + HAAPassword: + default: HA Addon Password + HAPort: + default: HA Addon Port + HAALicense: + default: HA Addon License Key + RDSDBName: + default: RDS Database Name + RDSAllocatedStorage: + default: RDS Allocated Storage + RDSMaxAllocatedStorage: + default: Maximum allocated storage + RDSUsername: + default: DB Username + RDSPassword: + default: DB Password + OrchestratorVersion: + default: Orchestrator Version Number + OrchestratorInstanceCount: + default: Orchestrator Instance Count + OrchestratorLicense: + default: Orchestrator License Key + OrchestratorPassword: + default: Orchestrator Password + QSS3BucketName: + default: Template hosting Bucket + QSS3KeyPrefix: + default: Template folder +Parameters: + VPCCIDR: + AllowedPattern: '^([0-9]{1,3}\.){2}([0]{1}.)[0]{1}(\/[16]{2})$' + ConstraintDescription: CIDR block parameter must be in the form x.x.0.0/16 + Default: 10.0.0.0/16 + Description: 'CIDR block for the VPC, must be in the form x.x.0.0/16.' + Type: String + VPCID: + Type: 'AWS::EC2::VPC::Id' + Description: 'VPC ID' + PublicSubnetIDs: + Description: 'List of public subnet IDs' + Type: 'List' + PrivateSubnetIDs: + Description: 'List of private subnet IDs' + Type: 'List' + KeyPairName: + Description: 'Existing Key Pair, which is to be used to Log in to VM instances.' + Type: 'AWS::EC2::KeyPair::KeyName' + InstanceType: + AllowedValues: + - a1.xlarge + - a1.2xlarge + - a1.4xlarge + - t2.xlarge + - m4.xlarge + - m4.2xlarge + - m4.4xlarge + - m4.10xlarge + - c4.xlarge + - c4.2xlarge + - c4.4xlarge + - c4.8xlarge + - r4.xlarge + - r4.2xlarge + - r4.4xlarge + - r4.8xlarge + ConstraintDescription: 'Class of instance, must be a valid EC2 instance type from the list.' + Default: m4.xlarge + Description: EC2 instance type + Type: String + GatewayName: + Description: Name of S3 File Storage Gateway. + Type: String + Default: uipathgateway + AllowedPattern: '^[a-zA-Z]*$' + ConstraintDescription: >- + Gateway name should contain alphabetic characters only, and cannot be left + blank. + GatewayTimezone: + Description: Timezone to choose for File Gateway + Type: String + AllowedValues: + - Africa/Abidjan + - Africa/Accra + - Africa/Addis_Ababa + - Africa/Algiers + - Africa/Asmara + - Africa/Asmera + - Africa/Bamako + - Africa/Bangui + - Africa/Banjul + - Africa/Bissau + - Africa/Blantyre + - Africa/Brazzaville + - Africa/Bujumbura + - Africa/Cairo + - Africa/Casablanca + - Africa/Ceuta + - Africa/Conakry + - Africa/Dakar + - Africa/Dar_es_Salaam + - Africa/Djibouti + - Africa/Douala + - Africa/El_Aaiun + - Africa/Freetown + - Africa/Gaborone + - Africa/Harare + - Africa/Johannesburg + - Africa/Juba + - Africa/Kampala + - Africa/Khartoum + - Africa/Kigali + - Africa/Kinshasa + - Africa/Lagos + - Africa/Libreville + - Africa/Lome + - Africa/Luanda + - Africa/Lubumbashi + - Africa/Lusaka + - Africa/Malabo + - Africa/Maputo + - Africa/Maseru + - Africa/Mbabane + - Africa/Mogadishu + - Africa/Monrovia + - Africa/Nairobi + - Africa/Ndjamena + - Africa/Niamey + - Africa/Nouakchott + - Africa/Ouagadougou + - Africa/Porto-Novo + - Africa/Sao_Tome + - Africa/Timbuktu + - Africa/Tripoli + - Africa/Tunis + - Africa/Windhoek + - America/Adak + - America/Anchorage + - America/Anguilla + - America/Antigua + - America/Araguaina + - America/Argentina/Buenos_Aires + - America/Argentina/Catamarca + - America/Argentina/ComodRivadavia + - America/Argentina/Cordoba + - America/Argentina/Jujuy + - America/Argentina/La_Rioja + - America/Argentina/Mendoza + - America/Argentina/Rio_Gallegos + - America/Argentina/Salta + - America/Argentina/San_Juan + - America/Argentina/San_Luis + - America/Argentina/Tucuman + - America/Argentina/Ushuaia + - America/Aruba + - America/Asuncion + - America/Atikokan + - America/Atka + - America/Bahia + - America/Bahia_Banderas + - America/Barbados + - America/Belem + - America/Belize + - America/Blanc-Sablon + - America/Boa_Vista + - America/Bogota + - America/Boise + - America/Buenos_Aires + - America/Cambridge_Bay + - America/Campo_Grande + - America/Cancun + - America/Caracas + - America/Catamarca + - America/Cayenne + - America/Cayman + - America/Chicago + - America/Chihuahua + - America/Coral_Harbour + - America/Cordoba + - America/Costa_Rica + - America/Creston + - America/Cuiaba + - America/Curacao + - America/Danmarkshavn + - America/Dawson + - America/Dawson_Creek + - America/Denver + - America/Detroit + - America/Dominica + - America/Edmonton + - America/Eirunepe + - America/El_Salvador + - America/Ensenada + - America/Fort_Wayne + - America/Fortaleza + - America/Glace_Bay + - America/Godthab + - America/Goose_Bay + - America/Grand_Turk + - America/Grenada + - America/Guadeloupe + - America/Guatemala + - America/Guayaquil + - America/Guyana + - America/Halifax + - America/Havana + - America/Hermosillo + - America/Indiana/Indianapolis + - America/Indiana/Knox + - America/Indiana/Marengo + - America/Indiana/Petersburg + - America/Indiana/Tell_City + - America/Indiana/Vevay + - America/Indiana/Vincennes + - America/Indiana/Winamac + - America/Indianapolis + - America/Inuvik + - America/Iqaluit + - America/Jamaica + - America/Jujuy + - America/Juneau + - America/Kentucky/Louisville + - America/Kentucky/Monticello + - America/Knox_IN + - America/Kralendijk + - America/La_Paz + - America/Lima + - America/Los_Angeles + - America/Louisville + - America/Lower_Princes + - America/Maceio + - America/Managua + - America/Manaus + - America/Marigot + - America/Martinique + - America/Matamoros + - America/Mazatlan + - America/Mendoza + - America/Menominee + - America/Merida + - America/Metlakatla + - America/Mexico_City + - America/Miquelon + - America/Moncton + - America/Monterrey + - America/Montevideo + - America/Montreal + - America/Montserrat + - America/Nassau + - America/New_York + - America/Nipigon + - America/Nome + - America/Noronha + - America/North_Dakota/Beulah + - America/North_Dakota/Center + - America/North_Dakota/New_Salem + - America/Ojinaga + - America/Panama + - America/Pangnirtung + - America/Paramaribo + - America/Phoenix + - America/Port_of_Spain + - America/Port-au-Prince + - America/Porto_Acre + - America/Porto_Velho + - America/Puerto_Rico + - America/Rainy_River + - America/Rankin_Inlet + - America/Recife + - America/Regina + - America/Resolute + - America/Rio_Branco + - America/Rosario + - America/Santa_Isabel + - America/Santarem + - America/Santiago + - America/Santo_Domingo + - America/Sao_Paulo + - America/Scoresbysund + - America/Shiprock + - America/Sitka + - America/St_Barthelemy + - America/St_Johns + - America/St_Kitts + - America/St_Lucia + - America/St_Thomas + - America/St_Vincent + - America/Swift_Current + - America/Tegucigalpa + - America/Thule + - America/Thunder_Bay + - America/Tijuana + - America/Toronto + - America/Tortola + - America/Vancouver + - America/Virgin + - America/Whitehorse + - America/Winnipeg + - America/Yakutat + - America/Yellowknife + - Antarctica/Casey + - Antarctica/Davis + - Antarctica/DumontDUrville + - Antarctica/Macquarie + - Antarctica/Mawson + - Antarctica/McMurdo + - Antarctica/Palmer + - Antarctica/Rothera + - Antarctica/South_Pole + - Antarctica/Syowa + - Antarctica/Vostok + - Arctic/Longyearbyen + - Asia/Aden + - Asia/Almaty + - Asia/Amman + - Asia/Anadyr + - Asia/Aqtau + - Asia/Aqtobe + - Asia/Ashgabat + - Asia/Ashkhabad + - Asia/Baghdad + - Asia/Bahrain + - Asia/Baku + - Asia/Bangkok + - Asia/Beirut + - Asia/Bishkek + - Asia/Brunei + - Asia/Calcutta + - Asia/Choibalsan + - Asia/Chongqing + - Asia/Chungking + - Asia/Colombo + - Asia/Dacca + - Asia/Damascus + - Asia/Dhaka + - Asia/Dili + - Asia/Dubai + - Asia/Dushanbe + - Asia/Gaza + - Asia/Harbin + - Asia/Hebron + - Asia/Ho_Chi_Minh + - Asia/Hong_Kong + - Asia/Hovd + - Asia/Irkutsk + - Asia/Istanbul + - Asia/Jakarta + - Asia/Jayapura + - Asia/Jerusalem + - Asia/Kabul + - Asia/Kamchatka + - Asia/Karachi + - Asia/Kashgar + - Asia/Kathmandu + - Asia/Katmandu + - Asia/Khandyga + - Asia/Kolkata + - Asia/Krasnoyarsk + - Asia/Kuala_Lumpur + - Asia/Kuching + - Asia/Kuwait + - Asia/Macao + - Asia/Macau + - Asia/Magadan + - Asia/Makassar + - Asia/Manila + - Asia/Muscat + - Asia/Nicosia + - Asia/Novokuznetsk + - Asia/Novosibirsk + - Asia/Omsk + - Asia/Oral + - Asia/Phnom_Penh + - Asia/Pontianak + - Asia/Pyongyang + - Asia/Qatar + - Asia/Qyzylorda + - Asia/Rangoon + - Asia/Riyadh + - Asia/Saigon + - Asia/Sakhalin + - Asia/Samarkand + - Asia/Seoul + - Asia/Shanghai + - Asia/Singapore + - Asia/Taipei + - Asia/Tashkent + - Asia/Tbilisi + - Asia/Tehran + - Asia/Tel_Aviv + - Asia/Thimbu + - Asia/Thimphu + - Asia/Tokyo + - Asia/Ujung_Pandang + - Asia/Ulaanbaatar + - Asia/Ulan_Bator + - Asia/Urumqi + - Asia/Ust-Nera + - Asia/Vientiane + - Asia/Vladivostok + - Asia/Yakutsk + - Asia/Yekaterinburg + - Asia/Yerevan + - Atlantic/Azores + - Atlantic/Bermuda + - Atlantic/Canary + - Atlantic/Cape_Verde + - Atlantic/Faeroe + - Atlantic/Faroe + - Atlantic/Jan_Mayen + - Atlantic/Madeira + - Atlantic/Reykjavik + - Atlantic/South_Georgia + - Atlantic/St_Helena + - Atlantic/Stanley + - Australia/ACT + - Australia/Adelaide + - Australia/Brisbane + - Australia/Broken_Hill + - Australia/Canberra + - Australia/Currie + - Australia/Darwin + - Australia/Eucla + - Australia/Hobart + - Australia/LHI + - Australia/Lindeman + - Australia/Lord_Howe + - Australia/Melbourne + - Australia/North + - Australia/NSW + - Australia/Perth + - Australia/Queensland + - Australia/South + - Australia/Sydney + - Australia/Tasmania + - Australia/Victoria + - Australia/West + - Australia/Yancowinna + - Brazil/Acre + - Brazil/DeNoronha + - Brazil/East + - Brazil/West + - Canada/Atlantic + - Canada/Central + - Canada/Eastern + - Canada/Mountain + - Canada/Newfoundland + - Canada/Pacific + - Canada/Saskatchewan + - Canada/Yukon + - CET + - Chile/Continental + - Chile/EasterIsland + - CST6CDT + - Cuba + - EET + - Egypt + - Eire + - EST + - EST5EDT + - Etc/GMT + - Etc/GMT+0 + - Etc/GMT+1 + - Etc/GMT+10 + - Etc/GMT+11 + - Etc/GMT+12 + - Etc/GMT+2 + - Etc/GMT+3 + - Etc/GMT+4 + - Etc/GMT+5 + - Etc/GMT+6 + - Etc/GMT+7 + - Etc/GMT+8 + - Etc/GMT+9 + - Etc/GMT0 + - Etc/GMT-0 + - Etc/GMT-1 + - Etc/GMT-10 + - Etc/GMT-11 + - Etc/GMT-12 + - Etc/GMT-13 + - Etc/GMT-14 + - Etc/GMT-2 + - Etc/GMT-3 + - Etc/GMT-4 + - Etc/GMT-5 + - Etc/GMT-6 + - Etc/GMT-7 + - Etc/GMT-8 + - Etc/GMT-9 + - Etc/Greenwich + - Etc/UCT + - Etc/Universal + - Etc/UTC + - Etc/Zulu + - Europe/Amsterdam + - Europe/Andorra + - Europe/Athens + - Europe/Belfast + - Europe/Belgrade + - Europe/Berlin + - Europe/Bratislava + - Europe/Brussels + - Europe/Bucharest + - Europe/Budapest + - Europe/Busingen + - Europe/Chisinau + - Europe/Copenhagen + - Europe/Dublin + - Europe/Gibraltar + - Europe/Guernsey + - Europe/Helsinki + - Europe/Isle_of_Man + - Europe/Istanbul + - Europe/Jersey + - Europe/Kaliningrad + - Europe/Kiev + - Europe/Lisbon + - Europe/Ljubljana + - Europe/London + - Europe/Luxembourg + - Europe/Madrid + - Europe/Malta + - Europe/Mariehamn + - Europe/Minsk + - Europe/Monaco + - Europe/Moscow + - Europe/Nicosia + - Europe/Oslo + - Europe/Paris + - Europe/Podgorica + - Europe/Prague + - Europe/Riga + - Europe/Rome + - Europe/Samara + - Europe/San_Marino + - Europe/Sarajevo + - Europe/Simferopol + - Europe/Skopje + - Europe/Sofia + - Europe/Stockholm + - Europe/Tallinn + - Europe/Tirane + - Europe/Tiraspol + - Europe/Uzhgorod + - Europe/Vaduz + - Europe/Vatican + - Europe/Vienna + - Europe/Vilnius + - Europe/Volgograd + - Europe/Warsaw + - Europe/Zagreb + - Europe/Zaporozhye + - Europe/Zurich + - GB + - GB-Eire + - GMT + - GMT+0 + - GMT0 + - GMT-0 + - Greenwich + - Hongkong + - HST + - Iceland + - Indian/Antananarivo + - Indian/Chagos + - Indian/Christmas + - Indian/Cocos + - Indian/Comoro + - Indian/Kerguelen + - Indian/Mahe + - Indian/Maldives + - Indian/Mauritius + - Indian/Mayotte + - Indian/Reunion + - Iran + - Israel + - Jamaica + - Japan + - Kwajalein + - Libya + - MET + - Mexico/BajaNorte + - Mexico/BajaSur + - Mexico/General + - MST + - MST7MDT + - Navajo + - NZ + - NZ-CHAT + - Pacific/Apia + - Pacific/Auckland + - Pacific/Chatham + - Pacific/Chuuk + - Pacific/Easter + - Pacific/Efate + - Pacific/Enderbury + - Pacific/Fakaofo + - Pacific/Fiji + - Pacific/Funafuti + - Pacific/Galapagos + - Pacific/Gambier + - Pacific/Guadalcanal + - Pacific/Guam + - Pacific/Honolulu + - Pacific/Johnston + - Pacific/Kiritimati + - Pacific/Kosrae + - Pacific/Kwajalein + - Pacific/Majuro + - Pacific/Marquesas + - Pacific/Midway + - Pacific/Nauru + - Pacific/Niue + - Pacific/Norfolk + - Pacific/Noumea + - Pacific/Pago_Pago + - Pacific/Palau + - Pacific/Pitcairn + - Pacific/Pohnpei + - Pacific/Ponape + - Pacific/Port_Moresby + - Pacific/Rarotonga + - Pacific/Saipan + - Pacific/Samoa + - Pacific/Tahiti + - Pacific/Tarawa + - Pacific/Tongatapu + - Pacific/Truk + - Pacific/Wake + - Pacific/Wallis + - Pacific/Yap + - Poland + - Portugal + - PRC + - PST8PDT + - ROK + - Singapore + - Turkey + - UCT + - Universal + - US/Alaska + - US/Aleutian + - US/Arizona + - US/Central + - US/Eastern + - US/East-Indiana + - US/Hawaii + - US/Indiana-Starke + - US/Michigan + - US/Mountain + - US/Pacific + - US/Samoa + - UTC + - WET + - W-SU + - Zulu + Default: US/Eastern + UiPathFQDN: + Description: The fully qualified domain name where the UiPath Orchestrator should be installed + Type: String + HostedZoneID: + Description: The Route 53 hosted zone Id + Type: AWS::Route53::HostedZone::Id + HAAUser: + Description: High Availability Add-on username. A valid email must be used as username + Type: String + AllowedPattern: '^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$' + ConstraintDescription: A valid email must be used as HA username + HAAPassword: + Description: High Availability Add-on password. + Type: String + NoEcho: 'true' + MinLength: '6' + MaxLength: '124' + AllowedPattern: >- + (?=^.{6,124}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.* + ConstraintDescription: >- + HA Password must contain at least one uppercase letter and be longer than + six charaters. + HAPort: + Description: Port on which High Availability Add-on listens to + Type: String + Default: 10000 + AllowedValues: + - 10000 + HAALicense: + Description: >- + License key, purchased from UIPath, to be used with the High + Availability Add-on. + Default: '' + Type: String + RDSDBName: + Description: Name for UIPath Database to be created. + Type: String + Default: uipathdb + AllowedPattern: '^[a-zA-Z]*$' + ConstraintDescription: >- + Database name should contain alphabetic characters only, and cannot be + left blank. + RDSAllocatedStorage: + Description: Storage amount to be allocated to RDS DB Instance. + Type: String + Default: '200' + AllowedPattern: '^(?:[1-9]\d{0,3}|1[0-5]\d{3}|16[0-2]\d{2}|163[0-7]\d|1638[0-4])$' + ConstraintDescription: RDS Allocated storage should be less or equal to 16384 GB + RDSMaxAllocatedStorage: + Description: The upper limit to which Amazon RDS can automatically scale the storage of the DB instance. + Type: Number + Default: '1000' + MaxValue: '16384' + ConstraintDescription: RDS Max Allocated storage should be less or equal to RDS Allocated Storage + RDSUsername: + Description: Username to be used to log on to Database. + Type: String + AllowedPattern: '^[0-9a-zA-Z]*$' + ConstraintDescription: Database username cannot be left blank. + RDSPassword: + Description: Password to be used to log on to Database. + Type: String + NoEcho: 'true' + MinLength: '8' + AllowedPattern: '^(?=.*[0-9]+.*)(?=.*[a-zA-Z]+.*)[0-9a-zA-Z]{8,}$' + ConstraintDescription: >- + Database Password must contain at least one letter, at least one number, + and be longer than eight characters. + OrchestratorVersion: + Description: UIPath Orchestrator Version to be installed. + Type: String + Default: 20.10.2 + AllowedValues: + - 20.10.2 + ConstraintDescription: 'Orchestrator version number can contain only digits and dot(.), and must .' + OrchestratorInstanceCount: + Description: Desired Capacity (instance count) for Orchestrator instances. + Type: String + Default: '2' + AllowedPattern: '^(?:[1-9]|0[1-9]|1[0-9]|20)$' + ConstraintDescription: Orchestrator Instances can be between 1 to 20. + OrchestratorLicense: + Description: 'License key, purchased from UiPath, or trial key. If not provided, Orchestrator will be unlicensed' + Default: '' + Type: String + OrchestratorPassword: + Description: Orchestrator Administrator password to login as. + Type: String + NoEcho: 'true' + MinLength: '6' + MaxLength: '124' + AllowedPattern: >- + (?=^.{6,124}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.* + ConstraintDescription: >- + Orchestrator Password must contain at least one uppercase letter and be + longer than six characters. + QSS3BucketName: + AllowedPattern: '^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$' + ConstraintDescription: >- + Quick Start bucket name can include numbers, lowercase letters, uppercase + letters, and hyphens (-). It cannot start or end with a hyphen (-). + Description: S3 bucket name hosting the Quickstart templates. + Type: String + Default: aws-quickstart + QSS3BucketRegion: + Default: us-east-1 + Description: >- + The AWS Region where the Quick Start S3 bucket (QSS3BucketName) is hosted. + When using your own bucket, you must specify this value. + Type: String + AllowedPattern: (us(-gov)?|ap|ca|cn|eu|sa)-(central|(north|south)?(east|west)?)-\d + ConstraintDescription: Quick Start bucket region must be a valid AWS Region code. + QSS3KeyPrefix: + AllowedPattern: '^[0-9a-zA-Z-/]*/$' + ConstraintDescription: >- + Quick Start key prefix can include numbers, lowercase letters, uppercase + letters, and hyphens (-), and ending with a slash(/). + Default: quickstart-uipath-orchestrator/ + Description: S3 key prefix under which Quick Start templates are hosted. + Type: String +Conditions: + UsingDefaultBucket: !Equals + - !Ref QSS3BucketName + - aws-quickstart +Resources: + FindAmiLambdaRole: + Type: 'AWS::IAM::Role' + Properties: + Description: >- + IAM Role to be assumed by Lambda functions which find AMI ids + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + Path: / + Policies: + - PolicyName: root + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'ec2:DescribeImages' + Resource: '*' + - Effect: Allow + Action: + - 'logs:CreateLogGroup' + - 'logs:CreateLogStream' + - 'logs:PutLogEvents' + Resource: 'arn:aws:logs:*:*:*' + - Effect: Allow + Action: + - 'xray:PutTraceSegments' + Resource: '*' + FindAMIFunction: + Type: 'AWS::Lambda::Function' + Properties: + Description: Finds a matching AMI Id + Handler: index.handler + Role: !GetAtt FindAmiLambdaRole.Arn + Code: + ZipFile: !Join + - |+ + + - - import boto3 + - import json + - import cfnresponse + - 'def create(properties, physical_id):' + - ' regionName = properties[''RegionName'']' + - ' imageName = properties[''ImageName'']' + - ' architecture = properties[''Architecture'']' + - ' virtualizationType = properties[''VirtualizationType'']' + - ' owners = properties[''Owners'']' + - ' imageId = ''''' + - ' ec2 = boto3.client(''ec2'', regionName)' + - ' images = ec2.describe_images(' + - ' ExecutableUsers=[''all''],' + - ' Filters=[' + - ' { ''Name'': ''name'', ''Values'': [imageName] },' + - ' { ''Name'': ''state'', ''Values'': [''available''] },' + - ' { ''Name'': ''architecture'', ''Values'': [architecture] },' + - ' { ''Name'': ''virtualization-type'', ''Values'': [virtualizationType] }' + - ' ],' + - ' Owners=[owners]' + - ' )[''Images'']' + - ' if len(images) > 0:' + - ' imageId = images[0][''ImageId'']' + - ' print(regionName, imageId)' + - ' returnAttribute = {}' + - ' returnAttribute[''ImageId''] = imageId' + - ' returnAttribute[''Action''] = ''CREATE''' + - ' return cfnresponse.SUCCESS, imageId, returnAttribute' + - 'def update(properties, physical_id):' + - ' imageId = physical_id' + - ' returnAttribute = {}' + - ' returnAttribute[''ImageId''] = imageId' + - ' returnAttribute[''Action''] = ''UPDATE''' + - ' return cfnresponse.SUCCESS, imageId, returnAttribute' + - 'def delete(properties, physical_id):' + - ' imageId = physical_id' + - ' returnAttribute = {}' + - ' returnAttribute[''ImageId''] = imageId' + - ' returnAttribute[''Action''] = ''DELETE''' + - ' return cfnresponse.SUCCESS, imageId, returnAttribute' + - 'def handler(event, context):' + - ' print(''Received event: '' + json.dumps(event))' + - ' status = cfnresponse.FAILED' + - ' new_physical_id = None' + - ' returnAttribute = {}' + - ' try:' + - ' properties = event.get(''ResourceProperties'')' + - ' physical_id = event.get(''PhysicalResourceId'')' + - ' status, new_physical_id, returnAttribute = {' + - ' ''Create'': create,' + - ' ''Update'': update,' + - ' ''Delete'': delete' + - ' }.get(event[''RequestType''], lambda x, y: (cfnresponse.FAILED, None))(properties, physical_id)' + - ' except Exception as e:' + - ' print(''Exception: '' + str(e))' + - ' status = cfnresponse.FAILED' + - ' finally:' + - ' cfnresponse.send(event, context, status, returnAttribute, new_physical_id)' + Runtime: python3.6 + Timeout: 30 + TracingConfig: + Mode: Active + StorageStack: + Type: 'AWS::CloudFormation::Stack' + Properties: + TemplateURL: !Sub + - >- + https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/storage.template.yaml + - S3Bucket: !If + - UsingDefaultBucket + - !Sub '${QSS3BucketName}-${AWS::Region}' + - !Ref QSS3BucketName + S3Region: !If + - UsingDefaultBucket + - !Ref 'AWS::Region' + - !Ref QSS3BucketRegion + Parameters: + SubnetId: !Select [0, !Ref PrivateSubnetIDs] + KeyPairName: !Ref KeyPairName + GatewayName: !Ref GatewayName + GatewayTimezone: !Ref GatewayTimezone + VPCID: !Ref VPCID + VPCCIDR: !Ref VPCCIDR + RoutingStack: + Type: 'AWS::CloudFormation::Stack' + Properties: + TemplateURL: !Sub + - >- + https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/routing.template.yaml + - S3Bucket: !If + - UsingDefaultBucket + - !Sub '${QSS3BucketName}-${AWS::Region}' + - !Ref QSS3BucketName + S3Region: !If + - UsingDefaultBucket + - !Ref 'AWS::Region' + - !Ref QSS3BucketRegion + Parameters: + VPCID: !Ref VPCID + VPCCIDR: !Ref VPCCIDR + SubnetIDs: !Join [",", !Ref PublicSubnetIDs] + Fqdn: !Ref UiPathFQDN + HostedZoneID: !Ref HostedZoneID + SSLCertificateArn: !GetAtt ConfigureSSLStack.Outputs.ACMCertificate + HAStack: + Type: 'AWS::CloudFormation::Stack' + Properties: + TemplateURL: !Sub + - >- + https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/ha.template.yaml + - S3Bucket: !If + - UsingDefaultBucket + - !Sub '${QSS3BucketName}-${AWS::Region}' + - !Ref QSS3BucketName + S3Region: !If + - UsingDefaultBucket + - !Ref 'AWS::Region' + - !Ref QSS3BucketRegion + Parameters: + VPCID: !Ref VPCID + VPCCIDR: !Ref VPCCIDR + SubnetIDs: !Join [",", !Ref PrivateSubnetIDs] + KeyPairName: !Ref KeyPairName + HAAUser: !Ref HAAUser + HAAPassword: !Ref HAAPassword + HAALicense: !Ref HAALicense + FindAMIFunctionArn: !GetAtt + - FindAMIFunction + - Arn + DatabaseStack: + Type: 'AWS::CloudFormation::Stack' + Properties: + TemplateURL: !Sub + - >- + https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/database.template.yaml + - S3Bucket: !If + - UsingDefaultBucket + - !Sub '${QSS3BucketName}-${AWS::Region}' + - !Ref QSS3BucketName + S3Region: !If + - UsingDefaultBucket + - !Ref 'AWS::Region' + - !Ref QSS3BucketRegion + Parameters: + VPCID: !Ref VPCID + VPCCIDR: !Ref VPCCIDR + SubnetIDs: !Join [",", !Ref PrivateSubnetIDs] + RDSDBName: !Ref RDSDBName + RDSAllocatedStorage: !Ref RDSAllocatedStorage + RDSMaxAllocatedStorage: !Ref RDSMaxAllocatedStorage + RDSUsername: !Ref RDSUsername + RDSPassword: !Ref RDSPassword + ServerStack: + Type: 'AWS::CloudFormation::Stack' + Properties: + TemplateURL: !Sub + - >- + https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/server.template.yaml + - S3Bucket: !If + - UsingDefaultBucket + - !Sub '${QSS3BucketName}-${AWS::Region}' + - !Ref QSS3BucketName + S3Region: !If + - UsingDefaultBucket + - !Ref 'AWS::Region' + - !Ref QSS3BucketRegion + Parameters: + VPCID: !Ref VPCID + VPCCIDR: !Ref VPCCIDR + PrivateSubnetIDs: !Join [",", !Ref PrivateSubnetIDs] + PublicSubnetIDs: !Join [",", !Ref PublicSubnetIDs] + TargetGroupARN: !GetAtt + - RoutingStack + - Outputs.TargetGroupARN + KeyPairName: !Ref KeyPairName + InstanceType: !Ref InstanceType + FindAMIFunctionArn: !GetAtt + - FindAMIFunction + - Arn + OrchestratorVersion: !Ref OrchestratorVersion + OrchestratorInstanceCount: !Ref OrchestratorInstanceCount + OrchestratorLicense: !Ref OrchestratorLicense + OrchestratorPassword: !Ref OrchestratorPassword + DatabaseServerName: !GetAtt + - DatabaseStack + - Outputs.RDSDBInstanceEndpointAddress + DatabaseName: !Ref RDSDBName + DatabaseUsername: !Ref RDSUsername + DatabasePassword: !Ref RDSPassword + NugetStorageBucketName: !GetAtt + - StorageStack + - Outputs.NugetStorageBucketName + NugetStorageBucketArn: !GetAtt + - StorageStack + - Outputs.NugetStorageBucketArn + GatewayPrivateIP: !GetAtt + - StorageStack + - Outputs.GatewayVMPrivateIP1 + HAMasterPrivateIP: !GetAtt + - HAStack + - Outputs.HAMasterPrivateIP + HASlave1PrivateIP: !GetAtt + - HAStack + - Outputs.HASlave1PrivateIP + HASlave2PrivateIP: !GetAtt + - HAStack + - Outputs.HASlave2PrivateIP + HAPort: !Ref HAPort + HAAPassword: !Ref HAAPassword + QSS3BucketName: !Ref QSS3BucketName + QSS3BucketRegion: !Ref QSS3BucketRegion + QSS3KeyPrefix: !Ref QSS3KeyPrefix + Fqdn: !Ref UiPathFQDN + ConfigureSSLStack: + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: !Sub + - "https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}submodules/quickstart-aws-acm-certificate/templates/quickstart-aws-acm-certificate.template.yml" + - S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion] + S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName] + Parameters: + QSS3BucketName: !Ref QSS3BucketName + QSS3BucketRegion: !Ref QSS3BucketRegion + QSS3KeyPrefix: !Sub "${QSS3KeyPrefix}submodules/quickstart-aws-acm-certificate/" + DomainName: !Ref UiPathFQDN + HostedZoneID: !Ref HostedZoneID + +Outputs: + FQDNLoadBalancer: + Description: UiPath Orchestrator Login page + Value: !Ref UiPathFQDN + BastionHost: + Description: Bastion Host's Public IP address + Value: !GetAtt + - ServerStack + - Outputs.BastionHostPublicIP + HaaNodesIPs: + Description: High Availability Add-on's list of IP addresses + Value: !Join + - ',' + - - !GetAtt + - HAStack + - Outputs.HAMasterPrivateIP + - !GetAtt + - HAStack + - Outputs.HASlave1PrivateIP + - !GetAtt + - HAStack + - Outputs.HASlave2PrivateIP + NugetStorageS3Bucket: + Description: S3 bucket name that contains Orchestrator's Nuget packages + Value: !GetAtt + - StorageStack + - Outputs.NugetStorageBucketName