mirror of
https://github.com/AdaCore/e3-aws.git
synced 2026-02-12 13:02:04 -08:00
182 lines
5.9 KiB
Python
182 lines
5.9 KiB
Python
|
|
#!/usr/bin/env python
|
||
|
|
"""Provide a Command Line Interface to manage MySimpleStack stack.
|
||
|
|
|
||
|
|
The stack consists of a VPC with private and public subnets in eu-west-1a AZ
|
||
|
|
and a NAT Gateway to route traffic from the private subnet to the Internet.
|
||
|
|
It also deploys an instance in the private subnet with its IAM profile, and
|
||
|
|
a security group.
|
||
|
|
|
||
|
|
As it relies on CFNProjectMain it requires a deployment and a
|
||
|
|
CloudFormation roles named respectively cfn-user/CFNAllowDeployOfMySimpleStack
|
||
|
|
and cfn-service/CFNServiceRoleForMySimpleStack.
|
||
|
|
|
||
|
|
The 'CFNAllow' role must be assumable by the user deploying the stack.
|
||
|
|
The 'CFNServiceRole' must trust the CloudFormation service.
|
||
|
|
|
||
|
|
For more details on how to manage the stack run:
|
||
|
|
./deploy_simple_stack.py --help
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
from functools import cached_property
|
||
|
|
import sys
|
||
|
|
from typing import TYPE_CHECKING
|
||
|
|
|
||
|
|
from e3.aws.troposphere import CFNProjectMain, Construct, name_to_id, Stack
|
||
|
|
from e3.aws.troposphere.ec2 import VPCv2
|
||
|
|
from e3.aws.troposphere.iam.role import Role
|
||
|
|
from e3.aws.troposphere.iam.policy_statement import Trust
|
||
|
|
from troposphere import ec2, iam, Ref, GetAtt, Tags
|
||
|
|
|
||
|
|
if TYPE_CHECKING:
|
||
|
|
from troposphhere import AWSObject
|
||
|
|
|
||
|
|
STACK_NAME = "MySimpleStack"
|
||
|
|
ACCOUNT_ID = "012345678910"
|
||
|
|
REGION = "eu-west-1"
|
||
|
|
AZ = "eu-west-1a"
|
||
|
|
IAM_PATH = "/my-simple-stack/"
|
||
|
|
INSTANCE_AMI = "ami-1234"
|
||
|
|
|
||
|
|
# S3 Bucket where templates are pushed for deployment
|
||
|
|
# The "CFNAllowDeployOf" role must be allowed to push files to:
|
||
|
|
# my-cfn-bucket/my-simple-stack/*
|
||
|
|
# The "CFNServiceRole" must be allowed to read files from:
|
||
|
|
# my-cfn-bucket/my-simple-stack/*
|
||
|
|
CFN_BUCKET = "my-cfn-bucket"
|
||
|
|
|
||
|
|
|
||
|
|
class SimpleInstance(Construct):
|
||
|
|
"""Provide a construct deploying a simple instance."""
|
||
|
|
|
||
|
|
def __init__(self, name: str, vpc: VPCv2, ami: str, instance_type: str) -> None:
|
||
|
|
"""Initialize a SimpleInstance instance.
|
||
|
|
|
||
|
|
:param name: name of the instance
|
||
|
|
:param vpc: a vpc to host the instance
|
||
|
|
:param ami: AMI for the instance
|
||
|
|
:param instance_type: the EC2 instance type
|
||
|
|
"""
|
||
|
|
self.name = name
|
||
|
|
self.vpc = vpc
|
||
|
|
self.ami = ami
|
||
|
|
self.instance_type = instance_type
|
||
|
|
|
||
|
|
@cached_property
|
||
|
|
def role(self) -> Role:
|
||
|
|
"""Return a role for the simple instance."""
|
||
|
|
return Role(
|
||
|
|
name=f"{self.name}InstanceRole",
|
||
|
|
description="Simple instance instance role",
|
||
|
|
path=IAM_PATH,
|
||
|
|
trust=Trust(services=["ec2"]),
|
||
|
|
managed_policy_arns=[
|
||
|
|
# Access to CloudWatch and SSM
|
||
|
|
"arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy",
|
||
|
|
"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
|
||
|
|
"arn:aws:iam::aws:policy/AmazonSSMPatchAssociation",
|
||
|
|
],
|
||
|
|
)
|
||
|
|
|
||
|
|
@cached_property
|
||
|
|
def profile(self) -> iam.InstanceProfile:
|
||
|
|
"""Return an instance profile for the simple instance."""
|
||
|
|
profile_name = f"{self.name}InstanceProfile"
|
||
|
|
return iam.InstanceProfile(
|
||
|
|
title=name_to_id(profile_name),
|
||
|
|
InstanceProfileName=profile_name,
|
||
|
|
Path=IAM_PATH,
|
||
|
|
Roles=[self.role.name],
|
||
|
|
DependsOn=self.role.name,
|
||
|
|
)
|
||
|
|
|
||
|
|
@cached_property
|
||
|
|
def security_group(self) -> ec2.SecurityGroup:
|
||
|
|
"""Return instance security group.
|
||
|
|
|
||
|
|
Allow no inbound and all outbound.
|
||
|
|
"""
|
||
|
|
group_name = f"{self.name}SG"
|
||
|
|
return ec2.SecurityGroup(
|
||
|
|
name_to_id(group_name),
|
||
|
|
GroupDescription=f"Security group for {self.name} instance",
|
||
|
|
GroupName=group_name,
|
||
|
|
SecurityGroupEgress=[
|
||
|
|
ec2.SecurityGroupRule(CidrIp="0.0.0.0/0", IpProtocol="-1"),
|
||
|
|
ec2.SecurityGroupRule(CidrIpv6="::/0", IpProtocol="-1"),
|
||
|
|
],
|
||
|
|
SecurityGroupIngress=[],
|
||
|
|
VpcId=Ref(self.vpc.vpc),
|
||
|
|
)
|
||
|
|
|
||
|
|
@cached_property
|
||
|
|
def instance(self) -> ec2.Instance:
|
||
|
|
"""Return a simple instance."""
|
||
|
|
return ec2.Instance(
|
||
|
|
title=name_to_id(self.name),
|
||
|
|
ImageId=self.ami,
|
||
|
|
IamInstanceProfile=Ref(self.profile),
|
||
|
|
InstanceType=self.instance_type,
|
||
|
|
SubnetId=Ref(self.vpc.private_subnets[AZ]),
|
||
|
|
# Use default security group that comes with the VPC
|
||
|
|
SecurityGroupIds=[GetAtt(self.security_group, "GroupId")],
|
||
|
|
PropagateTagsToVolumeOnCreation=True,
|
||
|
|
BlockDeviceMappings=[
|
||
|
|
ec2.BlockDeviceMapping(
|
||
|
|
Ebs=ec2.EBSBlockDevice(VolumeType="gp3", VolumeSize="20"),
|
||
|
|
DeviceName="/dev/sda1",
|
||
|
|
)
|
||
|
|
],
|
||
|
|
Tags=Tags({"Name": self.name}),
|
||
|
|
)
|
||
|
|
|
||
|
|
def resources(self, stack: Stack) -> list[AWSObject | Construct]:
|
||
|
|
"""Return resources for this construct."""
|
||
|
|
return [
|
||
|
|
self.role,
|
||
|
|
self.profile,
|
||
|
|
self.security_group,
|
||
|
|
self.instance,
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
class MySimpleStackMain(CFNProjectMain):
|
||
|
|
"""Provide CLI to manage MySimpleStack stack."""
|
||
|
|
|
||
|
|
def create_stack(self) -> list[Stack]:
|
||
|
|
"""Create MySimpleStack stack."""
|
||
|
|
vpc = VPCv2(
|
||
|
|
name_prefix=self.stack.name,
|
||
|
|
cidr_block="10.50.0.0/16",
|
||
|
|
availability_zones=[AZ],
|
||
|
|
)
|
||
|
|
self.add(vpc)
|
||
|
|
self.add(
|
||
|
|
SimpleInstance(
|
||
|
|
name="MySimpleInstance",
|
||
|
|
vpc=vpc,
|
||
|
|
ami="MYAMi-1234",
|
||
|
|
instance_type="t4g.small",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
return self.stack
|
||
|
|
|
||
|
|
|
||
|
|
def main(args: list[str] | None = None) -> None:
|
||
|
|
"""Entry point.
|
||
|
|
|
||
|
|
:param args: the list of positional parameters. If None then
|
||
|
|
``sys.argv[1:]`` is used
|
||
|
|
"""
|
||
|
|
project = MySimpleStackMain(
|
||
|
|
name=STACK_NAME,
|
||
|
|
account_id=ACCOUNT_ID,
|
||
|
|
stack_description="Stack deploying an instance",
|
||
|
|
s3_bucket=f"cfn-gitlab-adacore-{REGION}",
|
||
|
|
regions=[REGION],
|
||
|
|
)
|
||
|
|
sys.exit(project.execute(args))
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|