diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/.gitignore b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/.gitignore index e52f47dfa..d833683c1 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/.gitignore +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/.gitignore @@ -17,4 +17,6 @@ dist !license-header.js # The staged files for Deadline -stage \ No newline at end of file +stage + +installers \ No newline at end of file diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/bin/app.ts b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/bin/app.ts index 885167ad2..916d5ec4f 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/bin/app.ts +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/bin/app.ts @@ -5,9 +5,20 @@ */ import 'source-map-support/register'; -import { config } from './config'; + +import { + InstanceClass, + InstanceSize, + InstanceType, + MachineImage, +} from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; + +import { config } from './config'; + +import { ComputeTier } from '../lib/compute-tier'; import { NetworkTier } from '../lib/network-tier'; +import { SecurityTier } from '../lib/security-tier'; import { ServiceTier, } from '../lib/service-tier'; @@ -16,14 +27,9 @@ import { StorageTierDocDB, StorageTierMongoDB, } from '../lib/storage-tier'; -import { SecurityTier } from '../lib/security-tier'; -import { - InstanceClass, - InstanceSize, - InstanceType, - MachineImage, -} from '@aws-cdk/aws-ec2'; -import { ComputeTier } from '../lib/compute-tier'; +import { SSMInstancePolicyAspect } from '../lib/ssm-policy-aspect'; +import { WorkstationTier } from '../lib/workstation-tier'; + // ------------------------------ // // --- Validate Config Values --- // @@ -119,7 +125,19 @@ new ComputeTier(app, 'ComputeTier', { vpc: network.vpc, renderQueue: service.renderQueue, workerMachineImage: MachineImage.genericLinux(config.deadlineClientLinuxAmiMap), - keyPairName: config.keyPairName ? config.keyPairName : undefined, + keyPairName: config.keyPairName, usageBasedLicensing: service.ublLicensing, licenses: config.ublLicenses, }); + +new WorkstationTier(app, 'WorkstationTier', { + env, + vpc: network.vpc, + renderQueue: service.renderQueue, + keyPairName: config.keyPairName, + deadlineInstallerBucketName: config.deadlineInstallerBucketName, + deadlineInstallerObjectNameLinux: config.deadlineInstallerObjectNameLinux, + deadlineInstallerObjectNameWindows: config.deadlineInstallerObjectNameWindows, +}); + +cdk.Aspects.of(app).add(new SSMInstancePolicyAspect()); diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/bin/config.ts b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/bin/config.ts index 3bab1831e..a818980c3 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/bin/config.ts +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/bin/config.ts @@ -21,7 +21,7 @@ class AppConfig { * * See https://www.awsthinkbox.com/end-user-license-agreement for the terms of the agreement. */ - public readonly acceptAwsThinkboxEula: AwsThinkboxEulaAcceptance = AwsThinkboxEulaAcceptance.USER_REJECTS_AWS_THINKBOX_EULA; + public readonly acceptAwsThinkboxEula: AwsThinkboxEulaAcceptance = AwsThinkboxEulaAcceptance.USER_ACCEPTS_AWS_THINKBOX_EULA; /** * Fill this in if you want to receive alarm emails when: @@ -38,29 +38,33 @@ class AppConfig { * "10.1.12" * @default The latest available version of Deadline is used */ - public readonly deadlineVersion?: string; + public readonly deadlineVersion?: string = '10.1.17.4'; /** - * A map of regions to Deadline Client Linux AMIs. As an example, the Linux Deadline 10.1.15.2 AMI ID from us-west-2 - * is filled in. It can be used as-is, added to, or replaced. Ideally the version here should match the version of - * Deadline used in any connected Deadline constructs. + * A map of regions to Deadline Client Linux AMIs. Currently using: + * Deadline Worker Base Image Linux 2 10.1.17.4 with Houdini 18.0.287 and Mantra 18.0.287 2021-06-30T073302Z */ - public readonly deadlineClientLinuxAmiMap: Record = {['us-west-2']: 'ami-0c8431fc72742c110'}; + public readonly deadlineClientLinuxAmiMap: Record = {['us-west-2']: 'ami-01bedc3d422729a29'}; /** * (Optional) A secret (in binary form) in SecretsManager that stores the UBL certificates in a .zip file. */ - public readonly ublCertificatesSecretArn?: string; + // public readonly ublCertificatesSecretArn?: string = 'arn:aws:secretsmanager:us-west-2:#:secret:Certificates-#'; + public readonly ublCertificatesSecretArn?: string = 'arn:aws:secretsmanager:us-west-2:#:secret:UBLCertificates-#'; /** * (Optional) The UBL licenses to use. */ - public readonly ublLicenses?: UsageBasedLicense[]; + public readonly ublLicenses?: UsageBasedLicense[] = [ UsageBasedLicense.forHoudini(), UsageBasedLicense.forMantra() ]; + + public readonly deadlineInstallerBucketName: string = 'rfdk-secrets-management-deadline-installers'; + public readonly deadlineInstallerObjectNameLinux: string = 'DeadlineClient-rev.10.1.18.2.31.g1ac1c7077-linux-x64-installer_rfdk-sm-1.run'; + public readonly deadlineInstallerObjectNameWindows: string = 'DeadlineClient-rev.10.1.18.2.31.g1ac1c7077-windows-installer_rfdk-sm-1.exe'; /** * (Optional) The name of the EC2 keypair to associate with instances. */ - public readonly keyPairName?: string; + public readonly keyPairName?: string = '***'; /** * Whether to use MongoDB to back the render farm. diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/compute-tier.ts b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/compute-tier.ts index 97609cd0b..55ec876c7 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/compute-tier.ts +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/compute-tier.ts @@ -4,17 +4,18 @@ */ import { - BastionHostLinux, + InstanceClass, + InstanceSize, + InstanceType, IMachineImage, IVpc, - Port, } from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; import { - IHost, - InstanceUserDataProvider, + ConfigureSpotEventPlugin, IRenderQueue, IWorkerFleet, + SpotEventPluginFleet, UsageBasedLicense, UsageBasedLicensing, WorkerInstanceFleet, @@ -24,8 +25,6 @@ import { IHealthMonitor, SessionManagerHelper, } from 'aws-rfdk'; -import { Asset } from '@aws-cdk/aws-s3-assets'; -import * as path from 'path' /** * Properties for {@link ComputeTier}. @@ -51,11 +50,6 @@ export interface ComputeTierProps extends cdk.StackProps { */ readonly keyPairName?: string; - /** - * The bastion host to allow connection to Worker nodes. - */ - readonly bastion?: BastionHostLinux; - /** * Licensing source for UBL for worker nodes. */ @@ -67,36 +61,6 @@ export interface ComputeTierProps extends cdk.StackProps { readonly licenses?: UsageBasedLicense[]; } -class UserDataProvider extends InstanceUserDataProvider { - preCloudWatchAgent(host: IHost): void { - host.userData.addCommands('echo preCloudWatchAgent'); - } - preRenderQueueConfiguration(host: IHost): void { - host.userData.addCommands('echo preRenderQueueConfiguration'); - } - preWorkerConfiguration(host: IHost): void { - host.userData.addCommands('echo preWorkerConfiguration'); - } - postWorkerLaunch(host: IHost): void { - host.userData.addCommands('echo postWorkerLaunch'); - if (host.node.scope != undefined) { - const testScript = new Asset( - host.node.scope as cdk.Construct, - 'SampleAsset', - {path: path.join(__dirname, '..', '..', 'scripts', 'configure_worker.sh')}, - ); - testScript.grantRead(host); - const localPath = host.userData.addS3DownloadCommand({ - bucket: testScript.bucket, - bucketKey: testScript.s3ObjectKey, - }); - host.userData.addExecuteFileCommand({ - filePath: localPath, - }) - } - } -} - /** * The computer tier consists of raw compute power. For a Deadline render farm, * this will be the fleet of Worker nodes that render Deadline jobs. @@ -127,6 +91,9 @@ export class ComputeTier extends cdk.Stack { // cleanly remove everything when this stack is destroyed. If you would like to ensure // that this resource is not accidentally deleted, you should set this to true. deletionProtection: false, + vpcSubnets: { + subnetGroupName: "WorkerFleet", + }, }); this.workerFleet = new WorkerInstanceFleet(this, 'WorkerFleet', { @@ -135,7 +102,9 @@ export class ComputeTier extends cdk.Stack { workerMachineImage: props.workerMachineImage, healthMonitor: this.healthMonitor, keyName: props.keyPairName, - userDataProvider: new UserDataProvider(this, 'UserDataProvider'), + vpcSubnets: { + subnetGroupName: "WorkerFleet", + }, }); // This is an optional feature that will set up your EC2 instances to be enabled for use with @@ -146,10 +115,49 @@ export class ComputeTier extends cdk.Stack { if (props.usageBasedLicensing && props.licenses) { props.usageBasedLicensing.grantPortAccess(this.workerFleet, props.licenses); } + const fleet1 = new SpotEventPluginFleet(this, 'SpotEventPluginFleet1', { + vpc: props.vpc, + renderQueue: props.renderQueue, + deadlineGroups: [ + 'group_name1', + ], + instanceTypes: [ + InstanceType.of(InstanceClass.T3, InstanceSize.LARGE), + ], + workerMachineImage: props.workerMachineImage, + maxCapacity: 5, + vpcSubnets: { + subnetGroupName: 'SpotFleet1', + }, + }); - if (props.bastion) { - this.workerFleet.connections.allowFrom(props.bastion, Port.tcp(22)); - } + const fleet2 = new SpotEventPluginFleet(this, 'SpotEventPluginFleet2', { + vpc: props.vpc, + renderQueue: props.renderQueue, + deadlineGroups: [ + 'group_name2', + ], + instanceTypes: [ + InstanceType.of(InstanceClass.T3, InstanceSize.LARGE), + ], + workerMachineImage: props.workerMachineImage, + maxCapacity: 5, + vpcSubnets: { + subnetGroupName: 'SpotFleet2', + }, + }); + + new ConfigureSpotEventPlugin(this, 'ConfigureSpotEventPlugin', { + vpc: props.vpc, + renderQueue: props.renderQueue, + spotFleets: [ + fleet1, + fleet2, + ], + configuration: { + enableResourceTracker: true, + }, + }); } } diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/network-tier.ts b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/network-tier.ts index 07b37b9b5..fa226edcd 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/network-tier.ts +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/network-tier.ts @@ -74,12 +74,27 @@ export class NetworkTier extends cdk.Stack { { name: 'Public', subnetType: SubnetType.PUBLIC, - cidrMask: 28, + cidrMask: 28, // 14 IP addresses }, { name: 'Private', subnetType: SubnetType.PRIVATE, - cidrMask: 18, // 16,382 IP addresses + cidrMask: 20, // 4,094 IP addresses + }, + { + name: 'WorkerFleet', + subnetType: SubnetType.PRIVATE, + cidrMask: 20, // 4,094 IP addresses + }, + { + name: 'SpotFleet1', + subnetType: SubnetType.PRIVATE, + cidrMask: 20, // 4,094 IP addresses + }, + { + name: 'SpotFleet2', + subnetType: SubnetType.PRIVATE, + cidrMask: 20, // 4,094 IP addresses }, ], // VPC flow logs are a security best-practice as they allow us @@ -136,7 +151,7 @@ export class NetworkTier extends cdk.Stack { this.dnsZone = new PrivateHostedZone(this, 'DnsZone', { vpc: this.vpc, - zoneName: 'deadline-test.internal', + zoneName: 'aws-rfdk.com', }); } } diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/service-tier.ts b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/service-tier.ts index 41f1b2479..e2b20975f 100644 --- a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/service-tier.ts +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/service-tier.ts @@ -4,10 +4,7 @@ */ import { - BastionHostLinux, - BlockDeviceVolume, IVpc, - SubnetType, } from '@aws-cdk/aws-ec2'; import { ApplicationProtocol, @@ -15,9 +12,14 @@ import { import { IPrivateHostedZone, } from '@aws-cdk/aws-route53'; +import { + Secret, +} from '@aws-cdk/aws-secretsmanager'; import * as cdk from '@aws-cdk/core'; + import { MountableEfs, + SessionManagerHelper, X509CertificatePem, } from 'aws-rfdk'; import { @@ -25,15 +27,14 @@ import { DatabaseConnection, RenderQueue, Repository, + Stage, ThinkboxDockerImages, + ThinkboxDockerRecipes, UsageBasedLicense, UsageBasedLicensing, VersionQuery, } from 'aws-rfdk/deadline'; -import { - Secret, -} from '@aws-cdk/aws-secretsmanager'; -import { SessionManagerHelper } from 'aws-rfdk/lib/core'; +import * as path from 'path'; /** * Properties for {@link ServiceTier}. @@ -92,11 +93,6 @@ export interface ServiceTierProps extends cdk.StackProps { * The service tier contains all "business-logic" constructs (e.g. Render Queue, UBL Licensing / License Forwarder, etc.). */ export class ServiceTier extends cdk.Stack { - /** - * A bastion host to connect to the render farm with. - */ - public readonly bastion: BastionHostLinux; - /** * The render queue. */ @@ -121,32 +117,6 @@ export class ServiceTier extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props: ServiceTierProps) { super(scope, id, props); - // Bastion instance for convenience (e.g. SSH into RenderQueue and WorkerFleet instances). - // Not a critical component of the farm, so this can be safely removed. An alternative way - // to access your hosts is also provided by the Session Manager, which is also configured - // later in this example. - this.bastion = new BastionHostLinux(this, 'Bastion', { - vpc: props.vpc, - subnetSelection: { - subnetType: SubnetType.PUBLIC, - }, - blockDevices: [{ - deviceName: '/dev/xvda', - volume: BlockDeviceVolume.ebs(50, { - encrypted: true, - })}, - ], - }); - props.database.allowConnectionsFrom(this.bastion); - - // Granting the bastion access to the entire EFS file-system. - // This can also be safely removed - new MountableEfs(this, { - filesystem: props.mountableFileSystem.fileSystem, - }).mountToLinuxInstance(this.bastion.instance, { - location: '/mnt/efs', - }); - this.version = new VersionQuery(this, 'Version', { version: props.deadlineVersion, }); @@ -158,11 +128,9 @@ export class ServiceTier extends cdk.Stack { fileSystem: props.mountableFileSystem, repositoryInstallationTimeout: cdk.Duration.minutes(20), repositoryInstallationPrefix: "/", - }); - - const images = new ThinkboxDockerImages(this, 'Images', { - version: this.version, - userAwsThinkboxEulaAcceptance: props.acceptAwsThinkboxEula, + secretsManagementSettings: { + enabled: true, + }, }); const serverCert = new X509CertificatePem(this, 'RQCert', { @@ -174,9 +142,12 @@ export class ServiceTier extends cdk.Stack { signingCertificate: props.rootCa, }); + const recipes = new ThinkboxDockerRecipes(this, 'Recipes', { + stage: Stage.fromDirectory(path.join(__dirname, "..", "stage")), + }); this.renderQueue = new RenderQueue(this, 'RenderQueue', { vpc: props.vpc, - images: images, + images: recipes.renderQueueImages, repository, hostname: { hostname: 'renderqueue', @@ -198,7 +169,6 @@ export class ServiceTier extends cdk.Stack { // For an EFS and NFS filesystem, this requires the 'fsc' mount option. enableLocalFileCaching: true, }); - this.renderQueue.connections.allowDefaultPortFrom(this.bastion); // This is an optional feature that will set up your EC2 instances to be enabled for use with // the Session Manager. RFDK deploys EC2 instances that aren't available through a public subnet, @@ -214,6 +184,10 @@ export class ServiceTier extends cdk.Stack { } const ublCertSecret = Secret.fromSecretCompleteArn(this, 'UBLCertsSecret', props.ublCertsSecretArn); + const images = new ThinkboxDockerImages(this, 'Images', { + version: this.version, + userAwsThinkboxEulaAcceptance: props.acceptAwsThinkboxEula, + }); this.ublLicensing = new UsageBasedLicensing(this, 'UBLLicensing', { vpc: props.vpc, images: images, diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/ssm-policy-aspect.ts b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/ssm-policy-aspect.ts new file mode 100644 index 000000000..61a81f310 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/ssm-policy-aspect.ts @@ -0,0 +1,13 @@ +import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; +import { Instance } from '@aws-cdk/aws-ec2'; +import { ManagedPolicy } from '@aws-cdk/aws-iam'; +import * as cdk from '@aws-cdk/core'; + +export class SSMInstancePolicyAspect implements cdk.IAspect { + private static SSM_POLICY = ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'); + public visit(node: cdk.IConstruct): void { + if (node instanceof Instance || node instanceof AutoScalingGroup) { + node.role.addManagedPolicy(SSMInstancePolicyAspect.SSM_POLICY); + } + } +} diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/workstation-tier.ts b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/workstation-tier.ts new file mode 100644 index 000000000..55006196d --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/lib/workstation-tier.ts @@ -0,0 +1,188 @@ +import * as path from 'path'; + +import { + Instance, + InstanceClass, + InstanceSize, + InstanceType, + IVpc, + MachineImage, + OperatingSystemType, + Port, + SubnetType, + WindowsVersion, +} from '@aws-cdk/aws-ec2'; +import { Bucket } from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; + +import { + IRenderQueue, +} from 'aws-rfdk/deadline'; +import { + ScriptAsset, + SessionManagerHelper, +} from 'aws-rfdk'; + +export interface WorkstationTierProps extends cdk.StackProps { + /** + * The VPC to deploy service tier resources into. + */ + readonly vpc: IVpc; + + /** + * The render queue. + */ + readonly renderQueue: IRenderQueue; + + /** + * The name of the EC2 keypair to associate with Worker nodes. + */ + readonly keyPairName?: string; + + readonly deadlineInstallerBucketName: string; + readonly deadlineInstallerObjectNameLinux: string; + readonly deadlineInstallerObjectNameWindows: string; +} + +/** + * The workstation tier contains a workstation host that can be used + */ +export class WorkstationTier extends cdk.Stack { + constructor(scope: cdk.Construct, id: string, props: WorkstationTierProps) { + super(scope, id, props); + + const installersBucket = Bucket.fromBucketName(this, 'InstallersBucket', props.deadlineInstallerBucketName); + + // Setup a Windows instance with Deadline installed and configured to connect to the render queue, with + // Session Manager and RDP access + const windowsFarmMonitor = new Instance(this, 'WindowsBastionInstance', { + instanceName: 'WindowsBastionInstance', + vpc: props.vpc, + vpcSubnets: { subnetType: SubnetType.PUBLIC }, + instanceType: InstanceType.of(InstanceClass.T2, InstanceSize.MEDIUM), + machineImage: MachineImage.latestWindows(WindowsVersion.WINDOWS_SERVER_2019_ENGLISH_FULL_BASE), + keyName: props.keyPairName, + }); + SessionManagerHelper.grantPermissionsTo(windowsFarmMonitor); + windowsFarmMonitor.connections.allowFromAnyIpv4(Port.tcp(3389)); + + installersBucket.grantRead(windowsFarmMonitor); + const windowsInstallerScriptAsset = ScriptAsset.fromPathConvention(this, 'WindowsInstallerScript', { + osType: OperatingSystemType.WINDOWS, + baseName: 'installDeadlineClient', + rootDir: path.join( + __dirname, + '..', + 'scripts', + ), + }); + windowsInstallerScriptAsset.executeOn({ + host: windowsFarmMonitor, + args: [ + cdk.Stack.of(installersBucket).region, + installersBucket.bucketName, + props.deadlineInstallerObjectNameWindows, + ], + }); + + props.renderQueue.configureClientInstance({ + host: windowsFarmMonitor, + restartLauncher: true, + }); + + // Setup a Windows instance with Deadline installed and configured to connect to the render queue, with + // Session Manager and RDP access + const windowsControlInstance = new Instance(this, 'WindowsControlInstance', { + instanceName: 'WindowsControlInstance', + vpc: props.vpc, + vpcSubnets: { subnetType: SubnetType.PUBLIC }, + instanceType: InstanceType.of(InstanceClass.T2, InstanceSize.MEDIUM), + machineImage: MachineImage.latestWindows(WindowsVersion.WINDOWS_SERVER_2019_ENGLISH_FULL_BASE), + keyName: props.keyPairName, + }); + SessionManagerHelper.grantPermissionsTo(windowsControlInstance); + windowsControlInstance.connections.allowFromAnyIpv4(Port.tcp(3389)); + + installersBucket.grantRead(windowsControlInstance); + windowsInstallerScriptAsset.executeOn({ + host: windowsControlInstance, + args: [ + cdk.Stack.of(installersBucket).region, + installersBucket.bucketName, + props.deadlineInstallerObjectNameWindows, + ], + }); + + props.renderQueue.configureClientInstance({ + host: windowsControlInstance, + restartLauncher: true, + }); + + // Setup a Linux instance with Deadline installed and configured to connect to the render queue, with + // Session Manager and SSH access + const linuxFarmMonitor = new Instance(this, 'LinuxBastionInstance', { + instanceName: 'LinuxBastionInstance', + vpc: props.vpc, + vpcSubnets: { + subnetType: SubnetType.PUBLIC, + }, + instanceType: InstanceType.of(InstanceClass.T2, InstanceSize.MEDIUM), + machineImage: MachineImage.latestAmazonLinux(), + keyName: props.keyPairName, + }); + SessionManagerHelper.grantPermissionsTo(linuxFarmMonitor); + linuxFarmMonitor.connections.allowFromAnyIpv4(Port.tcp(22)); + + installersBucket.grantRead(linuxFarmMonitor); + const linuxInstallerScriptAsset = ScriptAsset.fromPathConvention(this, 'LinuxInstallerScript', { + osType: OperatingSystemType.LINUX, + baseName: 'installDeadlineClient', + rootDir: path.join( + __dirname, + '..', + 'scripts', + ), + }); + linuxInstallerScriptAsset.executeOn({ + host: linuxFarmMonitor, + args: [ + cdk.Stack.of(installersBucket).region, + installersBucket.bucketName, + props.deadlineInstallerObjectNameLinux, + ], + }); + + props.renderQueue.configureClientInstance({ + host: linuxFarmMonitor, + restartLauncher: true, + }); + + const linuxControlInstance = new Instance(this, 'LinuxControlInstance', { + instanceName: 'LinuxControlInstance', + vpc: props.vpc, + vpcSubnets: { + subnetType: SubnetType.PUBLIC, + }, + instanceType: InstanceType.of(InstanceClass.T2, InstanceSize.MEDIUM), + machineImage: MachineImage.latestAmazonLinux(), + keyName: props.keyPairName, + }); + SessionManagerHelper.grantPermissionsTo(linuxControlInstance); + linuxControlInstance.connections.allowFromAnyIpv4(Port.tcp(22)); + + installersBucket.grantRead(linuxControlInstance); + linuxInstallerScriptAsset.executeOn({ + host: linuxControlInstance, + args: [ + cdk.Stack.of(installersBucket).region, + installersBucket.bucketName, + props.deadlineInstallerObjectNameLinux, + ], + }); + + props.renderQueue.configureClientInstance({ + host: linuxControlInstance, + restartLauncher: true, + }); + } +} diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/scripts/bash/installDeadlineClient.sh b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/scripts/bash/installDeadlineClient.sh new file mode 100644 index 000000000..3ca4dbe80 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/scripts/bash/installDeadlineClient.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# exit when any command fails +set -xeuo pipefail + +USAGE="Usage: $0 + +This script downloads the deadline client installer and executes it." + +if test $# -lt 3 +then + echo "Usage: $0 " + exit 1 +fi + +REGION=$1 +DEADLINE_INSTALLERS_BUCKET_NAME=$2 +DEADLINE_INSTALLERS_OBJECT_KEY=$3 + +CLIENT_INSTALLER=/tmp/deadline_installer.run + +aws s3 cp --region $REGION "s3://$DEADLINE_INSTALLERS_BUCKET_NAME/$DEADLINE_INSTALLERS_OBJECT_KEY" $CLIENT_INSTALLER +chmod +x $CLIENT_INSTALLER + +$CLIENT_INSTALLER --mode unattended \ + --connectiontype Remote \ + --noguimode true \ + --slavestartup false \ + --launcherdaemon true \ + --restartstalled true \ + --autoupdateoverride false + +bash + +if [ -z "$DEADLINE_PATH" ]; then + echo "ERROR: DEADLINE_PATH environment variable not set" + exit 1 +fi + +DEADLINE_COMMAND="$DEADLINE_PATH/deadlinecommand" +if [ ! -f "$DEADLINE_COMMAND" ]; then + echo "ERROR: $DEADLINE_COMMAND not found!" + exit 1 +fi + +# keep worker running +"$DEADLINE_COMMAND" -SetIniFileSetting KeepWorkerRunning True +# Disable S3Backed Cache +"$DEADLINE_COMMAND" -SetIniFileSetting UseS3BackedCache False +# Blank the S3BackedCache Url +"$DEADLINE_COMMAND" -SetIniFileSetting S3BackedCacheUrl "" + +service deadline10launcher stop +killall -w deadlineworker || true +service deadline10launcher start + +echo "Script completed successfully." diff --git a/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/scripts/powershell/installDeadlineClient.ps1 b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/scripts/powershell/installDeadlineClient.ps1 new file mode 100644 index 000000000..1e5a5dad0 --- /dev/null +++ b/examples/deadline/All-In-AWS-Infrastructure-Basic/ts/scripts/powershell/installDeadlineClient.ps1 @@ -0,0 +1,63 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +param ( + [Parameter(Mandatory=$True)] + $region, + [Parameter(Mandatory=$True)] + $deadlineInstallerBucketName, + [Parameter(Mandatory=$True)] + $deadlineInstallerObjectName +) + +Set-PSDebug -Trace 1 + +$ErrorActionPreference = "Stop" + +$deadlineInstaller = "$env:temp\$deadlineInstallerObjectName" +Read-S3Object -BucketName $deadlineInstallerBucketName -Key $deadlineInstallerObjectName -File $deadlineInstaller -Region $region + +$installerArgs = "--mode unattended --licensemode UsageBased --connectiontype Remote --noguimode true --slavestartup false --restartstalled true --launcherservice true --serviceuser `"NT AUTHORITY\NetworkService`" --autoupdateoverride false" + +Start-Process -FilePath $deadlineInstaller -ArgumentList $installerArgs -Wait + +if (-not (net localgroup administrators | Select-String "^NT AUTHORITY\\NETWORK SERVICE$" -Quiet)) { + net localgroup administrators /add "NT AUTHORITY\NETWORK SERVICE" +} + +foreach($level in "Machine","User") { + [Environment]::GetEnvironmentVariables($level).GetEnumerator() | % { + # For Path variables, append the new values, if they're not already in there + if($_.Name -match 'Path$') { + $_.Value = ($((Get-Content "Env:$($_.Name)") + ";$($_.Value)") -split ';' | Select -unique) -join ';' + } + $_ + } | Set-Content -Path { "Env:$($_.Name)" } +} + + + +$DEADLINE_PATH = [Environment]::GetEnvironmentVariables("Machine")["DEADLINE_PATH"] +if (!(Test-Path $DEADLINE_PATH)) { + Write-Host "DEADLINE_PATH does not exists. Exiting..." + exit 1 +} +$DEADLINE_COMMAND = $DEADLINE_PATH + '/deadlinecommand.exe' + +if (!(Test-Path $DEADLINE_COMMAND)) { + Write-Host "DeadlineCommand.exe does not exists. Exiting..." + exit 1 +} + +# keep worker running +& $DEADLINE_COMMAND -SetIniFileSetting KeepWorkerRunning False | Out-Default +# Disable S3Backed Cache +& $DEADLINE_COMMAND -SetIniFileSetting UseS3BackedCache False | Out-Default +# Blank the S3BackedCache Url +& $DEADLINE_COMMAND -SetIniFileSetting S3BackedCacheUrl "" | Out-Default + +Stop-Service -Name "deadline10launcherservice" +taskkill /f /fi "IMAGENAME eq deadlineworker.exe" +Start-Service -Name "deadline10launcherservice" + +Write-Host "Script completed successfully." diff --git a/packages/aws-rfdk/lib/deadline/lib/repository.ts b/packages/aws-rfdk/lib/deadline/lib/repository.ts index 5c3c9cd34..ffcf2debc 100644 --- a/packages/aws-rfdk/lib/deadline/lib/repository.ts +++ b/packages/aws-rfdk/lib/deadline/lib/repository.ts @@ -729,6 +729,9 @@ export class Repository extends Construct implements IRepository { this.node.defaultChild = this.installerGroup; // Ensure the DB is serving before we try to connect to it. this.databaseConnection.addChildDependency(this.installerGroup); + if (this.secretsManagementSettings.enabled) { + this.installerGroup.node.addDependency(this.secretsManagementSettings.credentials!); + } // Updating the user data with installation logs stream -- ALWAYS DO THIS FIRST. this.configureCloudWatchLogStream(this.installerGroup, `${id}`, props.logGroupProps); @@ -1012,7 +1015,7 @@ export class Repository extends Construct implements IRepository { if (this.secretsManagementSettings.enabled) { installerArgs.push('-r', Stack.of(this.secretsManagementSettings.credentials ?? this).region); this.secretsManagementSettings.credentials!.grantRead(installerGroup); - installerArgs.push('-c', this.secretsManagementSettings.credentials!.secretArn ?? ''); + installerArgs.push('-c', this.secretsManagementSettings.credentials!.secretArn); } if (settings) { diff --git a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh index f4deb1349..f3547d29d 100644 --- a/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh +++ b/packages/aws-rfdk/lib/deadline/scripts/bash/installDeadlineRepository.sh @@ -25,7 +25,7 @@ while getopts "i:p:v:s:o:c:r:" opt; do case $opt in i) S3PATH="$OPTARG" ;; - p) PREFIX="$OPTARG" + p) PREFIX="$OPTARG" ;; v) DEADLINE_REPOSITORY_VERSION="$OPTARG" ;; @@ -74,7 +74,7 @@ if test -f "$REPOSITORY_FILE_PATH"; then exit 1 fi echo "Database Connection is valid. Validating Deadline Version." - + # Following runs the .ini file as a script while suppressing all the errors. This creates bash variables with # the key's name and sets its value to the value specified in .ini file. # This is a quick way to read the .ini values but is not a full-proof way. Since we dont have common keys in @@ -123,7 +123,7 @@ if [ ! -z "${SECRET_MANAGEMENT_ARN+x}" ]; then SM_SECRET_STRING=$(jq -r '.SecretString' <<< "$SM_SECRET_VALUE") SECRET_MANAGEMENT_USER=$(jq -r '.username' <<< "$SM_SECRET_STRING") SECRET_MANAGEMENT_PASSWORD=$(jq -r '.password' <<< "$SM_SECRET_STRING") - if !([[ ${#SECRET_MANAGEMENT_PASSWORD} -ge 8 ]] && + if !([[ ${#SECRET_MANAGEMENT_PASSWORD} -ge 8 ]] && echo $SECRET_MANAGEMENT_PASSWORD | grep -q [0-9] && echo $SECRET_MANAGEMENT_PASSWORD | grep -q [a-z] && echo $SECRET_MANAGEMENT_PASSWORD | grep -q [A-Z] && diff --git a/packages/aws-rfdk/lib/deadline/scripts/python/client-rq-connection.py b/packages/aws-rfdk/lib/deadline/scripts/python/client-rq-connection.py index c40a5939a..e6abc6e02 100644 --- a/packages/aws-rfdk/lib/deadline/scripts/python/client-rq-connection.py +++ b/packages/aws-rfdk/lib/deadline/scripts/python/client-rq-connection.py @@ -189,10 +189,10 @@ def configure_deadline( config ): passphrase = fetch_secret(config.client_tls_cert_passphrase) repo_args.append(passphrase) - change_repo_results = call_deadline_command(repo_args) - if change_repo_results.startswith('Deadline configuration error:'): - print(change_repo_results) - raise Exception(change_repo_results) + change_repo_results = call_deadline_command(repo_args) + if change_repo_results.startswith('Deadline configuration error:'): + print(change_repo_results) + raise Exception(change_repo_results) def call_deadline_command(arguments): """ diff --git a/packages/aws-rfdk/lib/deadline/test/asset-constants.ts b/packages/aws-rfdk/lib/deadline/test/asset-constants.ts index 54f752640..16667fba6 100644 --- a/packages/aws-rfdk/lib/deadline/test/asset-constants.ts +++ b/packages/aws-rfdk/lib/deadline/test/asset-constants.ts @@ -51,8 +51,8 @@ export const REPO_DC_ASSET = { }; export const RQ_CONNECTION_ASSET = { - Bucket: 'AssetParameters74fd6cba5cebe5a13738b535ab6b010a0fe1154689bad4df3ef49ed7bddc1075S3Bucket0337801D', - Key: 'AssetParameters74fd6cba5cebe5a13738b535ab6b010a0fe1154689bad4df3ef49ed7bddc1075S3VersionKey144181B5', + Bucket: 'AssetParametersb378c2efaef674bc39b85562be77f98e03a2f367ff21e04b39c7986e532bec59S3Bucket9B034A91', + Key: 'AssetParametersb378c2efaef674bc39b85562be77f98e03a2f367ff21e04b39c7986e532bec59S3VersionKey0BBE496C', }; export function linuxCloudWatchScriptBoilerplate(scriptParams: string) {