├── CODE_OF_CONDUCT.md ├── CHANGELOG.MD ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── example_setup.sh ├── LICENSE ├── README.md ├── CONTRIBUTING.MD ├── CONTRIBUTING.md └── example_instancestack.yaml /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | -------------------------------------------------------------------------------- /CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | 2 | # Change Log 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [1.2.0] - 2022-09-30 6 | 7 | ### Changed 8 | - #2: better exception handling 9 | - #4: better cfn response 10 | - bump python runtime to 3.9 11 | - remove software not needed by default 12 | ## [1.1.0] - 2020-11-11 13 | 14 | ### Changed 15 | - bump terraform version to 0.13.5 16 | - add parameter and bootstrap code to resize the cloud9 system volume. 17 | 18 | ## [1.0.0] - inital release 19 | 20 | ### inital version -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /example_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Simple bash for Workshop C9 Deployment. 3 | # will move towards real automation tools later on 4 | # created by David Surey, Amazon Web Services 5 | # suredavi@amazon.de 6 | # NOT FOR PRODUCTION USE - Only for Workshop purposes 7 | C9STACK="ExampleC9" 8 | PROFILE=$2 9 | REGION=$1 10 | WORKSHOPUSER="CHANGEME@example.com" 11 | 12 | if ! [ -x "$(command -v aws)" ]; then 13 | echo 'Error: aws cli is not installed.' >&2 14 | exit 1 15 | fi 16 | 17 | if [ ! -z "$PROFILE" ]; then 18 | export AWS_PROFILE=$PROFILE 19 | fi 20 | 21 | if [ ! -z "$REGION" ]; then 22 | export AWS_DEFAULT_REGION=$REGION 23 | fi 24 | 25 | echo Building $PROFILE C9 26 | 27 | # build the c9 environment 28 | aws cloudformation deploy --stack-name $C9STACK --capabilities CAPABILITY_IAM --template ./example_instancestack.yaml 29 | 30 | exit 0 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Cloud9 Bootstrapping 2 | 3 | # AWS Cloud9 might not be available to new customers, please read: https://aws.amazon.com/de/blogs/devops/how-to-migrate-from-aws-cloud9-to-aws-ide-toolkits-or-aws-cloudshell/" 4 | 5 | ### Disclaimber 6 | Only or testing purpose, do not use in production. - Enables Admin access via AWS Cloud9 within your AWS Account! - 7 | 8 | ### Summary 9 | This example Cloudformation Deployment shows how to deploy an AWS Cloud9 Environment for yourself or team members and run an automated bootstrap so you can install software components needed on the fly. 10 | This is usefull for workshops, unified deployments, etc. 11 | 12 | you may use the template directly or leverage the bash shell script for deployment 13 | ### Parameters 14 | 15 | There are a few parameters you might want to adjust. 16 | 17 | | Parameter Name | Description | 18 | | ------------- | ------------- | 19 | | ExampleC9InstanceType | Example Cloud9 EC2 instance type | 20 | | ExampleC9EnvType | Environment Type. For yourself or to be deployed to a team member 3rd person by you? | 21 | | ExampleOwnerArn | if you selected "3rd person" when choosing ExampleC9EnvType please add the OwnerARN of the User or Role | 22 | | ExampleC9InstanceVolumeSize | The size of the System Volume for the Cloud9 instance | 23 | 24 | ### Some tech Details 25 | This deployment uses Cloudformation to deploy Cloud9 26 | It does include a Custom:Resource to pull the EC2 InstanceID from the Cloud9 Instance and applies an InstanceRole and Profile to the Cloud9 Instance. 27 | Also it creates a AWS Systems Manager Automation Document and Job which does the bootstrapping once the instance booted up. 28 | After the bootstrap process the instance gets a one-time reboot. 29 | 30 | Please plan around 15min to a finished, ready to use Cloud9 Instance/Environment 31 | 32 | ## Security 33 | 34 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 35 | 36 | ## License 37 | 38 | This library is licensed under the MIT-0 License. See the LICENSE file. -------------------------------------------------------------------------------- /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 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *mainline* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 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. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | 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' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | 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. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /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 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *mainline* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 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. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | 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' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | 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. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /example_instancestack.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Description: AWS CloudFormation template for dynamic Cloud 9 setups. Creates a Cloud9 4 | bootstraps the instance. ONLY FOR TESTING - enables Instance with Administrative permissions! 5 | Parameters: 6 | ExampleC9InstanceType: 7 | Description: Example Cloud9 instance type 8 | Type: String 9 | Default: t3.small 10 | AllowedValues: 11 | - t2.micro 12 | - t3.micro 13 | - t3.small 14 | - t3.medium 15 | ConstraintDescription: Must be a valid Cloud9 instance type 16 | ExampleC9EnvType: 17 | Description: Environment type. 18 | Default: self 19 | Type: String 20 | AllowedValues: 21 | - self 22 | - 3rdParty 23 | ConstraintDescription: must specify self or 3rdParty. 24 | ExampleOwnerArn: 25 | Type: String 26 | Description: The Arn of the Cloud9 Owner to be set if 3rdParty deployment. 27 | Default: "" 28 | ExampleC9InstanceVolumeSize: 29 | Type: String 30 | Description: The Size in GB of the Cloud9 Instance Volume. 31 | Default: "15" 32 | 33 | Conditions: 34 | Create3rdPartyResources: !Equals [ !Ref ExampleC9EnvType, 3rdParty ] 35 | 36 | Resources: 37 | ################## PERMISSIONS AND ROLES ################# 38 | ExampleC9Role: 39 | Type: AWS::IAM::Role 40 | Properties: 41 | Tags: 42 | - Key: Environment 43 | Value: AWS Example 44 | AssumeRolePolicyDocument: 45 | Version: '2012-10-17' 46 | Statement: 47 | - Effect: Allow 48 | Principal: 49 | Service: 50 | - ec2.amazonaws.com 51 | - ssm.amazonaws.com 52 | Action: 53 | - sts:AssumeRole 54 | ManagedPolicyArns: 55 | - arn:aws:iam::aws:policy/AdministratorAccess 56 | Path: "/" 57 | 58 | ExampleC9LambdaExecutionRole: 59 | Type: AWS::IAM::Role 60 | Properties: 61 | AssumeRolePolicyDocument: 62 | Version: '2012-10-17' 63 | Statement: 64 | - Effect: Allow 65 | Principal: 66 | Service: 67 | - lambda.amazonaws.com 68 | Action: 69 | - sts:AssumeRole 70 | Path: "/" 71 | Policies: 72 | - PolicyName: 73 | Fn::Join: 74 | - '' 75 | - - ExampleC9LambdaPolicy- 76 | - Ref: AWS::Region 77 | PolicyDocument: 78 | Version: '2012-10-17' 79 | Statement: 80 | - Effect: Allow 81 | Action: 82 | - logs:CreateLogGroup 83 | - logs:CreateLogStream 84 | - logs:PutLogEvents 85 | Resource: arn:aws:logs:*:*:* 86 | - Effect: Allow 87 | Action: 88 | - cloudformation:DescribeStacks 89 | - cloudformation:DescribeStackEvents 90 | - cloudformation:DescribeStackResource 91 | - cloudformation:DescribeStackResources 92 | - ec2:DescribeInstances 93 | - ec2:AssociateIamInstanceProfile 94 | - ec2:ModifyInstanceAttribute 95 | - ec2:ReplaceIamInstanceProfileAssociation 96 | - ec2:RebootInstances 97 | - iam:ListInstanceProfiles 98 | - iam:PassRole 99 | Resource: "*" 100 | 101 | ################## LAMBDA BOOTSTRAP FUNCTION ################ 102 | 103 | ExampleC9BootstrapInstanceLambda: 104 | Description: Bootstrap Cloud9 instance 105 | Type: Custom::ExampleC9BootstrapInstanceLambda 106 | DependsOn: 107 | - ExampleC9BootstrapInstanceLambdaFunction 108 | - ExampleC9Instance 109 | - ExampleC9InstanceProfile 110 | - ExampleC9LambdaExecutionRole 111 | Properties: 112 | Tags: 113 | - Key: Environment 114 | Value: AWS Example 115 | ServiceToken: 116 | Fn::GetAtt: 117 | - ExampleC9BootstrapInstanceLambdaFunction 118 | - Arn 119 | REGION: 120 | Ref: AWS::Region 121 | StackName: 122 | Ref: AWS::StackName 123 | EnvironmentId: 124 | Ref: ExampleC9Instance 125 | LabIdeInstanceProfileName: 126 | Ref: ExampleC9InstanceProfile 127 | LabIdeInstanceProfileArn: 128 | Fn::GetAtt: 129 | - ExampleC9InstanceProfile 130 | - Arn 131 | 132 | ExampleC9BootstrapInstanceLambdaFunction: 133 | Type: AWS::Lambda::Function 134 | Properties: 135 | Tags: 136 | - Key: Environment 137 | Value: AWS Example 138 | Handler: index.lambda_handler 139 | Role: 140 | Fn::GetAtt: 141 | - ExampleC9LambdaExecutionRole 142 | - Arn 143 | Runtime: python3.12 144 | MemorySize: 256 145 | Timeout: '600' 146 | Code: 147 | ZipFile: | 148 | from __future__ import annotations 149 | from dataclasses import dataclass 150 | from typing import Any, Dict, Optional 151 | import boto3 152 | import botocore 153 | import time 154 | import traceback 155 | import cfnresponse 156 | import logging 157 | from functools import wraps 158 | 159 | # Set up structured logging 160 | logger = logging.getLogger() 161 | logger.setLevel(logging.INFO) 162 | 163 | @dataclass 164 | class InstanceProfile: 165 | """Data class for instance profile details.""" 166 | arn: str 167 | name: str 168 | 169 | def to_dict(self) -> dict[str, str]: 170 | """Convert to format required by boto3.""" 171 | return {'Arn': self.arn, 'Name': self.name} 172 | 173 | def log_execution(func): 174 | """Decorator to log function execution with timing.""" 175 | @wraps(func) 176 | def wrapper(*args, **kwargs): 177 | start_time = time.perf_counter() 178 | logger.info(f"Starting {func.__name__}") 179 | try: 180 | result = func(*args, **kwargs) 181 | execution_time = time.perf_counter() - start_time 182 | logger.info(f"Completed {func.__name__} in {execution_time:.2f} seconds") 183 | return result 184 | except Exception as e: 185 | logger.error(f"Failed {func.__name__}: {str(e)}") 186 | raise 187 | return wrapper 188 | 189 | class Cloud9InstanceManager: 190 | """Manager class for Cloud9 instance operations.""" 191 | 192 | def __init__(self, ec2_client): 193 | self.ec2_client = ec2_client 194 | 195 | @log_execution 196 | def get_instance(self, stack_name: str, environment_id: str) -> Dict[str, Any]: 197 | """Get Cloud9 IDE instance details.""" 198 | try: 199 | instances = self.ec2_client.describe_instances( 200 | Filters=[{ 201 | 'Name': 'tag:Name', 202 | 'Values': [f'aws-cloud9-{stack_name}-{environment_id}'] 203 | }] 204 | )['Reservations'][0]['Instances'] 205 | 206 | if not instances: 207 | raise ValueError("No matching Cloud9 instance found") 208 | 209 | instance = instances[0] 210 | logger.info(f"Found Cloud9 instance: {instance['InstanceId']}") 211 | return instance 212 | 213 | except (IndexError, KeyError) as e: 214 | raise ValueError(f"Error finding Cloud9 instance: {str(e)}") 215 | 216 | @log_execution 217 | def attach_profile(self, instance_id: str, profile: InstanceProfile) -> None: 218 | """Attach IAM instance profile to EC2 instance.""" 219 | try: 220 | self.ec2_client.associate_iam_instance_profile( 221 | IamInstanceProfile=profile.to_dict(), 222 | InstanceId=instance_id 223 | ) 224 | logger.info(f"Successfully attached profile to instance {instance_id}") 225 | 226 | self.reboot_instance(instance_id) 227 | 228 | except botocore.exceptions.ClientError as error: 229 | error_code = error.response['Error']['Code'] 230 | error_message = error.response['Error']['Message'] 231 | 232 | if (error_code == 'IncorrectState' and 233 | f"There is an existing association for instance {instance_id}" in error_message): 234 | logger.warning(f"Instance profile already associated with {instance_id}") 235 | return 236 | 237 | raise error 238 | 239 | @log_execution 240 | def reboot_instance(self, instance_id: str) -> None: 241 | """Reboot EC2 instance.""" 242 | self.ec2_client.reboot_instances( 243 | InstanceIds=[instance_id], 244 | DryRun=False 245 | ) 246 | logger.info(f"Instance {instance_id} reboot initiated") 247 | 248 | @log_execution 249 | def wait_for_running_state(self, instance_id: str, timeout: int = 300) -> None: 250 | """Wait for instance to be in running state with timeout.""" 251 | start_time = time.perf_counter() 252 | 253 | while True: 254 | if time.perf_counter() - start_time > timeout: 255 | raise TimeoutError(f"Instance {instance_id} did not reach running state within {timeout} seconds") 256 | 257 | response = self.ec2_client.describe_instances(InstanceIds=[instance_id]) 258 | state = response['Reservations'][0]['Instances'][0]['State']['Name'] 259 | 260 | if state == 'running': 261 | break 262 | 263 | logger.info(f"Waiting for instance {instance_id}. Current state: {state}") 264 | time.sleep(5) 265 | 266 | class CustomResourceHandler: 267 | """Handler for CloudFormation custom resource.""" 268 | 269 | def __init__(self, event: Dict[str, Any], context: Any): 270 | self.event = event 271 | self.context = context 272 | self.response_data: Dict[str, str] = {} 273 | self.status = cfnresponse.SUCCESS 274 | 275 | def handle(self) -> None: 276 | """Main handler method.""" 277 | try: 278 | if self.event['RequestType'] == 'Delete': 279 | self.handle_delete() 280 | elif self.event['RequestType'] == 'Create': 281 | self.handle_create() 282 | except Exception as e: 283 | logger.error(f"Error: {str(e)}") 284 | logger.error(f"Traceback: {traceback.format_exc()}") 285 | self.status = cfnresponse.FAILED 286 | self.response_data = {'Error': str(e)} 287 | finally: 288 | self.send_response() 289 | 290 | def handle_delete(self) -> None: 291 | """Handle delete request.""" 292 | self.response_data = {'Success': 'Custom Resource removed'} 293 | 294 | @log_execution 295 | def handle_create(self) -> None: 296 | """Handle create request.""" 297 | props = self.event['ResourceProperties'] 298 | ec2_client = boto3.client('ec2') 299 | manager = Cloud9InstanceManager(ec2_client) 300 | 301 | # Get Cloud9 instance 302 | instance = manager.get_instance( 303 | props['StackName'], 304 | props['EnvironmentId'] 305 | ) 306 | instance_id = instance['InstanceId'] 307 | 308 | # Wait for instance to be ready 309 | manager.wait_for_running_state(instance_id) 310 | 311 | # Create and attach instance profile 312 | profile = InstanceProfile( 313 | arn=props['LabIdeInstanceProfileArn'], 314 | name=props['LabIdeInstanceProfileName'] 315 | ) 316 | manager.attach_profile(instance_id, profile) 317 | 318 | self.response_data = { 319 | 'Success': f'Started bootstrapping for instance: {instance_id}' 320 | } 321 | 322 | def send_response(self) -> None: 323 | """Send response to CloudFormation.""" 324 | cfnresponse.send( 325 | self.event, 326 | self.context, 327 | self.status, 328 | self.response_data, 329 | 'CustomResourcePhysicalID' 330 | ) 331 | 332 | def lambda_handler(event: Dict[str, Any], context: Any) -> None: 333 | """Lambda handler function.""" 334 | logger.info(f"Received event: {event}") 335 | handler = CustomResourceHandler(event, context) 336 | handler.handle() 337 | 338 | 339 | ################## SSM BOOTSRAP HANDLER ############### 340 | ExampleC9OutputBucket: 341 | Type: AWS::S3::Bucket 342 | DeletionPolicy: Delete 343 | Properties: 344 | VersioningConfiguration: 345 | Status: Enabled 346 | BucketEncryption: 347 | ServerSideEncryptionConfiguration: 348 | - ServerSideEncryptionByDefault: 349 | SSEAlgorithm: AES256 350 | 351 | ExampleC9SSMDocument: 352 | Type: AWS::SSM::Document 353 | DependsOn: ExampleC9BootstrapInstanceLambdaFunction 354 | Properties: 355 | Tags: 356 | - Key: Environment 357 | Value: AWS Example 358 | DocumentType: Command 359 | DocumentFormat: YAML 360 | Content: 361 | schemaVersion: "2.2" 362 | description: "Bootstrap Cloud9 Instance with automatic region detection" 363 | parameters: 364 | ExampleC9InstanceVolumeSize: 365 | type: String 366 | default: !Ref ExampleC9InstanceVolumeSize 367 | description: "Size of the volume in GB" 368 | mainSteps: 369 | - action: "aws:runShellScript" 370 | name: "ExampleC9bootstrap" 371 | inputs: 372 | runCommand: 373 | - | 374 | #!/bin/bash 375 | set -e 376 | 377 | # Set up logging 378 | exec 1> >(tee -a /var/log/cloud9-bootstrap.log) 379 | exec 2> >(tee -a /var/log/cloud9-bootstrap.error.log) 380 | 381 | # Function for logging 382 | log() { 383 | echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" 384 | } 385 | 386 | # Function to handle errors 387 | handle_error() { 388 | log "ERROR: $1" 389 | exit 1 390 | } 391 | 392 | # Function to get instance metadata 393 | get_metadata() { 394 | TOKEN=`sudo curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` \ 395 | && sudo curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/$1 2>/dev/null 396 | } 397 | 398 | # Get instance region 399 | get_region() { 400 | local az region 401 | az=$(get_metadata "placement/availability-zone") || handle_error "Failed to get AZ" 402 | region=${az%?} 403 | echo "$region" 404 | } 405 | 406 | # 1. Initial Setup 407 | log "Setting up environment variables and basic configuration" 408 | { 409 | echo "LANG=en_US.utf-8" >> /etc/environment 410 | echo "LC_ALL=en_US.UTF-8" >> /etc/environment 411 | . /home/ec2-user/.bashrc 412 | } || handle_error "Failed to set up environment variables" 413 | 414 | # Get and set AWS region 415 | REGION=$(get_region) 416 | log "Detected AWS Region: $REGION" 417 | 418 | # 2. Package Management 419 | log "Installing required packages" 420 | { 421 | yum -y remove aws-cli 422 | yum -y install sqlite telnet jq strace tree gcc glibc-static python3 python3-pip gettext bash-completion 423 | } || handle_error "Failed to install packages" 424 | 425 | # 3. Python and AWS CLI Configuration 426 | log "Configuring Python and AWS CLI" 427 | { 428 | PATH=$PATH:/usr/bin 429 | sudo -H -u ec2-user bash -c "pip install --user -U boto boto3 botocore awscli" 430 | 431 | mkdir -p /home/ec2-user/.aws 432 | printf "[default]\nregion = %s\noutput = json\n" "$REGION" > /home/ec2-user/.aws/config 433 | chmod 600 /home/ec2-user/.aws/config 434 | } || handle_error "Failed to configure Python and AWS CLI" 435 | 436 | # 4. Volume Resizing 437 | log "Checking current volume size" 438 | { 439 | # Get current volume size in GB 440 | CURRENT_SIZE=$(df -BG / | awk 'NR==2 {print $2}' | sed 's/G//') 441 | TARGET_SIZE={{ ExampleC9InstanceVolumeSize }} 442 | 443 | log "Current size: ${CURRENT_SIZE}GB, Target size: ${TARGET_SIZE}GB" 444 | 445 | # Only proceed if current size is less than target size 446 | if [ "${CURRENT_SIZE}" -lt "${TARGET_SIZE}" ]; then 447 | log "Starting volume resize operation" 448 | { 449 | INSTANCEID=$(get_metadata "instance-id") 450 | VOLUMEID=$(sudo aws ec2 describe-instances \ 451 | --instance-id $INSTANCEID \ 452 | --query "Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId" \ 453 | --output text --region $REGION) || exit 3010 454 | 455 | log "Modifying volume $VOLUMEID" 456 | sudo aws ec2 modify-volume --volume-id $VOLUMEID --size {{ ExampleC9InstanceVolumeSize }} --region $REGION || exit 3010 457 | 458 | while [ \ 459 | "$(sudo aws ec2 describe-volumes-modifications \ 460 | --volume-id $VOLUMEID \ 461 | --filters Name=modification-state,Values="optimizing","completed" \ 462 | --query "length(VolumesModifications)" \ 463 | --output text --region $REGION)" != "1" ]; do 464 | sleep 1 465 | done 466 | 467 | DEVICE_PATH=$(readlink -f /dev/xvda) 468 | if [ "$DEVICE_PATH" = "/dev/xvda" ]; then 469 | sudo growpart /dev/xvda 1 || exit 3010 470 | STR=$(cat /etc/os-release) 471 | if [[ "$STR" =~ "VERSION_ID=\"2\"" ]] || [[ "$STR" =~ "VERSION_ID=\"2022\"" ]] || [[ "$STR" =~ "VERSION_ID=\"2023\"" ]]; then 472 | sudo xfs_growfs -d / || exit 3010 473 | else 474 | sudo resize2fs /dev/xvda1 || exit 3010 475 | fi 476 | else 477 | sudo growpart /dev/nvme0n1 1 || exit 3010 478 | STR=$(cat /etc/os-release) 479 | if [[ "$STR" =~ "VERSION_ID=\"2\"" ]] || [[ "$STR" =~ "VERSION_ID=\"2022\"" ]] || [[ "$STR" =~ "VERSION_ID=\"2023\"" ]]; then 480 | sudo xfs_growfs -d / || exit 3010 481 | else 482 | sudo resize2fs /dev/nvme0n1p1 || exit 3010 483 | fi 484 | fi 485 | 486 | log "Volume resize completed successfully. Rebooting..." 487 | exit 3010 488 | } || { 489 | log "Error in volume resize operations" 490 | exit 3010 491 | } 492 | else 493 | log "Current volume size is adequate. Skipping resize operation." 494 | fi 495 | } || { 496 | log "Error in volume resize block" 497 | exit 3010 498 | } 499 | 500 | # 5. AWS Configuration 501 | log "Setting up additional AWS configuration" 502 | { 503 | echo 'PATH=$PATH:/usr/local/bin' >> /home/ec2-user/.bashrc 504 | echo 'export PATH' >> /home/ec2-user/.bashrc 505 | } || handle_error "Failed to set up AWS configuration" 506 | 507 | # 6. Cleanup 508 | log "Performing cleanup" 509 | { 510 | rm -rf /home/ec2-user/cloud9 511 | chown -R ec2-user:ec2-user /home/ec2-user/ 512 | } || handle_error "Cleanup failed" 513 | 514 | # 7. Schedule Reboot 515 | log "Scheduling reboot" 516 | { 517 | FILE=$(mktemp) 518 | echo '#!/bin/bash' > $FILE 519 | echo 'reboot -f --verbose' >> $FILE 520 | at now + 1 minute -f $FILE 521 | } || handle_error "Failed to schedule reboot" 522 | 523 | log "Bootstrap completed successfully in region $REGION" 524 | 525 | ExampleC9BootstrapAssociation: 526 | Type: AWS::SSM::Association 527 | DependsOn: ExampleC9OutputBucket 528 | Properties: 529 | Name: !Ref ExampleC9SSMDocument 530 | OutputLocation: 531 | S3Location: 532 | OutputS3BucketName: !Ref ExampleC9OutputBucket 533 | OutputS3KeyPrefix: bootstrapoutput 534 | Targets: 535 | - Key: tag:SSMBootstrap 536 | Values: 537 | - Active 538 | 539 | ################## INSTANCE ##################### 540 | ExampleC9InstanceProfile: 541 | Type: AWS::IAM::InstanceProfile 542 | Properties: 543 | Path: "/" 544 | Roles: 545 | - Ref: ExampleC9Role 546 | 547 | ExampleC9Instance: 548 | Description: "Cloud9 Instance Example" 549 | DependsOn: ExampleC9BootstrapAssociation 550 | Type: AWS::Cloud9::EnvironmentEC2 551 | Properties: 552 | Description: AWS Cloud9 instance for Examples 553 | AutomaticStopTimeMinutes: 3600 554 | ImageId: amazonlinux-2023-x86_64 555 | InstanceType: 556 | Ref: ExampleC9InstanceType 557 | Name: 558 | Ref: AWS::StackName 559 | OwnerArn: !If [Create3rdPartyResources, !Ref ExampleOwnerArn, !Ref "AWS::NoValue" ] 560 | Tags: 561 | - 562 | Key: SSMBootstrap 563 | Value: Active 564 | - 565 | Key: Environment 566 | Value: AWS Example 567 | 568 | Outputs: 569 | Cloud9IDE: 570 | Value: 571 | Fn::Join: 572 | - '' 573 | - - https:// 574 | - Ref: AWS::Region 575 | - ".console.aws.amazon.com/cloud9/ide/" 576 | - Ref: ExampleC9Instance 577 | - "?region=" 578 | - Ref: AWS::Region 579 | --------------------------------------------------------------------------------