Skip to content

Commit ccb5c5f

Browse files
committed
ec2 managing script
1 parent 5fa94eb commit ccb5c5f

File tree

14 files changed

+578
-0
lines changed

14 files changed

+578
-0
lines changed

bin/aws-utils/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# EC2 Instance Manager
2+
3+
# Table of contents
4+
5+
<!--ts-->
6+
- [EC2 Instance Manager](#ec2-instance-manager)
7+
- [Table of contents](#table-of-contents)
8+
- [Python Version](#python-version)
9+
- [Third Party Libraries and Dependencies](#third-party-libraries-and-dependencies)
10+
- [Usage](#usage)
11+
- [Examples](#examples)
12+
<!--te-->
13+
14+
## Python Version
15+
Python 3.9+ are supported and tested
16+
17+
18+
## Third Party Libraries and Dependencies
19+
20+
The following libraries will be installed when you install the client library:
21+
* [typer](https://github.com/tiangolo/typer)
22+
* [boto3](https://github.com/boto/boto3)
23+
24+
## Usage
25+
26+
To start using the library you need to setup a list of ENV variables with your AWS credentials as follows:
27+
```sh
28+
export AWS_DEFAULT_REGION=<...>
29+
export AWS_ACCESS_KEY_ID=<...>
30+
export AWS_SECRET_ACCESS_KEY=<...>
31+
```
32+
Then you can install a library with pip:
33+
34+
```sh
35+
pip install .
36+
```
37+
38+
If you plan to do a local developent you may also want install it in [editable mode](https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#working-in-development-mode).
39+
```sh
40+
pip install --editable .
41+
```
42+
43+
Afther the library is installed you can simply start it with --help option for retrieveing the further insructions
44+
```sh
45+
ecmgm --help
46+
```
47+
48+
## Examples
49+
50+
Here is the list of example commands:
51+
52+
Create a VM, using existing AWS SSH key
53+
```sh
54+
ecmgm create <vm-name> <image> --osnick <osnick> --ssh-key-name <ssh-keypair-name>
55+
```
56+
57+
Retrieve VM description
58+
```sh
59+
ecmgm describe <ami-id>
60+
```
61+
62+
Teardown VM
63+
```sh
64+
ecmgm teardown <ami-id>
65+
```

bin/aws-utils/ecmgm/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__app_name__ = "ecmgm"
2+
__version__ = "0.1.0"

bin/aws-utils/ecmgm/__main__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from ecmgm import cli, __app_name__
2+
3+
4+
def main():
5+
cli.app(prog_name=__app_name__)
6+
7+
8+
if __name__ == "__main__":
9+
main()

bin/aws-utils/ecmgm/cli.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import getpass
2+
from typing import Optional
3+
4+
import boto3
5+
import typer
6+
from rich.console import Console, Group
7+
from rich.panel import Panel
8+
from rich.prompt import Confirm
9+
from rich.syntax import Syntax
10+
from rich.table import Table
11+
12+
from ecmgm import __app_name__, __version__, schemas, utils
13+
14+
app = typer.Typer()
15+
console = Console()
16+
17+
18+
@app.command()
19+
def describe(instance_id: str = typer.Argument(..., help="EC2 Instance Id")):
20+
ec2 = boto3.client("ec2")
21+
table = Table(title="Instance details")
22+
table.add_column("Attribute", style="magenta")
23+
table.add_column("Value", justify="left", style="cyan")
24+
25+
response = ec2.describe_instances(
26+
InstanceIds=[
27+
instance_id,
28+
],
29+
)
30+
31+
for k, v in response["Reservations"][0]["Instances"][0].items():
32+
table.add_row(k, str(v))
33+
34+
console.print(table)
35+
36+
37+
@app.command()
38+
def teardown(instance_id: str = typer.Argument(..., help="EC2 Instance Id")):
39+
instance = utils.terminate_instance(instance_id)
40+
with console.status("[bold green]Waiting for EC2 Instance being terminated..."):
41+
instance.wait_until_terminated()
42+
43+
44+
@app.command()
45+
def create(
46+
name: str = typer.Argument(..., help="Virtual machine name"),
47+
os_image: schemas.OsImage = typer.Argument(..., help="OS image"),
48+
instance_type: schemas.InstanceTypes = typer.Option(
49+
schemas.InstanceTypes.small.value, help="Instance types"
50+
),
51+
instance_arch: schemas.InstanceArch = typer.Option(
52+
schemas.InstanceArch.x86_64.value, help="Instance Arch"
53+
),
54+
osnick: str = typer.Option("", help="Optionally filter images by specified osnick"),
55+
ssh_key_name: str = typer.Option(
56+
"", help="You can specify your SSH key name in case its already created in AWS"
57+
),
58+
):
59+
ec2 = boto3.client("ec2")
60+
image_filter_query = schemas.OS_SEARCH_MAPPING[os_image]
61+
62+
if osnick:
63+
image_filter_query += f"{osnick}*"
64+
65+
with console.status(
66+
f"[bold green]Searching for image. Search params: query: {image_filter_query} arch: {instance_arch} osnick: {osnick}..."
67+
):
68+
images = ec2.describe_images(
69+
Filters=[
70+
{
71+
"Name": "architecture",
72+
"Values": [
73+
instance_arch,
74+
],
75+
},
76+
{"Name": "root-device-type", "Values": ["ebs"]},
77+
{"Name": "state", "Values": ["available"]},
78+
{"Name": "virtualization-type", "Values": ["hvm"]},
79+
{"Name": "hypervisor", "Values": ["xen"]},
80+
{"Name": "image-type", "Values": ["machine"]},
81+
{
82+
"Name": "name",
83+
"Values": [image_filter_query],
84+
},
85+
],
86+
Owners=["amazon"],
87+
)
88+
89+
sortedAmis = sorted(images["Images"], key=lambda x: x["CreationDate"], reverse=True)
90+
target_image = sortedAmis[0]
91+
panel_group = Group(
92+
Panel(
93+
target_image["ImageId"],
94+
title="ami-id",
95+
),
96+
Panel(target_image["Description"], title="Description"),
97+
Panel(target_image["ImageLocation"], title="Image"),
98+
)
99+
console.print("Image found, details:")
100+
console.print(Panel(panel_group))
101+
102+
is_continue = Confirm.ask("Do you want to continue?")
103+
if not is_continue:
104+
quit()
105+
106+
if not ssh_key_name:
107+
console.print(f"No SSH key was specified, creating new")
108+
private_key_name = f"{getpass.getuser()}-ec2-{os_image.value}-key"
109+
private_key_filename = f"{getpass.getuser()}-ec2-{os_image.value}-key-file.pem"
110+
key_pair = utils.create_key_pair(private_key_name, private_key_filename)
111+
console.print(f"Created a key pair [bold cyan]{key_pair.key_name}[/bold cyan]")
112+
113+
instance = utils.create_instance(
114+
target_image["ImageId"],
115+
name,
116+
instance_type,
117+
ssh_key_name or key_pair.key_name,
118+
["redis-io-group"],
119+
)
120+
121+
with console.status("[bold green]Waiting EC2 Instance to start..."):
122+
instance.wait_until_running()
123+
# updating instance attributes to obtain public ip/dns immediately
124+
instance.reload()
125+
126+
private_key_filename = private_key_filename if not ssh_key_name else "your-key.pem"
127+
panel_group = Group(
128+
Panel("You can now connect to your ec2 machine :thumbs_up:\n"),
129+
Panel(
130+
Syntax(
131+
f"ssh -i {private_key_filename} {schemas.OS_DEFAULT_USER_MAP[os_image]}@{instance.public_dns_name}",
132+
"shell",
133+
theme="monokai",
134+
),
135+
title="Command",
136+
),
137+
Panel(
138+
Syntax(
139+
f"# you also might need to make key to be only readable by you\nchmod 400 {private_key_filename}\n"
140+
f"# You may use that instance id [{instance.id}] in other CLI commands like\n"
141+
"# Get VM details\n"
142+
f"ec2 describe {instance.id}\n"
143+
"# Terminate VM\n"
144+
f"ec2 teardown {instance.id}",
145+
"shell",
146+
theme="monokai",
147+
),
148+
title="Tip",
149+
),
150+
)
151+
console.print(Panel(panel_group))
152+
153+
154+
def _version_callback(value: bool) -> None:
155+
if value:
156+
typer.echo(f"{__app_name__} v{__version__}")
157+
raise typer.Exit()
158+
159+
160+
@app.callback()
161+
def main(
162+
version: Optional[bool] = typer.Option(
163+
None,
164+
"--version",
165+
"-v",
166+
help="Show the application's version and exit.",
167+
callback=_version_callback,
168+
is_eager=True,
169+
)
170+
) -> None:
171+
return
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Metadata-Version: 2.1
2+
Name: ecmgm
3+
Version: 0.1.0
4+
Classifier: Programming Language :: Python :: 3
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
README.md
2+
pyproject.toml
3+
setup.cfg
4+
setup.py
5+
ecmgm/ecmgm.egg-info/PKG-INFO
6+
ecmgm/ecmgm.egg-info/SOURCES.txt
7+
ecmgm/ecmgm.egg-info/dependency_links.txt
8+
ecmgm/ecmgm.egg-info/not-zip-safe
9+
ecmgm/ecmgm.egg-info/top_level.txt
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

bin/aws-utils/ecmgm/schemas.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from enum import Enum
2+
3+
4+
class InstanceTypes(str, Enum):
5+
"""
6+
Instance vCPU* Mem (GiB)
7+
t2.nano 1 0.5
8+
t2.micro 1 1
9+
t2.small 1 2
10+
t2.medium 2 4
11+
t2.large 2 8
12+
t2.xlarge 4 16
13+
t2.2xlarge 8 32
14+
"""
15+
16+
nano = "t2.nano"
17+
micro = "t2.micro"
18+
small = "t2.small"
19+
medium = "t2.medium"
20+
large = "t2.large"
21+
xlarge = "t2.xlarge"
22+
doublelarge = "t2.2xlarge"
23+
24+
25+
class InstanceArch(str, Enum):
26+
i386 = "i386"
27+
x86_64 = "x86_64"
28+
x86_64_mac = "x86_64_mac"
29+
arm64 = "arm64"
30+
31+
32+
class OsImage(str, Enum):
33+
windows = "windows"
34+
ubuntu = "ubuntu"
35+
debian = "debian"
36+
suse = "suse"
37+
amazon_linux = "amazon_linux"
38+
redhat = "redhat"
39+
macos = "macos"
40+
41+
42+
OS_SEARCH_MAPPING = {
43+
OsImage.windows: "*Windows*",
44+
OsImage.ubuntu: "*ubuntu*/images/*",
45+
OsImage.debian: "*debian",
46+
OsImage.suse: "*suse*",
47+
OsImage.amazon_linux: "amzn2-ami-hvm-*",
48+
OsImage.redhat: "*RHEL*",
49+
OsImage.macos: "*macos*",
50+
}
51+
52+
OS_DEFAULT_USER_MAP = {OsImage.ubuntu: "ubuntu", OsImage.amazon_linux: "ec2"}

0 commit comments

Comments
 (0)