Skip to content

Commit 8fa0424

Browse files
committed
initial commit
0 parents  commit 8fa0424

File tree

5 files changed

+373
-0
lines changed

5 files changed

+373
-0
lines changed

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.vscode*
2+
# Byte-compiled / optimized / DLL files
3+
__pycache__/
4+
*.py[cod]
5+
*$py.class
6+
*.egg-info/
7+
.installed.cfg
8+
*.egg
9+
.private*

cloudctl/cli.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import click
2+
3+
4+
@click.group()
5+
@click.option('--debug/--no-debug', default=False)
6+
def cli(debug):
7+
click.echo('Debug mode is %s' % ('on' if debug else 'off'))
8+
9+
10+
@cli.command() # @cli, not @click!
11+
def sync():
12+
click.echo('Syncing')

cloudctl/cloudctl.py

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import click
2+
import logging
3+
import functools
4+
from tabulate import tabulate
5+
from .functions import *
6+
7+
logging.basicConfig(format=(
8+
'%(asctime)s %(name)-12s %(funcName)-8s %(message)s'))
9+
logger = logging.getLogger(__name__)
10+
logger.setLevel(logging.INFO)
11+
12+
13+
def common_options(f):
14+
options = [
15+
click.option('-d', '--debug', envvar="CLOUDCTL_DEBUG", show_default=True,
16+
is_flag=True, help='Enable debug mode'),
17+
18+
click.option('--dry-run', envvar="CLOUDCTL_DRY_RUN", show_default=True,
19+
is_flag=True, help='Dry Run mode'),
20+
21+
click.option('-a', '--auto-approve', envvar="CLOUDCTL_AUTO_APPROVE", show_default=True,
22+
is_flag=True, help='Auto Approve operation'),
23+
24+
click.option('-c', '--cloud', type=click.Choice(['aws', 'gcp', 'azure'], case_sensitive=False),
25+
envvar="CLOUDCTL_CLOUD", default='aws', show_default=True, help='Cloud provider to query'),
26+
27+
click.option('-t', '--tags', multiple=True,
28+
type=click.Tuple([str, str]), help='Query resources by tags'),
29+
30+
click.option('-r', '--role', multiple=False,
31+
type=str, help='Query resource by role Tag'),
32+
]
33+
return functools.reduce(lambda x, opt: opt(x), options, f)
34+
35+
36+
@click.group()
37+
@common_options
38+
@click.pass_context
39+
def cloudctl(ctx, **kwargs):
40+
""" Cloud Manager cli """
41+
if kwargs["debug"]:
42+
logger.setLevel(logging.DEBUG)
43+
ctx.obj['logger'] = logger
44+
45+
46+
@cloudctl.group()
47+
@click.pass_context
48+
def get(ctx):
49+
"""get action"""
50+
51+
52+
@cloudctl.group()
53+
@click.pass_context
54+
def stop(ctx):
55+
"""stop action"""
56+
57+
58+
@common_options
59+
@get.command('instances')
60+
@click.pass_context
61+
def get_instances_cmd(ctx, **kwargs):
62+
if kwargs["debug"]:
63+
logger.setLevel(logging.DEBUG)
64+
ctx.obj['cloud'] = kwargs['cloud']
65+
66+
# create tags dict for query
67+
tags = create_tags_dict(kwargs['tags'], kwargs['role'])
68+
logger.debug("tags=" + str(tags))
69+
70+
print_instances(instances=list_instances(
71+
logger=ctx.obj['logger'], tags=tags))
72+
73+
74+
@common_options
75+
@get.command('instances-api')
76+
@click.pass_context
77+
def get_instances_api(ctx, **kwargs):
78+
if kwargs["debug"]:
79+
logger.setLevel(logging.DEBUG)
80+
ctx.obj['cloud'] = kwargs['cloud']
81+
82+
# create tags dict for query
83+
tags = create_tags_dict(kwargs['tags'], kwargs['role'])
84+
logger.debug("tags=" + str(tags))
85+
86+
print_instances(instances=list_instances_api(
87+
logger=ctx.obj['logger'], tags=tags))
88+
89+
90+
@common_options
91+
@stop.command('instances')
92+
@click.pass_context
93+
def stop_instance_cmd(ctx, **kwargs):
94+
if kwargs["debug"]:
95+
logger.setLevel(logging.DEBUG)
96+
logger.debug(kwargs.keys())
97+
# create tags dict for query
98+
tags = create_tags_dict(kwargs['tags'], kwargs['role'])
99+
logger.debug("tags=" + str(tags))
100+
101+
instances = list_instances(logger=ctx.obj['logger'], tags=tags)
102+
print_instances(instances)
103+
auto_approve = kwargs['auto_approve']
104+
logger.debug("auto approve=" + str(auto_approve))
105+
106+
if not auto_approve:
107+
if click.confirm('Are you sure you want to stop instance/s above?', default=False):
108+
pass
109+
else:
110+
click.echo('Operation aborted')
111+
exit
112+
ids = [ins['Id'] for ins in instances if 'Id' in ins]
113+
logger.debug(ids)
114+
stop_instances(ids=ids)
115+
116+
117+
def print_instances(instances):
118+
if len(instances) == 0:
119+
click.echo('No instances found by criteria')
120+
121+
else:
122+
headers = instances[0].keys()
123+
table_values = [x.values() for x in instances]
124+
click.echo(tabulate(table_values, headers))
125+
126+
127+
def create_tags_dict(tags_tuple, role):
128+
tags = dict((k, v) for k, v in tags_tuple)
129+
if role:
130+
tags['role'] = role
131+
132+
return tags
133+
134+
135+
def start():
136+
137+
try:
138+
cloudctl(obj={})
139+
except Exception as e:
140+
print(e)
141+
142+
143+
if __name__ == '__main__':
144+
start()

