├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── LICENSE.txt
├── README.md
├── deployment
├── client_vpn.yaml
├── content-creation-workstation.template
├── outputs
│ └── vfx_packaged.yaml
├── parameters
│ └── test-param.json
├── vfxhost.yaml
└── vpc.yaml
├── documentation
├── Content-Creation-Workstation-Implementation-Guide.pdf
└── images
│ ├── Default_Architectural_Diagram.jpg
│ └── Default_Architectural_Diagram.jpg.license
└── source
├── crhelper-2.0.6.dist-info
├── INSTALLER
├── LICENSE
├── METADATA
├── NOTICE
├── RECORD
├── WHEEL
└── top_level.txt
├── crhelper
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-37.pyc
│ ├── log_helper.cpython-37.pyc
│ ├── resource_helper.cpython-37.pyc
│ └── utils.cpython-37.pyc
├── log_helper.py
├── resource_helper.py
└── utils.py
├── lambda_function.py
└── tests
├── __init__.py
├── __pycache__
├── __init__.cpython-37.pyc
├── test_log_helper.cpython-37.pyc
├── test_resource_helper.cpython-37.pyc
└── test_utils.cpython-37.pyc
├── test_log_helper.py
├── test_resource_helper.py
├── test_utils.py
└── unit
├── __init__.py
└── __pycache__
└── __init__.cpython-37.pyc
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # ChangeLog for Content Creation Workstation
2 |
3 | ## [0.1.7] - 2020-08-14
4 |
5 | ### Added
6 |
7 | - Added: AWS Solution project files and modified directory structure as recommended.
8 |
9 | ## [0.1.6] - 2020-07-21
10 |
11 | ### Added
12 |
13 | - Added: VPC Flow Logs Option
14 | - Added: Feature to have ability to enable termination protection on EC2 instance.
15 | - Added: Feature to create S3 bucket for file storage and sync it with VFX Host.
16 | - Added: Windows now has AWS CLI Downloaded onto the instance.
17 | - Added: IAM role for EC2 instance to have ability to talk to the S3 bucket. (least privilege).
18 |
19 | ### Removed
20 |
21 | - Removed: FTP Server functionality.
22 |
23 | ## [0.1.5] - 2020-06-11
24 |
25 | ### Fixed/Updated
26 |
27 | - Fixed: Client VPN Bug: Client VPN Route error when creating new Client VPN endpoint on a new VPC.
28 | - Fixed: Blender Installation Bug: Blender Installation failure when downloading Blender 2.82. Updated to use chocolatey for download.
29 | - Updated: "CreateVPNEndpoint" parameter to only allow values 'true' and 'false'
30 | - Updated: cfn-init to leverage Powershell file for the cfn-init process.
31 | - Updated: Reference architecture to reflect current installation progress.
32 | - Updated: Updated README.md
33 |
34 | ### Added
35 |
36 | - Added: Scripts to install Wacom drivers on Windows VFX host.
37 |
38 | ### Removed
39 |
40 | - Removed: Unneeded Architecture Diagram Pictures.
41 |
42 | ## [0.1.4] - 2020-06-05
43 |
44 | ### Added
45 |
46 | - Added: Name to VFX Host
47 | - Added: Description to Security Group inbound rules.
48 |
49 | ## [0.1.3] - 2020-05-30
50 |
51 | ### Added
52 |
53 | - Added: FTP Server to Windows VFX Host
54 |
55 | ## [0.1.2] - 2020-05-29
56 |
57 | ### Added
58 |
59 | - Added: FTP Server to Linux VFX Host
60 |
61 | ## [0.1.1] - 2020-05-19
62 |
63 | ### Added
64 |
65 | - Added:Reference Architecture.
66 |
67 | ### Removed
68 |
69 | - Added: FSx Lustre Resources.
70 |
71 | ## [0.1.0] - 2020-05-15
72 |
73 | ### Added
74 |
75 | - Initial solution for deploying Teradici Cloud Access on CentOS or Windows 2019 EC2 instances.
76 | - Architectural Diagrams in README
77 | - Client VPN Endpoint Resources
78 | - FSx for Lustre Resrouces
79 | - Additional Security Group, IAM Role and other AWS Resources.
80 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
4 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
5 | opensource-codeofconduct@amazon.com with any additional questions or comments.
6 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 | ## Reporting Bugs/Feature Requests
10 |
11 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
12 |
13 | When filing an issue, please check [existing open](https://github.com/awslabs/content-creation-workstation/issues), or [recently closed](https://github.com/awslabs/content-creation-workstation/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already
14 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
15 |
16 | * A reproducible test case or series of steps
17 | * The version of our code being used
18 | * Any modifications you've made relevant to the bug
19 | * Anything unusual about your environment or deployment
20 |
21 | ## Contributing via Pull Requests
22 |
23 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
24 |
25 | 1. You are working against the latest source on the *master* branch.
26 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
27 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
28 |
29 | To send us a pull request, please:
30 |
31 | 1. Fork the repository.
32 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
33 | 3. Ensure local tests pass.
34 | 4. Commit to your fork using clear commit messages.
35 | 5. Send us a pull request, answering any default questions in the pull request interface.
36 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
37 |
38 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
39 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
40 |
41 | ## Finding contributions to work on
42 |
43 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/content-creation-workstation/labels/help%20wanted) issues is a great place to start.
44 |
45 | ## Code of Conduct
46 |
47 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
48 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
49 | opensource-codeofconduct@amazon.com with any additional questions or comments.
50 |
51 | ## Security issue notifications
52 |
53 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
54 |
55 | ## Licensing
56 |
57 | See the [LICENSE](https://github.com/awslabs/content-creation-workstation/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
58 |
59 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes.
60 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 |
16 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Content Creation Workstation AWS Sample
2 |
3 | Based off the [Creating a Virtual Workstation on AWS](https://studio-in-the-cloud-tutorials.s3-us-west-1.amazonaws.com/streaming-workstation/Creating+a+Virtual+Workstation+on+AWS.pdf), the following CloudFormation template has been created for deploying a Teradici Cloud Access Software on either CentOS 7 or Windows Server 2019 GPU-Enabled EC2 instances. The CloudFormation also provides the ability to provision Client VPN Endpoint if elected during deployment.
4 |
5 | Below are architecture diagrams of some deployable configurations. These are not all configurations as the solutions offers ability to chose VPC deployment location (new or existing), subnet (public or private), and Client VPN deployment(true or false). This results in 8 possible deployment combinations.
6 |
7 | ## Content Creation Workstation deployed in public subnet in a new VPC
8 |
9 | 
10 |
11 | ## Notes
12 |
13 | Teradici CloudAccess Software is accessible using Marketplace AMI which costs $0.50/hr in addition to EC2 computing costs. EBS volumes are encrypted using the default `aws/ebs` KMS key.
14 |
15 | ## Prerequisites
16 |
17 | Please note that the template will be expecting your environment to be configured in the following way prior to running the template.
18 |
19 | 1. An EC2 Keypair must be created. Click [here](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html) to learn how to create an EC2 Keypair.
20 | 2. If deploying to an existing VPC. Ensure that the VPC must have **DNS Hostnames** and **DNS Support** enabled. For further details click [here](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html).
21 | 3. If deploying a Client VPN endpoint. User must generate a server/client certificate and upload those certificates to AWS Certificate Manager. Click [here](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/authentication-authorization.html#mutual) to see instructions on how to create server/client certs.
22 |
23 | ## Deploying the Cloudformation Templates
24 |
25 | Please note that the below instructions is how to deploy using the AWS CLI. For more detailed instructions on how to deploy this template, reference the [deployment guide](documentation/Content-Creation-Workstation-Implementation-Guide.pdf).
26 |
27 | ### 1. Specify correct parameter values for the CloudFormation template
28 |
29 | Modify existing parameters file (deployment/parameters/test-param.json) or create your own. Values must be specified for all the following parameters:
30 |
31 | | Parameters | Details | Default Value |
32 | | --------------------------- |:---------------------------------------------|:-------------------|
33 | |OSType | Specify whether you want to run Teradici on Linux or Windows OS. Allowed values "linux" or "windows". | linux |
34 | |EBSVolumeSize | Volume size for the VFX Host, in GiB | 100 |
35 | |VFXHostInstanceType | Amazon EC2 instance type for the VFX workstations. | g4dn.xlarge |
36 | |KeyPairName | Name of AWS EC2 Key Pair. | No Default |
37 | |VFXHostAccessCIDR | CIDR Range that will access the VFX Host. Input your network's current public or private IP depending if the VFX is being placed in a public or private subnet | 10.64.0.0/16 |
38 | |VFXHostSubnetPlacement | Specify if VFX host should be placed in "Public" or "Private" subnet. | Public |
39 | |EnableDeleteProtection | Specify if VFX host should have delete protection enabled. | false |
40 | |InstallBlenderSoftware | Specify if VFX host should download and install Blender software. | true |
41 | |CreateS3StorageBucket | Specify if template should create an AWS S3 Bucket and connect the host to sync files between local system and S3 bucket. | true |
42 | |ExistingVPCID | If solution should deploy into an existing VPC, Specify existing VPC ID. | 'N/A' |
43 | |ExistingSubnetID | If solution should deply into an existing VPN, Specify subnet id in which the VFX Host should be placed in. |'N/A' |
44 | |VPCCIDR | If solution should create a new VPC, specify CIDR Block for the VPC | 10.64.0.0/16 |
45 | |PublicSubnet1CIDR | If solution should create a new VPC, specify CIDR Block for the public subnet 1 located in Availability Zone 2 | 10.64.32.0/20 |
46 | |PrivateSubnet1CIDR | If solution should create a new VPC, specify CIDR Block for the private subnet 1 located in Availability Zone 2 | 10.64.96.0/20 |
47 | |EnableVPCFlowLogs | Specify if newly created VPC should have VPC flow logs enabled. The CloudFormation template will create a new S3 bucket to store the logs. It will also capture ALL logs including ACCEPTS and REJECTS.| false |
48 | |CreateVPNEndpoint | Should the CloudFormation create a Client VPN Endpoint. It is recommended if VFX Host is placed in private subnet and there is no other provisions created to connect to private subnet.(Specify 'true' or 'false') | false |
49 | |ClientCidrBlock | If creating Client VPN endpoint in the solution, specify the IPv4 address range. It should be in CIDR notation from which to assign client IP addresses. The address range cannot overlap with the local CIDR of the VPC in which the associated subnet is located, or the routes that you add manually. | 10.50.0.0/20 |
50 | |ServerCertArn | If creating Client VPN endpoint in the solution, specify Server Cert Arn for VPN endpoint. | 'N/A' |
51 | |ClientCertificateArn | If creating Client VPN endpoint in the solution, specify Client Cert Arn for VPN endpoint. | 'N/A' |
52 | |TargetNetworkCidr | If creating Client VPN endpoint in the solution, specify the IPv4 address range, in CIDR notation, of the network for which access is being authorized. | 10.64.0.0/16 |
53 |
54 | ### 2. Prepare the CloudFormation template
55 |
56 | Due to the use of Nested Templates in this solution, the parent template must be packaged in order to properly reference the child template(s). More information regarding CloudFormation packaging can be found [here.](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-cli-package.html)
57 |
58 | Use the following command to package the template:
59 |
60 | ```bash
61 | aws cloudformation package --template-file ./deployment/content-creation-workstation.template --s3-bucket [desired s3 bucket for CF artifacts] --output-template-file ./deployment/outputs/vfx_packaged.yaml --region [AWS deployment region, should be the same region where S3 bucket is located.]
62 | ```
63 |
64 | ### 3. Create CloudFormation stack
65 |
66 | Use the following command to create the CloudFormation Stack:
67 |
68 | ```bash
69 | aws cloudformation create-stack --template-body file://deployment/outputs/vfx_packaged.yaml --parameters file://deployment/parameters/test-param.json --stack-name [desired stack name] --region [AWS Deployment Region] --capabilities CAPABILITY_AUTO_EXPAND CAPABILITY_IAM
70 | ```
71 |
--------------------------------------------------------------------------------
/deployment/client_vpn.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | AWSTemplateFormatVersion: '2010-09-09'
5 | Description: 'This template provisions Client VPN Resources'
6 | Parameters:
7 | ClientCidrBlock:
8 | Description: The IPv4 address range, in CIDR notation, from which to assign client IP addresses. The address range cannot overlap with the local CIDR of the VPC in which the associated subnet is located, or the routes that you add manually.
9 | AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-9]|3[0-2]))$
10 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-32
11 | Type: String
12 | ServerCertArn:
13 | Description: Specify Server Cert Arn for VPN endpoint.
14 | Type: String
15 | ClientCertificateArn:
16 | Description: Specify Client Cert Arn for VPN endpoint.
17 | Type: String
18 | SubnetID:
19 | Description: The ID of the subnet to associate with the Client VPN endpoint.
20 | Type: String
21 | TargetNetworkCidr:
22 | Description: The IPv4 address range, in CIDR notation, of the network for which access is being authorized.
23 | AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(0[0-9]|1[0-9]|2[0-9]|3[0-2]))$
24 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/0-32
25 | Type: String
26 | VPC:
27 | Description: The ID of the VPC to associate with the Client VPN endpoint.
28 | Type: String
29 | #Conditions:
30 |
31 | Resources:
32 | ClientVpnEndpoint:
33 | Type: AWS::EC2::ClientVpnEndpoint
34 | Properties:
35 | AuthenticationOptions:
36 | -
37 | MutualAuthentication:
38 | ClientRootCertificateChainArn: !Ref ClientCertificateArn
39 | Type: "certificate-authentication"
40 | ClientCidrBlock: !Ref ClientCidrBlock
41 | ConnectionLogOptions:
42 | Enabled: false
43 | Description: "Client VPN to connect to VFX Host"
44 | ServerCertificateArn: !Ref ServerCertArn
45 | VpcId: !Ref VPC
46 | SecurityGroupIds:
47 | - !Ref VPNSecurityGroup
48 | Tags:
49 | - Key: Name
50 | Value: VFX-ClientVPN
51 | VpnEndpointTargetNetworkAssociation:
52 | Type: AWS::EC2::ClientVpnTargetNetworkAssociation
53 | Properties:
54 | ClientVpnEndpointId: !Ref ClientVpnEndpoint
55 | SubnetId: !Ref SubnetID
56 | VpnEndpointAuthorizationRule:
57 | Type: AWS::EC2::ClientVpnAuthorizationRule
58 | Properties:
59 | ClientVpnEndpointId: !Ref ClientVpnEndpoint
60 | AuthorizeAllGroups: true
61 | Description: Allow access to VFX Host subnet.
62 | TargetNetworkCidr: !Ref TargetNetworkCidr
63 | InternetAuthRule:
64 | Type: "AWS::EC2::ClientVpnAuthorizationRule"
65 | Properties:
66 | ClientVpnEndpointId: !Ref ClientVpnEndpoint
67 | AuthorizeAllGroups: true
68 | TargetNetworkCidr: "0.0.0.0/0"
69 | Description: "Allow access to internet"
70 | InternetRoute:
71 | Type: "AWS::EC2::ClientVpnRoute"
72 | DependsOn: VpnEndpointTargetNetworkAssociation
73 | Properties:
74 | ClientVpnEndpointId: !Ref ClientVpnEndpoint
75 | TargetVpcSubnetId: !Ref SubnetID
76 | DestinationCidrBlock: "0.0.0.0/0"
77 | Description: "Route to the internet"
78 |
79 | VPNSecurityGroup:
80 | Type: AWS::EC2::SecurityGroup
81 | Properties:
82 | GroupName: Security Group for VPN Clients
83 | GroupDescription: This security group is attached to the Client VPN Endpoint.
84 | VpcId: !Ref VPC
85 | VPNHostSecurityGroupIngress1:
86 | Type: AWS::EC2::SecurityGroupIngress
87 | Properties:
88 | GroupId: !Ref VPNSecurityGroup
89 | IpProtocol: -1
90 | SourceSecurityGroupId: !Ref VPNSecurityGroup
91 | Outputs:
92 | VPNSecurityGroupID:
93 | Description: VPN Security Group ID
94 | Value: !GetAtt VPNSecurityGroup.GroupId
95 | Export:
96 | Name: !Sub "${AWS::StackName}-vpn-security-group-id"
--------------------------------------------------------------------------------
/deployment/content-creation-workstation.template:
--------------------------------------------------------------------------------
1 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | AWSTemplateFormatVersion: 2010-09-09
5 | Transform: 'AWS::Serverless-2016-10-31'
6 | Description: >-
7 | This is a quick start template that deploys the VFX Workstation along with other optional resources such as VPC and Client VPN.
8 | Metadata:
9 | 'AWS::CloudFormation::Interface':
10 | ParameterGroups:
11 | - Label:
12 | default: Amazon EC2 Configuration
13 | Parameters:
14 | - OSType
15 | - EBSVolumeSize
16 | - KeyPairName
17 | - VFXHostInstanceType
18 | - VFXHostAccessCIDR
19 | - VFXHostSubnetPlacement
20 | - EnableDeleteProtection
21 | - InstallBlenderSoftware
22 | - CreateS3StorageBucket
23 | - Label:
24 | default: Existing VPC Configuration
25 | Parameters:
26 | - ExistingVPCID
27 | - ExistingSubnetID
28 | - Label:
29 | default: New VPC Configuration
30 | Parameters:
31 | - VPCCIDR
32 | - PublicSubnet1CIDR
33 | - PrivateSubnet1CIDR
34 | - EnableVPCFlowLogs
35 | - Label:
36 | default: VPN Endpoint Configuration
37 | Parameters:
38 | - CreateVPNEndpoint
39 | - ClientCidrBlock
40 | - ServerCertArn
41 | - ClientCertificateArn
42 | - TargetNetworkCidr
43 |
44 |
45 | ParameterLabels:
46 | OSType:
47 | default: VFX Host Operating System
48 | EBSVolumeSize:
49 | default: EBS Volume size for EC2 instance
50 | VFXHostInstanceType:
51 | default: VFX Host Instance Type
52 | KeyPairName:
53 | default: Key Pair Name
54 | VFXHostAccessCIDR:
55 | default: VFX Host Access CIDR
56 | VFXHostSubnetPlacement:
57 | default: VFX Host subnet placement.
58 | EnableDeleteProtection:
59 | default: Enable Termination Protection
60 | InstallBlenderSoftware:
61 | default: Install Blender Software
62 | CreateS3StorageBucket:
63 | default: Creates S3 bucket to store files and then sync with workstation.
64 | ExistingVPCID:
65 | default: Existing VPC ID
66 | ExistingSubnetID:
67 | default: Existing Subnet ID
68 | VPCCIDR:
69 | default: VPC CIDR Range
70 | PublicSubnet1CIDR:
71 | default: CIDR Range for Public Subnet in new VPC
72 | PrivateSubnet1CIDR:
73 | default: CIDR Range for Private Subnet in new VPC
74 | EnableVPCFlowLogs:
75 | default: Enable VPC Flow Logs
76 | CreateVPNEndpoint:
77 | default: Create VPN Endpoint.
78 | ClientCidrBlock:
79 | default: Client CIDR for VPN Endpoint.
80 | ServerCertArn:
81 | default: Specify Server Cert Arn for VPN endpoint.
82 | ClientCertificateArn:
83 | default: Specify Client Cert Arn for VPN endpoint.
84 | TargetNetworkCidr:
85 | default: Target Network CIDR for VPN Endpoint.
86 |
87 | Parameters:
88 | ############################
89 | ### VFX Host Parameters#####
90 | ############################
91 | OSType:
92 | AllowedValues:
93 | - linux
94 | - windows
95 | Default: linux
96 | Description: Specify whether you want to run Teradici on Linux or Windows OS.
97 | Type: String
98 | EBSVolumeSize:
99 | Default: '100'
100 | Description: 'Volume size for the VFX Host, in GiB'
101 | MaxValue: '16000'
102 | MinValue: '100'
103 | Type: Number
104 | VFXHostInstanceType:
105 | AllowedValues:
106 | - g4dn.xlarge
107 | - g4dn.2xlarge
108 | - g4dn.4xlarge
109 | - g4dn.8xlarge
110 | - g4dn.12xlarge
111 | - g4dn.16xlarge
112 | Default: g4dn.xlarge
113 | Description: Amazon EC2 instance type for the VFX workstations
114 | Type: String
115 | KeyPairName:
116 | Description: >-
117 | Name of AWS EC2 Key Pair.
118 | Type: 'AWS::EC2::KeyPair::KeyName'
119 | VFXHostAccessCIDR:
120 | AllowedPattern: >-
121 | ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-9]|3[0-2]))$
122 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-32
123 | Default: '10.64.0.0/16'
124 | Description: CIDR Block from which the VFX Host will be accessible.
125 | Type: String
126 | VFXHostSubnetPlacement:
127 | AllowedValues:
128 | - Public
129 | - Private
130 | ConstraintDescription: Specify if VFX host should be placed in "Public" or "Private" subnet.
131 | Default: 'Public'
132 | Description: Specify if VFX host should be placed in "Public" or "Private" subnet.
133 | Type: String
134 | EnableDeleteProtection:
135 | AllowedValues:
136 | - "true"
137 | - "false"
138 | ConstraintDescription: Value must be either a true or false.
139 | Default: 'false'
140 | Description: Specify if VFX host should have delete protection enabled.
141 | Type: String
142 | InstallBlenderSoftware:
143 | AllowedValues:
144 | - "true"
145 | - "false"
146 | ConstraintDescription: Value must be either a true or false.
147 | Default: 'true'
148 | Description: Specify if VFX host should download and install Blender software.
149 | Type: String
150 | CreateS3StorageBucket:
151 | AllowedValues:
152 | - "true"
153 | - "false"
154 | ConstraintDescription: Value must be either a true or false.
155 | Default: 'true'
156 | Description: Specify if template should create an AWS S3 Bucket and connect the host to sync files between local system and S3 bucket.
157 | Type: String
158 | ############################
159 | ## Existing VPC Parameters##
160 | ############################
161 | ExistingVPCID:
162 | Default: 'N/A'
163 | Description: If solution should deploy into an existing VPC, Specify existing VPC ID.
164 | Type: String
165 | ExistingSubnetID:
166 | Default: 'N/A'
167 | Description: If solution should deply into an existing VPN, Specify subnet id in which the VFX Host should be placed in.
168 | Type: String
169 | ############################
170 | ##### New VPC Parameters####
171 | ############################
172 | VPCCIDR:
173 | AllowedPattern: >-
174 | ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
175 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
176 | Default: 10.64.0.0/16
177 | Description: If solution should create a new VPC, specify CIDR Block for the VPC
178 | Type: String
179 | PublicSubnet1CIDR:
180 | AllowedPattern: >-
181 | ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
182 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
183 | Default: 10.64.32.0/20
184 | Description: If solution should create a new VPC, specify CIDR Block for the public subnet 1 located in Availability Zone 2
185 | Type: String
186 | PrivateSubnet1CIDR:
187 | AllowedPattern: >-
188 | ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
189 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
190 | Default: 10.64.96.0/20
191 | Description: If solution should create a new VPC, specify CIDR Block for the private subnet 1 located in Availability Zone 2
192 | Type: String
193 | EnableVPCFlowLogs:
194 | AllowedValues:
195 | - "true"
196 | - "false"
197 | ConstraintDescription: Value must be either a true or false.
198 | Default: 'false'
199 | Description: Specify if newly created VPC should have VPC flow logs enabled. The CloudFormation template will create a new S3 bucket to store the logs. It will also capture ALL logs including ACCEPTS and REJECTS.
200 | Type: String
201 | ############################
202 | ### VPN Parameters###
203 | ############################
204 | CreateVPNEndpoint:
205 | ConstraintDescription: Must specify 'true' or 'false'
206 | AllowedValues:
207 | - 'false'
208 | - 'true'
209 | Default: 'false'
210 | Description: "Should the CloudFormation create a Client VPN Endpoint. It is recommended if VFX Host is placed in private subnet and there is no other provisions created to connect to private subnet.(Specify 'true' or 'false')"
211 | Type: String
212 | ClientCidrBlock:
213 | Description: If creating Client VPN endpoint in the solution, specify the IPv4 address range. It should be in CIDR notation from which to assign client IP addresses. The address range cannot overlap with the local CIDR of the VPC in which the associated subnet.
214 | AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-9]|3[0-2]))$
215 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-32
216 | Default: 10.50.0.0/20
217 | Type: String
218 | ServerCertArn:
219 | Description: If creating Client VPN endpoint in the solution, specify Server Cert Arn for VPN endpoint.
220 | Default: 'N/A'
221 | Type: String
222 | ClientCertificateArn:
223 | Description: If creating Client VPN endpoint in the solution, specify Client Cert Arn for VPN endpoint.
224 | Default: 'N/A'
225 | Type: String
226 | TargetNetworkCidr:
227 | Description: If creating Client VPN endpoint in the solution, specify the IPv4 address range, in CIDR notation, of the network for which access is being authorized.
228 | AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(0[0-9]|1[0-9]|2[0-9]|3[0-2]))$
229 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/0-32
230 | Default: '10.64.0.0/16'
231 | Type: String
232 |
233 | Conditions:
234 |
235 | PublicVFXHostPlacement: !Equals [ !Ref VFXHostSubnetPlacement, Public ]
236 | PrivateVFXHostPlacement: !Equals [ !Ref VFXHostSubnetPlacement, Private ]
237 | ExistingVPC: !Not [ !Equals [ 'N/A', !Ref ExistingVPCID ]]
238 | NoExistingVPC: !Equals [ 'N/A', !Ref ExistingVPCID ]
239 | CreateVPN: !Equals [ 'true', !Ref CreateVPNEndpoint ]
240 |
241 | Resources:
242 |
243 | ##############################
244 | ### VPC Resources #####
245 | ##############################
246 | VPCStack:
247 | Type: 'AWS::CloudFormation::Stack'
248 | Condition: "NoExistingVPC"
249 | Properties:
250 | TemplateURL: ./vpc.yaml
251 | Parameters:
252 | PublicAZBSubnetBlock: !Ref PublicSubnet1CIDR
253 | PrivateAZBSubnetBlock: !Ref PrivateSubnet1CIDR
254 | VpcCidrParam: !Ref VPCCIDR
255 | EnableVPCFlowLogs: !Ref EnableVPCFlowLogs
256 |
257 |
258 | ##############################
259 | ### VPN Resources #####
260 | ##############################
261 | VPNStack:
262 | Type: 'AWS::CloudFormation::Stack'
263 | Condition: "CreateVPN"
264 | Properties:
265 | TemplateURL: ./client_vpn.yaml
266 | Parameters:
267 | ClientCidrBlock: !Ref ClientCidrBlock
268 | ServerCertArn: !Ref ServerCertArn
269 | ClientCertificateArn: !Ref ClientCertificateArn
270 | SubnetID: !If [ExistingVPC, !Ref ExistingSubnetID, !GetAtt VPCStack.Outputs.PrivateAZBSubnetId]
271 | TargetNetworkCidr: !Ref TargetNetworkCidr
272 | VPC: !If [ExistingVPC, !Ref ExistingVPCID, !GetAtt VPCStack.Outputs.VpcId]
273 |
274 |
275 | ##############################
276 | ### VFX Host Resources #####
277 | ##############################
278 | CloudVFXHostStack:
279 | Type: 'AWS::CloudFormation::Stack'
280 | Properties:
281 | TemplateURL: ./vfxhost.yaml
282 | Parameters:
283 | EBSVolumeSize: !Ref EBSVolumeSize
284 | VFXHostInstanceType: !Ref VFXHostInstanceType
285 | KeyPairName: !Ref KeyPairName
286 | SubnetID: !If [ExistingVPC, !Ref ExistingSubnetID, !If [PublicVFXHostPlacement, !GetAtt VPCStack.Outputs.PublicAZBSubnetId, !GetAtt VPCStack.Outputs.PrivateAZBSubnetId]]
287 | VPCID: !If [ExistingVPC, !Ref ExistingVPCID, !GetAtt VPCStack.Outputs.VpcId]
288 | VFXHostAccessCIDR: !Ref VFXHostAccessCIDR
289 | AMIID: !GetAtt AMIInfo.AMIID
290 | OSType: !Ref OSType
291 | VFXHostSubnetPlacement: !Ref VFXHostSubnetPlacement
292 | AdditionalSecurityGroupId: !If [CreateVPN,!GetAtt VPNStack.Outputs.VPNSecurityGroupID, !Ref 'AWS::NoValue']
293 | EnableDeleteProtection: !Ref EnableDeleteProtection
294 | InstallBlenderSoftware: !Ref InstallBlenderSoftware
295 | CreateS3StorageBucket: !Ref CreateS3StorageBucket
296 |
297 | ##############################
298 | ### AMI Lookup Resources #####
299 | ##############################
300 | AMILookupLambda:
301 | Type: 'AWS::Serverless::Function'
302 | Properties:
303 | Handler: lambda_function.handler
304 | Runtime: python3.7
305 | CodeUri: ../source/
306 | Role: !GetAtt LambdaExecutionRole.Arn
307 |
308 |
309 | LambdaExecutionRole:
310 | Type: AWS::IAM::Role
311 | Properties:
312 | AssumeRolePolicyDocument:
313 | Version: '2012-10-17'
314 | Statement:
315 | - Effect: Allow
316 | Principal:
317 | Service:
318 | - lambda.amazonaws.com
319 | Action:
320 | - sts:AssumeRole
321 | Path: "/"
322 | Policies:
323 | - PolicyName: root
324 | PolicyDocument:
325 | Version: '2012-10-17'
326 | Statement:
327 | - Effect: Allow
328 | Action:
329 | - logs:CreateLogGroup
330 | - logs:CreateLogStream
331 | - logs:PutLogEvents
332 | Resource: arn:aws:logs:*:*:*
333 | - Effect: Allow
334 | Action:
335 | - ec2:DescribeImages
336 | Resource: "*"
337 |
338 |
339 | AMIInfo:
340 | Type: AWS::CloudFormation::CustomResource
341 | Properties:
342 | ServiceToken: !GetAtt AMILookupLambda.Arn
343 | OSType: !Ref OSType
344 |
345 |
--------------------------------------------------------------------------------
/deployment/outputs/vfx_packaged.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | AWSTemplateFormatVersion: 2010-09-09
5 | Transform: AWS::Serverless-2016-10-31
6 | Description: This is a quick start template that deploys the VFX Workstation along
7 | with other optional resources such as VPC and Client VPN.
8 | Metadata:
9 | AWS::CloudFormation::Interface:
10 | ParameterGroups:
11 | - Label:
12 | default: Amazon EC2 Configuration
13 | Parameters:
14 | - OSType
15 | - EBSVolumeSize
16 | - KeyPairName
17 | - VFXHostInstanceType
18 | - VFXHostAccessCIDR
19 | - VFXHostSubnetPlacement
20 | - EnableDeleteProtection
21 | - InstallBlenderSoftware
22 | - CreateS3StorageBucket
23 | - Label:
24 | default: Existing VPC Configuration
25 | Parameters:
26 | - ExistingVPCID
27 | - ExistingSubnetID
28 | - Label:
29 | default: New VPC Configuration
30 | Parameters:
31 | - VPCCIDR
32 | - PublicSubnet1CIDR
33 | - PrivateSubnet1CIDR
34 | - EnableVPCFlowLogs
35 | - Label:
36 | default: VPN Endpoint Configuration
37 | Parameters:
38 | - CreateVPNEndpoint
39 | - ClientCidrBlock
40 | - ServerCertArn
41 | - ClientCertificateArn
42 | - TargetNetworkCidr
43 | ParameterLabels:
44 | OSType:
45 | default: VFX Host Operating System
46 | EBSVolumeSize:
47 | default: EBS Volume size for EC2 instance
48 | VFXHostInstanceType:
49 | default: VFX Host Instance Type
50 | KeyPairName:
51 | default: Key Pair Name
52 | VFXHostAccessCIDR:
53 | default: VFX Host Access CIDR
54 | VFXHostSubnetPlacement:
55 | default: VFX Host subnet placement.
56 | EnableDeleteProtection:
57 | default: Enable Termination Protection
58 | InstallBlenderSoftware:
59 | default: Install Blender Software
60 | CreateS3StorageBucket:
61 | default: Creates S3 bucket to store files and then sync with workstation.
62 | ExistingVPCID:
63 | default: Existing VPC ID
64 | ExistingSubnetID:
65 | default: Existing Subnet ID
66 | VPCCIDR:
67 | default: VPC CIDR Range
68 | PublicSubnet1CIDR:
69 | default: CIDR Range for Public Subnet in new VPC
70 | PrivateSubnet1CIDR:
71 | default: CIDR Range for Private Subnet in new VPC
72 | EnableVPCFlowLogs:
73 | default: Enable VPC Flow Logs
74 | CreateVPNEndpoint:
75 | default: Create VPN Endpoint.
76 | ClientCidrBlock:
77 | default: Client CIDR for VPN Endpoint.
78 | ServerCertArn:
79 | default: Specify Server Cert Arn for VPN endpoint.
80 | ClientCertificateArn:
81 | default: Specify Client Cert Arn for VPN endpoint.
82 | TargetNetworkCidr:
83 | default: Target Network CIDR for VPN Endpoint.
84 | Parameters:
85 | OSType:
86 | AllowedValues:
87 | - linux
88 | - windows
89 | Default: linux
90 | Description: Specify whether you want to run Teradici on Linux or Windows OS.
91 | Type: String
92 | EBSVolumeSize:
93 | Default: '100'
94 | Description: Volume size for the VFX Host, in GiB
95 | MaxValue: '16000'
96 | MinValue: '100'
97 | Type: Number
98 | VFXHostInstanceType:
99 | AllowedValues:
100 | - g4dn.xlarge
101 | - g4dn.2xlarge
102 | - g4dn.4xlarge
103 | - g4dn.8xlarge
104 | - g4dn.12xlarge
105 | - g4dn.16xlarge
106 | Default: g4dn.xlarge
107 | Description: Amazon EC2 instance type for the VFX workstations
108 | Type: String
109 | KeyPairName:
110 | Description: Name of AWS EC2 Key Pair.
111 | Type: AWS::EC2::KeyPair::KeyName
112 | VFXHostAccessCIDR:
113 | AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-9]|3[0-2]))$
114 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-32
115 | Default: 10.64.0.0/16
116 | Description: CIDR Block from which the VFX Host will be accessible.
117 | Type: String
118 | VFXHostSubnetPlacement:
119 | AllowedValues:
120 | - Public
121 | - Private
122 | ConstraintDescription: Specify if VFX host should be placed in "Public" or "Private"
123 | subnet.
124 | Default: Public
125 | Description: Specify if VFX host should be placed in "Public" or "Private" subnet.
126 | Type: String
127 | EnableDeleteProtection:
128 | AllowedValues:
129 | - 'true'
130 | - 'false'
131 | ConstraintDescription: Value must be either a true or false.
132 | Default: 'false'
133 | Description: Specify if VFX host should have delete protection enabled.
134 | Type: String
135 | InstallBlenderSoftware:
136 | AllowedValues:
137 | - 'true'
138 | - 'false'
139 | ConstraintDescription: Value must be either a true or false.
140 | Default: 'true'
141 | Description: Specify if VFX host should download and install Blender software.
142 | Type: String
143 | CreateS3StorageBucket:
144 | AllowedValues:
145 | - 'true'
146 | - 'false'
147 | ConstraintDescription: Value must be either a true or false.
148 | Default: 'true'
149 | Description: Specify if template should create an AWS S3 Bucket and connect the
150 | host to sync files between local system and S3 bucket.
151 | Type: String
152 | ExistingVPCID:
153 | Default: N/A
154 | Description: If solution should deploy into an existing VPC, Specify existing
155 | VPC ID.
156 | Type: String
157 | ExistingSubnetID:
158 | Default: N/A
159 | Description: If solution should deply into an existing VPN, Specify subnet id
160 | in which the VFX Host should be placed in.
161 | Type: String
162 | VPCCIDR:
163 | AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
164 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
165 | Default: 10.64.0.0/16
166 | Description: If solution should create a new VPC, specify CIDR Block for the VPC
167 | Type: String
168 | PublicSubnet1CIDR:
169 | AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
170 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
171 | Default: 10.64.32.0/20
172 | Description: If solution should create a new VPC, specify CIDR Block for the public
173 | subnet 1 located in Availability Zone 2
174 | Type: String
175 | PrivateSubnet1CIDR:
176 | AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
177 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
178 | Default: 10.64.96.0/20
179 | Description: If solution should create a new VPC, specify CIDR Block for the private
180 | subnet 1 located in Availability Zone 2
181 | Type: String
182 | EnableVPCFlowLogs:
183 | AllowedValues:
184 | - 'true'
185 | - 'false'
186 | ConstraintDescription: Value must be either a true or false.
187 | Default: 'false'
188 | Description: Specify if newly created VPC should have VPC flow logs enabled. The
189 | CloudFormation template will create a new S3 bucket to store the logs. It will
190 | also capture ALL logs including ACCEPTS and REJECTS.
191 | Type: String
192 | CreateVPNEndpoint:
193 | ConstraintDescription: Must specify 'true' or 'false'
194 | AllowedValues:
195 | - 'false'
196 | - 'true'
197 | Default: 'false'
198 | Description: Should the CloudFormation create a Client VPN Endpoint. It is recommended
199 | if VFX Host is placed in private subnet and there is no other provisions created
200 | to connect to private subnet.(Specify 'true' or 'false')
201 | Type: String
202 | ClientCidrBlock:
203 | Description: If creating Client VPN endpoint in the solution, specify the IPv4
204 | address range. It should be in CIDR notation from which to assign client IP
205 | addresses. The address range cannot overlap with the local CIDR of the VPC in
206 | which the associated subnet.
207 | AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-9]|3[0-2]))$
208 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-32
209 | Default: 10.50.0.0/20
210 | Type: String
211 | ServerCertArn:
212 | Description: If creating Client VPN endpoint in the solution, specify Server Cert
213 | Arn for VPN endpoint.
214 | Default: N/A
215 | Type: String
216 | ClientCertificateArn:
217 | Description: If creating Client VPN endpoint in the solution, specify Client Cert
218 | Arn for VPN endpoint.
219 | Default: N/A
220 | Type: String
221 | TargetNetworkCidr:
222 | Description: If creating Client VPN endpoint in the solution, specify the IPv4
223 | address range, in CIDR notation, of the network for which access is being authorized.
224 | AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(0[0-9]|1[0-9]|2[0-9]|3[0-2]))$
225 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/0-32
226 | Default: 10.64.0.0/16
227 | Type: String
228 | Conditions:
229 | PublicVFXHostPlacement:
230 | Fn::Equals:
231 | - Ref: VFXHostSubnetPlacement
232 | - Public
233 | PrivateVFXHostPlacement:
234 | Fn::Equals:
235 | - Ref: VFXHostSubnetPlacement
236 | - Private
237 | ExistingVPC:
238 | Fn::Not:
239 | - Fn::Equals:
240 | - N/A
241 | - Ref: ExistingVPCID
242 | NoExistingVPC:
243 | Fn::Equals:
244 | - N/A
245 | - Ref: ExistingVPCID
246 | CreateVPN:
247 | Fn::Equals:
248 | - 'true'
249 | - Ref: CreateVPNEndpoint
250 | Resources:
251 | VPCStack:
252 | Type: AWS::CloudFormation::Stack
253 | Condition: NoExistingVPC
254 | Properties:
255 | TemplateURL: https://s3.us-west-2.amazonaws.com/kashman-demo-vfxhost/aebb1ec94779960f49f61b0a9f3c0c65.template
256 | Parameters:
257 | PublicAZBSubnetBlock:
258 | Ref: PublicSubnet1CIDR
259 | PrivateAZBSubnetBlock:
260 | Ref: PrivateSubnet1CIDR
261 | VpcCidrParam:
262 | Ref: VPCCIDR
263 | EnableVPCFlowLogs:
264 | Ref: EnableVPCFlowLogs
265 | VPNStack:
266 | Type: AWS::CloudFormation::Stack
267 | Condition: CreateVPN
268 | Properties:
269 | TemplateURL: https://s3.us-west-2.amazonaws.com/kashman-demo-vfxhost/02a941e6be109965f30a8a29943a07e5.template
270 | Parameters:
271 | ClientCidrBlock:
272 | Ref: ClientCidrBlock
273 | ServerCertArn:
274 | Ref: ServerCertArn
275 | ClientCertificateArn:
276 | Ref: ClientCertificateArn
277 | SubnetID:
278 | Fn::If:
279 | - ExistingVPC
280 | - Ref: ExistingSubnetID
281 | - Fn::GetAtt:
282 | - VPCStack
283 | - Outputs.PrivateAZBSubnetId
284 | TargetNetworkCidr:
285 | Ref: TargetNetworkCidr
286 | VPC:
287 | Fn::If:
288 | - ExistingVPC
289 | - Ref: ExistingVPCID
290 | - Fn::GetAtt:
291 | - VPCStack
292 | - Outputs.VpcId
293 | CloudVFXHostStack:
294 | Type: AWS::CloudFormation::Stack
295 | Properties:
296 | TemplateURL: https://s3.us-west-2.amazonaws.com/kashman-demo-vfxhost/af58ed50609dc9b0ccce8108d15eb30e.template
297 | Parameters:
298 | EBSVolumeSize:
299 | Ref: EBSVolumeSize
300 | VFXHostInstanceType:
301 | Ref: VFXHostInstanceType
302 | KeyPairName:
303 | Ref: KeyPairName
304 | SubnetID:
305 | Fn::If:
306 | - ExistingVPC
307 | - Ref: ExistingSubnetID
308 | - Fn::If:
309 | - PublicVFXHostPlacement
310 | - Fn::GetAtt:
311 | - VPCStack
312 | - Outputs.PublicAZBSubnetId
313 | - Fn::GetAtt:
314 | - VPCStack
315 | - Outputs.PrivateAZBSubnetId
316 | VPCID:
317 | Fn::If:
318 | - ExistingVPC
319 | - Ref: ExistingVPCID
320 | - Fn::GetAtt:
321 | - VPCStack
322 | - Outputs.VpcId
323 | VFXHostAccessCIDR:
324 | Ref: VFXHostAccessCIDR
325 | AMIID:
326 | Fn::GetAtt:
327 | - AMIInfo
328 | - AMIID
329 | OSType:
330 | Ref: OSType
331 | VFXHostSubnetPlacement:
332 | Ref: VFXHostSubnetPlacement
333 | AdditionalSecurityGroupId:
334 | Fn::If:
335 | - CreateVPN
336 | - Fn::GetAtt:
337 | - VPNStack
338 | - Outputs.VPNSecurityGroupID
339 | - Ref: AWS::NoValue
340 | EnableDeleteProtection:
341 | Ref: EnableDeleteProtection
342 | InstallBlenderSoftware:
343 | Ref: InstallBlenderSoftware
344 | CreateS3StorageBucket:
345 | Ref: CreateS3StorageBucket
346 | AMILookupLambda:
347 | Type: AWS::Serverless::Function
348 | Properties:
349 | Handler: lambda_function.handler
350 | Runtime: python3.7
351 | CodeUri: s3://kashman-demo-vfxhost/479001191e1c9521f545f28f2e02b347
352 | Role:
353 | Fn::GetAtt:
354 | - LambdaExecutionRole
355 | - Arn
356 | LambdaExecutionRole:
357 | Type: AWS::IAM::Role
358 | Properties:
359 | AssumeRolePolicyDocument:
360 | Version: '2012-10-17'
361 | Statement:
362 | - Effect: Allow
363 | Principal:
364 | Service:
365 | - lambda.amazonaws.com
366 | Action:
367 | - sts:AssumeRole
368 | Path: /
369 | Policies:
370 | - PolicyName: root
371 | PolicyDocument:
372 | Version: '2012-10-17'
373 | Statement:
374 | - Effect: Allow
375 | Action:
376 | - logs:CreateLogGroup
377 | - logs:CreateLogStream
378 | - logs:PutLogEvents
379 | Resource: arn:aws:logs:*:*:*
380 | - Effect: Allow
381 | Action:
382 | - ec2:DescribeImages
383 | Resource: '*'
384 | AMIInfo:
385 | Type: AWS::CloudFormation::CustomResource
386 | Properties:
387 | ServiceToken:
388 | Fn::GetAtt:
389 | - AMILookupLambda
390 | - Arn
391 | OSType:
392 | Ref: OSType
393 |
--------------------------------------------------------------------------------
/deployment/parameters/test-param.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "ParameterKey": "KeyPairName",
4 | "ParameterValue": "Customer-KeyPair-Name"
5 | },
6 | {
7 | "ParameterKey": "VFXHostAccessCIDR",
8 | "ParameterValue": "123.45.67.89/32"
9 | },
10 | {
11 | "ParameterKey": "VFXHostSubnetPlacement",
12 | "ParameterValue": "Public"
13 | },
14 | {
15 | "ParameterKey": "OSType",
16 | "ParameterValue": "windows"
17 | },
18 | {
19 | "ParameterKey": "EnableDeleteProtection",
20 | "ParameterValue": "false"
21 | },
22 | {
23 | "ParameterKey": "InstallBlenderSoftware",
24 | "ParameterValue": "false"
25 | },
26 | {
27 | "ParameterKey": "CreateS3StorageBucket",
28 | "ParameterValue": "true"
29 | },
30 | {
31 | "ParameterKey": "EnableVPCFlowLogs",
32 | "ParameterValue": "false"
33 | },
34 | {
35 | "ParameterKey": "ExistingVPCID",
36 | "ParameterValue": "vpc-id"
37 | },
38 | {
39 | "ParameterKey": "ExistingSubnetID",
40 | "ParameterValue": "subnet-id"
41 | }
42 | ]
--------------------------------------------------------------------------------
/deployment/vfxhost.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | AWSTemplateFormatVersion: '2010-09-09'
5 | Description: 'This template provisions EC2 instance with Teradici and Blender software installed.'
6 | Parameters:
7 | OSType:
8 | AllowedValues:
9 | - linux
10 | - windows
11 | Default: linux
12 | Description: Specify whether you want to run Teradici on Linux or Windows OS.
13 | Type: String
14 | EBSVolumeSize:
15 | Default: '100'
16 | Description: 'Volume size for the VFX Host, in GiB'
17 | MaxValue: '16000'
18 | MinValue: '100'
19 | Type: Number
20 | VFXHostInstanceType:
21 | # AllowedValues:
22 | # - g3.4xlarge
23 | # - g3.8xlarge
24 | # - g3.16xlarge
25 | # - g3s.xlarge
26 | Default: g4dn.xlarge
27 | Description: Amazon EC2 instance type for the VFX workstations
28 | Type: String
29 | KeyPairName:
30 | Description: >-
31 | Public/private key pairs allow you to securely connect to your instance
32 | after it launches
33 | Type: String
34 | VFXHostAccessCIDR:
35 | AllowedPattern: >-
36 | ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]|3[0-2]))$
37 | ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-32
38 | Default: 10.0.128.0/20
39 | Description: CIDR Block for the VFX Host to control authorized access.
40 | Type: String
41 | VPCID:
42 | Description: VPC ID in which the VFX Host placed in.
43 | Type: String
44 | SubnetID:
45 | Description: ID of the public subnet 1 in Availability Zone 1 (e.g., subnet-e3246d8e)
46 | Type: String
47 | AMIID:
48 | Description: VFX Host AMI ID.
49 | Type: String
50 | VFXHostSubnetPlacement:
51 | AllowedValues:
52 | - Public
53 | - Private
54 | ConstraintDescription: Specify if VFX host should be placed in "Public" or "Private" subnet.
55 | Default: 'Public'
56 | Description: Specify if VFX host should be placed in "Public" or "Private" subnet.
57 | Type: String
58 | AdditionalSecurityGroupId:
59 | Default: '0'
60 | Description: "Provide a security group id from which you want VFX Host to be accessed."
61 | Type: String
62 | EnableDeleteProtection:
63 | AllowedValues:
64 | - "true"
65 | - "false"
66 | ConstraintDescription: Value must be either a true or false.
67 | Default: 'false'
68 | Description: Specify if VFX host should have delete protection enabled..
69 | Type: String
70 | InstallBlenderSoftware:
71 | AllowedValues:
72 | - "true"
73 | - "false"
74 | ConstraintDescription: Value must be either a true or false.
75 | Default: 'true'
76 | Description: Specify if VFX host should download and install Blender software.
77 | Type: String
78 | CreateS3StorageBucket:
79 | AllowedValues:
80 | - "true"
81 | - "false"
82 | ConstraintDescription: Value must be either a true or false.
83 | Default: 'true'
84 | Description: Specify if template should create an AWS S3 Bucket and connect the host to sync files between local system and S3 bucket.
85 | Type: String
86 | Conditions:
87 | PublicVFXHostPlacement: !Equals [ !Ref VFXHostSubnetPlacement, Public ]
88 | WindowsOS: !Equals [ !Ref OSType, 'windows' ]
89 | LinuxOS: !Equals [ !Ref OSType, 'linux' ]
90 | InstallBlenderSoftwareCond: !Equals [ !Ref InstallBlenderSoftware, 'true' ]
91 | CreateS3StorageBucketCond: !Equals [ !Ref CreateS3StorageBucket, 'true' ]
92 | AdditionalSecurityGroup: !Not [!Equals [ !Ref AdditionalSecurityGroupId, '0' ]]
93 | AdditionalSecurityGroupAndWindows: !And [ !Condition AdditionalSecurityGroup,!Condition WindowsOS ]
94 | AdditionalSecurityGroupAndLinux: !And [!Condition AdditionalSecurityGroup,!Condition LinuxOS ]
95 | Resources:
96 | ##############################
97 | #### VFX Host Resources ######
98 | ##############################
99 | PublicEIP:
100 | Type: AWS::EC2::EIP
101 | Condition: PublicVFXHostPlacement
102 | Properties:
103 | InstanceId: !If [LinuxOS, !Ref LinuxVFXHost, !Ref WindowsVFXHost]
104 | VFXHostRole:
105 | Type: AWS::IAM::Role
106 | Condition: CreateS3StorageBucketCond
107 | Properties:
108 | Path: "/"
109 | AssumeRolePolicyDocument:
110 | Version: 2012-10-17
111 | Statement:
112 | - Effect: Allow
113 | Principal:
114 | Service:
115 | - ec2.amazonaws.com
116 | Action:
117 | - 'sts:AssumeRole'
118 | Policies:
119 | -
120 | PolicyName: "allow-access-to-s3-storage-bucket"
121 | PolicyDocument:
122 | Version: "2012-10-17"
123 | Statement:
124 | -
125 | Effect: "Allow"
126 | Action:
127 | - "s3:ListBucket"
128 | Resource: !GetAtt StorageBucket.Arn
129 | -
130 | Effect: "Allow"
131 | Action:
132 | - "s3:PutObject"
133 | - "s3:GetObject"
134 | - "s3:DeleteObject"
135 | Resource: !Join ["",[!GetAtt StorageBucket.Arn,"/*"]]
136 | VFXHostInstanceProfile:
137 | Type: 'AWS::IAM::InstanceProfile'
138 | Condition: CreateS3StorageBucketCond
139 | Properties:
140 | Path: /
141 | Roles:
142 | - !Ref VFXHostRole
143 | LinuxVFXHost:
144 | Type: AWS::EC2::Instance
145 | Condition: "LinuxOS"
146 | Metadata:
147 | AWS::CloudFormation::Init:
148 | install_base:
149 | files:
150 | # These files are needed for CloudFormation::Init to work
151 | /etc/cfn/cfn-hup.conf:
152 | content: !Sub |
153 | [main]
154 | stack=${AWS::StackId}
155 | region=${AWS::Region}
156 | interval=1
157 | /etc/cfn/hooks.d/cfn-auto-reloader.conf:
158 | content: !Sub |
159 | [cfn-auto-reloader-hook]
160 | triggers=post.update
161 | path=Resources.LinuxVFXHost.Metadata.AWS::CloudFormation::Init
162 | action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --region ${AWS::Region} --resource LinuxVFXHost
163 | runas=root
164 | services:
165 | sysvinit:
166 | cfn-hup:
167 | enabled: true
168 | ensureRunning: true
169 | InstallBlender:
170 | commands:
171 | 30-install-snapd:
172 | command: "yum -y install snapd"
173 | 40-enable-snapd-socket:
174 | command: "systemctl enable --now snapd.socket"
175 | 50-create-symlink:
176 | command: "ln -s /var/lib/snapd/snap /snap"
177 | 60-snapd-wait:
178 | command: "snap wait system seed.loaded"
179 | 70-install-blender:
180 | command: "snap install blender --classic"
181 | Configure_S3:
182 | files:
183 | /root/configure_s3.sh:
184 | content:
185 | Fn::Sub:
186 | - |
187 | #!/bin/bash
188 | crontab -l > current_cron
189 | cat >> current_cron << EOF
190 | */1 * * * * aws s3 sync s3://${StorageBucket} /home/centos/s3
191 | */1 * * * * aws s3 sync /home/centos/s3 s3://${StorageBucket}
192 | EOF
193 | crontab < current_cron
194 | rm -f current_cron
195 | - StorageBucket: !If [CreateS3StorageBucketCond, !Ref StorageBucket, '']
196 | commands:
197 | 10-make-directory:
198 | command: 'mkdir /home/centos/s3'
199 | 15-create-aws-sync:
200 | command: 'bash /root/configure_s3.sh'
201 |
202 |
203 | Success:
204 | commands:
205 | 10-send-success-signal:
206 | command: !Join ['',['/opt/aws/bin/cfn-signal -e 0 --stack ',!Ref 'AWS::StackName',' --resource LinuxVFXHost --region ', !Ref 'AWS::Region']]
207 | configSets:
208 | CentOSConfigSet:
209 | - "install_base"
210 | - Fn::If: [InstallBlenderSoftwareCond, "InstallBlender", !Ref "AWS::NoValue"]
211 | - Fn::If: [CreateS3StorageBucketCond, "Configure_S3", !Ref "AWS::NoValue"]
212 | - Success
213 | default:
214 | -
215 | ConfigSet: "CentOSConfigSet"
216 | CreationPolicy:
217 | ResourceSignal:
218 | Count: '1'
219 | Timeout: PT25M
220 | Properties:
221 | KeyName: !Ref KeyPairName
222 | DisableApiTermination: !Ref EnableDeleteProtection
223 | ImageId: !Ref AMIID #'ami-03dc6d7f59e5f1765'
224 | InstanceType: !Ref VFXHostInstanceType
225 | IamInstanceProfile: !If [CreateS3StorageBucketCond, !Ref VFXHostInstanceProfile, !Ref "AWS::NoValue"]
226 | SecurityGroupIds:
227 | - !Ref VFXSecurityGroup
228 | SubnetId: !Ref SubnetID
229 | BlockDeviceMappings:
230 | - DeviceName: /dev/sda1
231 | Ebs:
232 | VolumeType: gp2
233 | VolumeSize: !Ref EBSVolumeSize
234 | DeleteOnTermination: 'true'
235 | Encrypted: 'true'
236 | UserData:
237 | Fn::Base64: !Sub |
238 | #!/bin/bash
239 | yum install -y epel-release
240 | yum install -y awscli
241 | /usr/bin/easy_install --script-dir /opt/aws/bin https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz
242 | cp -v /usr/lib/python2*/site-packages/aws_cfn_bootstrap*/init/redhat/cfn-hup /etc/init.d
243 | chmod +x /etc/init.d/cfn-hup
244 | /opt/aws/bin/cfn-init --stack ${AWS::StackId} --resource LinuxVFXHost --region ${AWS::Region}
245 | /opt/aws/bin/cfn-signal -e 0 --stack ${AWS::StackName} --resource LinuxVFXHost --region ${AWS::Region}
246 | Tags:
247 | - Key: Name
248 | Value: VFXHost
249 |
250 | WindowsVFXHost:
251 | Type: AWS::EC2::Instance
252 | Condition: "WindowsOS"
253 | Metadata:
254 | AWS::CloudFormation::Init:
255 | InstallBlender:
256 | files:
257 | C:\cfn\scripts\install_blender.ps1:
258 | content: |
259 | <#
260 | .SYNOPSIS
261 |
262 | This script downloads and installs chocolatey package manager and then installs Blender for Windows.
263 |
264 | .DESCRIPTION
265 |
266 | The command enables Powershell to download the chocolatey package manager Then it installs Blender which is an open source 3D graphics software.
267 |
268 | .EXAMPLE
269 |
270 | C:\PS> .\install_blender.ps1
271 | #>
272 |
273 |
274 | # Write log to local txt file
275 | Start-Transcript -Path C:\cfn\log\install_chocolatey.ps1.txt -Append
276 | $ErrorActionPreference = "Stop"
277 |
278 | Set-ExecutionPolicy Bypass -Scope Process -Force
279 | [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
280 | iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
281 | choco install --yes --limitoutput blender
282 |
283 | commands:
284 | 10-install-chocolatey:
285 | command: powershell.exe -Command ".\install_blender.ps1"
286 | cwd: C:\cfn\scripts\
287 | waitAfterCompletion: 0
288 | Configure_EBS_Volume:
289 | files:
290 | C:\cfn\scripts\diskpart.txt:
291 | content: |
292 | select disk 1
293 | attributes disk clear readonly
294 | convert mbr
295 | create partition primary
296 | format quick fs=ntfs label=EBS_Volume
297 | assign letter=D
298 | commands:
299 | 10-configure-ebs:
300 | command: powershell.exe -Command "diskpart /s C:\cfn\scripts\diskpart.txt"
301 | waitAfterCompletion: 0
302 | Configure_S3:
303 | files:
304 | C:\cfn\scripts\s3_sync.ps1:
305 | content:
306 | Fn::Sub:
307 | - |
308 | # Write log to local txt file
309 | Start-Transcript -Path C:\cfn\log\s3_sync.ps1.txt -Append
310 | $ErrorActionPreference = "Stop"
311 |
312 | Set-ExecutionPolicy Bypass -Scope Process -Force
313 |
314 | aws s3 sync s3://${StorageBucket} D:\s3
315 | aws s3 sync D:\s3 s3://${StorageBucket}
316 | - StorageBucket: !If [CreateS3StorageBucketCond, !Ref StorageBucket, '']
317 | C:\cfn\scripts\s3_sync.vbs:
318 | content: |
319 | command = "powershell.exe -nologo -command C:\cfn\scripts\s3_sync.ps1"
320 | set shell = CreateObject("WScript.Shell")
321 | shell.Run command,0
322 | C:\cfn\scripts\s3_configure.ps1:
323 | content: |
324 | <#
325 | .SYNOPSIS
326 |
327 | This script downloads and installs AWS CLI
328 |
329 | .DESCRIPTION
330 |
331 | The command enables Powershell to download the AWS CLI and then installs it. Later it creates a task definition for two AWS sync commands.
332 |
333 | .EXAMPLE
334 |
335 | C:\PS> .\s3_configure.ps1
336 | #>
337 |
338 |
339 | # Write log to local txt file
340 | Start-Transcript -Path C:\cfn\log\s3_configure.ps1.txt -Append
341 | $ErrorActionPreference = "Stop"
342 |
343 | Set-ExecutionPolicy Bypass -Scope Process -Force
344 | [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
345 |
346 | Invoke-Expression -Command "((New-Object System.Net.WebClient).DownloadFile('https://awscli.amazonaws.com/AWSCLIV2.msi', 'C:\cfn\scripts\AWSCLIV2.msi'))"
347 |
348 | # Install AWS CLI
349 |
350 | Start-Process msiexec.exe -Wait -ArgumentList '/I C:\cfn\scripts\AWSCLIV2.msi /quiet'
351 |
352 | # Create a New Folder for S3 Bucket
353 |
354 | New-Item -Path 'D:\s3' -ItemType Directory
355 |
356 | # Add Scheduled Task Definitions
357 |
358 | Schtasks /create /tn "Sync with AWS S3 Bucket" /sc minute /mo 1 /tr "C:\cfn\scripts\s3_sync.vbs"
359 | commands:
360 | 010-configure-s3-sync:
361 | command: powershell.exe -Command ".\s3_configure.ps1"
362 | cwd: C:\cfn\scripts\
363 | waitAfterCompletion: 0
364 | Install_Wacom_Drivers:
365 | files:
366 | C:\cfn\scripts\install-wacom-drivers.ps1:
367 | content: |
368 | [CmdletBinding()]
369 | param(
370 | [Parameter(Mandatory=$false)]
371 | [string]$Source = 'http://cdn.wacom.com/u/productsupport/drivers/win/professional/WacomTablet_6.3.39-1.exe',
372 | # 'http://cdn.wacom.com/u/productsupport/drivers/win/professional/WacomTablet_6.3.29-6.exe'
373 |
374 | [Parameter(Mandatory=$false)]
375 | [string]$Destination = 'C:\cfn\downloads\wacom-installer.exe'
376 | )
377 |
378 | try {
379 | $ErrorActionPreference = "Stop"
380 |
381 | $parentDir = Split-Path $Destination -Parent
382 | if (-not (Test-Path $parentDir)) {
383 | New-Item -Path $parentDir -ItemType directory -Force | Out-Null
384 | }
385 |
386 | Write-Verbose "Trying to download Wacom driver from $Source to $Destination"
387 | $tries = 5
388 | while ($tries -ge 1) {
389 | try {
390 | (New-Object System.Net.WebClient).DownloadFile($Source,$Destination)
391 | break
392 | }
393 | catch {
394 | $tries--
395 | Write-Verbose "Exception:"
396 | Write-Verbose "$_"
397 | if ($tries -lt 1) {
398 | throw $_
399 | }
400 | else {
401 | Write-Verbose "Failed download. Retrying again in 5 seconds"
402 | Start-Sleep 5
403 | }
404 | }
405 | }
406 |
407 | if ([System.IO.Path]::GetExtension($Destination) -eq '.exe') {
408 | Write-Verbose "Start install of Wacom drivers ..."
409 | # '/NoPostReboot' - to prevent reboot
410 | #
411 | Start-Process -FilePath $Destination -ArgumentList '/S', '/NoPostReboot' -Wait
412 |
413 | } else {
414 | throw "Unable to install Wacom drivers, not .exe extension"
415 | }
416 | Write-Verbose "Install Wacom drivers complete"
417 | }
418 | catch {
419 | Write-Verbose "catch: $_"
420 | $_ | Write-AWSQuickStartException
421 | }
422 | commands:
423 | 10-install-wacom-drivers:
424 | command: powershell.exe -Command ".\install-wacom-drivers.ps1"
425 | cwd: C:\cfn\scripts\
426 | waitAfterCompletion: 0
427 | SignalSuccess:
428 | commands:
429 | 200-signal-success:
430 | command: !Join ['',['powershell.exe -Command "cfn-signal.exe --success true --stack ', !Ref 'AWS::StackName', ' --resource WindowsVFXHost --region ', !Ref 'AWS::Region']]
431 | waitAfterCompletion: 0
432 |
433 | configSets:
434 | WindowsOSConfigSet:
435 | - Fn::If: [InstallBlenderSoftwareCond, "InstallBlender", !Ref "AWS::NoValue"]
436 | - "Configure_EBS_Volume"
437 | - Fn::If: [CreateS3StorageBucketCond, "Configure_S3", !Ref "AWS::NoValue"]
438 | - "Install_Wacom_Drivers"
439 | - "SignalSuccess"
440 |
441 |
442 | default:
443 | -
444 | ConfigSet: "WindowsOSConfigSet"
445 | CreationPolicy:
446 | ResourceSignal:
447 | Count: '1'
448 | Timeout: PT25M
449 | Properties:
450 | KeyName: !Ref KeyPairName
451 | ImageId: !Ref AMIID
452 | InstanceType: !Ref VFXHostInstanceType
453 | IamInstanceProfile: !If [CreateS3StorageBucketCond, !Ref VFXHostInstanceProfile, !Ref "AWS::NoValue"]
454 | DisableApiTermination: !Ref EnableDeleteProtection
455 | SecurityGroupIds:
456 | - !Ref VFXSecurityGroup
457 | SubnetId: !Ref SubnetID
458 | BlockDeviceMappings:
459 | - DeviceName: /dev/sda1
460 | Ebs:
461 | VolumeType: gp2
462 | VolumeSize: !Ref EBSVolumeSize
463 | DeleteOnTermination: 'true'
464 | Encrypted: 'true'
465 | UserData:
466 | Fn::Base64: !Sub |
467 |
468 | Start-Transcript -Path "C:\cfn\log\userdata.log"
469 | cfn-init.exe --stack ${AWS::StackName} --resource WindowsVFXHost --region ${AWS::Region}
470 | Stop-Transcript
471 |
472 | false
473 | Tags:
474 | - Key: Name
475 | Value: VFXHost
476 |
477 |
478 | VFXSecurityGroup:
479 | Type: AWS::EC2::SecurityGroup
480 | Properties:
481 | GroupDescription: This security group is based on recommended settings for Teradici Cloud Access Software for CentOS 7 version 20.01.1-a provided by Teradici
482 | VpcId: !Ref VPCID
483 | VFXHostSecurityGroupIngress1:
484 | Type: AWS::EC2::SecurityGroupIngress
485 | Properties:
486 | GroupId: !Ref VFXSecurityGroup
487 | FromPort: 4172
488 | ToPort: 4172
489 | IpProtocol: tcp
490 | CidrIp: !Ref VFXHostAccessCIDR
491 | Description: 'PCoIP Session Establishment Port (Access from approved CIDR)'
492 | VFXHostSecurityGroupIngress2:
493 | Type: AWS::EC2::SecurityGroupIngress
494 | Properties:
495 | GroupId: !Ref VFXSecurityGroup
496 | FromPort: 4172
497 | ToPort: 4172
498 | IpProtocol: udp
499 | CidrIp: !Ref VFXHostAccessCIDR
500 | Description: 'PCoIP Session Data (Access from approved CIDR)'
501 | VFXHostSecurityGroupIngress3:
502 | Type: AWS::EC2::SecurityGroupIngress
503 | Properties:
504 | GroupId: !Ref VFXSecurityGroup
505 | Description:
506 | FromPort: 443
507 | ToPort: 443
508 | IpProtocol: tcp
509 | CidrIp: !Ref VFXHostAccessCIDR
510 | Description: 'Client Authentication (Access from approved CIDR)'
511 | VFXHostSecurityGroupIngress4:
512 | Type: AWS::EC2::SecurityGroupIngress
513 | Condition: LinuxOS
514 | Properties:
515 | GroupId: !Ref VFXSecurityGroup
516 | FromPort: 22
517 | ToPort: 22
518 | IpProtocol: tcp
519 | CidrIp: !Ref VFXHostAccessCIDR
520 | Description: 'SSH Port (Access from approved CIDR)'
521 | VFXHostSecurityGroupIngress5:
522 | Type: AWS::EC2::SecurityGroupIngress
523 | Condition: WindowsOS
524 | Properties:
525 | GroupId: !Ref VFXSecurityGroup
526 | FromPort: 3389
527 | ToPort: 3389
528 | IpProtocol: tcp
529 | CidrIp: !Ref VFXHostAccessCIDR
530 | Description: 'Remote Desktop Port (Access from approved CIDR)'
531 | VFXHostSecurityGroupIngress6:
532 | Type: AWS::EC2::SecurityGroupIngress
533 | Condition: AdditionalSecurityGroup
534 | Properties:
535 | GroupId: !Ref VFXSecurityGroup
536 | FromPort: 4172
537 | ToPort: 4172
538 | IpProtocol: tcp
539 | SourceSecurityGroupId: !Ref AdditionalSecurityGroupId
540 | Description: 'PCoIP Session Establishment Port (Access from VPN Security Group)'
541 | VFXHostSecurityGroupIngress7:
542 | Type: AWS::EC2::SecurityGroupIngress
543 | Condition: AdditionalSecurityGroup
544 | Properties:
545 | GroupId: !Ref VFXSecurityGroup
546 | FromPort: 4172
547 | ToPort: 4172
548 | IpProtocol: udp
549 | SourceSecurityGroupId: !Ref AdditionalSecurityGroupId
550 | Description: 'PCoIP Session Data (Access from VPN Security Group)'
551 | VFXHostSecurityGroupIngress8:
552 | Type: AWS::EC2::SecurityGroupIngress
553 | Condition: AdditionalSecurityGroup
554 | Properties:
555 | GroupId: !Ref VFXSecurityGroup
556 | FromPort: 443
557 | ToPort: 443
558 | IpProtocol: tcp
559 | SourceSecurityGroupId: !Ref AdditionalSecurityGroupId
560 | Description: 'Client Authentication (Access from VPN Security Group)'
561 | VFXHostSecurityGroupIngress9:
562 | Type: AWS::EC2::SecurityGroupIngress
563 | Condition: AdditionalSecurityGroupAndLinux
564 | Properties:
565 | GroupId: !Ref VFXSecurityGroup
566 | FromPort: 22
567 | ToPort: 22
568 | IpProtocol: tcp
569 | SourceSecurityGroupId: !Ref AdditionalSecurityGroupId
570 | Description: 'SSH Port (Access from VPN Security Group)'
571 | VFXHostSecurityGroupIngress10:
572 | Type: AWS::EC2::SecurityGroupIngress
573 | Condition: AdditionalSecurityGroupAndWindows
574 | Properties:
575 | GroupId: !Ref VFXSecurityGroup
576 | FromPort: 3389
577 | ToPort: 3389
578 | IpProtocol: tcp
579 | SourceSecurityGroupId: !Ref AdditionalSecurityGroupId
580 | Description: 'Remote Desktop Port (Access from VPN Security Group)'
581 |
582 |
583 |
584 |
585 |
586 |
587 | ##############################
588 | ###### S3 Resources ##########
589 | ##############################
590 | StorageBucket:
591 | Type: "AWS::S3::Bucket"
592 | Condition: CreateS3StorageBucketCond
593 | DeletionPolicy: Retain
594 | Properties:
595 | BucketEncryption:
596 | ServerSideEncryptionConfiguration:
597 | - ServerSideEncryptionByDefault:
598 | SSEAlgorithm: AES256
599 | BucketName: !Join
600 | - "-"
601 | - - "vfx-storage"
602 | - !Select
603 | - 0
604 | - !Split
605 | - "-"
606 | - !Select
607 | - 2
608 | - !Split
609 | - "/"
610 | - !Ref "AWS::StackId"
611 | Outputs:
612 | StorageS3Bucket:
613 | Description: "Storage S3 Bucket"
614 | Condition: CreateS3StorageBucketCond
615 | Value: !Ref StorageBucket
616 | Export:
617 | Name: !Sub "${AWS::StackName}:StorageS3Bucket"
618 | WindowsVFXInstanceID:
619 | Description: Instance ID of the VFX Host.
620 | Condition: WindowsOS
621 | Value: !Ref WindowsVFXHost
622 | Export:
623 | Name: !Sub "${AWS::StackName}:VFXInstanceID"
624 | WindowsVFXInstanceIP:
625 | Description: IP address of VFX Host.
626 | Condition: WindowsOS
627 | Value: !If [PublicVFXHostPlacement, !GetAtt WindowsVFXHost.PublicIp, !GetAtt WindowsVFXHost.PrivateIp]
628 | Export:
629 | Name: !Sub "${AWS::StackName}:VFXInstanceIP"
630 | LinuxVFXInstanceID:
631 | Description: Instance ID of the VFX Host.
632 | Condition: LinuxOS
633 | Value: !Ref LinuxVFXHost
634 | Export:
635 | Name: !Sub "${AWS::StackName}VFXInstanceID"
636 | LinuxVFXInstanceIP:
637 | Description: IP address of VFX Host.
638 | Condition: LinuxOS
639 | Value: !If [PublicVFXHostPlacement, !GetAtt LinuxVFXHost.PublicIp, !GetAtt LinuxVFXHost.PrivateIp]
640 | Export:
641 | Name: !Sub "${AWS::StackName}:VFXInstanceIP"
--------------------------------------------------------------------------------
/deployment/vpc.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | AWSTemplateFormatVersion: 2010-09-09
5 | Description: >
6 | Creates a VPC with public subnet for a given AWS Account.
7 | Parameters:
8 | VpcCidrParam:
9 | Type: String
10 | Description: VPC CIDR. For more info, see http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html#VPC_Sizing
11 | AllowedPattern: "^(10|172|192)\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\/(16|17|18|19|20|21|22|23|24|25|26|27|28)$"
12 | ConstraintDescription: must be valid IPv4 CIDR block (/16 to /28) from the private address ranges defined in RFC 1918.
13 |
14 | # Public Subnets
15 | PublicAZBSubnetBlock:
16 | Type: String
17 | Description: Subnet CIDR for first Availability Zone
18 | AllowedPattern: "^(10|172|192)\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\/(16|17|18|19|20|21|22|23|24|25|26|27|28)$"
19 | ConstraintDescription: must be valid IPv4 CIDR block (/16 to /28) from the private address ranges defined in RFC 1918.
20 | PrivateAZBSubnetBlock:
21 | Type: String
22 | Description: Subnet CIDR for second Availability Zone (e.g. us-west-2b, us-east-1c)
23 | AllowedPattern: "^(10|172|192)\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\/(16|17|18|19|20|21|22|23|24|25|26|27|28)$"
24 | ConstraintDescription: must be valid IPv4 CIDR block (/16 to /28) from the private address ranges defined in RFC 1918.
25 |
26 | # VPC Flow Logs
27 | EnableVPCFlowLogs:
28 | AllowedValues:
29 | - "true"
30 | - "false"
31 | ConstraintDescription: Value must be either a true or false.
32 | Default: 'false'
33 | Description: Specify if newly created VPC should have VPC flow logs enabled. The CloudFormation template will create a new S3 bucket to store the logs. It will also capture ALL logs including ACCEPTS and REJECTS.
34 | Type: String
35 | Conditions:
36 | EnableVPCFlowLogsCond: !Equals [ !Ref EnableVPCFlowLogs, 'true' ]
37 | Outputs:
38 | VpcId:
39 | Description: VPC Id
40 | Value: !Ref Vpc
41 | Export:
42 | Name: !Sub "${AWS::StackName}-vpc-cidr"
43 | VpcCidr:
44 | Description: VPC Cidr
45 | Value: !GetAtt Vpc.CidrBlock
46 | Export:
47 | Name: !Sub "${AWS::StackName}-vpc-id"
48 |
49 | PublicRouteTableId:
50 | Description: Route Table for public subnets
51 | Value: !Ref PublicRouteTable
52 | Export:
53 | Name: !Sub "${AWS::StackName}-public-rtb"
54 |
55 | PublicAZBSubnetId:
56 | Description: Availability Zone A public subnet Id
57 | Value: !Ref PublicAZBSubnet
58 | Export:
59 | Name: !Sub "${AWS::StackName}-public-az-a-subnet"
60 |
61 | PrivateAZBSubnetId:
62 | Description: Availability Zone B private subnet Id
63 | Value: !Ref PrivateAZBSubnet
64 | Export:
65 | Name: !Sub "${AWS::StackName}-private-az-b-subnet"
66 |
67 | VPCFlowLogsBucket:
68 | Description: S3 Bucket where VPC Flow logs are stored
69 | Condition: EnableVPCFlowLogsCond
70 | Value: !GetAtt VPCFlowLogsBucket.Arn
71 | Export:
72 | Name: !Sub "${AWS::StackName}-vpc-flowlogs-bucket"
73 |
74 |
75 | Resources:
76 | Vpc:
77 | Type: AWS::EC2::VPC
78 | Properties:
79 | CidrBlock: !Ref VpcCidrParam
80 | EnableDnsHostnames: True
81 | EnableDnsSupport: True
82 | Tags:
83 | - Key: Name
84 | Value: !Sub ${AWS::StackName}
85 |
86 | InternetGateway:
87 | Type: AWS::EC2::InternetGateway
88 | Properties:
89 | Tags:
90 | - Key: Name
91 | Value: !Sub ${AWS::StackName}
92 |
93 | VPCGatewayAttachment:
94 | Type: AWS::EC2::VPCGatewayAttachment
95 | Properties:
96 | InternetGatewayId: !Ref InternetGateway
97 | VpcId: !Ref Vpc
98 |
99 | # Public Subnets - Route Table
100 | PublicRouteTable:
101 | Type: AWS::EC2::RouteTable
102 | Properties:
103 | VpcId: !Ref Vpc
104 | Tags:
105 | - Key: Name
106 | Value: !Sub ${AWS::StackName}-public
107 | - Key: Type
108 | Value: public
109 |
110 | PublicSubnetsRoute:
111 | Type: AWS::EC2::Route
112 | Properties:
113 | RouteTableId: !Ref PublicRouteTable
114 | DestinationCidrBlock: 0.0.0.0/0
115 | GatewayId: !Ref InternetGateway
116 | DependsOn: VPCGatewayAttachment
117 |
118 | # Public Subnets
119 | # First Availability Zone
120 | PublicAZBSubnet:
121 | Type: AWS::EC2::Subnet
122 | Properties:
123 | VpcId: !Ref Vpc
124 | CidrBlock: !Ref PublicAZBSubnetBlock
125 | AvailabilityZone: !Select [1, !GetAZs ""]
126 | MapPublicIpOnLaunch: true
127 | Tags:
128 | - Key: Name
129 | Value: !Sub
130 | - ${AWS::StackName}-public-${AZ}
131 | - { AZ: !Select [1, !GetAZs ""] }
132 | - Key: Type
133 | Value: public
134 |
135 | PublicAZBSubnetRouteTableAssociation:
136 | Type: AWS::EC2::SubnetRouteTableAssociation
137 | Properties:
138 | SubnetId: !Ref PublicAZBSubnet
139 | RouteTableId: !Ref PublicRouteTable
140 |
141 | AZBNatGatewayEIP:
142 | Type: AWS::EC2::EIP
143 | Properties:
144 | Domain: vpc
145 | DependsOn: VPCGatewayAttachment
146 |
147 | AZBNatGateway:
148 | Type: AWS::EC2::NatGateway
149 | Properties:
150 | AllocationId: !GetAtt AZBNatGatewayEIP.AllocationId
151 | SubnetId: !Ref PublicAZBSubnet
152 | PrivateAZBSubnet:
153 | Type: AWS::EC2::Subnet
154 | Properties:
155 | VpcId: !Ref Vpc
156 | CidrBlock: !Ref PrivateAZBSubnetBlock
157 | AvailabilityZone: !Select [1, !GetAZs ""]
158 | Tags:
159 | - Key: Name
160 | Value: !Sub
161 | - ${AWS::StackName}-private-${AZ}
162 | - { AZ: !Select [1, !GetAZs ""] }
163 | - Key: Type
164 | Value: private
165 |
166 | PrivateAZBRouteTable:
167 | Type: AWS::EC2::RouteTable
168 | Properties:
169 | VpcId: !Ref Vpc
170 | Tags:
171 | - Key: Name
172 | Value: !Sub
173 | - ${AWS::StackName}-private-${AZ}
174 | - { AZ: !Select [1, !GetAZs ""] }
175 | - Key: Type
176 | Value: private
177 |
178 | PrivateAZBRoute:
179 | Type: AWS::EC2::Route
180 | Properties:
181 | RouteTableId: !Ref PrivateAZBRouteTable
182 | DestinationCidrBlock: 0.0.0.0/0
183 | NatGatewayId: !Ref AZBNatGateway
184 |
185 | PrivateAZBRouteTableAssociation:
186 | Type: AWS::EC2::SubnetRouteTableAssociation
187 | Properties:
188 | SubnetId: !Ref PrivateAZBSubnet
189 | RouteTableId: !Ref PrivateAZBRouteTable
190 |
191 |
192 | S3VPCEndpoint:
193 | Type: "AWS::EC2::VPCEndpoint"
194 | Properties:
195 | RouteTableIds:
196 | - !Ref PublicRouteTable
197 | ServiceName: !Join
198 | - ""
199 | - - com.amazonaws.
200 | - !Ref "AWS::Region"
201 | - .s3
202 | VpcId: !Ref Vpc
203 |
204 | #VPC Flow Logs.
205 | VPCFlowLogs:
206 | Type: AWS::EC2::FlowLog
207 | Condition: EnableVPCFlowLogsCond
208 | Properties:
209 | LogDestination: !GetAtt VPCFlowLogsBucket.Arn
210 | LogDestinationType: s3
211 | ResourceId: !Ref Vpc
212 | ResourceType: VPC
213 | TrafficType: ALL
214 |
215 | VPCFlowLogsBucket:
216 | Type: "AWS::S3::Bucket"
217 | DeletionPolicy: Retain
218 | Condition: EnableVPCFlowLogsCond
219 | Properties:
220 | VersioningConfiguration:
221 | Status: Enabled
222 | BucketName: !Join
223 | - "-"
224 | - - "vpcflowlogs"
225 | - !Select
226 | - 0
227 | - !Split
228 | - "-"
229 | - !Select
230 | - 2
231 | - !Split
232 | - "/"
233 | - !Ref "AWS::StackId"
--------------------------------------------------------------------------------
/documentation/Content-Creation-Workstation-Implementation-Guide.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/documentation/Content-Creation-Workstation-Implementation-Guide.pdf
--------------------------------------------------------------------------------
/documentation/images/Default_Architectural_Diagram.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/documentation/images/Default_Architectural_Diagram.jpg
--------------------------------------------------------------------------------
/documentation/images/Default_Architectural_Diagram.jpg.license:
--------------------------------------------------------------------------------
1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/source/crhelper-2.0.6.dist-info/INSTALLER:
--------------------------------------------------------------------------------
1 | pip
2 |
--------------------------------------------------------------------------------
/source/crhelper-2.0.6.dist-info/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
--------------------------------------------------------------------------------
/source/crhelper-2.0.6.dist-info/METADATA:
--------------------------------------------------------------------------------
1 | Metadata-Version: 2.1
2 | Name: crhelper
3 | Version: 2.0.6
4 | Summary: crhelper simplifies authoring CloudFormation Custom Resources
5 | Home-page: https://github.com/aws-cloudformation/custom-resource-helper
6 | Author: Jay McConnell
7 | Author-email: jmmccon@amazon.com
8 | License: Apache2
9 | Platform: UNKNOWN
10 | Classifier: Programming Language :: Python :: 3.6
11 | Classifier: Programming Language :: Python :: 3.7
12 | Classifier: License :: OSI Approved :: Apache Software License
13 | Classifier: Operating System :: OS Independent
14 | Description-Content-Type: text/markdown
15 |
16 | ## Custom Resource Helper
17 |
18 | Simplify best practice Custom Resource creation, sending responses to CloudFormation and providing exception, timeout
19 | trapping, and detailed configurable logging.
20 |
21 | [](https://pypi.org/project/crhelper/)
22 | 
23 | [](https://travis-ci.com/aws-cloudformation/custom-resource-helper)
24 | [](https://codecov.io/gh/aws-cloudformation/custom-resource-helper)
25 |
26 | ## Features
27 |
28 | * Dead simple to use, reduces the complexity of writing a CloudFormation custom resource
29 | * Guarantees that CloudFormation will get a response even if an exception is raised
30 | * Returns meaningful errors to CloudFormation Stack events in the case of a failure
31 | * Polling enables run times longer than the lambda 15 minute limit
32 | * JSON logging that includes request id's, stack id's and request type to assist in tracing logs relevant to a
33 | particular CloudFormation event
34 | * Catches function timeouts and sends CloudFormation a failure response
35 | * Static typing (mypy) compatible
36 |
37 | ## Installation
38 |
39 | Install into the root folder of your lambda function
40 |
41 | ```json
42 | cd my-lambda-function/
43 | pip install crhelper -t .
44 | ```
45 |
46 | ## Example Usage
47 |
48 | [This blog](https://aws.amazon.com/blogs/infrastructure-and-automation/aws-cloudformation-custom-resource-creation-with-python-aws-lambda-and-crhelper/) covers usage in more detail.
49 |
50 | ```python
51 | from __future__ import print_function
52 | from crhelper import CfnResource
53 | import logging
54 |
55 | logger = logging.getLogger(__name__)
56 | # Initialise the helper, all inputs are optional, this example shows the defaults
57 | helper = CfnResource(json_logging=False, log_level='DEBUG', boto_level='CRITICAL', sleep_on_delete=120)
58 |
59 | try:
60 | ## Init code goes here
61 | pass
62 | except Exception as e:
63 | helper.init_failure(e)
64 |
65 |
66 | @helper.create
67 | def create(event, context):
68 | logger.info("Got Create")
69 | # Optionally return an ID that will be used for the resource PhysicalResourceId,
70 | # if None is returned an ID will be generated. If a poll_create function is defined
71 | # return value is placed into the poll event as event['CrHelperData']['PhysicalResourceId']
72 | #
73 | # To add response data update the helper.Data dict
74 | # If poll is enabled data is placed into poll event as event['CrHelperData']
75 | helper.Data.update({"test": "testdata"})
76 |
77 | # To return an error to cloudformation you raise an exception:
78 | if not helper.Data.get("test"):
79 | raise ValueError("this error will show in the cloudformation events log and console.")
80 |
81 | return "MyResourceId"
82 |
83 |
84 | @helper.update
85 | def update(event, context):
86 | logger.info("Got Update")
87 | # If the update resulted in a new resource being created, return an id for the new resource.
88 | # CloudFormation will send a delete event with the old id when stack update completes
89 |
90 |
91 | @helper.delete
92 | def delete(event, context):
93 | logger.info("Got Delete")
94 | # Delete never returns anything. Should not fail if the underlying resources are already deleted.
95 | # Desired state.
96 |
97 |
98 | @helper.poll_create
99 | def poll_create(event, context):
100 | logger.info("Got create poll")
101 | # Return a resource id or True to indicate that creation is complete. if True is returned an id
102 | # will be generated
103 | return True
104 |
105 |
106 | def handler(event, context):
107 | helper(event, context)
108 | ```
109 |
110 | ### Polling
111 |
112 | If you need longer than the max runtime of 15 minutes, you can enable polling by adding additional decorators for
113 | `poll_create`, `poll_update` or `poll_delete`. When a poll function is defined for `create`/`update`/`delete` the
114 | function will not send a response to CloudFormation and instead a CloudWatch Events schedule will be created to
115 | re-invoke the lambda function every 2 minutes. When the function is invoked the matching `@helper.poll_` function will
116 | be called, logic to check for completion should go here, if the function returns `None` then the schedule will run again
117 | in 2 minutes. Once complete either return a PhysicalResourceID or `True` to have one generated. The schedule will be
118 | deleted and a response sent back to CloudFormation. If you use polling the following additional IAM policy must be
119 | attached to the function's IAM role:
120 |
121 | ```yaml
122 | {
123 | "Version": "2012-10-17",
124 | "Statement": [
125 | {
126 | "Effect": "Allow",
127 | "Action": [
128 | "lambda:AddPermission",
129 | "lambda:RemovePermission",
130 | "events:PutRule",
131 | "events:DeleteRule",
132 | "events:PutTargets",
133 | "events:RemoveTargets"
134 | ],
135 | "Resource": "*"
136 | }
137 | ]
138 | }
139 | ```
140 |
141 | ## Credits
142 |
143 | Decorator implementation inspired by https://github.com/ryansb/cfn-wrapper-python
144 |
145 | Log implementation inspired by https://gitlab.com/hadrien/aws_lambda_logging
146 |
147 | ## License
148 |
149 | This library is licensed under the Apache 2.0 License.
150 |
151 |
152 |
--------------------------------------------------------------------------------
/source/crhelper-2.0.6.dist-info/NOTICE:
--------------------------------------------------------------------------------
1 | Custom Resource Helper
2 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 |
--------------------------------------------------------------------------------
/source/crhelper-2.0.6.dist-info/RECORD:
--------------------------------------------------------------------------------
1 | crhelper-2.0.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2 | crhelper-2.0.6.dist-info/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
3 | crhelper-2.0.6.dist-info/METADATA,sha256=0FEfmNkHpgUGUHmR-GGoiZwcGJsEYmJE92mkBI_tQ1Q,5537
4 | crhelper-2.0.6.dist-info/NOTICE,sha256=gDru0mjdrGkrCJfnHTVboKMdS7U85Ha8bV_PQTCckfM,96
5 | crhelper-2.0.6.dist-info/RECORD,,
6 | crhelper-2.0.6.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
7 | crhelper-2.0.6.dist-info/top_level.txt,sha256=pe_5uNErAyss8aUfseYKAjd3a1-LXM6bPjnkun7vbso,15
8 | crhelper/__init__.py,sha256=VSvHU2MKgP96DHSDXR1OYxnbC8j7yfuVhZubBLU7Pns,66
9 | crhelper/__pycache__/__init__.cpython-37.pyc,,
10 | crhelper/__pycache__/log_helper.cpython-37.pyc,,
11 | crhelper/__pycache__/resource_helper.cpython-37.pyc,,
12 | crhelper/__pycache__/utils.cpython-37.pyc,,
13 | crhelper/log_helper.py,sha256=18n4WKlGgxXL_iiYPqE8dWv9TW4sPZc4Ae3px5dbHmY,2665
14 | crhelper/resource_helper.py,sha256=jlFCL0YMi1lEN9kOqhRtKkMcDovoJJpwq1oTk3W5hX0,12637
15 | crhelper/utils.py,sha256=HX_ZnUy3DP81L5ofOVshhWK9NwYnZ9dzIWUPnOfFm5w,1384
16 | tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17 | tests/__pycache__/__init__.cpython-37.pyc,,
18 | tests/__pycache__/test_log_helper.cpython-37.pyc,,
19 | tests/__pycache__/test_resource_helper.cpython-37.pyc,,
20 | tests/__pycache__/test_utils.cpython-37.pyc,,
21 | tests/test_log_helper.py,sha256=T25g-RnRYrwp05v__25thYiodWIIDtoSXDFAqe9Z7rQ,3256
22 | tests/test_resource_helper.py,sha256=5BzbcWX49kSZN0GveRpG8Bt3PHAYUGubJMOmbAigFP0,14462
23 | tests/test_utils.py,sha256=HbLMvoXfYbF952AMM-ey8RNasbYHFqfX17rqajluOKM,1407
24 | tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25 | tests/unit/__pycache__/__init__.cpython-37.pyc,,
26 |
--------------------------------------------------------------------------------
/source/crhelper-2.0.6.dist-info/WHEEL:
--------------------------------------------------------------------------------
1 | Wheel-Version: 1.0
2 | Generator: bdist_wheel (0.34.2)
3 | Root-Is-Purelib: true
4 | Tag: py3-none-any
5 |
6 |
--------------------------------------------------------------------------------
/source/crhelper-2.0.6.dist-info/top_level.txt:
--------------------------------------------------------------------------------
1 | crhelper
2 | tests
3 |
--------------------------------------------------------------------------------
/source/crhelper/__init__.py:
--------------------------------------------------------------------------------
1 | from crhelper.resource_helper import CfnResource, SUCCESS, FAILED
2 |
--------------------------------------------------------------------------------
/source/crhelper/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/source/crhelper/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/source/crhelper/__pycache__/log_helper.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/source/crhelper/__pycache__/log_helper.cpython-37.pyc
--------------------------------------------------------------------------------
/source/crhelper/__pycache__/resource_helper.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/source/crhelper/__pycache__/resource_helper.cpython-37.pyc
--------------------------------------------------------------------------------
/source/crhelper/__pycache__/utils.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/source/crhelper/__pycache__/utils.cpython-37.pyc
--------------------------------------------------------------------------------
/source/crhelper/log_helper.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 | import json
3 | import logging
4 |
5 |
6 | def _json_formatter(obj):
7 | """Formatter for unserialisable values."""
8 | return str(obj)
9 |
10 |
11 | class JsonFormatter(logging.Formatter):
12 | """AWS Lambda Logging formatter.
13 |
14 | Formats the log message as a JSON encoded string. If the message is a
15 | dict it will be used directly. If the message can be parsed as JSON, then
16 | the parse d value is used in the output record.
17 | """
18 |
19 | def __init__(self, **kwargs):
20 | super(JsonFormatter, self).__init__()
21 | self.format_dict = {
22 | 'timestamp': '%(asctime)s',
23 | 'level': '%(levelname)s',
24 | 'location': '%(name)s.%(funcName)s:%(lineno)d',
25 | }
26 | self.format_dict.update(kwargs)
27 | self.default_json_formatter = kwargs.pop(
28 | 'json_default', _json_formatter)
29 |
30 | def format(self, record):
31 | record_dict = record.__dict__.copy()
32 | record_dict['asctime'] = self.formatTime(record)
33 |
34 | log_dict = {
35 | k: v % record_dict
36 | for k, v in self.format_dict.items()
37 | if v
38 | }
39 |
40 | if isinstance(record_dict['msg'], dict):
41 | log_dict['message'] = record_dict['msg']
42 | else:
43 | log_dict['message'] = record.getMessage()
44 |
45 | # Attempt to decode the message as JSON, if so, merge it with the
46 | # overall message for clarity.
47 | try:
48 | log_dict['message'] = json.loads(log_dict['message'])
49 | except (TypeError, ValueError):
50 | pass
51 |
52 | if record.exc_info:
53 | # Cache the traceback text to avoid converting it multiple times
54 | # (it's constant anyway)
55 | # from logging.Formatter:format
56 | if not record.exc_text:
57 | record.exc_text = self.formatException(record.exc_info)
58 |
59 | if record.exc_text:
60 | log_dict['exception'] = record.exc_text
61 |
62 | json_record = json.dumps(log_dict, default=self.default_json_formatter)
63 |
64 | if hasattr(json_record, 'decode'): # pragma: no cover
65 | json_record = json_record.decode('utf-8')
66 |
67 | return json_record
68 |
69 |
70 | def setup(level='DEBUG', formatter_cls=JsonFormatter, boto_level=None, **kwargs):
71 | if formatter_cls:
72 | for handler in logging.root.handlers:
73 | handler.setFormatter(formatter_cls(**kwargs))
74 |
75 | logging.root.setLevel(level)
76 |
77 | if not boto_level:
78 | boto_level = level
79 |
80 | logging.getLogger('boto').setLevel(boto_level)
81 | logging.getLogger('boto3').setLevel(boto_level)
82 | logging.getLogger('botocore').setLevel(boto_level)
83 | logging.getLogger('urllib3').setLevel(boto_level)
84 |
--------------------------------------------------------------------------------
/source/crhelper/resource_helper.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | TODO:
4 | * Async mode – take a wait condition handle as an input, increases max timeout to 12 hours
5 | * Idempotency – If a duplicate request comes in (say there was a network error in signaling back to cfn) the subsequent
6 | request should return the already created response, will need a persistent store of some kind...
7 | * Functional tests
8 | """
9 |
10 | from __future__ import print_function
11 | import threading
12 | from crhelper.utils import _send_response
13 | from crhelper import log_helper
14 | import logging
15 | import random
16 | import boto3
17 | import string
18 | import json
19 | import os
20 | from time import sleep
21 |
22 | logger = logging.getLogger(__name__)
23 |
24 | SUCCESS = 'SUCCESS'
25 | FAILED = 'FAILED'
26 |
27 |
28 | class CfnResource(object):
29 |
30 | def __init__(self, json_logging=False, log_level='DEBUG', boto_level='ERROR', polling_interval=2, sleep_on_delete=120):
31 | self._sleep_on_delete= sleep_on_delete
32 | self._create_func = None
33 | self._update_func = None
34 | self._delete_func = None
35 | self._poll_create_func = None
36 | self._poll_update_func = None
37 | self._poll_delete_func = None
38 | self._timer = None
39 | self._init_failed = None
40 | self._json_logging = json_logging
41 | self._log_level = log_level
42 | self._boto_level = boto_level
43 | self._send_response = False
44 | self._polling_interval = polling_interval
45 | self.Status = ""
46 | self.Reason = ""
47 | self.PhysicalResourceId = ""
48 | self.StackId = ""
49 | self.RequestId = ""
50 | self.LogicalResourceId = ""
51 | self.Data = {}
52 | self._event = {}
53 | self._context = None
54 | self._response_url = ""
55 | self._sam_local = os.getenv('AWS_SAM_LOCAL')
56 | self._region = os.getenv('AWS_REGION')
57 | try:
58 | if not self._sam_local:
59 | self._lambda_client = boto3.client('lambda', region_name=self._region)
60 | self._events_client = boto3.client('events', region_name=self._region)
61 | self._logs_client = boto3.client('logs', region_name=self._region)
62 | if json_logging:
63 | log_helper.setup(log_level, boto_level=boto_level, RequestType='ContainerInit')
64 | else:
65 | log_helper.setup(log_level, formatter_cls=None, boto_level=boto_level)
66 | except Exception as e:
67 | logger.error(e, exc_info=True)
68 | self.init_failure(e)
69 |
70 | def __call__(self, event, context):
71 | try:
72 | self._log_setup(event, context)
73 | logger.debug(event)
74 | if not self._crhelper_init(event, context):
75 | return
76 | # Check for polling functions
77 | if self._poll_enabled() and self._sam_local:
78 | logger.info("Skipping poller functionality, as this is a local invocation")
79 | elif self._poll_enabled():
80 | self._polling_init(event)
81 | # If polling is not enabled, then we should respond
82 | else:
83 | logger.debug("enabling send_response")
84 | self._send_response = True
85 | logger.debug("_send_response: %s" % self._send_response)
86 | if self._send_response:
87 | if self.RequestType == 'Delete':
88 | self._wait_for_cwlogs()
89 | self._cfn_response(event)
90 | except Exception as e:
91 | logger.error(e, exc_info=True)
92 | self._send(FAILED, str(e))
93 | finally:
94 | if self._timer:
95 | self._timer.cancel()
96 |
97 | def _wait_for_cwlogs(self, sleep=sleep):
98 | time_left = int(self._context.get_remaining_time_in_millis() / 1000) - 15
99 | sleep_time = 0
100 |
101 | if time_left > self._sleep_on_delete:
102 | sleep_time = self._sleep_on_delete
103 |
104 | if sleep_time > 1:
105 | sleep(sleep_time)
106 |
107 | def _log_setup(self, event, context):
108 | if self._json_logging:
109 | log_helper.setup(self._log_level, boto_level=self._boto_level, RequestType=event['RequestType'],
110 | StackId=event['StackId'], RequestId=event['RequestId'],
111 | LogicalResourceId=event['LogicalResourceId'], aws_request_id=context.aws_request_id)
112 | else:
113 | log_helper.setup(self._log_level, boto_level=self._boto_level, formatter_cls=None)
114 |
115 | def _crhelper_init(self, event, context):
116 | self._send_response = False
117 | self.Status = SUCCESS
118 | self.Reason = ""
119 | self.PhysicalResourceId = ""
120 | self.StackId = event["StackId"]
121 | self.RequestId = event["RequestId"]
122 | self.LogicalResourceId = event["LogicalResourceId"]
123 | self.Data = {}
124 | if "CrHelperData" in event.keys():
125 | self.Data = event["CrHelperData"]
126 | self.RequestType = event["RequestType"]
127 | self._event = event
128 | self._context = context
129 | self._response_url = event['ResponseURL']
130 | if self._timer:
131 | self._timer.cancel()
132 | if self._init_failed:
133 | self._send(FAILED, str(self._init_failed))
134 | return False
135 | self._set_timeout()
136 | self._wrap_function(self._get_func())
137 | return True
138 |
139 | def _polling_init(self, event):
140 | # Setup polling on initial request
141 | logger.debug("pid1: %s" % self.PhysicalResourceId)
142 | if 'CrHelperPoll' not in event.keys() and self.Status != FAILED:
143 | logger.info("Setting up polling")
144 | self.Data["PhysicalResourceId"] = self.PhysicalResourceId
145 | self._setup_polling()
146 | self.PhysicalResourceId = None
147 | logger.debug("pid2: %s" % self.PhysicalResourceId)
148 | # if physical id is set, or there was a failure then we're done
149 | logger.debug("pid3: %s" % self.PhysicalResourceId)
150 | if self.PhysicalResourceId or self.Status == FAILED:
151 | logger.info("Polling complete, removing cwe schedule")
152 | self._remove_polling()
153 | self._send_response = True
154 |
155 | def generate_physical_id(self, event):
156 | return '_'.join([
157 | event['StackId'].split('/')[1],
158 | event['LogicalResourceId'],
159 | self._rand_string(8)
160 | ])
161 |
162 | def _cfn_response(self, event):
163 | # Use existing PhysicalResourceId if it's in the event and no ID was set
164 | if not self.PhysicalResourceId and "PhysicalResourceId" in event.keys():
165 | logger.info("PhysicalResourceId present in event, Using that for response")
166 | self.PhysicalResourceId = event['PhysicalResourceId']
167 | # Generate a physical id if none is provided
168 | elif not self.PhysicalResourceId or self.PhysicalResourceId is True:
169 | logger.info("No physical resource id returned, generating one...")
170 | self.PhysicalResourceId = self.generate_physical_id(event)
171 | self._send()
172 |
173 | def _poll_enabled(self):
174 | return getattr(self, "_poll_{}_func".format(self._event['RequestType'].lower()))
175 |
176 | def create(self, func):
177 | self._create_func = func
178 | return func
179 |
180 | def update(self, func):
181 | self._update_func = func
182 | return func
183 |
184 | def delete(self, func):
185 | self._delete_func = func
186 | return func
187 |
188 | def poll_create(self, func):
189 | self._poll_create_func = func
190 | return func
191 |
192 | def poll_update(self, func):
193 | self._poll_update_func = func
194 | return func
195 |
196 | def poll_delete(self, func):
197 | self._poll_delete_func = func
198 | return func
199 |
200 | def _wrap_function(self, func):
201 | try:
202 | self.PhysicalResourceId = func(self._event, self._context) if func else ''
203 | except Exception as e:
204 | logger.error(str(e), exc_info=True)
205 | self.Reason = str(e)
206 | self.Status = FAILED
207 |
208 | def _timeout(self):
209 | logger.error("Execution is about to time out, sending failure message")
210 | self._send(FAILED, "Execution timed out")
211 |
212 | def _set_timeout(self):
213 | self._timer = threading.Timer((self._context.get_remaining_time_in_millis() / 1000.00) - 0.5,
214 | self._timeout)
215 | self._timer.start()
216 |
217 | def _get_func(self):
218 | request_type = "_{}_func"
219 | if "CrHelperPoll" in self._event.keys():
220 | request_type = "_poll" + request_type
221 | return getattr(self, request_type.format(self._event['RequestType'].lower()))
222 |
223 | def _send(self, status=None, reason="", send_response=_send_response):
224 | if len(str(str(self.Reason))) > 256:
225 | self.Reason = "ERROR: (truncated) " + str(self.Reason)[len(str(self.Reason)) - 240:]
226 | if len(str(reason)) > 256:
227 | reason = "ERROR: (truncated) " + str(reason)[len(str(reason)) - 240:]
228 | response_body = {
229 | 'Status': self.Status,
230 | 'PhysicalResourceId': str(self.PhysicalResourceId),
231 | 'StackId': self.StackId,
232 | 'RequestId': self.RequestId,
233 | 'LogicalResourceId': self.LogicalResourceId,
234 | 'Reason': str(self.Reason),
235 | 'Data': self.Data,
236 | }
237 | if status:
238 | response_body.update({'Status': status, 'Reason': reason})
239 | send_response(self._response_url, response_body)
240 |
241 | def init_failure(self, error):
242 | self._init_failed = error
243 | logger.error(str(error), exc_info=True)
244 |
245 | def _cleanup_response(self):
246 | for k in ["CrHelperPoll", "CrHelperPermission", "CrHelperRule"]:
247 | if k in self.Data.keys():
248 | del self.Data[k]
249 |
250 | @staticmethod
251 | def _rand_string(l):
252 | return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(l))
253 |
254 | def _add_permission(self, rule_arn):
255 | sid = self._event['LogicalResourceId'] + self._rand_string(8)
256 | self._lambda_client.add_permission(
257 | FunctionName=self._context.function_name,
258 | StatementId=sid,
259 | Action='lambda:InvokeFunction',
260 | Principal='events.amazonaws.com',
261 | SourceArn=rule_arn
262 | )
263 | return sid
264 |
265 | def _put_rule(self):
266 | response = self._events_client.put_rule(
267 | Name=self._event['LogicalResourceId'] + self._rand_string(8),
268 | ScheduleExpression='rate({} minutes)'.format(self._polling_interval),
269 | State='ENABLED',
270 | )
271 | return response["RuleArn"]
272 |
273 | def _put_targets(self, func_name):
274 | region = self._event['CrHelperRule'].split(":")[3]
275 | account_id = self._event['CrHelperRule'].split(":")[4]
276 | partition = self._event['CrHelperRule'].split(":")[1]
277 | rule_name = self._event['CrHelperRule'].split("/")[1]
278 | logger.debug(self._event)
279 | self._events_client.put_targets(
280 | Rule=rule_name,
281 | Targets=[
282 | {
283 | 'Id': '1',
284 | 'Arn': 'arn:%s:lambda:%s:%s:function:%s' % (partition, region, account_id, func_name),
285 | 'Input': json.dumps(self._event)
286 | }
287 | ]
288 | )
289 |
290 | def _remove_targets(self, rule_arn):
291 | self._events_client.remove_targets(
292 | Rule=rule_arn.split("/")[1],
293 | Ids=['1']
294 | )
295 |
296 | def _remove_permission(self, sid):
297 | self._lambda_client.remove_permission(
298 | FunctionName=self._context.function_name,
299 | StatementId=sid
300 | )
301 |
302 | def _delete_rule(self, rule_arn):
303 | self._events_client.delete_rule(
304 | Name=rule_arn.split("/")[1]
305 | )
306 |
307 | def _setup_polling(self):
308 | self._event['CrHelperData'] = self.Data
309 | self._event['CrHelperPoll'] = True
310 | self._event['CrHelperRule'] = self._put_rule()
311 | self._event['CrHelperPermission'] = self._add_permission(self._event['CrHelperRule'])
312 | self._put_targets(self._context.function_name)
313 |
314 | def _remove_polling(self):
315 | if 'CrHelperData' in self._event.keys():
316 | self._event.pop('CrHelperData')
317 | if "PhysicalResourceId" in self.Data.keys():
318 | self.Data.pop("PhysicalResourceId")
319 | if 'CrHelperRule' in self._event.keys():
320 | self._remove_targets(self._event['CrHelperRule'])
321 | else:
322 | logger.error("Cannot remove CloudWatch events rule, Rule arn not available in event")
323 | if 'CrHelperPermission' in self._event.keys():
324 | self._remove_permission(self._event['CrHelperPermission'])
325 | else:
326 | logger.error("Cannot remove lambda events permission, permission id not available in event")
327 | if 'CrHelperRule' in self._event.keys():
328 | self._delete_rule(self._event['CrHelperRule'])
329 | else:
330 | logger.error("Cannot remove CloudWatch events target, Rule arn not available in event")
331 |
--------------------------------------------------------------------------------
/source/crhelper/utils.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 | import json
3 | import logging as logging
4 | import time
5 | from urllib.parse import urlsplit, urlunsplit
6 | from http.client import HTTPSConnection
7 |
8 | logger = logging.getLogger(__name__)
9 |
10 |
11 | def _send_response(response_url, response_body):
12 | try:
13 | json_response_body = json.dumps(response_body)
14 | except Exception as e:
15 | msg = "Failed to convert response to json: {}".format(str(e))
16 | logger.error(msg, exc_info=True)
17 | response_body = {'Status': 'FAILED', 'Data': {}, 'Reason': msg}
18 | json_response_body = json.dumps(response_body)
19 | logger.debug("CFN response URL: {}".format(response_url))
20 | logger.debug(json_response_body)
21 | headers = {'content-type': '', 'content-length': str(len(json_response_body))}
22 | split_url = urlsplit(response_url)
23 | host = split_url.netloc
24 | url = urlunsplit(("", "", *split_url[2:]))
25 | while True:
26 | try:
27 | connection = HTTPSConnection(host)
28 | connection.request(method="PUT", url=url, body=json_response_body, headers=headers)
29 | response = connection.getresponse()
30 | logger.info("CloudFormation returned status code: {}".format(response.reason))
31 | break
32 | except Exception as e:
33 | logger.error("Unexpected failure sending response to CloudFormation {}".format(e), exc_info=True)
34 | time.sleep(5)
35 |
--------------------------------------------------------------------------------
/source/lambda_function.py:
--------------------------------------------------------------------------------
1 | from crhelper import CfnResource
2 | import boto3
3 | import datetime
4 |
5 | helper = CfnResource()
6 |
7 | @helper.create
8 | @helper.update
9 | def get_ami(event, _):
10 | client = boto3.client('ec2')
11 |
12 | product_id = get_productid(event)
13 | print('Got ProductID: %s' % product_id)
14 | response = client.describe_images(
15 | Filters=[
16 |
17 | {
18 | 'Name': 'product-code',
19 | 'Values': [product_id]
20 | },
21 | {
22 | 'Name': 'product-code.type',
23 | 'Values': ['marketplace']
24 | }
25 | ]
26 | # ImageIds=[
27 | # 'ami-03dc6d7f59e5f1765'],
28 |
29 | )
30 | #rint(response)
31 | images = response['Images']
32 | latest_creation_date = datetime.datetime.min
33 | id = ''
34 | for image in images:
35 |
36 | date_time_str = image['CreationDate']
37 | date_time_obj = datetime.datetime.strptime(date_time_str, "%Y-%m-%dT%H:%M:%S.%fZ")
38 |
39 | print ('This image has AMI name: %s and the image-id is: %s. It was created on: %s' % (image['Name'], image['ImageId'], image['CreationDate']))
40 | print ('Proudct ID: %s' % image['ProductCodes'])
41 | print ('Owner is %s' % image['OwnerId'])
42 | if date_time_obj >= latest_creation_date:
43 | latest_creation_date = date_time_obj
44 | id = image['ImageId']
45 |
46 | print('Latest AMI ID: %s' % id)
47 | print('Latest Creation Date: %s' % latest_creation_date)
48 | helper.Data['AMIID'] = id
49 |
50 |
51 | def get_productid(event):
52 | productids = {'linux': 'ai7s3sm9muv8bw9y12z9t6o8i', 'windows': '4af6zv023dsu3c28day09b2a9'}
53 | os_type = event['ResourceProperties']['OSType']
54 | return productids.get(os_type)
55 |
56 | @helper.delete
57 | def no_op(_, __):
58 | pass
59 |
60 | def handler(event, context):
61 | #get_ami(event,context)
62 | helper(event, context)
--------------------------------------------------------------------------------
/source/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/source/tests/__init__.py
--------------------------------------------------------------------------------
/source/tests/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/source/tests/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/source/tests/__pycache__/test_log_helper.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/source/tests/__pycache__/test_log_helper.cpython-37.pyc
--------------------------------------------------------------------------------
/source/tests/__pycache__/test_resource_helper.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/source/tests/__pycache__/test_resource_helper.cpython-37.pyc
--------------------------------------------------------------------------------
/source/tests/__pycache__/test_utils.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/source/tests/__pycache__/test_utils.cpython-37.pyc
--------------------------------------------------------------------------------
/source/tests/test_log_helper.py:
--------------------------------------------------------------------------------
1 | from crhelper.log_helper import *
2 | import unittest
3 | import logging
4 |
5 |
6 | class TestLogHelper(unittest.TestCase):
7 |
8 | def test_logging_no_formatting(self):
9 | logger = logging.getLogger('1')
10 | handler = logging.StreamHandler()
11 | logger.addHandler(handler)
12 | orig_formatters = []
13 | for c in range(len(logging.root.handlers)):
14 | orig_formatters.append(logging.root.handlers[c].formatter)
15 | setup(level='DEBUG', formatter_cls=None, boto_level='CRITICAL')
16 | new_formatters = []
17 | for c in range(len(logging.root.handlers)):
18 | new_formatters.append(logging.root.handlers[c].formatter)
19 | self.assertEqual(orig_formatters, new_formatters)
20 |
21 | def test_logging_boto_explicit(self):
22 | logger = logging.getLogger('2')
23 | handler = logging.StreamHandler()
24 | logger.addHandler(handler)
25 | setup(level='DEBUG', formatter_cls=None, boto_level='CRITICAL')
26 | for t in ['boto', 'boto3', 'botocore', 'urllib3']:
27 | b_logger = logging.getLogger(t)
28 | self.assertEqual(b_logger.level, 50)
29 |
30 | def test_logging_json(self):
31 | logger = logging.getLogger('3')
32 | handler = logging.StreamHandler()
33 | logger.addHandler(handler)
34 | setup(level='DEBUG', formatter_cls=JsonFormatter, RequestType='ContainerInit')
35 | for handler in logging.root.handlers:
36 | self.assertEqual(JsonFormatter, type(handler.formatter))
37 |
38 | def test_logging_boto_implicit(self):
39 | logger = logging.getLogger('4')
40 | handler = logging.StreamHandler()
41 | logger.addHandler(handler)
42 | setup(level='DEBUG', formatter_cls=JsonFormatter, RequestType='ContainerInit')
43 | for t in ['boto', 'boto3', 'botocore', 'urllib3']:
44 | b_logger = logging.getLogger(t)
45 | self.assertEqual(b_logger.level, 10)
46 |
47 | def test_logging_json_keys(self):
48 | with self.assertLogs() as ctx:
49 | logger = logging.getLogger()
50 | handler = logging.StreamHandler()
51 | logger.addHandler(handler)
52 | setup(level='DEBUG', formatter_cls=JsonFormatter, RequestType='ContainerInit')
53 | logger.info("test")
54 | logs = json.loads(ctx.output[0])
55 | self.assertEqual(["timestamp", "level", "location", "RequestType", "message"], list(logs.keys()))
56 |
57 | def test_logging_json_parse_message(self):
58 | with self.assertLogs() as ctx:
59 | logger = logging.getLogger()
60 | handler = logging.StreamHandler()
61 | logger.addHandler(handler)
62 | setup(level='DEBUG', formatter_cls=JsonFormatter, RequestType='ContainerInit')
63 | logger.info("{}")
64 | logs = json.loads(ctx.output[0])
65 | self.assertEqual({}, logs["message"])
66 |
67 | def test_logging_json_exception(self):
68 | with self.assertLogs() as ctx:
69 | logger = logging.getLogger()
70 | handler = logging.StreamHandler()
71 | logger.addHandler(handler)
72 | setup(level='DEBUG', formatter_cls=JsonFormatter, RequestType='ContainerInit')
73 | try:
74 | 1 + 't'
75 | except Exception as e:
76 | logger.info("[]", exc_info=True)
77 | logs = json.loads(ctx.output[0])
78 | self.assertIn("exception", logs.keys())
79 |
--------------------------------------------------------------------------------
/source/tests/test_resource_helper.py:
--------------------------------------------------------------------------------
1 | import os
2 | import crhelper
3 | import unittest
4 | from unittest.mock import call, patch, Mock
5 | import threading
6 |
7 | test_events = {
8 | "Create": {
9 | "RequestType": "Create",
10 | "RequestId": "test-event-id",
11 | "StackId": "arn/test-stack-id/guid",
12 | "LogicalResourceId": "TestResourceId",
13 | "ResponseURL": "response_url"
14 | },
15 | "Update": {
16 | "RequestType": "Update",
17 | "RequestId": "test-event-id",
18 | "StackId": "test-stack-id",
19 | "LogicalResourceId": "TestResourceId",
20 | "PhysicalResourceId": "test-pid",
21 | "ResponseURL": "response_url"
22 | },
23 | "Delete": {
24 | "RequestType": "Delete",
25 | "RequestId": "test-event-id",
26 | "StackId": "test-stack-id",
27 | "LogicalResourceId": "TestResourceId",
28 | "PhysicalResourceId": "test-pid",
29 | "ResponseURL": "response_url"
30 | }
31 | }
32 |
33 |
34 | class MockContext(object):
35 |
36 | function_name = "test-function"
37 | ms_remaining = 9000
38 |
39 | @staticmethod
40 | def get_remaining_time_in_millis():
41 | return MockContext.ms_remaining
42 |
43 |
44 | class TestCfnResource(unittest.TestCase):
45 | def setUp(self):
46 | os.environ['AWS_REGION'] = 'us-east-1'
47 |
48 | def tearDown(self):
49 | os.environ.pop('AWS_REGION', None)
50 |
51 | @patch('crhelper.log_helper.setup', return_value=None)
52 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
53 | def test_init(self, mock_method):
54 | crhelper.resource_helper.CfnResource()
55 | mock_method.assert_called_once_with('DEBUG', boto_level='ERROR', formatter_cls=None)
56 |
57 | crhelper.resource_helper.CfnResource(json_logging=True)
58 | mock_method.assert_called_with('DEBUG', boto_level='ERROR', RequestType='ContainerInit')
59 |
60 | @patch('crhelper.log_helper.setup', return_value=None)
61 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
62 | def test_init_failure(self, mock_method):
63 | mock_method.side_effect = Exception("test")
64 | c = crhelper.resource_helper.CfnResource(json_logging=True)
65 | self.assertTrue(c._init_failed)
66 |
67 | @patch('crhelper.log_helper.setup', Mock())
68 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
69 | @patch('crhelper.resource_helper.CfnResource._polling_init', Mock())
70 | @patch('crhelper.resource_helper.CfnResource._wait_for_cwlogs', Mock())
71 | @patch('crhelper.resource_helper.CfnResource._send')
72 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
73 | @patch('crhelper.resource_helper.CfnResource._wrap_function', Mock())
74 | def test_init_failure_call(self, mock_send):
75 | c = crhelper.resource_helper.CfnResource()
76 | c.init_failure(Exception('TestException'))
77 |
78 | event = test_events["Create"]
79 | c.__call__(event, MockContext)
80 |
81 | self.assertEqual([call('FAILED', 'TestException')], mock_send.call_args_list)
82 |
83 | @patch('crhelper.log_helper.setup', Mock())
84 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
85 | @patch('crhelper.resource_helper.CfnResource._polling_init', Mock())
86 | @patch('crhelper.resource_helper.CfnResource._wait_for_cwlogs', Mock())
87 | @patch('crhelper.resource_helper.CfnResource._send', Mock())
88 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
89 | @patch('crhelper.resource_helper.CfnResource._wrap_function', Mock())
90 | @patch('crhelper.resource_helper.CfnResource._cfn_response', return_value=None)
91 | def test_call(self, cfn_response_mock):
92 | c = crhelper.resource_helper.CfnResource()
93 | event = test_events["Create"]
94 | c.__call__(event, MockContext)
95 | self.assertTrue(c._send_response)
96 | cfn_response_mock.assert_called_once_with(event)
97 |
98 | c._sam_local = True
99 | c._poll_enabled = Mock(return_value=True)
100 | c._polling_init = Mock()
101 | c.__call__(event, MockContext)
102 | c._polling_init.assert_not_called()
103 | self.assertEqual(1, len(cfn_response_mock.call_args_list))
104 |
105 | c._sam_local = False
106 | c._send_response = False
107 | c.__call__(event, MockContext)
108 | c._polling_init.assert_called()
109 | self.assertEqual(1, len(cfn_response_mock.call_args_list))
110 |
111 | event = test_events["Delete"]
112 | c._wait_for_cwlogs = Mock()
113 | c._poll_enabled = Mock(return_value=False)
114 | c.__call__(event, MockContext)
115 | c._wait_for_cwlogs.assert_called()
116 |
117 | c._send = Mock()
118 | cfn_response_mock.side_effect = Exception("test")
119 | c.__call__(event, MockContext)
120 | c._send.assert_called_with('FAILED', "test")
121 |
122 | @patch('crhelper.log_helper.setup', Mock())
123 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
124 | @patch('crhelper.resource_helper.CfnResource._polling_init', Mock())
125 | @patch('crhelper.resource_helper.CfnResource._send', Mock())
126 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
127 | @patch('crhelper.resource_helper.CfnResource._wrap_function', Mock())
128 | @patch('crhelper.resource_helper.CfnResource._cfn_response', Mock(return_value=None))
129 | def test_wait_for_cwlogs(self):
130 |
131 | c = crhelper.resource_helper.CfnResource()
132 | c._context = MockContext
133 | s = Mock()
134 | c._wait_for_cwlogs(sleep=s)
135 | s.assert_not_called()
136 | MockContext.ms_remaining = 140000
137 | c._wait_for_cwlogs(sleep=s)
138 | s.assert_called_once()
139 |
140 | @patch('crhelper.log_helper.setup', Mock())
141 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
142 | @patch('crhelper.resource_helper.CfnResource._wait_for_cwlogs', Mock())
143 | @patch('crhelper.resource_helper.CfnResource._send', Mock())
144 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
145 | @patch('crhelper.resource_helper.CfnResource._wrap_function', Mock())
146 | @patch('crhelper.resource_helper.CfnResource._cfn_response', Mock())
147 | def test_polling_init(self):
148 | c = crhelper.resource_helper.CfnResource()
149 | event = test_events['Create']
150 | c._setup_polling = Mock()
151 | c._remove_polling = Mock()
152 | c._polling_init(event)
153 | c._setup_polling.assert_called_once()
154 | c._remove_polling.assert_not_called()
155 | self.assertEqual(c.PhysicalResourceId, None)
156 |
157 | c.Status = 'FAILED'
158 | c._setup_polling.assert_called_once()
159 | c._setup_polling.assert_called_once()
160 |
161 | c = crhelper.resource_helper.CfnResource()
162 | event = test_events['Create']
163 | c._setup_polling = Mock()
164 | c._remove_polling = Mock()
165 | event['CrHelperPoll'] = "Some stuff"
166 | c.PhysicalResourceId = None
167 | c._polling_init(event)
168 | c._remove_polling.assert_not_called()
169 | c._setup_polling.assert_not_called()
170 |
171 | c.Status = 'FAILED'
172 | c._polling_init(event)
173 | c._remove_polling.assert_called_once()
174 | c._setup_polling.assert_not_called()
175 |
176 | c.Status = ''
177 | c.PhysicalResourceId = "some-id"
178 | c._remove_polling.assert_called()
179 | c._setup_polling.assert_not_called()
180 |
181 | @patch('crhelper.log_helper.setup', Mock())
182 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
183 | @patch('crhelper.resource_helper.CfnResource._wait_for_cwlogs', Mock())
184 | @patch('crhelper.resource_helper.CfnResource._send', Mock())
185 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
186 | @patch('crhelper.resource_helper.CfnResource._wrap_function', Mock())
187 | def test_cfn_response(self):
188 | c = crhelper.resource_helper.CfnResource()
189 | event = test_events['Create']
190 | c._send = Mock()
191 |
192 | orig_pid = c.PhysicalResourceId
193 | self.assertEqual(orig_pid, '')
194 | c._cfn_response(event)
195 | c._send.assert_called_once()
196 | print("RID: [%s]" % [c.PhysicalResourceId])
197 | self.assertEqual(True, c.PhysicalResourceId.startswith('test-stack-id_TestResourceId_'))
198 |
199 | c._send = Mock()
200 | c.PhysicalResourceId = 'testpid'
201 | c._cfn_response(event)
202 | c._send.assert_called_once()
203 | self.assertEqual('testpid', c.PhysicalResourceId)
204 |
205 | c._send = Mock()
206 | c.PhysicalResourceId = True
207 | c._cfn_response(event)
208 | c._send.assert_called_once()
209 | self.assertEqual(True, c.PhysicalResourceId.startswith('test-stack-id_TestResourceId_'))
210 |
211 | c._send = Mock()
212 | c.PhysicalResourceId = ''
213 | event['PhysicalResourceId'] = 'pid-from-event'
214 | c._cfn_response(event)
215 | c._send.assert_called_once()
216 | self.assertEqual('pid-from-event', c.PhysicalResourceId)
217 |
218 | @patch('crhelper.log_helper.setup', Mock())
219 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
220 | @patch('crhelper.resource_helper.CfnResource._wait_for_cwlogs', Mock())
221 | @patch('crhelper.resource_helper.CfnResource._send', Mock())
222 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
223 | def test_wrap_function(self):
224 | c = crhelper.resource_helper.CfnResource()
225 |
226 | def func(e, c):
227 | return 'testpid'
228 |
229 | c._wrap_function(func)
230 | self.assertEqual('testpid', c.PhysicalResourceId)
231 | self.assertNotEqual('FAILED', c.Status)
232 |
233 | def func(e, c):
234 | raise Exception('test exception')
235 |
236 | c._wrap_function(func)
237 | self.assertEqual('FAILED', c.Status)
238 | self.assertEqual('test exception', c.Reason)
239 |
240 | @patch('crhelper.log_helper.setup', Mock())
241 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
242 | @patch('crhelper.resource_helper.CfnResource._wait_for_cwlogs', Mock())
243 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
244 | def test_send(self):
245 | c = crhelper.resource_helper.CfnResource()
246 | s = Mock()
247 | c._send(send_response=s)
248 | s.assert_called_once()
249 |
250 | @patch('crhelper.log_helper.setup', Mock())
251 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
252 | @patch('crhelper.resource_helper.CfnResource._wait_for_cwlogs', Mock())
253 | @patch('crhelper.resource_helper.CfnResource._send', return_value=None)
254 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
255 | def test_timeout(self, s):
256 | c = crhelper.resource_helper.CfnResource()
257 | c._timeout()
258 | s.assert_called_with('FAILED', "Execution timed out")
259 |
260 | @patch('crhelper.log_helper.setup', Mock())
261 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
262 | @patch('crhelper.resource_helper.CfnResource._wait_for_cwlogs', Mock())
263 | @patch('crhelper.resource_helper.CfnResource._send', Mock())
264 | def test_set_timeout(self):
265 | c = crhelper.resource_helper.CfnResource()
266 | c._context = MockContext()
267 | def func():
268 | return None
269 |
270 | c._set_timeout()
271 | t = threading.Timer(1000, func)
272 | self.assertEqual(type(t), type(c._timer))
273 | t.cancel()
274 | c._timer.cancel()
275 |
276 | @patch('crhelper.log_helper.setup', Mock())
277 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
278 | @patch('crhelper.resource_helper.CfnResource._wait_for_cwlogs', Mock())
279 | @patch('crhelper.resource_helper.CfnResource._send', Mock())
280 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
281 | def test_cleanup_response(self):
282 | c = crhelper.resource_helper.CfnResource()
283 | c.Data = {"CrHelperPoll": 1, "CrHelperPermission": 2, "CrHelperRule": 3}
284 | c._cleanup_response()
285 | self.assertEqual({}, c.Data)
286 |
287 | @patch('crhelper.log_helper.setup', Mock())
288 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
289 | @patch('crhelper.resource_helper.CfnResource._wait_for_cwlogs', Mock())
290 | @patch('crhelper.resource_helper.CfnResource._send', Mock())
291 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
292 | def test_remove_polling(self):
293 | c = crhelper.resource_helper.CfnResource()
294 | c._context = MockContext()
295 |
296 | c._events_client.remove_targets = Mock()
297 | c._events_client.delete_rule = Mock()
298 | c._lambda_client.remove_permission = Mock()
299 |
300 | with self.assertRaises(Exception) as e:
301 | c._remove_polling()
302 |
303 | self.assertEqual("failed to cleanup CloudWatch event polling", str(e))
304 | c._events_client.remove_targets.assert_not_called()
305 | c._events_client.delete_rule.assert_not_called()
306 | c._lambda_client.remove_permission.assert_not_called()
307 |
308 | c._event["CrHelperRule"] = "1/2"
309 | c._event["CrHelperPermission"] = "1/2"
310 | c._remove_polling()
311 | c._events_client.remove_targets.assert_called()
312 | c._events_client.delete_rule.assert_called()
313 | c._lambda_client.remove_permission.assert_called()
314 |
315 | @patch('crhelper.log_helper.setup', Mock())
316 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
317 | @patch('crhelper.resource_helper.CfnResource._wait_for_cwlogs', Mock())
318 | @patch('crhelper.resource_helper.CfnResource._send', Mock())
319 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
320 | def test_setup_polling(self):
321 | c = crhelper.resource_helper.CfnResource()
322 | c._context = MockContext()
323 | c._event = test_events["Update"]
324 | c._lambda_client.add_permission = Mock()
325 | c._events_client.put_rule = Mock(return_value={"RuleArn": "arn:aws:lambda:blah:blah:function:blah/blah"})
326 | c._events_client.put_targets = Mock()
327 | c._setup_polling()
328 | c._events_client.put_targets.assert_called()
329 | c._events_client.put_rule.assert_called()
330 | c._lambda_client.add_permission.assert_called()
331 |
332 | @patch('crhelper.log_helper.setup', Mock())
333 | @patch('crhelper.resource_helper.CfnResource._poll_enabled', Mock(return_value=False))
334 | @patch('crhelper.resource_helper.CfnResource._wait_for_cwlogs', Mock())
335 | @patch('crhelper.resource_helper.CfnResource._send', Mock())
336 | @patch('crhelper.resource_helper.CfnResource._set_timeout', Mock())
337 | def test_wrappers(self):
338 | c = crhelper.resource_helper.CfnResource()
339 |
340 | def func():
341 | pass
342 |
343 | for f in ["create", "update", "delete", "poll_create", "poll_update", "poll_delete"]:
344 | self.assertEqual(None, getattr(c, "_%s_func" % f))
345 | getattr(c, f)(func)
346 | self.assertEqual(func, getattr(c, "_%s_func" % f))
347 |
--------------------------------------------------------------------------------
/source/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | import json
2 | from unittest.mock import patch, Mock
3 | from crhelper import utils
4 | import unittest
5 |
6 |
7 | class TestLogHelper(unittest.TestCase):
8 | TEST_URL = "https://test_url/this/is/the/url?query=123#aaa"
9 |
10 | @patch('crhelper.utils.HTTPSConnection', autospec=True)
11 | def test_send_succeeded_response(self, https_connection_mock):
12 | utils._send_response(self.TEST_URL, {})
13 | https_connection_mock.assert_called_once_with("test_url")
14 | https_connection_mock.return_value.request.assert_called_once_with(
15 | body='{}',
16 | headers={"content-type": "", "content-length": "2"},
17 | method="PUT",
18 | url="/this/is/the/url?query=123#aaa",
19 | )
20 |
21 | @patch('crhelper.utils.HTTPSConnection', autospec=True)
22 | def test_send_failed_response(self, https_connection_mock):
23 | utils._send_response(self.TEST_URL, Mock())
24 | https_connection_mock.assert_called_once_with("test_url")
25 | response = json.loads(https_connection_mock.return_value.request.call_args[1]["body"])
26 | expected_body = '{"Status": "FAILED", "Data": {}, "Reason": "' + response["Reason"] + '"}'
27 | https_connection_mock.return_value.request.assert_called_once_with(
28 | body=expected_body,
29 | headers={"content-type": "", "content-length": str(len(expected_body))},
30 | method="PUT",
31 | url="/this/is/the/url?query=123#aaa",
32 | )
33 |
--------------------------------------------------------------------------------
/source/tests/unit/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/source/tests/unit/__init__.py
--------------------------------------------------------------------------------
/source/tests/unit/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/content-creation-workstation/cd69718d2cc83e751b126f77d11b1bce38a89b88/source/tests/unit/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------