cloudctl/functions.py

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import boto3
2+
import os
3+
from os import path
4+
from configparser import ConfigParser
5+
from botocore.exceptions import *
6+
import sys
7+
import os
8+
import base64
9+
import datetime
10+
import hashlib
11+
import hmac
12+
import requests
13+
import urllib
14+
15+
16+
def list_instances(logger, tags, ** kwargs):
17+
ec2 = boto3.resource('ec2')
18+
19+
ec2_filters = []
20+
for tag_key in tags.keys():
21+
ec2_filters.append(
22+
{'Name': 'tag:' + tag_key, 'Values': [tags[tag_key]]})
23+
instances = ec2.instances.filter(Filters=ec2_filters)
24+
logger.debug(instances)
25+
ec2_instances = []
26+
for instance in instances:
27+
# logger.debug(instance)
28+
instance_struct = {}
29+
instance_struct['Id'] = instance.id
30+
instance_struct['Name'] = ''
31+
instance_struct['State'] = instance.state['Name']
32+
logger.debug(instance.tags)
33+
if instance.tags != None:
34+
for tag in instance.tags:
35+
if tag['Key'] == 'Name':
36+
instance_struct['name'] = tag['Value']
37+
38+
ec2_instances.append(instance_struct)
39+
logger.debug(ec2_instances)
40+
return ec2_instances
41+
42+
43+
def stop_instances(ids):
44+
45+
ec2 = boto3.resource('ec2')
46+
ec2.instances.filter(InstanceIds=ids).stop()
47+
48+
49+
# Key derivation functions. See:
50+
# http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-python
51+
def sign(key, msg):
52+
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
53+
54+
55+
def getSignatureKey(key, dateStamp, regionName, serviceName):
56+
kDate = sign(('AWS4' + key).encode('utf-8'), dateStamp)
57+
kRegion = sign(kDate, regionName)
58+
kService = sign(kRegion, serviceName)
59+
kSigning = sign(kService, 'aws4_request')
60+
return kSigning
61+
62+
63+
def list_instances_api(logger, tags, ** kwargs):
64+
65+
# Read AWS access key from env. variables or configuration file. Best practice is NOT
66+
# to embed credentials in code.
67+
access_key = os.environ.get('AWS_ACCESS_KEY_ID')
68+
secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
69+
if access_key is None or secret_key is None:
70+
config = ConfigParser()
71+
config.read([path.join(path.expanduser("~"), '.aws/credentials')])
72+
try:
73+
access_key = config.get('default', 'aws_access_key_id')
74+
secret_key = config.get(
75+
'default', 'aws_secret_access_key')
76+
except:
77+
raise Exception("No access and secret key is available.")
78+
logger.debug(access_key)
79+
logger.debug(secret_key)
80+
81+
method = 'GET'
82+
service = 'ec2'
83+
host = 'ec2.amazonaws.com'
84+
# region = 'us-east-1'
85+
region = 'us-west-2'
86+
endpoint = 'https://ec2.amazonaws.com'
87+
# request_parameters = 'Action=DescribeRegions&Version=2013-10-15'
88+
request_parameters = 'Action=DescribeInstances&Version=2013-10-15'
89+
90+
ec2_filters = []
91+
for tag_key in tags.keys():
92+
ec2_filters.append(
93+
{'Name': 'tag:' + tag_key, 'Values': [tags[tag_key]]})
94+
95+
# Create a date for headers and the credential string
96+
t = datetime.datetime.utcnow()
97+
amzdate = t.strftime('%Y%m%dT%H%M%SZ')
98+
logger.debug('amzdate=' + amzdate + '|')
99+
datestamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope
100+
101+
# ************* TASK 1: CREATE A CANONICAL REQUEST *************
102+
# http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
103+
104+
# Step 1 is to define the verb (GET, POST, etc.)--already done.
105+
106+
# Step 2: Create canonical URI--the part of the URI from domain to query
107+
# string (use '/' if no path)
108+
canonical_uri = '/'
109+
110+
# Step 3: Create the canonical query string. In this example (a GET request),
111+
# request parameters are in the query string. Query string values must
112+
# be URL-encoded (space=%20). The parameters must be sorted by name.
113+
# For this example, the query string is pre-formatted in the request_parameters variable.
114+
canonical_querystring = request_parameters
115+
116+
# Step 4: Create the canonical headers and signed headers. Header names
117+
# must be trimmed and lowercase, and sorted in code point order from
118+
# low to high. Note that there is a trailing \n.
119+
canonical_headers = 'host:' + host + '\n' + 'x-amz-date:' + amzdate + '\n'
120+
121+
# Step 5: Create the list of signed headers. This lists the headers
122+
# in the canonical_headers list, delimited with ";" and in alpha order.
123+
# Note: The request can include any headers; canonical_headers and
124+
# signed_headers lists those that you want to be included in the
125+
# hash of the request. "Host" and "x-amz-date" are always required.
126+
signed_headers = 'host;x-amz-date'
127+
128+
# Step 6: Create payload hash (hash of the request body content). For GET
129+
# requests, the payload is an empty string ("").
130+
payload_hash = hashlib.sha256(('').encode('utf-8')).hexdigest()
131+
132+
# Step 7: Combine elements to create canonical request
133+
canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + \
134+
'\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash
135+
136+
# ************* TASK 2: CREATE THE STRING TO SIGN*************
137+
# Match the algorithm to the hashing algorithm you use, either SHA-1 or
138+
# SHA-256 (recommended)
139+
algorithm = 'AWS4-HMAC-SHA256'
140+
credential_scope = datestamp + '/' + region + \
141+
'/' + service + '/' + 'aws4_request'
142+
string_to_sign = algorithm + '\n' + amzdate + '\n' + credential_scope + \
143+
'\n' + hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()
144+
145+
# ************* TASK 3: CALCULATE THE SIGNATURE *************
146+
# Create the signing key using the function defined above.
147+
signing_key = getSignatureKey(secret_key, datestamp, region, service)
148+
149+
# Sign the string_to_sign using the signing_key
150+
signature = hmac.new(signing_key, (string_to_sign).encode(
151+
'utf-8'), hashlib.sha256).hexdigest()
152+
153+
# ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
154+
# The signing information can be either in a query string value or in
155+
# a header named Authorization. This code shows how to use a header.
156+
# Create authorization header and add to request headers
157+
authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + \
158+
credential_scope + ', ' + 'SignedHeaders=' + \
159+
signed_headers + ', ' + 'Signature=' + signature
160+
161+
# The request can include any headers, but MUST include "host", "x-amz-date",
162+
# and (for this scenario) "Authorization". "host" and "x-amz-date" must
163+
# be included in the canonical_headers and signed_headers, as noted
164+
# earlier. Order here is not significant.
165+
# Python note: The 'host' header is added automatically by the Python 'requests' library.
166+
headers = {'x-amz-date': amzdate, 'Authorization': authorization_header}
167+
logger.debug(headers)
168+
# ************* SEND THE REQUEST *************
169+
request_url = endpoint + '?' + canonical_querystring
170+
171+
print('\nBEGIN REQUEST++++++++++++++++++++++++++++++++++++')
172+
print('Request URL = ' + request_url)
173+
r = requests.get(request_url, headers=headers)
174+
175+
print('\nRESPONSE++++++++++++++++++++++++++++++++++++')
176+
print('Response code: %d\n' % r.status_code)
177+
print(r.text)
178+
179+
ec2_instances = []
180+
for instance in instances:
181+
182+
instance_struct = {}
183+
instance_struct['Id'] = instance.id
184+
instance_struct['Name'] = ''
185+
instance_struct['State'] = instance.state['Name']
186+
for tag in instance.tags:
187+
if tag['Key'] == 'Name':
188+
instance_struct['name'] = tag['Value']
189+
190+
ec2_instances.append(instance_struct)
191+
192+
return ec2_instances

setup.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from setuptools import setup, find_packages
2+
3+
setup(
4+
# mandatory
5+
name='cloudctl',
6+
# mandatory
7+
version='0.1',
8+
# mandatory
9+
author_email='[email protected]',
10+
packages=['cloudctl'],
11+
package_data={},
12+
install_requires=['pylint', 'boto3', 'click', 'tabulate'],
13+
entry_points={
14+
'console_scripts': ['cloudctl = cloudctl.cloudctl:start']
15+
}
16+
)

0 commit comments

Comments
 (0)