From 36236a30a11616eed3d236e3bd80f9ead0b4690d Mon Sep 17 00:00:00 2001 From: Claudio Caceres Date: Mon, 12 Jun 2023 12:23:05 -0300 Subject: [PATCH 1/2] Refactor for python2.7 --- PKG-INFO | 34 ++ build/lib/pyaws/__init__.py | 22 + build/lib/pyaws/_version.py | 1 + build/lib/pyaws/awslambda/__init__.py | 6 + build/lib/pyaws/awslambda/env.py | 90 ++++ build/lib/pyaws/awslambda/lambda_utils.py | 144 ++++++ build/lib/pyaws/colors.py | 97 ++++ build/lib/pyaws/core/__init__.py | 10 + build/lib/pyaws/core/create_client.py | 44 ++ build/lib/pyaws/core/cross_account_utils.py | 110 ++++ build/lib/pyaws/core/loggers.py | 22 + build/lib/pyaws/core/oscodes_unix.py | 121 +++++ build/lib/pyaws/core/oscodes_win.py | 80 +++ build/lib/pyaws/dynamodb/__init__.py | 4 + build/lib/pyaws/dynamodb/dynamodb.py | 137 +++++ build/lib/pyaws/dynamodb/table.py | 49 ++ build/lib/pyaws/ec2/__init__.py | 5 + build/lib/pyaws/ec2/ec2_utils.py | 278 +++++++++++ build/lib/pyaws/ec2/snapshot_ops.py | 228 +++++++++ build/lib/pyaws/ec2/state.py | 107 ++++ build/lib/pyaws/environment.py | 68 +++ build/lib/pyaws/helpers.py | 36 ++ build/lib/pyaws/logd.py | 146 ++++++ build/lib/pyaws/s3/__init__.py | 3 + build/lib/pyaws/s3/check_authenticated_s3.py | 19 + build/lib/pyaws/s3/object_operations.py | 37 ++ build/lib/pyaws/script_utils.py | 302 +++++++++++ build/lib/pyaws/session.py | 208 ++++++++ build/lib/pyaws/statics.py | 71 +++ build/lib/pyaws/sts/__init__.py | 3 + build/lib/pyaws/sts/cross_account_utils.py | 123 +++++ build/lib/pyaws/tags/__init__.py | 4 + build/lib/pyaws/tags/bulk-modify-tags.py | 334 +++++++++++++ .../lib/pyaws/tags/copy-tags-all-instances.py | 241 +++++++++ build/lib/pyaws/tags/ec2-update-tags.py | 248 +++++++++ build/lib/pyaws/tags/tag_utils.py | 308 ++++++++++++ build/lib/pyaws/utils/__init__.py | 8 + dist/pyaws-0.4.1-py2.7.egg | Bin 0 -> 101743 bytes dist/pyaws-0.4.1-py3.10.egg | Bin 0 -> 96693 bytes future/lambda/get_price.py | 471 +++++++++--------- pyaws.egg-info/PKG-INFO | 34 ++ pyaws.egg-info/SOURCES.txt | 42 ++ pyaws.egg-info/dependency_links.txt | 1 + pyaws.egg-info/entry_points.txt | 3 + pyaws.egg-info/not-zip-safe | 1 + pyaws.egg-info/requires.txt | 4 + pyaws.egg-info/top_level.txt | 1 + pyaws/__init__.pyc | Bin 0 -> 826 bytes pyaws/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 685 bytes pyaws/__pycache__/_version.cpython-310.pyc | Bin 0 -> 169 bytes pyaws/__pycache__/environment.cpython-310.pyc | Bin 0 -> 1754 bytes .../__pycache__/script_utils.cpython-310.pyc | Bin 0 -> 8478 bytes pyaws/__pycache__/statics.cpython-310.pyc | Bin 0 -> 1620 bytes pyaws/_version.pyc | Bin 0 -> 174 bytes pyaws/ec2/ec2_utils.py | 7 +- pyaws/environment.py | 9 +- pyaws/environment.pyc | Bin 0 -> 2021 bytes pyaws/logd.py | 7 +- pyaws/script_utils.pyc | Bin 0 -> 9810 bytes pyaws/session.py | 17 +- pyaws/statics.pyc | Bin 0 -> 1918 bytes pyaws/tags/bulk-modify-tags.py | 3 +- pyaws/tags/copy-tags-all-instances.py | 5 + pyaws/tags/tag_utils.py | 5 - setup.cfg | 4 + setup.py | 3 +- 66 files changed, 4104 insertions(+), 261 deletions(-) create mode 100644 PKG-INFO create mode 100644 build/lib/pyaws/__init__.py create mode 100644 build/lib/pyaws/_version.py create mode 100644 build/lib/pyaws/awslambda/__init__.py create mode 100644 build/lib/pyaws/awslambda/env.py create mode 100644 build/lib/pyaws/awslambda/lambda_utils.py create mode 100644 build/lib/pyaws/colors.py create mode 100644 build/lib/pyaws/core/__init__.py create mode 100644 build/lib/pyaws/core/create_client.py create mode 100644 build/lib/pyaws/core/cross_account_utils.py create mode 100644 build/lib/pyaws/core/loggers.py create mode 100644 build/lib/pyaws/core/oscodes_unix.py create mode 100644 build/lib/pyaws/core/oscodes_win.py create mode 100644 build/lib/pyaws/dynamodb/__init__.py create mode 100644 build/lib/pyaws/dynamodb/dynamodb.py create mode 100644 build/lib/pyaws/dynamodb/table.py create mode 100644 build/lib/pyaws/ec2/__init__.py create mode 100644 build/lib/pyaws/ec2/ec2_utils.py create mode 100644 build/lib/pyaws/ec2/snapshot_ops.py create mode 100644 build/lib/pyaws/ec2/state.py create mode 100644 build/lib/pyaws/environment.py create mode 100644 build/lib/pyaws/helpers.py create mode 100644 build/lib/pyaws/logd.py create mode 100644 build/lib/pyaws/s3/__init__.py create mode 100644 build/lib/pyaws/s3/check_authenticated_s3.py create mode 100644 build/lib/pyaws/s3/object_operations.py create mode 100644 build/lib/pyaws/script_utils.py create mode 100644 build/lib/pyaws/session.py create mode 100644 build/lib/pyaws/statics.py create mode 100644 build/lib/pyaws/sts/__init__.py create mode 100644 build/lib/pyaws/sts/cross_account_utils.py create mode 100644 build/lib/pyaws/tags/__init__.py create mode 100644 build/lib/pyaws/tags/bulk-modify-tags.py create mode 100644 build/lib/pyaws/tags/copy-tags-all-instances.py create mode 100644 build/lib/pyaws/tags/ec2-update-tags.py create mode 100644 build/lib/pyaws/tags/tag_utils.py create mode 100644 build/lib/pyaws/utils/__init__.py create mode 100644 dist/pyaws-0.4.1-py2.7.egg create mode 100644 dist/pyaws-0.4.1-py3.10.egg create mode 100644 pyaws.egg-info/PKG-INFO create mode 100644 pyaws.egg-info/SOURCES.txt create mode 100644 pyaws.egg-info/dependency_links.txt create mode 100644 pyaws.egg-info/entry_points.txt create mode 100644 pyaws.egg-info/not-zip-safe create mode 100644 pyaws.egg-info/requires.txt create mode 100644 pyaws.egg-info/top_level.txt create mode 100644 pyaws/__init__.pyc create mode 100644 pyaws/__pycache__/__init__.cpython-310.pyc create mode 100644 pyaws/__pycache__/_version.cpython-310.pyc create mode 100644 pyaws/__pycache__/environment.cpython-310.pyc create mode 100644 pyaws/__pycache__/script_utils.cpython-310.pyc create mode 100644 pyaws/__pycache__/statics.cpython-310.pyc create mode 100644 pyaws/_version.pyc create mode 100644 pyaws/environment.pyc create mode 100644 pyaws/script_utils.pyc create mode 100644 pyaws/statics.pyc create mode 100644 setup.cfg diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..e4b38b4 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,34 @@ +Metadata-Version: 1.2 +Name: pyaws +Version: 0.4.1 +Summary: Python Utilities for Amazon Web Services +Home-page: http://pyaws.readthedocs.io +Author: Blake Huber +Author-email: blakeca00@gmail.com +License: GPL-3.0 +Description: + **pyaws** | Utilities Library for Amazon Web Services (AWS) + ----------------------------------------------------------- + + PACKAGE: pyaws + + ``pyaws``: reusable library of utility classes and functions common AWS use cases and capabilities: + + * uploading to s3 + * adding/ deleting resource tags + * adding data elements to dynamodb table + * Determining the latest Amazon Machine Image in a region for Windows, Linux, etc + + + +Keywords: Amazon Web Services AWS iam ec2 lambda rds s3 sts +Platform: UNKNOWN +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities +Classifier: Development Status :: 4 - Beta +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) +Classifier: Operating System :: POSIX :: Linux +Requires-Python: >=3.6, <4 diff --git a/build/lib/pyaws/__init__.py b/build/lib/pyaws/__init__.py new file mode 100644 index 0000000..18b93c8 --- /dev/null +++ b/build/lib/pyaws/__init__.py @@ -0,0 +1,22 @@ +from pyaws._version import __version__ as version + + +__author__ = 'Blake Huber' +__version__ = version +__credits__ = [] +__license__ = "GPL-3.0" +__maintainer__ = "Blake Huber" +__email__ = "blakeca00@gmail.com" +__status__ = "Development" + + +# the following imports require __version__ # + +try: + import logging + + # shared, global logger object + logger = logging.getLogger(__version__) + +except Exception: + pass diff --git a/build/lib/pyaws/_version.py b/build/lib/pyaws/_version.py new file mode 100644 index 0000000..f0ede3d --- /dev/null +++ b/build/lib/pyaws/_version.py @@ -0,0 +1 @@ +__version__ = '0.4.1' diff --git a/build/lib/pyaws/awslambda/__init__.py b/build/lib/pyaws/awslambda/__init__.py new file mode 100644 index 0000000..e0c2535 --- /dev/null +++ b/build/lib/pyaws/awslambda/__init__.py @@ -0,0 +1,6 @@ +""" +Functional Utilities for AWS Lambda +""" + +from pyaws.awslambda.lambda_utils import * +from pyaws.awslambda.env import read_env_variable diff --git a/build/lib/pyaws/awslambda/env.py b/build/lib/pyaws/awslambda/env.py new file mode 100644 index 0000000..2a2c3b2 --- /dev/null +++ b/build/lib/pyaws/awslambda/env.py @@ -0,0 +1,90 @@ +""" + +lambda_utils (python3) + + Common functionality for use with AWS Lambda Service + +Author: + Blake Huber + Copyright Blake Huber, All Rights Reserved. + +License: + + MIT License. + Additional terms may be found in the complete license agreement: + https://opensource.org/licenses/MIT + + Project README: + https://github.com/fstab50/pyaws/blob/master/README.md +""" + +import os +import re +import inspect +from pyaws import logger + + +def read_env_variable(arg, default=None, patterns=None): + """ + Summary. + + Parse environment variables, validate characters, convert + type(s). default should be used to avoid conversion of an + variable and retain string type + + Usage: + >>> from lambda_utils import read_env_variable + >>> os.environ['DBUGMODE'] = 'True' + >>> myvar = read_env_variable('DBUGMODE') + >>> type(myvar) + True + + >>> from lambda_utils import read_env_variable + >>> os.environ['MYVAR'] = '1345' + >>> myvar = read_env_variable('MYVAR', 'default') + >>> type(myvar) + str + + Args: + :arg (str): Environment variable name (external name) + :default (str): Default if no variable found in the environment under + name in arg parameter + :patterns (None): Unused; not user callable. Used preservation of the + patterns tuple between calls during runtime + + Returns: + environment variable value, TYPE str + + """ + if patterns is None: + patterns = ( + (re.compile('^[-+]?[0-9]+$'), int), + (re.compile('\d+\.\d+'), float), + (re.compile(r'^(true|false)$', flags=re.IGNORECASE), lambda x: x.lower() == 'true'), + (re.compile('[a-z/]+', flags=re.IGNORECASE), str), + (re.compile('[a-z/]+\.[a-z/]+', flags=re.IGNORECASE), str), + ) + + if arg in os.environ: + var = os.environ[arg] + if var is None: + ex = KeyError('environment variable %s not set' % arg) + logger.exception(ex) + raise ex + else: + if default: + return str(var) # force default type (str) + else: + for pattern, func in patterns: + if pattern.match(var): + return func(var) + # type not identified + logger.warning( + '%s: failed to identify environment variable [%s] type. May contain \ + special characters' % (inspect.stack()[0][3], arg) + ) + return str(var) + else: + ex = KeyError('environment variable %s not set' % arg) + logger.exception(ex) + raise ex diff --git a/build/lib/pyaws/awslambda/lambda_utils.py b/build/lib/pyaws/awslambda/lambda_utils.py new file mode 100644 index 0000000..c6b33bf --- /dev/null +++ b/build/lib/pyaws/awslambda/lambda_utils.py @@ -0,0 +1,144 @@ +""" + +lambda_utils (python3) + + Common functionality for use with AWS Lambda Service + +Author: + Blake Huber + Copyright Blake Huber, All Rights Reserved. + +License: + + MIT License. + Additional terms may be found in the complete license agreement: + https://opensource.org/licenses/MIT + + Project README: + https://github.com/fstab50/pyaws/blob/master/README.md +""" + + +import os +import re +import json +import time +import inspect +import boto3 +from botocore.exceptions import ClientError +from pyaws import logger + + +def get_account_info(account_profile=None): + """ + Summary. + + Queries AWS iam and sts services to discover account id information + in the form of account name and account alias (if assigned) + + Returns: + TYPE: tuple + + Example usage: + + >>> account_number, account_name = lambda_utils.get_account_info() + >>> print(account_number, account_name) + 103562488773 tooling-prod + + """ + if account_profile: + session = boto3.Session(profile_name=account_profile) + sts_client = session.client('sts') + iam_client = session.client('iam') + else: + sts_client = boto3.client('sts') + iam_client = boto3.client('iam') + + try: + number = sts_client.get_caller_identity()['Account'] + name = iam_client.list_account_aliases()['AccountAliases'][0] + + except IndexError as e: + name = '' + logger.info('Error: %s. No account alias defined. account_name set to %s' % (e, name)) + return (number, name) + except ClientError as e: + logger.warning( + "%s: problem retrieving caller identity (Code: %s Message: %s)" % + (inspect.stack()[0][3], e.response['Error']['Code'], e.response['Error']['Message']) + ) + raise e + return (number, name) + + +def get_regions(): + """ + Summary. + + Returns list of region codes for all AWS regions worldwide + + Returns: + TYPE: list + + """ + try: + client = boto3.client('ec2') + region_response = client.describe_regions() + regions = [region['RegionName'] for region in region_response['Regions']] + + except ClientError as e: + logger.critical( + "%s: problem retrieving aws regions (Code: %s Message: %s)" % + (inspect.stack()[0][3], e.response['Error']['Code'], + e.response['Error']['Message'])) + raise e + return regions + + +def sns_notification(topic_arn, subject, message, account_id=None, account_name=None): + """ + Summary. + + Sends message to AWS sns service topic provided as a + parameter + + Args: + topic_arn (str): sns topic arn + subject (str): subject of sns message notification + message (str): message body + + Returns: + TYPE: Boolean | Success or Failure + + """ + if not (account_id or account_name): + account_id, account_name = get_account_info() + + # assemble msg + header = 'AWS Account: %s (%s) | %s' % \ + (str(account_name).upper(), str(account_id), subject) + msg = '\n%s\n\n%s' % (time.strftime('%c'), message) + msg_dict = {'default': msg} + + # client + region = (topic_arn.split('sns:', 1)[1]).split(":", 1)[0] + client = boto3.client('sns', region_name=region) + + try: + # sns publish + response = client.publish( + TopicArn=topic_arn, + Subject=header, + Message=json.dumps(msg_dict), + MessageStructure='json' + ) + if response['ResponseMetadata']['HTTPStatusCode'] == '200': + return True + else: + return False + except ClientError as e: + logger.exception( + '%s: problem sending sns msg (Code: %s Message: %s)' % + (inspect.stack()[0][3], e.response['Error']['Code'], + e.response['Error']['Message'])) + return False diff --git a/build/lib/pyaws/colors.py b/build/lib/pyaws/colors.py new file mode 100644 index 0000000..d7dd01f --- /dev/null +++ b/build/lib/pyaws/colors.py @@ -0,0 +1,97 @@ +""" +Summary: + ANSI color and formatting code class + See: http://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors + + VERSION: 1.14 + +Args: + None + +Returns: + ansi codes + +Raises: + None. AttributeError if no code match returns the reset ansi codes + +""" + + +class Colors(): + """ + Class attributes provide different format variations + """ + # forground colors + AQUA = '\u001b[38;5;14m' + PURPLE = '\033[95m' + CYAN = '\033[96m' + DARK_CYAN = '\033[36m' + BLUE = '\033[94m' + DARK_BLUE = '\033[38;5;95;38;5;24m' + PURE_BLUE = '\u001b[38;5;27m' + GREEN = '\033[92m' + DARK_GREEN = '\u001b[38;5;2m' + YELLOW = '\033[93m' + RED = '\033[91m' + ORANGE = '\033[38;5;95;38;5;214m' + GOLD2 = '\033[38;5;95;38;5;136m' # yellow-orange, lighter than GOLD3 + GOLD3 = '\033[38;5;95;38;5;178m' # yellow-orange + WHITE = '\033[37m' + WHITE_GRAY = '\033[38;5;95;38;5;250m' # white-gray + LT1GRAY = '\033[38;5;95;38;5;245m' # light gray + LT2GRAY = '\033[38;5;95;38;5;246m' + LT3GRAY = '\u001b[38;5;249m' + DARK_GRAY1 = '\033[90m' + DARK_GRAY2 = '\033[38;5;95;38;5;8m' # darkest gray + + # bright colors + BRIGHT_BLUE = '\033[38;5;51m' + BRIGHT_CYAN = '\033[38;5;36m' + BRIGHT_GREEN = '\033[38;5;95;38;5;46m' + BRIGHT_PURPLE = '\033[38;5;68m' + BRIGHT_RED = '\u001b[31;1m' + BRIGHT_YELLOW = '\033[38;5;11m' + BRIGHT_YELLOW2 = '\033[38;5;95;38;5;226m' + BRIGHT_YELLOWGREEN = '\033[38;5;95;38;5;155m' + BRIGHT_WHITE = '\033[38;5;15m' + + # background colors + BKGND_BLACK = '\u001b[0m' + BKGND_GREY = '\u001b[40m' + BKGND_BLUE = '\u001b[44m' + BKGND_CYAN = '\u001b[46m' + BKGND_GREEN = '\u001b[42m' + BKGND_MAGENTA = '\u001b[45m' + BKGND_RED = '\u001b[41m' + BKGND_WHITE = '\u001b[47m' + BKGND_WHITE_BOLD = '\u001b[47;1m' + BKGND_YELLOW = '\u001b[43m' + + # background colors; bright + BKGND_BRIGHT_BLACK = '\u001b[40;1m' + BKGND_BRIGHT_BLUE = '\u001b[44;1m' + BKGND_BRIGHT_CYAN = '\u001b[46;1m' + BKGND_BRIGHT_GREEN = '\u001b[42;1m' + BKGND_BRIGHT_GRAY = '\u001b[38;1m' + BKGND_BRIGHT_MAGENTA = '\u001b[45;1m' + BKGND_BRIGHT_RED = '\u001b[41;1m' + BKGND_BRIGHT_WHITE = '\u001b[47;1m' + BKGND_BRIGHT_YELLOW = '\u001b[43;1m' + + # formats + BOLD = '\033[1m' + UNBOLD = '\033[22m' + UNDERLINE = '\033[4m' + ITALIC = '\e[3m' + END = '\033[0m' + REVERSE = '\033[;7m' + RESET = '\033[0;0m' + RESET_ALT = '\u001b[0m' + CLEARSCN = '\033[2J' + + # special formats + URL = UNDERLINE + CYAN + TITLE = UNDERLINE + BOLD + + #except AttributeError as e: + # logger.info('Ansi color code not found (%s), returning reset code' % str(e)) diff --git a/build/lib/pyaws/core/__init__.py b/build/lib/pyaws/core/__init__.py new file mode 100644 index 0000000..18f9100 --- /dev/null +++ b/build/lib/pyaws/core/__init__.py @@ -0,0 +1,10 @@ +""" +Common AWS Functionality required to support all services +""" + +try: + + from pyaws.core.oscodes_unix import exit_codes + +except Exception: + from pyaws.core.oscodes_win import exit_codes diff --git a/build/lib/pyaws/core/create_client.py b/build/lib/pyaws/core/create_client.py new file mode 100644 index 0000000..baf468f --- /dev/null +++ b/build/lib/pyaws/core/create_client.py @@ -0,0 +1,44 @@ +def create_client(service, account=None, role=None): + """ + Summary: + Creates the appropriate boto3 client for a particular AWS service + Args: + :type service: str + :param service: name of service at Amazon Web Services (AWS), + e.g. s3, ec2, etc + :type credentials: sts credentials object + :param credentials: authentication credentials to resource in AWS + :type role: str + :param role: IAM role designation used to access AWS resources + in an account + Returns: + Boto3 Client Object + """ + try: + if role and account: # create client for a different AWS account + account_obj = AssumeAWSRole(account, role) + if account_obj.status.get('STATUS') == 'SUCCESS': + credentials = account_obj.credentials + client = boto3.client( + service, + aws_access_key_id=credentials['AccessKeyId'], + aws_secret_access_key=credentials['SecretAccessKey'], + aws_session_token=credentials['SessionToken'] + ) + else: + logger.critical('failed to create client - Error: %s' % + str(account_obj.status.get('STATUS'))) + raise ClientError( + '%s: Problem creating client using role %s' % + (inspect.stack()[0][3], str(role)) + ) + else: + return boto3.client(service) # create client in the current AWS account + except ClientError as e: + logger.exception( + "%s: Problem creating client %s in account %s (Code: %s Message: %s)" % + (inspect.stack()[0][3], role, account, e.response['Error']['Code'], + e.response['Error']['Message']) + ) + raise + return client diff --git a/build/lib/pyaws/core/cross_account_utils.py b/build/lib/pyaws/core/cross_account_utils.py new file mode 100644 index 0000000..2953e09 --- /dev/null +++ b/build/lib/pyaws/core/cross_account_utils.py @@ -0,0 +1,110 @@ +import inspect +import boto3 +from botocore.exceptions import ClientError +import loggers +from _version import __version__ + + +# lambda custom log object +logger = loggers.getLogger(__version__) + + +class AssumeAWSRole(): + """ class def for assuming roles in AWS """ + def __init__(self, role_name=None, account=None, profile=None): + self.role = role_name + if account: + self.account_number = str(account) + self.credentials = self.assume_role(str(account), role_name) + elif profile: + self.profile = profile + r = sts_client.get_caller_identity() + self.account_number = r['Account'] + self.credentials = self.assume_role() + self.status = {} + + def assume_role(self, account=None, role=None): + """ + Summary: + Assumes a DynamoDB role in 'destination' AWS account + Args: + :type account: str + :param account: AWS account number + :type role: str + :param role: IAM role designation used to access AWS resources + in an account + :type profile: str + :param role: profile_name is an IAM user or IAM role name represented + in the local awscli configuration as a profile entry + Returns: dict (Credentials) + """ + if self.profile: + session = boto3.Session(profile_name=self.profile) + sts_client = session.client('sts') + else: + sts_client = boto3.client('sts') + + try: + # assume role in destination account + if account and role: + assumed_role = sts_client.assume_role( + RoleArn="arn:aws:iam::%s:role/%s" % (account, role), + RoleSessionName="AssumeAWSRoleSession" + ) + else: + assumed_role = sts_client.assume_role( + RoleArn="arn:aws:iam::%s:role/%s" % (self.account_number, self.role), + RoleSessionName="AssumeAWSRoleSession" + ) + except ClientError as e: + logger.exception( + "%s: Problem assuming role %s in account %s (Code: %s Message: %s)" % + (inspect.stack()[0][3], role, account, + e.response['Error']['Code'], e.response['Error']['Message']) + ) + if e.response['Error']['Code'] == 'AccessDenied': + self.status = {'STATUS': 'AccessDenied', 'FLAG': False} + return {} + else: + self.status = {'STATUS': 'ERROR', 'FLAG': False} + return {} + self.status = {'STATUS': 'SUCCESS', 'FLAG': True} + return assumed_role['Credentials'] + + def create_service_client(self, aws_service, account=None, role=None): + """ + Summary: + Creates the appropriate boto3 client for a particular AWS service + Args: + :type service: str + :param service: name of service at Amazon Web Services (AWS), + e.g. s3, ec2, etc + :type credentials: sts credentials object + :param credentials: authentication credentials to resource in AWS + :type role: str + :param role: IAM role designation used to access AWS resources + in an account + Returns: + Success | Failure, TYPE: bool + """ + try: + if role and account: # create client for a different AWS account + if self.status.get('STATUS') == 'SUCCESS': + client = boto3.client( + aws_service, + aws_access_key_id=self.credentials['AccessKeyId'], + aws_secret_access_key=self.credentials['SecretAccessKey'], + aws_session_token=self.credentials['SessionToken'] + ) + else: + self.status = {'STATUS': 'ERROR', 'FLAG': False} + return self.status + else: + return boto3.client(aws_service) # create client in the current AWS account + except ClientError as e: + logger.exception( + "%s: Problem creating %s client in account %s (Code: %s Message: %s)" % + (inspect.stack()[0][3], aws_service, self.account_number, + e.response['Error']['Code'], e.response['Error']['Message'])) + raise + return client diff --git a/build/lib/pyaws/core/loggers.py b/build/lib/pyaws/core/loggers.py new file mode 100644 index 0000000..1cdb94f --- /dev/null +++ b/build/lib/pyaws/core/loggers.py @@ -0,0 +1,22 @@ +""" +Example usage: + +>>> __version__ = '1.2.3' # Or wherever the version is stored +>>> logger = getLogger(__version__) +>>> logger.warn('omg what is happening') + - 1.2.3 - omg what is happening +""" +import logging + + + +def getLogger(*args, **kwargs): + """ custom format logger """ + logger = logging.getLogger(*args, **kwargs) + logger.propagate = False + if not logger.handlers: + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter('%(pathname)s - %(name)s - [%(levelname)s]: %(message)s')) + logger.addHandler(handler) + logger.setLevel(logging.DEBUG) + return logger diff --git a/build/lib/pyaws/core/oscodes_unix.py b/build/lib/pyaws/core/oscodes_unix.py new file mode 100644 index 0000000..4e4eaee --- /dev/null +++ b/build/lib/pyaws/core/oscodes_unix.py @@ -0,0 +1,121 @@ +""" +Standard OS Module Exit Codes + - See https://docs.python.org/3.6/library/os.html#process-management + +Module Attributes: + - exit_codes (dict): exit error codes for Unix, Linux + +""" + +import os + + +# --- exit codes for Unix, Linux, Linux-based operating systems ---------------- + + +exit_codes = { + 'EX_OK': { + 'Code': 0, + 'Reason': 'No error occurred' + }, + 'E_DEPENDENCY': { + 'Code': 1, + 'Reason': 'Missing required dependency' + }, + 'E_DIR': { + 'Code': 2, + 'Reason': 'Failure to create log dir, log file' + }, + 'E_ENVIRONMENT': { + 'Code': 3, + 'Reason': 'Incorrect shell, language interpreter, or operating system' + }, + 'EX_AWSCLI': { + 'Code': 4, + 'Reason': 'Value could not be determined from local awscli configuration' + }, + 'EX_NOPERM': { + 'Code': os.EX_NOPERM, + 'Reason': 'IAM user or role permissions do not allow this action' + }, + 'E_AUTHFAIL': { + 'Code': 5, + 'Reason': 'Authentication Fail' + }, + 'E_BADPROFILE': { + 'Code': 6, + 'Reason': 'Local profile variable not set or incorrect' + }, + 'E_USER_CANCEL': { + 'Code': 7, + 'Reason': 'User abort' + }, + 'E_BADARG': { + 'Code': 8, + 'Reason': 'Bad input parameter' + }, + 'E_EXPIRED_CREDS': { + 'Code': 9, + 'Reason': 'Credentials expired or otherwise no longer valid' + }, + 'E_MISC': { + 'Code': 9, + 'Reason': 'Unknown Error' + }, + 'EX_NOUSER': { + 'Code': os.EX_NOUSER, + 'Reason': 'specified user does not exist' + }, + 'EX_CONFIG': { + 'Code': os.EX_CONFIG, + 'Reason': 'Configuration or config parameter error' + }, + 'EX_CREATE_FAIL': { + 'Code': 21, + 'Reason': 'Keyset failed to create. Possible Permissions issue' + }, + 'EX_DELETE_FAIL': { + 'Code': 22, + 'Reason': 'Keyset failed to delete. Possible Permissions issue' + }, + 'EX_DATAERR': { + 'Code': os.EX_DATAERR, + 'Reason': 'Input data incorrect' + }, + 'EX_NOINPUT': { + 'Code': os.EX_NOINPUT, + 'Reason': 'Input file does not exist or not readable.' + }, + 'EX_UNAVAILABLE': { + 'Code': os.EX_UNAVAILABLE, + 'Reason': 'Required service or dependency unavailable.' + }, + 'EX_PROTOCOL': { + 'Code': os.EX_PROTOCOL, + 'Reason': 'Protocol exchange was illegal, invalid, or not understood.' + }, + 'EX_OSERR': { + 'Code': os.EX_OSERR, + 'Reason': 'Operating system error.' + }, + 'EX_OSFILE': { + 'Code': os.EX_OSFILE, + 'Reason': 'System file does not exist, or could not be opened' + }, + 'EX_IOERR': { + 'Code': os.EX_IOERR, + 'Reason': 'Exit code that means that an error occurred while doing I/O on some file.' + }, + 'EX_NOHOST': { + 'Code': os.EX_NOHOST, + 'Reason': 'Network host does not exist or not found' + }, + 'EX_SOFTWARE': { + 'Code': os.EX_SOFTWARE, + 'Reason': 'Internal software error detected.' + }, + 'EX_CANTCREAT': { + 'Code': os.EX_CANTCREAT, + 'Reason': 'User specified output file could not be created.' + } +} diff --git a/build/lib/pyaws/core/oscodes_win.py b/build/lib/pyaws/core/oscodes_win.py new file mode 100644 index 0000000..b2690a3 --- /dev/null +++ b/build/lib/pyaws/core/oscodes_win.py @@ -0,0 +1,80 @@ +""" +Standard OS Module Exit Codes + - See https://docs.python.org/3.6/library/os.html#process-management + +Module Attributes: + - exit_codes (dict): exist error codes for Microsoft Windows +""" + +import os + + +# --- exit codes for Microsoft Windows operating systems ----------------------- + + +exit_codes = { + 'EX_OK': { + 'Code': 0, + 'Reason': 'No error occurred' + }, + 'E_DEPENDENCY': { + 'Code': 1, + 'Reason': 'Missing required dependency' + }, + 'E_DIR': { + 'Code': 2, + 'Reason': 'Failure to create log dir, log file' + }, + 'E_ENVIRONMENT': { + 'Code': 3, + 'Reason': 'Incorrect shell, language interpreter, or operating system' + }, + 'EX_AWSCLI': { + 'Code': 4, + 'Reason': 'Value could not be determined from local awscli configuration' + }, + 'EX_NOPERM': { + 'Code': 77, + 'Reason': 'IAM user or role permissions do not allow this action' + }, + 'E_AUTHFAIL': { + 'Code': 5, + 'Reason': 'Authentication Fail' + }, + 'E_BADPROFILE': { + 'Code': 6, + 'Reason': 'Local profile variable not set or incorrect' + }, + 'E_USER_CANCEL': { + 'Code': 7, + 'Reason': 'User abort' + }, + 'E_BADARG': { + 'Code': 8, + 'Reason': 'Bad input parameter' + }, + 'E_EXPIRED_CREDS': { + 'Code': 9, + 'Reason': 'Credentials expired or otherwise no longer valid' + }, + 'E_MISC': { + 'Code': 9, + 'Reason': 'Unknown Error' + }, + 'EX_NOUSER': { + 'Code': 67, + 'Reason': 'specified user does not exist' + }, + 'EX_CONFIG': { + 'Code': 78, + 'Reason': 'Configuration or config parameter error' + }, + 'EX_CREATE_FAIL': { + 'Code': 21, + 'Reason': 'Keyset failed to create. Possible Permissions issue' + }, + 'EX_DELETE_FAIL': { + 'Code': 22, + 'Reason': 'Keyset failed to delete. Possible Permissions issue' + } +} diff --git a/build/lib/pyaws/dynamodb/__init__.py b/build/lib/pyaws/dynamodb/__init__.py new file mode 100644 index 0000000..29dd5fa --- /dev/null +++ b/build/lib/pyaws/dynamodb/__init__.py @@ -0,0 +1,4 @@ +""" +Functional Utilities for Amazon DynamoDB Database Service +""" +from pyaws.dynamodb.dynamodb import DynamoDBReader diff --git a/build/lib/pyaws/dynamodb/dynamodb.py b/build/lib/pyaws/dynamodb/dynamodb.py new file mode 100644 index 0000000..39258a9 --- /dev/null +++ b/build/lib/pyaws/dynamodb/dynamodb.py @@ -0,0 +1,137 @@ +""" +Summary: + Boto3 DynamoDB Reader Operations + +""" + +import boto3 +from botocore.exceptions import ClientError +from boto3.dynamodb.conditions import Key, Attr +from pyaws import logger + + +class DynamoDBReader(): + def __init__(self, aws_account_id, service_role, tablename, region): + """ + Reads DynamoDB table + """ + self.tablename = tablename + self.region = region + self.aws_account_id = aws_account_id + self.service_role = service_role + self.aws_credentials = self.assume_role(aws_account_id, service_role) + + + def boto_dynamodb_resource(self, region): + """ + Initiates boto resource to communicate with AWS API + """ + try: + dynamodb_resource = boto3.resource( + 'dynamodb', + aws_access_key_id=self.aws_credentials['AccessKeyId'], + aws_secret_access_key=self.aws_credentials['SecretAccessKey'], + aws_session_token=self.aws_credentials['SessionToken'], + region_name=region + ) + except ClientError as e: + logger.exception("Unknown problem creating boto3 resource (Code: %s Message: %s)" % + (e.response['Error']['Code'], e.response['Error']['Message'])) + return 1 + return dynamodb_resource + + def assume_role(self, aws_account_id, service_role): + """ + Summary. + + Assumes a DynamoDB role in 'destination' AWS account + + Args: + aws_account_id (str): 12 digit AWS Account number containing dynamodb table + service_role (str): IAM role dynamodb service containing permissions + allowing interaction with dynamodb + + Returns: + temporary credentials for service_role when assumed, TYPE: json + """ + session = boto3.Session() + sts_client = session.client('sts') + + try: + + # assume role in destination account + assumed_role = sts_client.assume_role( + RoleArn="arn:aws:iam::%s:role/%s" % (str(aws_account_id), service_role), + RoleSessionName="DynamoDBReaderSession" + ) + + except ClientError as e: + logger.exception( + "Couldn't assume role to read DynamoDB, account " + + str(aws_account_id) + " (switching role) (Code: %s Message: %s)" % + (e.response['Error']['Code'], e.response['Error']['Message'])) + raise e + return assumed_role['Credentials'] + + def query_dynamodb(self, partition_key, key_value): + """ + Queries DynamoDB table using partition key, + returns the item matching key value + """ + try: + resource_dynamodb = self.boto_dynamodb_resource(self.region) + table = resource_dynamodb.Table(self.tablename) + logger.info('Table %s: Table Item Count is: %s' % (table.table_name, table.item_count)) + + # query on parition key + response = table.query(KeyConditionExpression=Key(partition_key).eq(str(key_value))) + if response['Items']: + item = response['Items'][0]['Account Name'] + else: + item = 'unIdentified' + + except ClientError as e: + logger.exception("Couldn\'t query DynamoDB table (Code: %s Message: %s)" % + (e.response['Error']['Code'], e.response['Error']['Message'])) + return 1 + return item + + def scan_accounts(self, account_type): + """ + Read method for DynamoDB table + """ + + accounts, account_ids = [], [] + valid_mpc_pkgs = ['B', 'RA-PKG-B', 'RA-PKG-C', 'P', 'ATA', 'BUP', 'DXA'] + + types = [x.strip(' ') for x in account_type.split(',')] # parse types + + try: + resource_dynamodb = self.boto_dynamodb_resource(self.region) + table = resource_dynamodb.Table(self.tablename) + # scan table + if set(types).issubset(set(valid_mpc_pkgs)): + for type in types: + response = table.scan(FilterExpression=Attr('MPCPackage').eq(type)) + for account_dict in response['Items']: + accounts.append(account_dict) + elif types[0] == 'All': + # all valid_mpc_pkgs accounts (commercial accounts) + response = table.scan(FilterExpression=Attr('MPCPackage').ne("P")) + for account_dict in response['Items']: + accounts.append(account_dict) + + except ClientError as e: + logger.exception("Couldn\'t scan DynamoDB table (Code: %s Message: %s)" % + (e.response['Error']['Code'], e.response['Error']['Message'])) + return 1 + + if accounts: + for account in accounts: + account_info = {} + account_info['AccountName'] = account['Account Name'] + account_info['AccountId'] = account['Account ID'] + account_ids.append(account_info) + else: + logger.info('No items returned from DyanamoDB') + return account_ids diff --git a/build/lib/pyaws/dynamodb/table.py b/build/lib/pyaws/dynamodb/table.py new file mode 100644 index 0000000..b04a700 --- /dev/null +++ b/build/lib/pyaws/dynamodb/table.py @@ -0,0 +1,49 @@ + +import boto3 + +# Get the service resource. +dynamodb = boto3.resource('dynamodb') + + +def create_dynamodb_table(tablename, keys, attributes): + """ + Summary. + + Creates table with keys and attributes + + Args: + :keys (list): List of dictionaries specifying hash, range + :attributes (list): List of dictionaries specifying table attributes + + Returns: + table name (str) + """ + # Create the DynamoDB table. + table = dynamodb.create_table( + TableName='users', + KeySchema=[ + { + 'AttributeName': 'username', + 'KeyType': 'HASH' + }, + { + 'AttributeName': 'last_name', + 'KeyType': 'RANGE' + } + ], + AttributeDefinitions=[ + { + 'AttributeName': 'username', + 'AttributeType': 'S' + }, + { + 'AttributeName': 'last_name', + 'AttributeType': 'S' + }, + + ], + ProvisionedThroughput={ + 'ReadCapacityUnits': 5, + 'WriteCapacityUnits': 5 + } + ) diff --git a/build/lib/pyaws/ec2/__init__.py b/build/lib/pyaws/ec2/__init__.py new file mode 100644 index 0000000..9346627 --- /dev/null +++ b/build/lib/pyaws/ec2/__init__.py @@ -0,0 +1,5 @@ +""" +Functionality utilising Amazon EC2 Service +""" +from pyaws.ec2.state import running_instances, stopped_instances +from pyaws.ec2.ec2_utils import * diff --git a/build/lib/pyaws/ec2/ec2_utils.py b/build/lib/pyaws/ec2/ec2_utils.py new file mode 100644 index 0000000..673e855 --- /dev/null +++ b/build/lib/pyaws/ec2/ec2_utils.py @@ -0,0 +1,278 @@ +""" +Summary: + ec2_utils (python3) | Common EC2 functionality implemented by boto3 SDK + +Author: + Blake Huber + Copyright Blake Huber, All Rights Reserved. + +License: + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both the copyright notice and this permission notice appear in + supporting documentation + + Additional terms may be found in the complete license agreement located at: + https://bitbucket.org/blakeca00/lambda-library-python/src/master/LICENSE.md + +""" +import os +import subprocess +import inspect +import boto3 +from botocore.exceptions import ClientError +from pyaws.session import boto3_session +from pyaws.core import loggers +from pyaws._version import __version__ + + +# global objects +REGION = os.environ['AWS_DEFAULT_REGION'] + +# lambda custom log object +logger = loggers.getLogger(__version__) + + +# -- declarations ------------------------------------------------------------- + + +def default_region(profile='default'): + """ + Summary: + Determines the default region of profilename present in the local awscli + configuration or set in the environment via 'AWS_DEFAULT_REGION' variable. + If all else fails, returns region code 'us-east-1' as a default region. + Args: + profile (str): profile_name of a valid profile from local awscli config + Returns: + AWS Region Code, TYPE str + """ + + stderr = ' 2>/dev/null' + region = subprocess.check_output( + 'aws configure get profile.{profile}.region {stderr}'.format( + profile=profile, stderr=stderr)) + + try: + if region: + return region + elif os.getenv('AWS_DEFAULT_REGION') is None: + os.environ['AWS_DEFAULT_REGION'] = 'us-east-1' + except Exception as e: + logger.exception( + '{i}: Unknown error while interrogating local awscli config: {e}'.format( + i=inspect.stack()[0][3], e=e) + ) + raise + return os.getenv('AWS_DEFAULT_REGION') + + +def get_instances(region, profile=None, pageSize=100): + """ + Returns: all EC2 instance Ids in a region + """ + ids = [] + try: + if profile: + session = boto3.Session(profile_name=profile, region_name=region) + client = session.client('ec2') + else: + client = boto3.client('ec2', region_name=region) + + # find ebs volumes associated with instances + paginator = client.get_paginator('describe_instances') + response_iterator = paginator.paginate(PaginationConfig={'PageSize': pageSize}) + + # collect all instances in region + for page in response_iterator: + # collect all instanceIds (not used) + for x in [x[0]['InstanceId'] for x in [x['Instances'] for x in page['Reservations']]]: + ids.append(x) + + except ClientError as e: + logger.critical( + "%s: problem retrieving instances in region %s (Code: %s Message: %s)" % + (inspect.stack()[0][3], str(region), e.response['Error']['Code'], + e.response['Error']['Message'])) + raise e + return ids + + +def get_regions(profile=None): + """ Return list of all regions """ + try: + if profile is None: + profile = 'default' + client = boto3_session(service='ec2', profile=profile) + + except ClientError as e: + logger.exception( + '%s: Boto error while retrieving regions (%s)' % + (inspect.stack()[0][3], str(e))) + raise e + return [x['RegionName'] for x in client.describe_regions()['Regions']] + + +def dns_hostname(instanceId, profile='default'): + """ + Summary: + Reverse DNS for EC2 instances public or private subnets + Really only useful when EC2 instance assigned non-AWS DNS name + Args: + ip_info (dict): + { + "Association": { + "IpOwnerId": "102512488663", + "PublicDnsName": "ec2-34-247-23-51.eu-west-1.compute.amazonaws.com", + "PublicIp": "34.247.23.51" + }, + "Primary": true, + "PrivateDnsName": "ip-172-31-28-93.eu-west-1.compute.internal", + "PrivateIpAddress": "172.31.28.93" + } + Returns: + hostname (tuple): First element of the following tuple: + ( + 'ec2-34-247-23-51.eu-west-1.compute.amazonaws.com', + ['34.247.23.51'], + 'ip-172-31-28-93.eu-west-1.compute.internal', + ['172.31.28.93'] + ) + """ + try: + session = boto3.Session(profile_name=profile) + client = session.client('ec2', region_name=default_region(profile)) + r = client.describe_instances(InstanceIds=[instanceId]) + # dict of ip information + ip_info = [x['PrivateIpAddresses'][0] for x in r['Reservations'][0]['Instances'][0]['NetworkInterfaces']][0] + private_name = r['Reservations'][0]['Instances'][0]['PrivateDnsName'] + public_name = r['Reservations'][0]['Instances'][0]['PublicDnsName'] + + """BELOW NEEDS DEBUGGING + if ip_info.get('Association'): + public_ip = ip_info['Association']['PublicIp'] + priv_ip = ip_info['PrivateIpAddress'] + else: + public_ip = '' + priv_ip = ip_info['PrivateIpAddress'] + return ( + socket.gethostbyaddr(public_ip), + [public_ip], + socket.gethostbyaddr(priv_ip), + [priv_ip] + ) + """ + except KeyError as e: + logger.exception('%s: KeyError parsing ip info (%s)' % (inspect.stack()[0][3], str(e))) + return ('', [], '', []) + except ClientError as e: + logger.exception('%s: Boto Error parsing ip info (%s)' % (inspect.stack()[0][3], str(e))) + return '' + except Exception: + logger.exception( + '%s: No dns info from reverse lookup - Unknown host' % inspect.stack()[0][3]) + return ('', [], '', [ip_info['PrivateIpAddress']]) + return public_name or private_name + + +def get_attached_ids(region, instanceId, profile=None): + """ + Summary: + Audits the entire namespace of an AWS Account (essentially an + entire region) for resource ids of the type requested + Args: + instanceId (str): a single EC2 instance Identifier + pageSize (int): paging is used, + Returns: + vids (str): ebs volume ids attached to instanceId + enids (str): elastic network_interface ids attached to instanceId + Raises: + botocore ClientError + """ + vids, eids = [], [] + + if profile: + session = boto3.Session(profile_name=profile, region_name=region) + ec2 = session.resource('ec2') + else: + ec2 = boto3.resource('ec2', region_name=region) + + try: + logger.info('%s: function start' % inspect.stack()[0][3]) + + base = ec2.instances.filter(InstanceIds=[instanceId]) + + for instance in base: + # get volume ids + for vol in instance.volumes.all(): + vids.append(vol.id) + # get network interfaces + for eni in instance.network_interfaces: + eids.append(eni.id) + + logger.info( + '%d volume(s), %d ENIs found for instance %s' % + (len(vids), len(eids), instanceId) + ) + except ClientError as e: + logger.exception( + '%s: Problem while retrieving list of volumes for region %s' % + (inspect.stack()[0][3], region)) + return [], [] + return vids, eids + + +def namespace_volumes_eids(region, profile=None, pageSize=200): + """ + Summary: + - Audits the entire namespace of an AWS Account (essentially an + entire region) for resource ids of the type requested (with paging) + Args: + pageSize (int): paging is used, + Returns: + vids (str): ebs volume ids attached to instanceId + enids (str): elastic network_interface ids attached to instanceId + Raises: + botocore ClientError + """ + + vids, enids = [], [] + + try: + logger.info('%s: function start' % inspect.stack()[0][3]) + + if profile: + session = boto3.Session(profile_name=profile, region_name=region) + client = session.client('ec2') + else: + client = boto3.client('ec2', region_name=region) + + # find ebs volumes associated with instances + paginator = client.get_paginator('describe_volumes') + response_iterator = paginator.paginate(PaginationConfig={'PageSize': pageSize}) + + # collect all instances in region + for page in response_iterator: + # collect all instanceIds (not used) + for z in [y['VolumeId'] for y in [x['Attachments'][0] for x in page['Volumes']]]: + vids.append(z) + + logger.info('%d volume(s) found in region %s' % (len(vids), region)) + + # find enis + paginator = client.get_paginator('describe_network_interfaces') + response_iterator = paginator.paginate(PaginationConfig={'PageSize': pageSize}) + + # collect all instances in region + for page in response_iterator: + # collect all instanceIds (not used) + for z in [x['NetworkInterfaceId'] for x in page['NetworkInterfaces']]: + enids.append(z) + + except ClientError as e: + logger.exception( + '%s: Problem while retrieving list of volumes for region %s' % + (inspect.stack()[0][3], REGION)) + return [], [] + return vids, enids diff --git a/build/lib/pyaws/ec2/snapshot_ops.py b/build/lib/pyaws/ec2/snapshot_ops.py new file mode 100644 index 0000000..cae5601 --- /dev/null +++ b/build/lib/pyaws/ec2/snapshot_ops.py @@ -0,0 +1,228 @@ +""" +Summary: + ec2_utils (python3) | Common EC2 functionality implemented by boto3 SDK + +Author: + Blake Huber + Copyright Blake Huber, All Rights Reserved. + +License: + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both the copyright notice and this permission notice appear in + supporting documentation + + Additional terms may be found in the complete license agreement located at: + https://bitbucket.org/blakeca00/lambda-library-python/src/master/LICENSE.md + +""" +import os +import datetime +import inspect +import boto3 +from botocore.exceptions import ClientError +from pyaws import logd, __version__ + + +# global objects + + +# lambda custom log object +logger = logd.getLogger(__version__) + + +# -- declarations ------------------------------------------------------------- + + +def snapshot_metadata(snapshot_id, region, tags=False, profile=None): + """ + Creates EC2 Snapshot Object to extract detailed metadata + Returns: + metadata (TYPE dict) | contains snapshot attribute values + """ + metadata = {} + try: + if profile: + session = boto3.Session(profile_name=profile, region_name=region) + ec2 = session.resource('ec2') + else: + ec2 = boto3.resource('ec2', region_name=region) + snapshot_obj = ec2.Snapshot(snapshot_id) + metadata = { + 'SnapshotId': snapshot_id, + 'State': snapshot_obj.state, + 'Description': snapshot_obj.description, + 'Encrypted': snapshot_obj.encrypted, + 'KmsKeyId': snapshot_obj.kms_key_id, + 'VolumeId': snapshot_obj.volume_id, + 'VolumeSize': snapshot_obj.volume_size, + 'StartTime': snapshot_obj.start_time, + 'ProgressPct': snapshot_obj.progress.split('%')[0], + 'OwnerId': snapshot_obj.owner_id + } + if tags: + metadata['Tags'] = snapshot_obj.tags + except ClientError as e: + logger.critical( + "%s: problem retrieving metadata for snapshot %s (Code: %s Message: %s)" % + (inspect.stack()[0][3], snapshot_id, e.response['Error']['Code'], + e.response['Error']['Message'])) + raise e + return metadata + + +class SnapshotOperations(): + """ + Summary: + EC2 Snapshot Operations Class: List, Create, Delete, CreateVolume + """ + def __init__(self, region, profile=None): + self.snapshot_list = [] + if profile: + session = boto3.Session(profile_name=profile, region_name=region) + self.client = session.client('ec2') + self.sts_client = session.client('sts') + else: + self.client = boto3.client('ec2', region_name=region) + self.sts_client = boto3.client('sts', region_name=region) + self.acct_number = self.sts_client.get_caller_identity()['Account'] + + def list(self, account=None, volume_ids=None): + """ + Args: + AWS Account (str) + Returns: + List (TYPE list): all snapshots in region owned by account + Works for large numbers of snapshots (pagination) + """ + if not account: + account = self.acct_number + try: + paginator = self.client.get_paginator('describe_snapshots') + + if volume_ids: + response_iterator = paginator.paginate( + Filters=[ + { + 'Name': 'volume-id', + 'Values': volume_ids + }, + ], + PaginationConfig={'PageSize': 100} + ) + else: + response_iterator = paginator.paginate( + OwnerIds=[str(account)], + PaginationConfig={'PageSize': 100} + ) + # page thru, retrieve all snapshots + snapshot_ids, temp = [], [] + for page in response_iterator: + temp = [x['SnapshotId'] for x in page['Snapshots']] + for pageid in temp: + if pageid not in snapshot_ids: + snapshot_ids.append(pageid) + # persist at the instance level for future use + self.snapshot_list = [x for x in snapshot_ids] + except ClientError as e: + logger.critical( + "%s: Problem during snapshot retrieval operation (Code: %s Message: %s)" % + (inspect.stack()[0][3], e.response['Error']['Code'], + e.response['Error']['Message'])) + raise e + return snapshot_ids + + def create(self, volume_list): + """ + Returns: + :result (dict): {'SnapshotID': 'State'} + """ + result, snap_dict = [], {} + now = datetime.datetime.utcnow() + try: + for vol_id in volume_list: + now = now + datetime.timedelta(seconds=1) + description = 'Snap Date: ' + str(now.strftime("%Y-%m-%dT%H:%M:%SZ")) + r = self.client.create_snapshot( + VolumeId=vol_id, + Description=description + ) + logger.info( + 'Created snapshot %s from volume %s' % (r['SnapshotId'], vol_id) + ) + result.append( + {r['SnapshotId']: r['State'], 'Description': description} + ) + except ClientError as e: + logger.critical( + "%s: Problem creating snapshot from volume %s (Code: %s Message: %s)" % + (inspect.stack()[0][3], vol_id, e.response['Error']['Code'], + e.response['Error']['Message'])) + raise e + return result + + def delete(self, snapshot_list): + """ + Args: + snapshot_list (list): list of snapshot ids + Returns: + :result (dict): {'SnapshotID': 'State'} + """ + result = [] + try: + for snapshot_id in snapshot_list: + r = self.client.delete_snapshot(SnapshotId=snapshot_id) + logger.info('Deleted snapshot %s' % str(snapshot_id)) + result.append( + { + 'SnapshotId': snapshot_id, + 'HTTPStatusCode': r['ResponseMetadata']['HTTPStatusCode'] + } + ) + except ClientError as e: + logger.critical( + "%s: Problem deleting snapshot %s (Code: %s Message: %s)" % + (inspect.stack()[0][3], snapshot_id, e.response['Error']['Code'], + e.response['Error']['Message'])) + raise e + return result + + def create_volume(self, snapshot_id, az, vol_type='gp2', + size=None, encrypted=False, kms_key=''): + """ + Summary: + Creates new ebs volume from snapshot(s) + Args: + - snapshot_id (str, required) + - az (str, required): Availability Zone in which to create volume + - vol_type (str): 'standard', 'gp2', 'io2', + - size (str): Size in GB, required only if larger than orig volume size + - encrypted (bool) + - kms_key (str): KMS key id (required if encrypted = True) + Returns: + :result (dict) + """ + try: + r = self.client.create_volume( + SnapshotId=snapshot_id, + Size=size, + VolumeType=vol_type, + KmsKeyId=kms_key, + Encrypted=encrypted + ) + logger.info( + 'Creating volume %s from snapshot %s' % (r['VolumeId'], snapshot_id) + ) + except ClientError as e: + logger.critical( + "%s: Problem creating volume from snapshot %s (Code: %s Message: %s)" % + (inspect.stack()[0][3], snapshot_id, e.response['Error']['Code'], + e.response['Error']['Message'])) + raise e + return { + 'VolumeId': r['VolumeId'], + 'State': r['State'], + 'Source': snapshot_id, + 'Encrypted': encrypted + } diff --git a/build/lib/pyaws/ec2/state.py b/build/lib/pyaws/ec2/state.py new file mode 100644 index 0000000..17a68d3 --- /dev/null +++ b/build/lib/pyaws/ec2/state.py @@ -0,0 +1,107 @@ +""" +Summary: + ec2_utils (python3) | Common EC2 functionality implemented by boto3 SDK + +Author: + Blake Huber + Copyright Blake Huber, All Rights Reserved. + +License: + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both the copyright notice and this permission notice appear in + supporting documentation + + Additional terms may be found in the complete license agreement located at: + https://bitbucket.org/blakeca00/lambda-library-python/src/master/LICENSE.md + +""" +import os +import inspect +import boto3 +from botocore.exceptions import ClientError, ProfileNotFound +from pyaws.utils import stdout_message +from pyaws import logger + + +# global objects +REGION = os.environ['AWS_DEFAULT_REGION'] + + +# -- declarations ------------------------------------------------------------- + + +def running_instances(region, profile=None, ids=False, debug=False): + """ + Summary. + Determines state of all ec2 machines in a region + + Returns: + :running ec2 instances, TYPE: ec2 objects + OR + :running ec2 instance ids, TYPE: str + """ + try: + if profile and profile != 'default': + session = boto3.Session(profile_name=profile) + ec2 = session.resource('ec2', region_name=region) + else: + ec2 = boto3.resource('ec2', region_name=region) + + instances = ec2.instances.all() + + if ids: + return [x.id for x in instances if x.state['Name'] == 'running'] + + except ClientError as e: + logger.exception( + "%s: IAM user or role not found (Code: %s Message: %s)" % + (inspect.stack()[0][3], e.response['Error']['Code'], + e.response['Error']['Message'])) + raise + except ProfileNotFound: + msg = ( + '%s: The profile (%s) was not found in your local config' % + (inspect.stack()[0][3], profile)) + stdout_message(msg, 'FAIL') + logger.warning(msg) + return [x for x in instances if x.state['Name'] == 'running'] + + +def stopped_instances(region, profile=None, ids=False, debug=False): + """ + Summary. + Determines state of all ec2 machines in a region + + Returns: + :stopped ec2 instances, TYPE: ec2 objects + OR + :stopped ec2 instance ids, TYPE: str + + """ + try: + if profile and profile != 'default': + session = boto3.Session(profile_name=profile) + ec2 = session.resource('ec2', region_name=region) + else: + ec2 = boto3.resource('ec2', region_name=region) + + instances = ec2.instances.all() + + if ids: + return [x.id for x in instances if x.state['Name'] == 'stopped'] + + except ClientError as e: + logger.exception( + "%s: IAM user or role not found (Code: %s Message: %s)" % + (inspect.stack()[0][3], e.response['Error']['Code'], + e.response['Error']['Message'])) + raise + except ProfileNotFound: + msg = ( + '%s: The profile (%s) was not found in your local config' % + (inspect.stack()[0][3], profile)) + stdout_message(msg, 'FAIL') + logger.warning(msg) + return [x for x in instances if x.state['Name'] == 'stopped'] diff --git a/build/lib/pyaws/environment.py b/build/lib/pyaws/environment.py new file mode 100644 index 0000000..3d7522f --- /dev/null +++ b/build/lib/pyaws/environment.py @@ -0,0 +1,68 @@ +""" +Pretest Setup | pytest + + Calls set_environment() on module import +""" +import os +import subprocess +import inspect +import logging + + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + + +def awscli_region(profile_name): + """ + Summary: + Sets default AWS region + Args: + profile_name: a username in local awscli profile + Returns: + region (str): AWS region code | None + Raises: + Exception if profile_name not found in config + """ + awscli = 'aws' + + cmd = awscli + ' configure get ' + profile_name + '.region' + + try: + region = subprocess.check_output(cmd) + except Exception: + logger.exception( + '%s: Failed to identify AccessKeyId used in %s profile.' % + (inspect.stack()[0][3], profile_name)) + return None + return region + + +def set_default_region(profile=None): + """ + Sets AWS default region globally + """ + if os.getenv('AWS_DEFAULT_REGION'): + return os.getenv('AWS_DEFAULT_REGION') + elif profile is not None: + return awscli_region(profile_name=profile) + return awscli_region(profile_name='default') + + +def set_environment(): + """ + Sets global environment variables for testing + """ + # status + + logger.info('setting global environment variables') + + # set all env vars + os.environ['DBUGMODE'] = 'False' + os.environ['AWS_DEFAULT_REGION'] = set_default_region() or 'us-east-1' + + logger.info('AWS_DEFAULT_REGION determined as %s' % os.environ['AWS_DEFAULT_REGION']) + + +# execute on import +set_environment() diff --git a/build/lib/pyaws/helpers.py b/build/lib/pyaws/helpers.py new file mode 100644 index 0000000..44fee07 --- /dev/null +++ b/build/lib/pyaws/helpers.py @@ -0,0 +1,36 @@ +from contextlib import contextmanager +import inspect +import logging + +from botocore.exceptions import ClientError + +from pyaws import __version__ + +logger = logging.getLogger(__version__) + +@contextmanager +def handle_boto_error(message_template=None): + ''' + Context manager for wrapping a potentially-ClientError raising block of code + with a consistent error handler/reporter. + + Example usage: + + >>> from botocore.exceptions import ClientError + >>> with handle_boto_error(): + ... # do something that raises a ClientError + ... + + ''' + message_template = message_template or '{function}: boto3 error occured (Code: {code} Message: {message})' + try: + yield + except ClientError as e: + logger.exception( + message_template.format( + function=inspect.stack()[0][3], + code=e.response['Error']['Code'], + message=e.response['Error']['Message'] + ) + ) + raise \ No newline at end of file diff --git a/build/lib/pyaws/logd.py b/build/lib/pyaws/logd.py new file mode 100644 index 0000000..5c2cd53 --- /dev/null +++ b/build/lib/pyaws/logd.py @@ -0,0 +1,146 @@ +""" +Project-level logging module + +""" +import os +import sys +import inspect +import logging +import logging.handlers +from pathlib import Path +from pyaws.statics import local_config + + +syslog = logging.getLogger() +syslog.setLevel(logging.DEBUG) + + +def mode_assignment(arg): + """ + Translates arg to enforce proper assignment + """ + arg = arg.upper() + stream_args = ('STREAM', 'CONSOLE', 'STDOUT') + try: + if arg in stream_args: + return 'STREAM' + else: + return arg + except Exception: + return None + + +def logging_prep(mode): + """ + Summary: + prerequisites for log file generation + Return: + Success | Failure, TYPE: bool + """ + try: + if mode == 'FILE': + + log_path = local_config['LOGGING']['LOG_PATH'] + # path: path to log dir + path, log_dirname = os.path.split(log_path) + + if not os.path.exists(path): + os.makedirs(path) + + if not os.path.exists(log_path): + Path(log_path).touch(mode=0o644, exist_ok=True) + + except OSError as e: + syslog.exception( + '{i}: Failure while seeding log file path: {e}'.format( + i=inspect.stack()[0][3], e=e)) + return False + return True + + +def getLogger(*args, **kwargs): + """ + Summary: + custom format logger + + Args: + mode (str): The Logger module supprts the following log modes: + + - log to console / stdout. Log_mode = 'stream' + - log to file + - log to system logger (syslog) + + Returns: + logger object | TYPE: logging + """ + + log_mode = local_config['LOGGING']['LOG_MODE'] + + # log format - file + file_format = '%(asctime)s - %(pathname)s - %(name)s - [%(levelname)s]: %(message)s' + + # log format - stream + stream_format = '%(pathname)s - %(name)s - [%(levelname)s]: %(message)s' + + # log format - syslog + syslog_format = '- %(pathname)s - %(name)s - [%(levelname)s]: %(message)s' + # set facility for syslog: + if local_config['LOGGING']['SYSLOG_FILE']: + syslog_facility = 'local7' + else: + syslog_facility = 'user' + + # all formats + asctime_format = "%Y-%m-%d %H:%M:%S" + + # objects + logger = logging.getLogger(*args, **kwargs) + logger.propagate = False + + try: + if not logger.handlers: + # branch on output format, default to stream + if mode_assignment(log_mode) == 'FILE': + + # file handler + if logging_prep(mode_assignment(log_mode)): + + f_handler = logging.FileHandler(local_config['LOGGING']['LOG_PATH']) + f_formatter = logging.Formatter(file_format, asctime_format) + f_handler.setFormatter(f_formatter) + logger.addHandler(f_handler) + logger.setLevel(logging.DEBUG) + + else: + syslog.warning('{i}: Log preparation fail - exit'.format( + i=inspect.stack()[0][3])) + sys.exit(1) + + elif mode_assignment(log_mode) == 'STREAM': + # stream handlers + s_handler = logging.StreamHandler() + s_formatter = logging.Formatter(stream_format) + s_handler.setFormatter(s_formatter) + logger.addHandler(s_handler) + logger.setLevel(logging.DEBUG) + + elif mode_assignment(log_mode) == 'SYSLOG': + sys_handler = logging.handlers.SysLogHandler(address='/dev/log', facility=syslog_facility) + sys_formatter = logging.Formatter(syslog_format) + sys_handler.setFormatter(sys_formatter) + logger.addHandler(sys_handler) + logger.setLevel(logging.DEBUG) + + else: + syslog.warning( + '%s: [WARNING]: log_mode value of (%s) unrecognized - not supported' % + (inspect.stack()[0][3], str(log_mode)) + ) + ex = Exception( + '%s: Unsupported mode indicated by log_mode value: %s' % + (inspect.stack()[0][3], str(log_mode)) + ) + raise ex + except OSError as e: + raise e + return logger diff --git a/build/lib/pyaws/s3/__init__.py b/build/lib/pyaws/s3/__init__.py new file mode 100644 index 0000000..224436e --- /dev/null +++ b/build/lib/pyaws/s3/__init__.py @@ -0,0 +1,3 @@ +""" +Functionality utilising Amazon EC2 Service +""" diff --git a/build/lib/pyaws/s3/check_authenticated_s3.py b/build/lib/pyaws/s3/check_authenticated_s3.py new file mode 100644 index 0000000..b663b02 --- /dev/null +++ b/build/lib/pyaws/s3/check_authenticated_s3.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +def authenticated_s3(profile): + """ + Summary: + Tests authentication status to AWS Account using s3 + Args: + :profile (str): iam user name from local awscli configuration + Returns: + TYPE: bool, True (Authenticated)| False (Unauthenticated) + """ + try: + s3_client = boto3_session(service='s3', profile=profile) + httpstatus = s3_client.list_buckets()['ResponseMetadata']['HTTPStatusCode'] + if httpstatus == 200: + return True + except Exception: + return False + return False diff --git a/build/lib/pyaws/s3/object_operations.py b/build/lib/pyaws/s3/object_operations.py new file mode 100644 index 0000000..ad4f200 --- /dev/null +++ b/build/lib/pyaws/s3/object_operations.py @@ -0,0 +1,37 @@ +""" +Summary. + + S3 put object Class + +""" + +import boto3 +from botocore.exceptions import ClientError +from pyaws.session import boto3_session +from pyaws import logger + + +class S3FileOperations(): + """ + Summary. + + put, delete, put-acl object operations in Amazon S3 + """ + + def __init__(self, bucket, profile=None): + self.client = boto3_session(service='s3', profile_name=profile) + self.bucket = bucket + + def put_fileobject(self, key, file, bucket=self.bucket): + r = self.client.put_object( + Bucket=bucket, Key=key, Body=file + ) + return r + + def put_object_acl(self, key, acl, bucket=self.bucket): + r = self.client.put_object_acl( + Bucket=bucket, + Key=key, + ACL=acl + ) + return r diff --git a/build/lib/pyaws/script_utils.py b/build/lib/pyaws/script_utils.py new file mode 100644 index 0000000..f3ee674 --- /dev/null +++ b/build/lib/pyaws/script_utils.py @@ -0,0 +1,302 @@ +""" +Command-line Interface (CLI) Utilities Module + +Module Functions: + - get_os: + Retrieve localhost os type, ancillary environment specifics + - awscli_defaults: + determine awscli config file locations on localhost + - import_file_object: + import text filesystem object and convert to object + - export_json_object: + write a json object to a filesystem object + - read_local_config: + parse local config file +""" +import sys +import os +import json +import platform +import logging +import inspect +import distro +from pyaws._version import __version__ + +# globals +MODULE_VERSION = '1.16' +logger = logging.getLogger(__version__) +logger.setLevel(logging.INFO) + + +def debug_mode(header, data_object, debug=False, halt=False): + """ debug output """ + if debug: + print('\n ' + str(header) + '\n') + try: + print(json.dumps(data_object, indent=4)) + except Exception: + print(data_object) + if halt: + sys.exit(0) + return True + + +def awscli_defaults(os_type=None): + """ + Summary: + Parse, update local awscli config credentials + Args: + :user (str): USERNAME, only required when run on windows os + Returns: + TYPE: dict object containing key, value pairs describing + os information + """ + + try: + if os_type is None: + os_type = platform.system() + + if os_type == 'Linux': + HOME = os.environ['HOME'] + awscli_credentials = HOME + '/.aws/credentials' + awscli_config = HOME + '/.aws/config' + elif os_type == 'Windows': + username = os.getenv('username') + awscli_credentials = 'C:\\Users\\' + username + '\\.aws\\credentials' + awscli_config = 'C:\\Users\\' + username + '\\.aws\\config' + elif os_type == 'Java': + logger.warning('Unsupported OS. No information') + HOME = os.environ['HOME'] + awscli_credentials = HOME + '/.aws/credentials' + awscli_config = HOME + '/.aws/config' + alt_credentials = os.getenv('AWS_SHARED_CREDENTIALS_FILE') + except OSError as e: + logger.exception( + '%s: problem determining local os environment %s' % + (inspect.stack()[0][3], str(e)) + ) + raise e + return { + 'awscli_defaults': { + 'awscli_credentials': awscli_credentials, + 'awscli_config': awscli_config, + 'alt_credentials': alt_credentials + } + } + + +def config_init(config_file, json_config_obj, config_dirname=None): + """ + Summary: + Creates local config from JSON seed template + Args: + :config_file (str): filesystem object containing json dict of config values + :json_config_obj (json): data to be written to config_file + :config_dirname (str): dir name containing config_file + Returns: + TYPE: bool, Success | Failure + """ + from libtools.io import export_json_object + + HOME = os.environ['HOME'] + # client config dir + if config_dirname: + dir_path = HOME + '/' + config_dirname + if not os.path.exists(dir_path): + os.mkdir(dir_path) + os.chmod(dir_path, 0o755) + else: + dir_path = HOME + # client config file + r = export_json_object( + dict_obj=json_config_obj, + filename=dir_path + '/' + config_file + ) + return r + + +def get_os(detailed=False): + """ + Summary: + Retrieve local operating system environment characteristics + Args: + :user (str): USERNAME, only required when run on windows os + Returns: + TYPE: dict object containing key, value pairs describing + os information + """ + try: + + os_type = platform.system() + + if os_type == 'Linux': + os_detail = platform.platform() + distribution = ' '.join(distro.linux_distribution()[:2]) + HOME = os.getenv('HOME') + username = os.getenv('USER') + elif os_type == 'Windows': + os_detail = platform.platform() + username = os.getenv('username') or os.getenv('USER') + HOME = 'C:\\Users\\' + username + else: + logger.warning('Unsupported OS. No information') + os_type = 'Java' + os_detail = 'unknown' + HOME = os.getenv('HOME') + username = os.getenv('USER') + + except OSError as e: + raise e + except Exception as e: + logger.exception( + '%s: problem determining local os environment %s' % + (inspect.stack()[0][3], str(e)) + ) + if detailed and os_type == 'Linux': + return { + 'os_type': os_type, + 'os_detail': os_detail, + 'linux_distribution': distribution, + 'username': username, + 'HOME': HOME + } + elif detailed: + return { + 'os_type': os_type, + 'os_detail': os_detail, + 'username': username, + 'HOME': HOME + } + return {'os_type': os_type} + + +def import_file_object(filename): + """ + Summary: + Imports block filesystem object + Args: + :filename (str): block filesystem object + Returns: + dictionary obj (valid json file), file data object + """ + try: + handle = open(filename, 'r') + file_obj = handle.read() + dict_obj = json.loads(file_obj) + + except OSError as e: + logger.critical( + 'import_file_object: %s error opening %s' % (str(e), str(filename)) + ) + raise e + except ValueError: + logger.info( + '%s: import_file_object: %s not json. file object returned' % + (inspect.stack()[0][3], str(filename)) + ) + return file_obj # reg file, not valid json + return dict_obj + + +def json_integrity(baseline, suspect): + """ + Summary: + Validates baseline dict against suspect dict to ensure contain USERNAME + k,v parameters. + Args: + baseline (dict): baseline json structure + suspect (dict): json object validated against baseline structure + Returns: + Success (matches baseline) | Failure (no match), TYPE: bool + """ + try: + for k,v in baseline.items(): + for ks, vs in suspect.items(): + keys_baseline = set(v.keys()) + keys_suspect = set(vs.keys()) + intersect_keys = keys_baseline.intersection(keys_suspect) + added = keys_baseline - keys_suspect + rm = keys_suspect - keys_baseline + logger.info('keys added: %s, keys removed %s' % (str(added), str(rm))) + if keys_baseline != keys_suspect: + return False + except KeyError as e: + logger.info( + 'KeyError parsing pre-existing config (%s). Replacing config file' % + str(e)) + return True + + +def json_integrity_multilevel(d1, d2): + """ still under development """ + keys = [x for x in d2] + for key in keys: + d1_keys = set(d1.keys()) + d2_keys = set(d2.keys()) + intersect_keys = d1_keys.intersection(d2_keys) + added = d1_keys - d2_keys + removed = d2_keys - d1_keys + modified = {o : (d1[o], d2[o]) for o in intersect_keys if d1[o] != d2[o]} + same = set(o for o in intersect_keys if d1[o] == d2[o]) + if added == removed == set(): + d1_values = [x for x in d1.values()][0] + print('d1_values: ' + str(d1_values)) + d2_values = [x for x in d2.values()][0] + print('d2_values: ' + str(d2_values)) + length = len(d2_values) + print('length = %d' % length) + pdb.set_trace() + if length > 1: + d1 = d1_values.items() + d2 = d2_values.items() + else: + return False + return True + + +def read_local_config(cfg): + """ Parses local config file for override values + + Args: + :local_file (str): filename of local config file + + Returns: + dict object of values contained in local config file + """ + try: + if os.path.exists(cfg): + config = import_file_object(cfg) + return config + else: + logger.warning( + '%s: local config file (%s) not found, cannot be read' % + (inspect.stack()[0][3], str(cfg))) + except OSError as e: + logger.warning( + 'import_file_object: %s error opening %s' % (str(e), str(cfg)) + ) + return {} + + +def os_parityPath(path): + """ + Converts unix paths to correct windows equivalents. + Unix native paths remain unchanged (no effect) + """ + path = os.path.normpath(os.path.expanduser(path)) + if path.startswith('\\'): + return 'C:' + path + return path + + +def directory_contents(directory): + """Returns full paths of all file objects contained in a directory""" + filepaths = [] + try: + for dirpath,_,filenames in os.walk(directory): + for f in filenames: + filepaths.append(os.path.abspath(os.path.join(dirpath, f))) + except OSError as e: + logger.exception( + '{}: Problem walking directory contents: {}'.format(inspect.stack()[0][3], e)) + return filepaths diff --git a/build/lib/pyaws/session.py b/build/lib/pyaws/session.py new file mode 100644 index 0000000..45c2871 --- /dev/null +++ b/build/lib/pyaws/session.py @@ -0,0 +1,208 @@ +import os +import inspect +import subprocess +import boto3 +from botocore.exceptions import ClientError, ProfileNotFound +from pyaws.utils import stdout_message +from pyaws import logger + +try: + from pyaws.core.oscodes_unix import exit_codes + splitchar = '/' # character for splitting paths (linux) +except Exception: + from pyaws.core.oscodes_win import exit_codes # non-specific os-safe codes + splitchar = '\\' # character for splitting paths (window + + +DEFAULT_REGION = os.environ['AWS_DEFAULT_REGION'] or 'us-east-1' + + +def _profile_prefix(profile, prefix='gcreds'): + """ + Summary: + Determines if temporary STS credentials provided via + local awscli config; + - if yes, returns profile with correct prefix + - if no, returns profile (profile_name) unaltered + - Note: Caller is parse_profiles(), Not to be called directly + Args: + profile (str): profile_name of a valid profile from local awscli config + prefix (str): prefix prepended to profile containing STS temporary credentials + Returns: + awscli profilename, TYPE str + """ + + stderr = ' 2>/dev/null' + tempProfile = prefix + '-' + profile + + try: + if subprocess.check_output( + 'aws configure get profile.{profile}.aws_access_key_id {stderr}'.format( + profile=profile, stderr=stderr)): + return profile + elif subprocess.check_output( + 'aws configure get profile.{tempProfile}.aws_access_key_id {stderr}'.format( + tempProfile=tempProfile, stderr=stderr)): + return tempProfile + except Exception as e: + logger.exception( + '{i}: Unknown error while interrogating local awscli config: {e}'.format( + i=inspect.stack()[0][3], e=e)) + raise + return None + + +def parse_profiles(profiles): + """ + Summary: + Parse awscli profile_names given as parameter in 1 of 2 forms: + 1. single profilename given + 2. list of profile_names + Also, function prepends profile_name(s) with a prefix when it detects + profile_name refers to temp credentials in the local awscli configuration + Args: + profiles (str or file): Profiles parameter can be either: + - a single profile_name (str) + - a file containing multiple profile_names, 1 per line + Returns: + - list of awscli profilenames, TYPE: list + OR + - single profilename, TYPE: str + """ + + profile_list = [] + + try: + if isinstance(profiles, list): + return [_profile_prefix(x.strip()) for x in profiles] + elif os.path.isfile(profiles): + with open(profiles) as f1: + for line in f1: + profile_list.append(_profile_prefix(line.strip())) + else: + return _profile_prefix(profiles.strip()) + except Exception as e: + logger.exception( + '{i}: Unknown error while converting profile_names from local awscli config: {e}'.format( + i=inspect.stack()[0][3], e=e)) + raise + return profile_list + + +def boto3_session(service, region=DEFAULT_REGION, profile=None): + """ + Summary: + Establishes boto3 sessions, client + Args: + :service (str): boto3 service abbreviation ('ec2', 's3', etc) + :profile (str): profile_name of an iam user from local awscli config + :region (str): AWS region code, optional + + Returns: + client (boto3 object) + + """ + fx = inspect.stack()[0][3] + + try: + + if (not profile or profile == 'default') and service != 'iam': + return boto3.client(service, region_name=region) + + elif (not profile or profile == 'default') and service == 'iam': + return boto3.client(service) + + elif profile and profile != 'default': + session = boto3.Session(profile_name=profile) + return session.client(service, region_name=region) + + except ClientError as e: + if e.response['Error']['Code'] == 'InvalidClientTokenId': + logger.warning( + '{}: Invalid credentials used by profile user {}'.format(fx, profile or 'default') + ) + + elif e.response['Error']['Code'] == 'ExpiredToken': + logger.info( + '%s: Expired temporary credentials detected for profile user (%s) [Code: %d]' + % (fx, profile, exit_codes['EX_CONFIG']['Code'])) + + except ProfileNotFound: + msg = ('{}: Profile name {} was not found in your local config.'.format(fx, profile)) + stdout_message(msg, 'WARN') + logger.warning(msg) + return None + return boto3.client(service, region_name=region) + + +def authenticated(profile): + """ + Tests generic authentication status to AWS Account + + Args: + :profile (str): iam user name from local awscli configuration + + Returns: + TYPE: bool, True (Authenticated)| False (Unauthenticated) + + """ + try: + sts_client = boto3_session(service='sts', profile=profile) + httpstatus = sts_client.get_caller_identity()['ResponseMetadata']['HTTPStatusCode'] + if httpstatus == 200: + return True + + except ClientError as e: + if e.response['Error']['Code'] == 'InvalidClientTokenId': + logger.warning( + '%s: Invalid credentials to authenticate for profile user (%s). Exit. [Code: %d]' + % (inspect.stack()[0][3], profile, exit_codes['EX_NOPERM']['Code'])) + + elif e.response['Error']['Code'] == 'ExpiredToken': + logger.info( + '%s: Expired temporary credentials detected for profile user (%s) [Code: %d]' + % (inspect.stack()[0][3], profile, exit_codes['EX_CONFIG']['Code'])) + + else: + logger.exception( + '%s: Unknown Boto3 problem. Error: %s' % + (inspect.stack()[0][3], e.response['Error']['Message'])) + except Exception as e: + fx = inspect.stack()[0][3] + logger.exception('{}: Unknown error: {}'.format(fx, e)) + return False + return False + + +def client_wrapper(service, profile='default', region=DEFAULT_REGION): + """ + Summary. + + Single caller boto3 service wrapper. Instantiates client object while + using temporary credientials for profile_name, if available in + local configuration. Tests authentication prior to returning any + client object. + + Args: + :service (str): boto3 service abbreviation ('ec2', 's3', etc) + :profile (str): profile_name of an iam user from local awscli config + :region (str): AWS region code, optional + + Returns: + client (boto3 object) + + """ + profile_name = _profile_prefix(profile) + + try: + + if authenticated(profile_name): + return boto3_session(service=service, profile=profile_name, region=region) + + except ClientError as e: + logger.exception( + "%s: Unknown boto3 failure while establishing session (Code: %s Message: %s)" % + (inspect.stack()[0][3], e.response['Error']['Code'], + e.response['Error']['Message']) + ) + return None diff --git a/build/lib/pyaws/statics.py b/build/lib/pyaws/statics.py new file mode 100644 index 0000000..1d07077 --- /dev/null +++ b/build/lib/pyaws/statics.py @@ -0,0 +1,71 @@ +""" +Summary: + pyaws Project-level Defaults and Settings + + - **Local Default Settings**: Local defaults for your specific installation are derived from settings found in: + +Module Attributes: + - user_home (TYPE str): + $HOME environment variable, present for most Unix and Unix-like POSIX systems + - config_dir (TYPE str): + directory name default for stsaval config files (.stsaval) + - config_path (TYPE str): + default for stsaval config files, includes config_dir (~/.stsaval) +""" +import os +import inspect +import logging +from pyaws.script_utils import get_os, os_parityPath +from pyaws._version import __version__ + +logger = logging.getLogger(__version__) +logger.setLevel(logging.INFO) + + +# -- project-level DEFAULTS ------------------------------------------------ + + +try: + + env_info = get_os(detailed=True) + OS = env_info['os_type'] + user_home = env_info['HOME'] + + if user_home is None: + user_home = os.getenv('HOME') or '/tmp' + +except KeyError as e: + logger.critical( + '%s: %s variable is required and not found in the environment' % + (inspect.stack()[0][3], str(e))) + raise e + +else: + # project + PACKAGE = 'pyaws' + LICENSE = 'MIT' + LICENSE_DESC = 'MIT' + + # logging parameters + enable_logging = True + log_mode = 'STREAM' # log to cloudwatch logs + log_filename = PACKAGE + '.log' + log_dir = os_parityPath(user_home + '/logs') + log_path = os_parityPath(log_dir + '/' + log_filename) + + local_config = { + "PROJECT": { + "PACKAGE": PACKAGE, + "CONFIG_VERSION": __version__, + "HOME": user_home, + + }, + "LOGGING": { + "ENABLE_LOGGING": enable_logging, + "LOG_FILENAME": log_filename, + "LOG_DIR": log_dir, + "LOG_PATH": log_path, + "LOG_MODE": log_mode, + "SYSLOG_FILE": False + } + } diff --git a/build/lib/pyaws/sts/__init__.py b/build/lib/pyaws/sts/__init__.py new file mode 100644 index 0000000..224436e --- /dev/null +++ b/build/lib/pyaws/sts/__init__.py @@ -0,0 +1,3 @@ +""" +Functionality utilising Amazon EC2 Service +""" diff --git a/build/lib/pyaws/sts/cross_account_utils.py b/build/lib/pyaws/sts/cross_account_utils.py new file mode 100644 index 0000000..d5e7c4d --- /dev/null +++ b/build/lib/pyaws/sts/cross_account_utils.py @@ -0,0 +1,123 @@ +""" + +cross_account_utils (python3) + + Common Secure Token Service (STS) functionality for use + with AWS' Lambda Service + +Author: + Blake Huber + Copyright Blake Huber, All Rights Reserved. + +License: + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both the copyright notice and this permission notice appear in + supporting documentation + + Additional terms may be found in the complete license agreement: + https://bitbucket.org/blakeca00/lambda-library-python/src/master/LICENSE.md + +""" + +import inspect +import boto3 +from botocore.exceptions import ClientError +import loggers +from _version import __version__ + + +# lambda custom log object +logger = loggers.getLogger(__version__) + + +class AssumeAWSRole(): + """ class def for assuming roles in AWS """ + def __init__(self, account, role_name, profile=None): + self.role = role_name + self.account_number = str(account) + self.profile = profile + self.credentials = self.assume_role(role_name, self.account_number) + self.status = {'STATUS': ''} + + def assume_role(self, account, role): + """ + Summary: + Assumes a DynamoDB role in 'destination' AWS account + Args: + :type account: str + :param account: AWS account number + :type role: str + :param role: IAM role designation used to access AWS resources + in an account + :type profile: str + :param role: profile_name is an IAM user or IAM role name represented + in the local awscli configuration as a profile entry + Returns: dict (Credentials) + """ + if self.profile: + session = boto3.Session(profile_name=self.profile) + else: + session = boto3.Session() + sts_client = session.client('sts') + + try: + # assume role in destination account + assumed_role = sts_client.assume_role( + RoleArn="arn:aws:iam::%s:role/%s" % (account, role), + RoleSessionName="AssumeAWSRoleSession" + ) + self.status = {'STATUS': 'SUCCESS'} + except ClientError as e: + if e.response['Error']['Code'] == 'AccessDenied': + self.status = {'STATUS': 'AccessDenied'} + return {} + else: + logger.exception("Couldn't assume role %s in account %s (Code: %s Message: %s)" % + (self.role, self.account_number, e.response['Error']['Code'], + e.response['Error']['Message'])) + self.status = {'STATUS': 'ERROR'} + return {} + return assumed_role['Credentials'] + + def create_service_client(self, aws_service, credentials=None): + """ + Summary: + Creates the appropriate boto3 client for a particular AWS service + Args: + :type service: str + :param service: name of service at Amazon Web Services (AWS), + e.g. s3, ec2, etc + :type credentials: sts credentials object + :param credentials: authentication credentials to resource in AWS + :type role: str + :param role: IAM role designation used to access AWS resources + in an account + Returns: + Success | Failure, TYPE: bool + """ + try: + if role and account: # create client for a different AWS account + if self.status.get('STATUS') == 'SUCCESS': + client = boto3.client( + aws_service, + aws_access_key_id=self.credentials['AccessKeyId'], + aws_secret_access_key=self.credentials['SecretAccessKey'], + aws_session_token=self.credentials['SessionToken'] + ) + else: + logger.critical('failed to create client using AssumeAWSRole') + raise ClientError( + '%s: Problem creating client by assuming role %s in account %s' % + (inspect.stack()[0][3], str(role), str(account)) + ) + else: + return boto3.client(aws_service) # create client in the current AWS account + except ClientError as e: + logger.exception( + "%s: Problem creating %s client in account %s (Code: %s Message: %s)" % + (inspect.stack()[0][3], aws_service, self.account_number, + e.response['Error']['Code'], e.response['Error']['Message'])) + raise + return client diff --git a/build/lib/pyaws/tags/__init__.py b/build/lib/pyaws/tags/__init__.py new file mode 100644 index 0000000..db6bd29 --- /dev/null +++ b/build/lib/pyaws/tags/__init__.py @@ -0,0 +1,4 @@ +""" +Functional Utilities for working with Amazon Web Services' Resource Tags +""" +from pyaws.tags.tag_utils import * diff --git a/build/lib/pyaws/tags/bulk-modify-tags.py b/build/lib/pyaws/tags/bulk-modify-tags.py new file mode 100644 index 0000000..987e359 --- /dev/null +++ b/build/lib/pyaws/tags/bulk-modify-tags.py @@ -0,0 +1,334 @@ +#!/usr/bin/env python3 + +""" +Summary: + Script to copy tags on all EC2 instances in a region to their respective + EBS Volumes while eliminating or retaining certain tags as specified + +Args: + profiles (list): awscli profile roles. Denotes accounts in which to run + regions (list): AWS region codes + DEBUGMODE (bool): don't change tags, but print out tags to be copied + SUMMARY_REPORT (bool): gen summary report only + logger (logging object): logger + +Author: + Blake Huber, copyright 2017 + + (although it's hard to think someone would be desperate enough to rip + off such hastily written crap code. At some pt I'll clean it up, meanwhile + Use at your own risk) +""" + +import sys +import argparse +import inspect +import datetime +from time import sleep +import boto3 +from botocore.exceptions import ClientError +from libtools.io import export_json_object +from libtools import stdout_message +from pyaws import exit_codes, logger, local_config + + +DEBUGMODE = True # will not retag any resources +SUMMARY_REPORT = True # print summary report only +VALID_TYPES = ['instances', 'volumes', 'display'] + +regions = ['ap-southeast-1', 'eu-west-1', 'us-east-1'] + +profiles = [ + 'gcreds-phht-gen-ra1-pr', + 'gcreds-phht-gen-ra2-pr', + 'gcreds-phht-gen-ra3-pr', + 'gcreds-phht-gen-ra4-pr', + 'gcreds-phht-gen-ra5-pr', +] + +profiles = ['gcreds-phht-gen-ra3-pr'] + +# tags - to remove +TAGKEY_BACKUP = 'MPC-AWS-BACKUP' +TAGKEY_CPM = 'cpm backup' +TAGKEY_SNOW_CPM = 'MPC-SN-BACKUP' +NETWORKER = 'networker backup' +NAME = 'Name' + +# tags we should not copy from the ec2 instance to the ebs volume +NO_COPY_LIST = [TAGKEY_BACKUP, TAGKEY_CPM, TAGKEY_SNOW_CPM, NETWORKER, NAME] + +# tags on ebs volumes to preserve and ensure we do not overwrite or rm +PRESERVE_TAGS = ['Name'] + + +# -- declarations ------------------------------------------------------------- + + +def filter_tags(tag_list, *args): + """ + - Filters a tag set by exclusion + - variable tag keys given as parameters, tag keys corresponding to args + are excluded + + RETURNS + TYPE: list + """ + clean = tag_list.copy() + for tag in tag_list: + for arg in args: + if arg == tag['Key']: + clean.remove(tag) + return clean + + +def valid_tags(tag_list): + """ checks tags for invalid chars """ + for tag in tag_list: + if ':' in tag['Key']: + return False + return True + + +def pretty_print_tags(tag_list): + """ prints json tags with syntax highlighting """ + export_json_object(tag_list) + print('\n') + return + + +def select_tags(tag_list, key_list): + """ + Return selected tags from a list of many tags given a tag key + """ + select_list = [] + # select tags by keys in key_list + for tag in tag_list: + for key in key_list: + if key == tag['Key']: + select_list.append(tag) + # ensure only tag-appropriate k,v pairs in list + return [{'Key': x['Key'], 'Value': x['Value']} for x in select_list] + + +def get_instances(profile, rgn): + """ returns all EC2 instance Ids in a region """ + vm_ids = [] + session = boto3.Session(profile_name=profile, region_name=rgn) + client = session.client('ec2') + r = client.describe_instances() + for detail in [x['Instances'] for x in r['Reservations']]: + for instance in detail: + vm_ids.append(instance['InstanceId']) + return vm_ids + + +def get_volumes(profile, rgn): + """ + returns all EC2 volume Ids in a region + """ + session = boto3.Session(profile_name=profile, region_name=rgn) + client = session.client('ec2') + return [x['VolumeId'] for x in client.describe_volumes()['Volumes']] + + +def calc_runtime(start, end): + """ Calculates job runtime given start, end datetime stamps + Args: + - start (datetime object): job start timestamp + - end (datetime object): job end timestamp + """ + duration = end - start + if (duration.seconds / 60) < 1: + return (duration.seconds), 'seconds' + else: + return (duration.seconds / 60), 'minutes' + + +def display_valid(print_object): + """ + Help Function | Displays an attribute of this program + """ + if print_object in ('list', 'print', 'display'): + print('\n' + VALID_TYPES + '\n') + return True + + +def options(parser): + """ + Summary: + parse cli parameter options + Returns: + TYPE: argparse object, parser argument set + """ + parser.add_argument("-p", "--profile", nargs='?', default="default", + required=False, help="type (default: %(default)s)") + parser.add_argument("-t", "--type", nargs='?', default='list', type=str, + choices=VALID_TYPES, required=False) + parser.add_argument("-a", "--auto", dest='auto', action='store_true', required=False) + parser.add_argument("-d", "--debug", dest='debug', action='store_true', required=False) + parser.add_argument("-V", "--version", dest='version', action='store_true', required=False) + return parser.parse_args() + + +# -- main --------------------------------------------------------------------- + + +def main(): + """ copies ec2 instance tags to attached resources """ + for profile in profiles: + # derive account alias from profile + account = '-'.join(profile.split('-')[1:]) + for region in regions: + #instances = [] + session = boto3.Session(profile_name=profile, region_name=region) + client = session.client('ec2') + ec2 = session.resource('ec2') + instances = get_instances(profile, region) + volumes = get_volumes(profile, region) + # print summary + if SUMMARY_REPORT: + print('\nFor AWS Account %s, region %s, Found %d Instances\n' % (account, region, len(instances))) + continue + # copy tags + if instances: + try: + base = ec2.instances.filter(InstanceIds=instances) + ct = 0 + for instance in base: + ids, after_tags = [], [] + ct += 1 + if instance.tags: + # filter out tags to prohibited from copy + filtered_tags = filter_tags(instance.tags, *NO_COPY_LIST) + else: + # no tags on instance to copy + continue + + if not valid_tags(filtered_tags): + print('\nWARNING:') + logger.warning('Skipping instance ID %s, Invalid Tags\n' % instance.id) + continue + # collect attached resource ids to be tagged + for vol in instance.volumes.all(): + ids.append(vol.id) + for eni in instance.network_interfaces: + ids.append(eni.id) + logger.info('InstanceID %s, instance %d of %d:' % (instance.id, ct, len(instances))) + logger.info('Resource Ids to tag is:') + logger.info(str(ids) + '\n') + if DEBUGMODE: + # BEFORE tag copy + logger.info('BEFORE list of %d tags is:' % (len(instance.tags))) + pretty_print_tags(instance.tags) + + # AFTER tag copy | put Name tag back into apply tags, ie, after_tags + retain_tags = select_tags(instance.tags, PRESERVE_TAGS) + all_tags = retain_tags + filtered_tags + for tag in all_tags: + after_tags.append(tag) + logger.info('For InstanceID %s, the AFTER FILTERING list of %d tags is:' % (instance.id, len(after_tags))) + logger.info('Tags to apply are:') + pretty_print_tags(after_tags) + else: + logger.info('InstanceID %s, instance %d of %d:' % (instance.id, ct, len(instances))) + if filtered_tags: # we must have something to apply + # apply tags + for resourceId in ids: + # retain a copy of tags to preserve if is a volume + if resourceId.startswith('vol-'): + r = client.describe_tags( + Filters=[{ + 'Name': 'resource-id', + 'Values': [resourceId], + }, + ] + ) + retain_tags = select_tags(r['Tags'], PRESERVE_TAGS) + # add retained tags before appling to volume + if retain_tags: + for tag in retain_tags: + filtered_tags.append(tag) + # clear tags + print('\n') + logger.info('Clearing tags on resource: %s' % str(resourceId)) + client.delete_tags(Resources=[resourceId], Tags=[]) + + # create new tags + logger.info('Applying tags to resource %s\n' % resourceId) + ec2.create_tags(Resources=[resourceId], Tags=filtered_tags) + # delay to throttle API requests + sleep(1) + + except ClientError as e: + logger.exception( + "%s: Problem (Code: %s Message: %s)" % + (inspect.stack()[0][3], e.response['Error']['Code'], + e.response['Error']['Message']) + ) + raise + + +def init_cli(): + # parser = argparse.ArgumentParser(add_help=False, usage=help_menu()) + parser = argparse.ArgumentParser(add_help=False) + + try: + args = options(parser) + except Exception as e: + help_menu() + stdout_message(str(e), 'ERROR') + sys.exit(exit_codes['EX_OK']['Code']) + + if len(sys.argv) == 1: + help_menu() + sys.exit(exit_codes['EX_OK']['Code']) + + elif args.help: + help_menu() + sys.exit(exit_codes['EX_OK']['Code']) + + elif args.version: + package_version() + + elif args.configure: + r = option_configure(args.debug, local_config['PROJECT']['CONFIG_PATH']) + return r + else: + if precheck(): # if prereqs set, run + if authenticated(profile=args.profile): + # execute keyset operation + success = main( + operation=args.operation, + profile=args.profile, + user_name=args.username, + auto=args.auto, + debug=args.debug + ) + if success: + logger.info('IAM access keyset operation complete') + sys.exit(exit_codes['EX_OK']['Code']) + else: + stdout_message( + 'Authenication Failed to AWS Account for user %s' % args.profile, + prefix='AUTH', + severity='WARNING' + ) + sys.exit(exit_codes['E_AUTHFAIL']['Code']) + + failure = """ : Check of runtime parameters failed for unknown reason. + Please ensure local awscli is configured. Then run keyconfig to + configure keyup runtime parameters. Exiting. Code: """ + logger.warning(failure + 'Exit. Code: %s' % sys.exit(exit_codes['E_MISC']['Code'])) + print(failure) + + +if __name__ == '__main__': + start_time = datetime.datetime.now() + main() + end_time = datetime.datetime.now() + duration, unit = calc_runtime(start_time, end_time) + logger.info('Job Start: %s' % start_time.isoformat()) + logger.info('Job End: %s' % end_time.isoformat()) + logger.info('Job Completed. Duration: %d %s' % (duration, unit)) + sys.exit(0) diff --git a/build/lib/pyaws/tags/copy-tags-all-instances.py b/build/lib/pyaws/tags/copy-tags-all-instances.py new file mode 100644 index 0000000..8538d44 --- /dev/null +++ b/build/lib/pyaws/tags/copy-tags-all-instances.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 + +""" +Summary: + Script to copy tags on all EC2 instances in a region to their respective + EBS Volumes while eliminating or retaining certain tags as specified + +Args: + profiles (list): awscli profile roles. Denotes accounts in which to run + regions (list): AWS region codes + DEBUGMODE (bool): don't change tags, but print out tags to be copied + SUMMARY_REPORT (bool): gen summary report only + logger (logging object): logger + +Author: + Blake Huber, copyright 2017 + + (although it's hard to think someone would be desperate enough to rip + off such hastily written crap code. At some pt I'll clean it up, meanwhile + Use at your own risk) +""" + +import sys +import loggers +import inspect +import datetime +from time import sleep +from libtools.js import export_json_object +import boto3 +from botocore.exceptions import ClientError + +logger = loggers.getLogger('1.0') +DEBUGMODE = True # will not retag any resources +SUMMARY_REPORT = False # print summary report only + +regions = ['ap-southeast-1', 'eu-west-1', 'us-east-1'] + +profiles = [ + 'gcreds-phht-gen-ra1-pr', + 'gcreds-phht-gen-ra2-pr', + 'gcreds-phht-gen-ra3-pr', + 'gcreds-phht-gen-ra4-pr', + 'gcreds-phht-gen-ra5-pr', +] + +profiles = ['gcreds-phht-gen-ra3-pr'] + +# tags - to remove +TAGKEY_BACKUP = 'MPC-AWS-BACKUP' +TAGKEY_CPM = 'cpm backup' +TAGKEY_SNOW_CPM = 'MPC-SN-BACKUP' +NETWORKER = 'networker backup' +NAME = 'Name' + +# tags we should not copy from the ec2 instance to the ebs volume +NO_COPY_LIST = [TAGKEY_BACKUP, TAGKEY_CPM, TAGKEY_SNOW_CPM, NETWORKER, NAME] + +# tags on ebs volumes to preserve and ensure we do not overwrite or rm +PRESERVE_TAGS = ['Name'] + +# -- declarations ------------------------------------------------------------- + + +def filter_tags(tag_list, *args): + """ + - Filters a tag set by exclusion + - variable tag keys given as parameters, tag keys corresponding to args + are excluded + + RETURNS + TYPE: list + """ + clean = tag_list.copy() + for tag in tag_list: + for arg in args: + if arg == tag['Key']: + clean.remove(tag) + return clean + + +def valid_tags(tag_list): + """ checks tags for invalid chars """ + for tag in tag_list: + if ':' in tag['Key']: + return False + return True + + +def pretty_print_tags(tag_list): + """ prints json tags with syntax highlighting """ + export_json_object(tag_list) + print('\n') + return + + +def select_tags(tag_list, key_list): + """ + Return selected tags from a list of many tags given a tag key + """ + select_list = [] + # select tags by keys in key_list + for tag in tag_list: + for key in key_list: + if key == tag['Key']: + select_list.append(tag) + # ensure only tag-appropriate k,v pairs in list + clean = [{'Key': x['Key'], 'Value': x['Value']} for x in select_list] + return clean + + +def get_instances(profile, rgn): + """ returns all EC2 instance Ids in a region """ + vm_ids = [] + session = boto3.Session(profile_name=profile, region_name=rgn) + client = session.client('ec2') + r = client.describe_instances() + for detail in [x['Instances'] for x in r['Reservations']]: + for instance in detail: + vm_ids.append(instance['InstanceId']) + return vm_ids + + +def calc_runtime(start, end): + """ Calculates job runtime given start, end datetime stamps + Args: + - start (datetime object): job start timestamp + - end (datetime object): job end timestamp + """ + duration = end - start + if (duration.seconds / 60) < 1: + return (duration.seconds), 'seconds' + else: + return (duration.seconds / 60), 'minutes' + + +# -- main --------------------------------------------------------------------- + + +def main(): + """ copies ec2 instance tags to attached resources """ + for profile in profiles: + # derive account alias from profile + account = '-'.join(profile.split('-')[1:]) + for region in regions: + #instances = [] + session = boto3.Session(profile_name=profile, region_name=region) + client = session.client('ec2') + ec2 = session.resource('ec2') + instances = get_instances(profile, region) + + if SUMMARY_REPORT: + print('\nFor AWS Account %s, region %s, Found %d Instances\n' % (account, region, len(instances))) + continue + # copy tags + if instances: + try: + base = ec2.instances.filter(InstanceIds=instances) + ct = 0 + for instance in base: + ids, after_tags = [], [] + ct += 1 + if instance.tags: + # filter out tags to prohibited from copy + filtered_tags = filter_tags(instance.tags, *NO_COPY_LIST) + else: + # no tags on instance to copy + continue + + if not valid_tags(filtered_tags): + print('\nWARNING:') + logger.warning('Skipping instance ID %s, Invalid Tags\n' % instance.id) + continue + # collect attached resource ids to be tagged + for vol in instance.volumes.all(): + ids.append(vol.id) + for eni in instance.network_interfaces: + ids.append(eni.id) + logger.info('InstanceID %s, instance %d of %d:' % (instance.id, ct, len(instances))) + logger.info('Resource Ids to tag is:') + logger.info(str(ids) + '\n') + if DEBUGMODE: + # BEFORE tag copy + logger.info('BEFORE list of %d tags is:' % (len(instance.tags))) + pretty_print_tags(instance.tags) + + # AFTER tag copy | put Name tag back into apply tags, ie, after_tags + retain_tags = select_tags(instance.tags, PRESERVE_TAGS) + all_tags = retain_tags + filtered_tags + for tag in all_tags: + after_tags.append(tag) + logger.info('For InstanceID %s, the AFTER FILTERING list of %d tags is:' % (instance.id, len(after_tags))) + logger.info('Tags to apply are:') + pretty_print_tags(after_tags) + else: + logger.info('InstanceID %s, instance %d of %d:' % (instance.id, ct, len(instances))) + if filtered_tags: # we must have something to apply + # apply tags + for resourceId in ids: + # retain a copy of tags to preserve if is a volume + if resourceId.startswith('vol-'): + r = client.describe_tags( + Filters=[{ + 'Name': 'resource-id', + 'Values': [resourceId], + }, + ] + ) + retain_tags = select_tags(r['Tags'], PRESERVE_TAGS) + # add retained tags before appling to volume + if retain_tags: + for tag in retain_tags: + filtered_tags.append(tag) + # clear tags + print('\n') + logger.info('Clearing tags on resource: %s' % str(resourceId)) + client.delete_tags(Resources=[resourceId], Tags=[]) + + # create new tags + logger.info('Applying tags to resource %s\n' % resourceId) + ec2.create_tags(Resources=[resourceId], Tags=filtered_tags) + # delay to throttle API requests + sleep(1) + + except ClientError as e: + logger.exception( + "%s: Problem (Code: %s Message: %s)" % + (inspect.stack()[0][3], e.response['Error']['Code'], + e.response['Error']['Message']) + ) + raise + + +if __name__ == '__main__': + start_time = datetime.datetime.now() + main() + end_time = datetime.datetime.now() + duration, unit = calc_runtime(start_time, end_time) + logger.info('Job Start: %s' % start_time.isoformat()) + logger.info('Job End: %s' % end_time.isoformat()) + logger.info('Job Completed. Duration: %d %s' % (duration, unit)) + sys.exit(0) diff --git a/build/lib/pyaws/tags/ec2-update-tags.py b/build/lib/pyaws/tags/ec2-update-tags.py new file mode 100644 index 0000000..c729da7 --- /dev/null +++ b/build/lib/pyaws/tags/ec2-update-tags.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python + +import sys +import json +import argparse +import boto3 + +# -- functions ---------------------------------------------------------------- + + +def get_regions(): + """Summary + + Returns: + TYPE: Description + """ + client = boto3.client('ec2') + region_response = client.describe_regions() + regions = [region['RegionName'] for region in region_response['Regions']] + return regions + + +def remove_tags(id_list, tag_list): + """ + Deletes tags on resource ids provided as parameter + """ + pass + + +def clean_list(list): + """ cleans a list of all extraneous characters """ + clean_list = [] + try: + for element in list: + clean_list.append(element.strip()) + except Exception: + return -1 + return clean_list + + +def remove_duplicates(list): + """ + Removes duplicate dict in a list of dict by enforcing unique keys + """ + + clean_list, key_list = [], [] + + try: + for dict in list: + if dict['Key'] not in key_list: + clean_list.append(dict) + key_list.append(dict['Key']) + + except KeyError: + # dedup list of items, not dict + for item in list: + if clean_list: + if item not in clean_list: + clean_list.append(item) + else: + clean_list.append(item) + return clean_list + except Exception: + return -1 + return clean_list + + +def remove_restricted(list): + """ + Remove restricted Amazon tags from list of tags + """ + + clean_list = [] + + try: + for dict in list: + if 'aws' not in dict['Key']: + clean_list.append(dict) + except Exception: + return -1 + return clean_list + + +# -- MAIN ---------------------------------------------------------------------- + +# global vars +global DBUGMODE +global REGION +global PREFIX +PREFIX = 'gcreds-' # temporary credential prefix in local awscli config +global PROFILE + +# parse options setup +parser = argparse.ArgumentParser(description='Required parameters:') +parser.add_argument('-a','--accts', dest='file', action='store', + help='Profile name from local awscli config') +parser.add_argument('-c','--convert', action='store_true', + help='optional flag required to execute tag conversion') +args = parser.parse_args() + +if len(sys.argv) == 1: + parser.print_help() + sys.exit(0) + +if args.convert: + DBUGMODE = False +else: + DBUGMODE = True + +try: + fileobj1 = open(args.file) + accounts = fileobj1.readlines() + print("\nContents of accounts list: ") + for acct in accounts: + print(acct.strip()) + print("Length of accounts list: " + str(len(accounts))) + +except IOError as e: + print('File passed as parameter cannot be opened') +except Exception: + print("Unexpected error:", sys.exc_info()[0]) + raise + +# clean lists +accounts = clean_list(accounts) + +# list iam users for each account +for PROFILE in accounts: + # + TMPPROFILE = PREFIX + PROFILE + # + regions = get_regions() + change_record = [] # list to track number of resources for which config updated + regions = ['eu-west-1'] + for region in regions: + session = boto3.Session(profile_name=TMPPROFILE, region_name=region) + #base = AWSEC2_resource(PROFILE, REGION) + ec2 = session.resource('ec2') + base = ec2.instances.all() + # the following used if converting for one or more specific instances: + #ids = ['i-bc816f59'] + #base = ec2.instances.filter(InstanceIds=ids) + + ct = 0 + for instance in base: + ct += 1 + print('\n---------------------------------------------------------------------------------') + print('\n ' + str(ct) + ' instances found for account [' + str(PROFILE) + + '] in region [' + str(region) + ']\n') + print('---------------------------------------------------------------------------------\n') + + for instance in base: + # initialize tag lists + delete_tags = [] # k,v pairs (tags) to be deleted + tags = instance.tags # k,v pairs (pre-conversion tags on instance) + + # calc columns + col_width = 0 + for t in tags: + tag_width = len(t['Key']) + 2 + if tag_width > col_width: + col_width = tag_width + #print('col_width is: ' + str(col_width)) + col_width = 30 + + print('\nEC2 INSTANCE ID [' + str(instance.id) + '] ----------------------------\n') + print('\n\tExisting tag set:\n') + tags.sort() + for t in tags: + s1 = 'Key: ' + str(t['Key']) + s2 = 'Value: ' + str(t['Value']) + # print in 2 columns + print '{0:30} {1}'.format(s1, s2) + print('\n') + + # remove restricted tags before we attempt to convert and reapply + tags = remove_restricted(tags) + + # convert tag keys + for t in tags: + if t.get('MPC-SN-NAME'): + delete_tags.append(t) + tags.remove(t) + #change_record.append(instance.id) + + elif 'CreationDate' in t['Key']: + d = t.copy() + delete_tags.append(d) + t['Key'] = 'MPC-AWS-CREATIONDATE' + #change_record.append(instance.id) + + elif 'BillGroup' in t['Key']: + d = t.copy() + delete_tags.append(d) + t['Key'] = 'MPC-AWS-BILLGROUP' + #change_record.append(instance.id) + + elif t['Key'] == 'TAG-BACKUP': + d = t.copy() + delete_tags.append(d) + t['Key'] = 'MPC-AWS-BACKUP' + #change_record.append(instance.id) + + # output results + print('\n\tConverted tag set to be applied (aws* restricted tags omitted):\n') + tags = remove_duplicates(tags) + change_record = remove_duplicates(change_record) + tags.sort() + for t in tags: + s1 = 'Key: ' + str(t['Key']) + s2 = 'Value: ' + str(t['Value']) + # print in 2 columns + print '{0:30} {1}'.format(s1, s2) + print('\n') + + print('\n\tTags to be deleted:\n') + delete_tags.sort() + for t in delete_tags: + s1 = 'Key: ' + str(t['Key']) + s2 = 'Value: ' + str(t['Value']) + # print in 2 columns + print '{0:30} {1}'.format(s1, s2) + print('\n') + + if DBUGMODE == False: + # apply new converted tags + response_create = instance.create_tags(Tags=tags) + print('\nresponse_create is:') + print(str(response_create) + '\n') + if delete_tags: + # WARNING: calling instance.delete_tags() with empty tag list + # results in deletion of ALL pre-existing tags, must ensure + # contains at least 1 tag before calling delete method + response_delete = instance.delete_tags(Tags=delete_tags) + print('\nresponse_delete is:') + print(str(response_delete) + '\n') + + if DBUGMODE == True: + print('\n-------------------------------------') + print(' Converted tags not applied. DBUGMODE') + print('-------------------------------------\n') + # footer + print('\n---------------------------------------------------------------------------------') + print('Summary:\nUpdated config for ' + str(len(change_record)) + + ' resources in account ' + str(PROFILE)) + print('---------------------------------------------------------------------------------\n') +# <-- end --> +exit (0) diff --git a/build/lib/pyaws/tags/tag_utils.py b/build/lib/pyaws/tags/tag_utils.py new file mode 100644 index 0000000..e1587f4 --- /dev/null +++ b/build/lib/pyaws/tags/tag_utils.py @@ -0,0 +1,308 @@ +""" +pyaws.tags: Tag Utilities +""" +import json +import inspect +from functools import reduce +from botocore.exceptions import ClientError +from pyaws.session import boto3_session +from pyaws import logger + + +def create_taglist(dict): + """ + Summary: + Transforms tag dictionary back into a properly formatted tag list + Returns: + tags, TYPE: list + """ + tags = [] + for k, v in dict.items(): + temp = {} + temp['Key'] = k + temp['Value'] = v + tags.append(temp) + return tags + + +def create_tagdict(tags): + """ + Summary. + Converts tag list to tag dict + Args: + :tags (list): k,v + pair + { + [ + 'Key': k1, + 'Value': v1 + ] + } + Returns: + :tags (dict): {k1: v1, k2: v2} + + """ + return {x['Key']: x['Value'] for x in tags} + + +def delete_tags(resourceIds, region, tags): + """ Removes tags from an EC2 resource """ + client = boto3_session('ec2', region) + try: + for resourceid in resourceIds: + response = client.delete_tags( + Resources=[resourceid], + Tags=tags + ) + if response['ResponseMetadata']['HTTPStatusCode'] == 200: + logger.info('Existing Tags deleted from vol id %s' % resourceid) + return True + else: + logger.warning('Problem deleting existing tags from vol id %s' % resourceid) + return False + except ClientError as e: + logger.critical( + "%s: Problem apply tags to ec2 instances (Code: %s Message: %s)" % + (inspect.stack()[0][3], e.response['Error']['Code'], e.response['Error']['Message'])) + return False + + +def divide_tags(tag_list, *args): + """ + Summary: + Identifys a specific tag in tag_list by Key. When found, + creates a new tag list containing tags with keys provided in *args. + tag_list is returned without matching tags + Args: + tag_list (list): tag list starting reference + matching (list): tags which have keys matching any of *args + residual (list): tag_list after any matching tags are removed + RETURNS + TYPE: matching tag list, residual tag list + """ + matching = [] + residual = {x['Key']: x['Value'] for x in tag_list.copy()} + tag_dict = {x['Key']: x['Value'] for x in tag_list} + for key in args: + for k,v in tag_dict.items(): + try: + if key == k: + matching.append({'Key': k, 'Value': v}) + else: + residual.pop(key) + except KeyError: + continue + return matching, [{'Key': k, 'Value': v} for k,v in residual.items()] + + +def exclude_tags(tag_list, *args): + """ + - Filters a tag set by Exclusion + - variable tag keys given as parameters, tag keys corresponding to args + are excluded + + RETURNS + TYPE: list + """ + clean = tag_list.copy() + + for tag in tag_list: + for arg in args: + if arg == tag['Key']: + clean.remove(tag) + return clean + + +def extract_tag(tag_list, key): + """ + Summary: + Search tag list for prescence of tag matching key parameter + Returns: + tag, TYPE: list + """ + if {x['Key']: x['Value'] for x in tag_list}.get(key): + return list(filter(lambda x: x['Key'] == key, tag_list))[0] + return [] + + +def include_tags(tag_list, *args): + """ + - Filters a tag set by Inclusion + - variable tag keys given as parameters, tag keys corresponding to args + are excluded + + RETURNS + TYPE: list + """ + targets = [] + + for tag in tag_list: + for arg in args: + if arg == tag['Key']: + targets.append(tag) + return targets + + +def filter_tags(tag_list, *args): + """DEPRECATED + - Filters a tag set by exclusion + - variable tag keys given as parameters, tag keys corresponding to args + are excluded + + RETURNS + TYPE: list + """ + clean = tag_list.copy() + + for tag in tag_list: + for arg in args: + if arg == tag['Key']: + clean.remove(tag) + return clean + + +def json_tags(resource_list, tag_list, mode=''): + """ + - Prints tag keys, values applied to resources + - output: cloudwatch logs + - mode: INFO, DBUG, or UNKN (unknown or not provided) + """ + if mode == 0: + mode_text = 'DBUG' + else: + mode_text = 'INFO' + + try: + for resource in resource_list: + if mode == 0: + logger.debug('DBUGMODE enabled - Print tags found on resource %s:' % str(resource)) + else: + logger.info('Tags found resource %s:' % str(resource)) + print(json.dumps(tag_list, indent=4, sort_keys=True)) + except Exception as e: + logger.critical( + "%s: problem printing tag keys or values to cw logs: %s" % + (inspect.stack()[0][3], str(e))) + return False + return True + + +def pretty_print_tags(tag_list): + """ prints json tags with syntax highlighting """ + json_str = json.dumps(tag_list, indent=4, sort_keys=True) + print(highlight( + json_str, lexers.JsonLexer(), formatters.TerminalFormatter() + )) + print('\n') + return True + + +def print_tags(resource_list, tag_list, mode=''): + """ + - Prints tag keys, values applied to resources + - output: cloudwatch logs + - mode: INFO, DBUG, or UNKN (unknown or not provided) + """ + if mode == 0: + mode_text = 'DBUG' + else: + mode_text = 'INFO' + + try: + for resource in resource_list: + logger.info('Tags successfully applied to resource: ' + str(resource)) + ct = 0 + for t in tag_list: + logger.info('tag' + str(ct) + ': ' + str(t['Key']) + ' : ' + str(t['Value'])) + ct += 1 + if mode == 0: + logger.debug('DBUGMODE = True, No tags applied') + + except Exception as e: + logger.critical( + "%s: problem printing tag keys or values to cw logs: %s" % + (inspect.stack()[0][3], str(e))) + return 1 + return 0 + + +def remove_duplicates(alist): + """ + Removes duplicate dict in a list of dict by enforcing unique keys + """ + + clean_list, key_list = [], [] + + try: + for dict in alist: + if dict['Key'] not in key_list: + clean_list.append(dict) + key_list.append(dict['Key']) + except KeyError: + # dedup list of items, not dict + return list(reduce(lambda r, x: r + [x] if x not in r else r, alist, [])) + except Exception as e: + raise e + return clean_list + + +def remove_restricted(list): + """ + Remove restricted (system) Amazon tags from list of tags + """ + + clean_list = [] + + try: + for dict in list: + if 'aws' not in dict['Key']: + clean_list.append(dict) + except Exception: + return -1 + return clean_list + + +def remove_bykeys(tag_list, *keys): + """ + Summary: + Removes tags from a tag list for specified keys + Returns: + tags (list) + """ + tag_dict = {x['Key']: x['Value'] for x in tag_list} + for key in keys: + if key in tag_dict: + tag_dict.pop(key) + return [{'Key': k, 'Value': v} for k,v in tag_dict.items()] + + +def select_tags(tag_list, key_list): + """ + Return selected tags from a list of many tags given a tag key + """ + select_list = [] + # select tags by keys in key_list + for tag in tag_list: + for key in key_list: + if key == tag['Key']: + select_list.append(tag) + # ensure only tag-appropriate k,v pairs in list + clean = [{'Key': x['Key'], 'Value': x['Value']} for x in select_list] + return clean + + +def valid_tags(tag_list, invalid): + """ + Summary: + checks tags for invalid chars + Args: + tag_list (list): starting list of tags + invalid (list): list of illegal characters that should not be present in keys in tag_list + Returns: + Success | Failure, TYPE: bool + """ + for tag in tag_list: + for char in invalid: + if char in tag['Key']: + return False + return True diff --git a/build/lib/pyaws/utils/__init__.py b/build/lib/pyaws/utils/__init__.py new file mode 100644 index 0000000..61b8558 --- /dev/null +++ b/build/lib/pyaws/utils/__init__.py @@ -0,0 +1,8 @@ +""" +Utilities Module - Scripts +""" + +from libtools import bool_convert, bool_assignment, ascii_lowercase +from libtools import range_test, range_bind, userchoice_mapping +from libtools import stdout_message +from libtools.io import export_json_object diff --git a/dist/pyaws-0.4.1-py2.7.egg b/dist/pyaws-0.4.1-py2.7.egg new file mode 100644 index 0000000000000000000000000000000000000000..d220a210216f075b87cd167e9754e5517054879e GIT binary patch literal 101743 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
    W@&fs|+_2t(-yOyc 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⋘HF%=f|T1LBVD=k=b7wvWz=bbv6R_ZLe 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 E2P5tZ!vFvP literal 0 HcmV?d00001 diff --git a/future/lambda/get_price.py b/future/lambda/get_price.py index fdefcaf..552829a 100644 --- a/future/lambda/get_price.py +++ b/future/lambda/get_price.py @@ -1,236 +1,235 @@ -""" -Retrieve Amazon Web Services Pricing -""" -import argparse -import os -import sys -import json -import logging -import inspect -import requests -from functools import lru_cache -from itertools import chain -from pygments import highlight, lexers, formatters -import boto3 - - -# globals -PROFILE = 'default' -__version__ = '1.0 ' -logger = logging.getLogger(__version__) -logger.setLevel(logging.INFO) - -# set region default -default_region = os.getenv('AWS_DEFAULT_REGION', 'eu-west-1') -default_region = 'eu-west-1' - -RETURN_DATA = ['compute', 'transfer', 'request', 'requests', 'edge'] -INDEXURL = "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/index.json" -url_prefix = "https://pricing.us-east-1.amazonaws.com" - - - -def export_json_object(dict_obj, filename=None): - """ - Summary: - exports object to block filesystem object - - Args: - :dict_obj (dict): dictionary object - :filename (str): name of file to be exported (optional) - - Returns: - True | False Boolean export status - - """ - try: - if filename: - try: - with open(filename, 'w') as handle: - handle.write(json.dumps(dict_obj, indent=4, sort_keys=True)) - logger.info( - '%s: Wrote %s to local filesystem location' % - (inspect.stack()[0][3], filename)) - handle.close() - except TypeError as e: - logger.warning( - '%s: object in dict not serializable: %s' % - (inspect.stack()[0][3], str(e))) - else: - json_str = json.dumps(dict_obj, indent=4, sort_keys=True) - print(highlight(json_str, lexers.JsonLexer(), formatters.TerminalFormatter())) - logger.info('%s: successful export to stdout' % inspect.stack()[0][3]) - return True - except OSError as e: - logger.critical( - '%s: export_file_object: error writing to %s to filesystem. Error: %s' % - (inspect.stack()[0][3], filename, str(e))) - return False - else: - logger.info('export_file_object: successful export to %s' % filename) - return True - - -def get_regions(name): - session = boto3.Session(profile_name=name) - ec2 = session.client('ec2', region_name=default_region) - return [x['RegionName'] for x in ec2.describe_regions()['Regions'] if 'cn' not in x['RegionName']] - - -def global_index(service, url=INDEXURL): - """ - Retrieves master index file containing current price file urls for all AWS Services - """ - r = requests.get(INDEXURL) - f1 = json.loads(r.content) - return url_prefix + f1['offers'][service]['currentRegionIndexUrl'] - - -def region_index(region): - """ - Returns url of price file for specific region - """ - r2 = requests.get(global_index(service='AWSLambda')) - return url_prefix + json.loads(r2.content)['regions'][region]['currentVersionUrl'] - - -@lru_cache() -def price_data(region, sku=None): - """ - Summary: - all price data for an AWS service - Return: - data (json) - """ - r = requests.get(region_index(region)).json() - - products = [x for x in r['products'].values() if x['attributes']['servicecode'] == 'AWSLambda'] - skus = {x['sku'] for x in products} - - terms = list(chain( - *[ - y.values() for y in [x for x in r['terms'].values()] - ] - )) - - parsed = [] - - for term in terms: - if list(term.values())[0]['sku'] not in skus: - continue - parsed.append(term) - return products, skus, parsed - - -def help_menu(): - """ Displays command line parameter options """ - menu = ''' - help menu - --------- - -DESCRIPTION - - Code returns AWS Price data metrics for AWS Lambda - -OPTIONS - - $ python3 get_price.py [OPTIONS] - - [-e, --element ] - [-f, --filename ] - [-r, --region ] - [-d, --debug ] - [-h, --help ] - - -e, --element (string): Data Return Type. Data element - returned when one of the following specified: - - - compute : AWS Lambda Compute Price ($/GB-s) - - transfer : AWS Lambda Bandwidth Price ($/GB) - - request : Price per request ($/req) - - edge : Compute Price Lambda Edge ($/GB-s) - - If no --element specified, the entire pricing json object - for the region returned - - -f, --filename (string): Name of output file. Valid when - a data element is NOT specified and you want the entire - pricing json object returned and persisted to the - filesystem. No effect when --element given. - - -r, --region (string): Region for which you want to return - pricing. If no region parameter specified, defaults to - eu-west-1 - - -d, --debug: Debug mode, verbose output. - - -h, --help: Print this menu - ''' - print(menu) - return True - - -def main(region, dataType=None, output_path=None): - products, skus, response = price_data(region=region) - if dataType and dataType == 'compute': - for k,v in response[4]['SGGKTWDV7PGMPPSJ.JRTCKXETXF']['priceDimensions'].items(): - if isinstance(v, dict): - for key, value in v.items(): - if key == 'pricePerUnit': - return value['USD'] - elif dataType and dataType == 'transfer': - for k,v in response[1]['B5V2RNHAWGVJBZD3.JRTCKXETXF']['priceDimensions'].items(): - if isinstance(v, dict): - for key, value in v.items(): - if key == 'pricePerUnit': - return value['USD'] - elif dataType and dataType in ('request', 'requests'): - for k,v in response[0]['DDKXA6JP8NCVUFRZ.JRTCKXETXF']['priceDimensions'].items(): - if isinstance(v, dict): - for key, value in v.items(): - if key == 'pricePerUnit': - return value['USD'] - elif dataType and dataType == 'edge': - for k,v in response[-1]['WUTD23YJE55E5JCC.JRTCKXETXF']['priceDimensions'].items(): - if isinstance(v, dict): - for key, value in v.items(): - if key == 'pricePerUnit': - return value['USD'] - return export_json_object(dict_obj=response, filename=output_path) - - -def options(parser, help_menu=False): - """ - Summary: - parse cli parameter options - Returns: - TYPE: argparse object, parser argument set - """ - parser.add_argument("-e", "--element", nargs='?', default='list', type=str, - choices=RETURN_DATA, required=False) - parser.add_argument("-f", "--filename", nargs='?', default=None, - required=False, help="type (default: %(default)s)") - parser.add_argument("-r", "--region", nargs='?', default=default_region, type=str, - choices=get_regions(PROFILE), required=False) - parser.add_argument("-d", "--debug", dest='debug', action='store_true', required=False) - parser.add_argument("-h", "--help", dest='help', action='store_true', required=False) - return parser.parse_args() - - -def init_cli(): - # parser = argparse.ArgumentParser(add_help=False, usage=help_menu()) - parser = argparse.ArgumentParser(add_help=False) - - try: - args = options(parser) - except Exception as e: - logger.exception('Problem parsing provided parameters: %s' % str(e)) - if args.help: - return help_menu() - if args.debug: - print('\n\nParameters:\n\targs.region:\t%s\n\targs.element:\t%s\n' % (args.region, args.element)) - return main(region=args.region, dataType=args.element, output_path=args.filename) - - -if __name__ == '__main__': - sys.exit(init_cli()) +""" +Retrieve Amazon Web Services Pricing +""" +import argparse +import os +import sys +import json +import logging +import inspect +import requests +from functools import lru_cache +from itertools import chain +from pygments import highlight, lexers, formatters +import boto3 + + +# globals +PROFILE = 'default' +__version__ = '1.0 ' +logger = logging.getLogger(__version__) +logger.setLevel(logging.INFO) + +# set region default +default_region = os.getenv('AWS_DEFAULT_REGION', 'eu-west-1') +default_region = 'eu-west-1' + +RETURN_DATA = ['compute', 'transfer', 'request', 'requests', 'edge'] +INDEXURL = "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/index.json" +url_prefix = "https://pricing.us-east-1.amazonaws.com" + + +def export_json_object(dict_obj, filename=None): + """ + Summary: + exports object to block filesystem object + + Args: + :dict_obj (dict): dictionary object + :filename (str): name of file to be exported (optional) + + Returns: + True | False Boolean export status + + """ + try: + if filename: + try: + with open(filename, 'w') as handle: + handle.write(json.dumps(dict_obj, indent=4, sort_keys=True)) + logger.info( + '%s: Wrote %s to local filesystem location' % + (inspect.stack()[0][3], filename)) + handle.close() + except TypeError as e: + logger.warning( + '%s: object in dict not serializable: %s' % + (inspect.stack()[0][3], str(e))) + else: + json_str = json.dumps(dict_obj, indent=4, sort_keys=True) + print(highlight(json_str, lexers.JsonLexer(), formatters.TerminalFormatter())) + logger.info('%s: successful export to stdout' % inspect.stack()[0][3]) + return True + except OSError as e: + logger.critical( + '%s: export_file_object: error writing to %s to filesystem. Error: %s' % + (inspect.stack()[0][3], filename, str(e))) + return False + else: + logger.info('export_file_object: successful export to %s' % filename) + return True + + +def get_regions(name): + session = boto3.Session(profile_name=name) + ec2 = session.client('ec2', region_name=default_region) + return [x['RegionName'] for x in ec2.describe_regions()['Regions'] if 'cn' not in x['RegionName']] + + +def global_index(service, url=INDEXURL): + """ + Retrieves master index file containing current price file urls for all AWS Services + """ + r = requests.get(INDEXURL) + f1 = json.loads(r.content) + return url_prefix + f1['offers'][service]['currentRegionIndexUrl'] + + +def region_index(region): + """ + Returns url of price file for specific region + """ + r2 = requests.get(global_index(service='AWSLambda')) + return url_prefix + json.loads(r2.content)['regions'][region]['currentVersionUrl'] + + +@lru_cache() +def price_data(region, sku=None): + """ + Summary: + all price data for an AWS service + Return: + data (json) + """ + r = requests.get(region_index(region)).json() + + products = [x for x in r['products'].values() if x['attributes']['servicecode'] == 'AWSLambda'] + skus = {x['sku'] for x in products} + + terms = list(chain( + *[ + y.values() for y in [x for x in r['terms'].values()] + ] + )) + + parsed = [] + + for term in terms: + if list(term.values())[0]['sku'] not in skus: + continue + parsed.append(term) + return products, skus, parsed + + +def help_menu(): + """ Displays command line parameter options """ + menu = ''' + help menu + --------- + +DESCRIPTION + + Code returns AWS Price data metrics for AWS Lambda + +OPTIONS + + $ python3 get_price.py [OPTIONS] + + [-e, --element ] + [-f, --filename ] + [-r, --region ] + [-d, --debug ] + [-h, --help ] + + -e, --element (string): Data Return Type. Data element + returned when one of the following specified: + + - compute : AWS Lambda Compute Price ($/GB-s) + - transfer : AWS Lambda Bandwidth Price ($/GB) + - request : Price per request ($/req) + - edge : Compute Price Lambda Edge ($/GB-s) + + If no --element specified, the entire pricing json object + for the region returned + + -f, --filename (string): Name of output file. Valid when + a data element is NOT specified and you want the entire + pricing json object returned and persisted to the + filesystem. No effect when --element given. + + -r, --region (string): Region for which you want to return + pricing. If no region parameter specified, defaults to + eu-west-1 + + -d, --debug: Debug mode, verbose output. + + -h, --help: Print this menu + ''' + print(menu) + return True + + +def main(region, dataType=None, output_path=None): + products, skus, response = price_data(region=region) + if dataType and dataType == 'compute': + for k,v in response[4]['SGGKTWDV7PGMPPSJ.JRTCKXETXF']['priceDimensions'].items(): + if isinstance(v, dict): + for key, value in v.items(): + if key == 'pricePerUnit': + return value['USD'] + elif dataType and dataType == 'transfer': + for k,v in response[1]['B5V2RNHAWGVJBZD3.JRTCKXETXF']['priceDimensions'].items(): + if isinstance(v, dict): + for key, value in v.items(): + if key == 'pricePerUnit': + return value['USD'] + elif dataType and dataType in ('request', 'requests'): + for k,v in response[0]['DDKXA6JP8NCVUFRZ.JRTCKXETXF']['priceDimensions'].items(): + if isinstance(v, dict): + for key, value in v.items(): + if key == 'pricePerUnit': + return value['USD'] + elif dataType and dataType == 'edge': + for k,v in response[-1]['WUTD23YJE55E5JCC.JRTCKXETXF']['priceDimensions'].items(): + if isinstance(v, dict): + for key, value in v.items(): + if key == 'pricePerUnit': + return value['USD'] + return export_json_object(dict_obj=response, filename=output_path) + + +def options(parser, help_menu=False): + """ + Summary: + parse cli parameter options + Returns: + TYPE: argparse object, parser argument set + """ + parser.add_argument("-e", "--element", nargs='?', default='list', type=str, + choices=RETURN_DATA, required=False) + parser.add_argument("-f", "--filename", nargs='?', default=None, + required=False, help="type (default: %(default)s)") + parser.add_argument("-r", "--region", nargs='?', default=default_region, type=str, + choices=get_regions(PROFILE), required=False) + parser.add_argument("-d", "--debug", dest='debug', action='store_true', required=False) + parser.add_argument("-h", "--help", dest='help', action='store_true', required=False) + return parser.parse_args() + + +def init_cli(): + # parser = argparse.ArgumentParser(add_help=False, usage=help_menu()) + parser = argparse.ArgumentParser(add_help=False) + + try: + args = options(parser) + except Exception as e: + logger.exception('Problem parsing provided parameters: %s' % str(e)) + if args.help: + return help_menu() + if args.debug: + print('\n\nParameters:\n\targs.region:\t%s\n\targs.element:\t%s\n' % (args.region, args.element)) + return main(region=args.region, dataType=args.element, output_path=args.filename) + + +if __name__ == '__main__': + sys.exit(init_cli()) diff --git a/pyaws.egg-info/PKG-INFO b/pyaws.egg-info/PKG-INFO new file mode 100644 index 0000000..e4b38b4 --- /dev/null +++ b/pyaws.egg-info/PKG-INFO @@ -0,0 +1,34 @@ +Metadata-Version: 1.2 +Name: pyaws +Version: 0.4.1 +Summary: Python Utilities for Amazon Web Services +Home-page: http://pyaws.readthedocs.io +Author: Blake Huber +Author-email: blakeca00@gmail.com +License: GPL-3.0 +Description: + **pyaws** | Utilities Library for Amazon Web Services (AWS) + ----------------------------------------------------------- + + PACKAGE: pyaws + + ``pyaws``: reusable library of utility classes and functions common AWS use cases and capabilities: + + * uploading to s3 + * adding/ deleting resource tags + * adding data elements to dynamodb table + * Determining the latest Amazon Machine Image in a region for Windows, Linux, etc + + + +Keywords: Amazon Web Services AWS iam ec2 lambda rds s3 sts +Platform: UNKNOWN +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities +Classifier: Development Status :: 4 - Beta +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) +Classifier: Operating System :: POSIX :: Linux +Requires-Python: >=3.6, <4 diff --git a/pyaws.egg-info/SOURCES.txt b/pyaws.egg-info/SOURCES.txt new file mode 100644 index 0000000..9702ba0 --- /dev/null +++ b/pyaws.egg-info/SOURCES.txt @@ -0,0 +1,42 @@ +DESCRIPTION.rst +LICENSE +MANIFEST.in +README.md +setup.cfg +setup.py +pyaws/__init__.py +pyaws/_version.py +pyaws/colors.py +pyaws/environment.py +pyaws/helpers.py +pyaws/logd.py +pyaws/script_utils.py +pyaws/session.py +pyaws/statics.py +pyaws/awslambda/__init__.py +pyaws/awslambda/env.py +pyaws/awslambda/lambda_utils.py +pyaws/core/__init__.py +pyaws/core/create_client.py +pyaws/core/cross_account_utils.py +pyaws/core/loggers.py +pyaws/core/oscodes_unix.py +pyaws/core/oscodes_win.py +pyaws/dynamodb/__init__.py +pyaws/dynamodb/dynamodb.py +pyaws/dynamodb/table.py +pyaws/ec2/__init__.py +pyaws/ec2/ec2_utils.py +pyaws/ec2/snapshot_ops.py +pyaws/ec2/state.py +pyaws/s3/__init__.py +pyaws/s3/check_authenticated_s3.py +pyaws/s3/object_operations.py +pyaws/sts/__init__.py +pyaws/sts/cross_account_utils.py +pyaws/tags/__init__.py +pyaws/tags/bulk-modify-tags.py +pyaws/tags/copy-tags-all-instances.py +pyaws/tags/ec2-update-tags.py +pyaws/tags/tag_utils.py +pyaws/utils/__init__.py \ No newline at end of file diff --git a/pyaws.egg-info/dependency_links.txt b/pyaws.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/pyaws.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/pyaws.egg-info/entry_points.txt b/pyaws.egg-info/entry_points.txt new file mode 100644 index 0000000..1182316 --- /dev/null +++ b/pyaws.egg-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +pyconfig = pyaws.cli:option_configure + diff --git a/pyaws.egg-info/not-zip-safe b/pyaws.egg-info/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/pyaws.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/pyaws.egg-info/requires.txt b/pyaws.egg-info/requires.txt new file mode 100644 index 0000000..d02aa89 --- /dev/null +++ b/pyaws.egg-info/requires.txt @@ -0,0 +1,4 @@ +awscli>=1.16.100boto3>=1.9.100 +botocore +libtools>=0.2.5 +distro>=1.4.0 diff --git a/pyaws.egg-info/top_level.txt b/pyaws.egg-info/top_level.txt new file mode 100644 index 0000000..fd52136 --- /dev/null +++ b/pyaws.egg-info/top_level.txt @@ -0,0 +1 @@ +pyaws diff --git a/pyaws/__init__.pyc b/pyaws/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4aeaef0373d721d3d53dbda6ecec9893fbc8f2ac GIT binary patch literal 826 zcmYjOO^?$s5FID|{@8Ze!kGhyUN$WPi3>t2SOkX^Lg4_4KvwDuS#KS?irwz^%$9Qp8WZv`m*>4>G9uI!0?_UHQo;Ll@XxN_=3i=o*fUL4a{focZX4A@6~3@yA5>jBh1fH9^Sd?E`U z<|DEr|IqVp&M0+53Y_n-w$f_4oeKy8t*-+^;-rX3CROZtIA@0L)bXI<{Le*O4%x5QIY*fb`_^(ZL0a>gi|dXK3; zR*)*uU6``O+WB6nCiSX~F=1*<{Y)r+eIsz==(^^_z+JG<2re#7g$tqbZ6VECEY8if zRwgg(B1oMslc&k!CSPzaHT9Jzn|(Ok^U4%kg)e-M>>~;zHf7Ve!~T2QaYP)keKv_^ H(U|=Oun@NI literal 0 HcmV?d00001 diff --git a/pyaws/__pycache__/__init__.cpython-310.pyc b/pyaws/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd0597f68b8d634f24174834a4c3ed31af9fba34 GIT binary patch literal 685 zcmYjN&5qMB5Vq6&HBGu@yKqM0LJyl3fy4!&6)XaY6++^Gu!OAC8M4+oab>6NwhzIX zBOH09TsUyz6}Z4S1%a)Z`M&Y|d32T~1kX41@xAs4`R>U7Cq{CHVn3lONl6u9&U%X0 z9ve`H2gj) zGMI3b&o{vPLfJ9qjMTI~VJDQl#qMv2PvGY6B_-~@>pfdCOMl>WCw cR53|uO8tRP|6MOgJ?9Mv2|e~wFQneD-=9dmK>z>% literal 0 HcmV?d00001 diff --git a/pyaws/__pycache__/_version.cpython-310.pyc b/pyaws/__pycache__/_version.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9bf3ca50c5e53b861f4e1f13ac47c3237f3573d GIT binary patch literal 169 zcmd1j<>g`kg2Q=zv0^~_F^Gc<7=auIATH(r5-AK(3@MDk44O<;tOj}}dWL?QjJLSs z&2Jk;6rb5|Z=5Eif;0!DkswkRDNZZ#A)*Konxu*d4yuVNR!G)nXY6dd-mzw8 z6TC_eD82G0AUQ|=9IkuiRE``0sl1uBsVfj;&Bt5Md++yt@4ey6%OQgC!!N&m_mhXv zuUc3fHVB`?l;^Ne6eEhMmE#zj-HI)<+p%qSCw5@Bb9dy$9!AWW`nMjF3UxmsydWb^%bbyHH0DBuF99{`M!0q_&$IMA#af;X2lfEwBbYLW zh2m?h&=tPHvaKM@8x7a)!58YRqbszIsJo7?ttwE~)V{KA@wNRFUBL|pDQEH#2KX2$ zcj{43`P1MQQGWx`U;}gq);mat|E;ljiK15Zy9uRNjz)=?>=;4E2JuQl*&r!%MfRTd zh+xBv7v}#xF_f=wjs+iNIqMh6i0u%PkWwG2# z_Q`#zM6|Quk))h5Aim2B)@UY~WDAN1-=}P>02|2$a~B2-DYzm7UKUg%NqI5IhQ>l{ z1E6aCv`Ee%M~WMiip^kltCiX1QZO=Pid255L05geDR;y#}D9bZoMJv}WaK%eCE>+$7C#KrV`EbZY z-D2}FYdp|zk&co)sy-?Mi@uXgE?DiXrZ-F^UU~!fLE5Ht3wJfWR9k2bT zgDt#y!HPQxZ8kqX2O($h`mn literal 0 HcmV?d00001 diff --git a/pyaws/__pycache__/script_utils.cpython-310.pyc b/pyaws/__pycache__/script_utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c5ee02bfcedeadf6a1a1a7efbcf20dea8c4c95d GIT binary patch literal 8478 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 Fe*mRgj}-s_ literal 0 HcmV?d00001 diff --git a/pyaws/__pycache__/statics.cpython-310.pyc b/pyaws/__pycache__/statics.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..164e89ef41b9737e62708359866696024c418203 GIT binary patch literal 1620 zcmZ`(&2Aev5GGeEX)Vigq9k@5=ZA*|h>X~3fuaW^Xye*zM~y5AmfI$5i|s}n*&DBx zDoNRq4@J%%RT-mM*q`O#h$QjNz^KoY7%uI>k^TQvFAD!+_*UKxJL=(PNu+n zO?e4QC)1F7R(?$>Ilc~gkr##ED4&5DQCuLrg0qM7EX)dLMAQSICzk}C9DXUL`0RHi zx$P3TJpPFiUPTXA(BBnN0_WuFYeZZiVtRqVHK@L%eP@nX6sZ1+5%cQz)gyXDM48Wt z89tBSMZ9DYkUT~eD1-yun*?r1Ow)p)hU@EMN7Bh5NWK7*cFj^PkuwgLTc6dTRL zM6fOx6D51Pl$kHHrKP6qh37`+`lY2+HqnT)<9(^vSdJ9aL+C~QsK=s2n=p<;6Ul^y z3YY*D9fDweB?nBOLb1LaA##+gmP*@FjACGG#;EA&$bg=Nz05`$RIo1x!0zro>G(_= zRh#H+S|2}bZ~F|A!$`?w0EuCTp^Cz%G2CNA1={Y|@CH&F_DvE!&vLcGavU81>$JO# z$4rm4fq_2FyeE@>v=@j-eGn4&kwU4)ED15HQx-B5ZFG2uoJ~BieiVadcUMlgYyb5> z49)(B0Vcfuug81HST7z4^nEeZU+Vt?y|Jy+syS(44@@9+IwN&3L}tvmgRnK4P6xpu zD2?|M1iLlLJpm@fcm*$_sTmI;buG8YZru!q$M^5(Rdz?8v&$mQ6#P6wE<{FYA}!IA z>ttphF5bzT97dDovl8UF?Zz%Iba!|Bwe4efC6;@~`8tmJEv?sbX|c1m{%CE>=auz# zYqPNxeCzLY8|{|LqmRv6fmi(2+E-0KXtuYu8m%o}Mj_a2G;w>|=S92PXzXy$3Z1px zhZAYLz2Woe?vw7BbX;0w|H1Rm7v|!!f>|p~mZ3m`qqqW)Q9@cLV zC4I@Ac9&0`SA2GRs7t*^F!q&_Dy5Km2yP;5%`}fq);maZ*b-{WVilW>b-&g1k(7xL zZ1~-E{F^1VBZ9LmJQRX~6u`??vSn&ZncFmN+4Ic6)@OJu4gnXf3ot~%BK^#j)7vxdSy6Hz59rat^Ni6qKJNsgG;=M zM?HFpmTAc;Q>RdJ?AmiG)O}HLTb!T~yE*TL^Un2Lif_(En=+nqTn8=Ozn!9; KC$Iaq^w&RQv)Lj5 literal 0 HcmV?d00001 diff --git a/pyaws/_version.pyc b/pyaws/_version.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37644a46b60ef8b9d0ed310dc76acfc92c7a6e81 GIT binary patch literal 174 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 literal 0 HcmV?d00001 diff --git a/pyaws/session.py b/pyaws/session.py index 38410d0..16055e7 100644 --- a/pyaws/session.py +++ b/pyaws/session.py @@ -5,7 +5,6 @@ from botocore.exceptions import ClientError, ProfileNotFound from pyaws.utils import stdout_message from pyaws import logger -from pyaws._version import __version__ try: from pyaws.core.oscodes_unix import exit_codes @@ -37,14 +36,18 @@ def _profile_prefix(profile, prefix='gcreds'): tempProfile = prefix + '-' + profile try: - if subprocess.getoutput(f'aws configure get profile.{profile}.aws_access_key_id {stderr}'): + if subprocess.check_output( + 'aws configure get profile.{profile}.aws_access_key_id {stderr}'.format( + profile=profile, stderr=stderr)): return profile - elif subprocess.getoutput(f'aws configure get profile.{tempProfile}.aws_access_key_id {stderr}'): + elif subprocess.check_output( + 'aws configure get profile.{tempProfile}.aws_access_key_id {stderr}'.format( + tempProfile=tempProfile, stderr=stderr)): return tempProfile except Exception as e: logger.exception( - f'{inspect.stack()[0][3]}: Unknown error while interrogating local awscli config: {e}' - ) + '{i}: Unknown error while interrogating local awscli config: {e}'.format( + i=inspect.stack()[0][3], e=e)) raise return None @@ -80,8 +83,8 @@ def parse_profiles(profiles): return _profile_prefix(profiles.strip()) except Exception as e: logger.exception( - f'{inspect.stack()[0][3]}: Unknown error while converting profile_names from local awscli config: {e}' - ) + '{i}: Unknown error while converting profile_names from local awscli config: {e}'.format( + i=inspect.stack()[0][3], e=e)) raise return profile_list diff --git a/pyaws/statics.pyc b/pyaws/statics.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab621a148f91b1491dd19851df4148a6591889de GIT binary patch literal 1918 zcmZ`(TW{Mo6h5*OJ5_F8nkG$~v|P~wZqV2pFytX9hIz4)dhrdmx3HfxiR}E5hY1U`y~0 zjk&;FxLgJ!Trm=7b_Q$_=pwLHcn6NZ25b#z33mK-i^_;9z$zx3t0jGXHx)<$UZzc0V7KAQ1qqJhZ=!E>b_XcBFTmv%+6U^w4qb*Fwhe64SQr;z%fx}J z-yBGPhgWn3{JTI`!QZjtJrkOLALttRyTEX39k%^FU?m$?1-4P&e_+=SfPMzsY%iBR z1YQ7Mv^pDbh#b({IAJbjIHa3!$k7BEzJtO&pj$Yhfo+@~0=aMBQ-&ieom?cRlXGboXfECL4!YtQ84UN`_2po}Me$ z9zP)a`)v`0H-~Qa_xF!TzC-8gVcwU z5OwW}j75f?d3xl!9YM30k%rcizsWRHdDsUeQ%nXIB4MODdfoSk((-A(XVQB7qSx^V zOQ&3lG-0VG(@^s8P0XH=iDb%1On8Y=h9K`&Jk$!L3dPUSsX_lz#%p;MG z`FTLO{G>~iBZWfFNE%{P*DP2sN~>^+oaGkCn8!?!YVBJ5^uP8ep}zREgPhm@wfGDf zi{gx;?Tj^-UjO6y~Y1w^W;Dr!}&X4@U#9(XkF|u{c-eRNVk=La*EK`i*P;%Vs zp0rMbAH2b^)$8WlgWh-E@yKp_-Nv_VFKG8pPg~tny@DEugOgSpcRHRuZv@TOz|vrF zzcG4ov*`4io+WJfdN|jdYZ;T(M`+Rtf`Ez$*)&B!?hTLF7T6)$V$9(j`%Q14+@@Vq zk>nE{WSYlnfKnLP>X@wZ1M?CMY+Y}m9P8RfJ4{-xj7fy1eAr@b)8n>et?o(Bf|1Oa z-NuFqcsdp~xZY43TQ`knk9I}M=2r(#TdgxT^Q08gQlA^bkTu6=!d4G^KDx5i!zLZL zXftP~b?kMAUQTw7gQhn;wgq9SY1UxQhKU_<713nDu5SMlHoYbQ@%qSOEQN1bNrm+aAs#~59zNmdw`!dIM%n1`z zwaLtSe2#(PJN30GifZFfa#rkb%c&IBol2>QqwBbZ6=xSw$tgOI9Cx9Fs8lqg>r|Z0 QLJ{R<{4E#r+2FGCFHtttKmY&$ literal 0 HcmV?d00001 diff --git a/pyaws/tags/bulk-modify-tags.py b/pyaws/tags/bulk-modify-tags.py index b041888..987e359 100644 --- a/pyaws/tags/bulk-modify-tags.py +++ b/pyaws/tags/bulk-modify-tags.py @@ -224,7 +224,8 @@ def main(): # AFTER tag copy | put Name tag back into apply tags, ie, after_tags retain_tags = select_tags(instance.tags, PRESERVE_TAGS) - for tag in (*retain_tags, *filtered_tags): + all_tags = retain_tags + filtered_tags + for tag in all_tags: after_tags.append(tag) logger.info('For InstanceID %s, the AFTER FILTERING list of %d tags is:' % (instance.id, len(after_tags))) logger.info('Tags to apply are:') diff --git a/pyaws/tags/copy-tags-all-instances.py b/pyaws/tags/copy-tags-all-instances.py index 9ff74f0..5ef3e6a 100644 --- a/pyaws/tags/copy-tags-all-instances.py +++ b/pyaws/tags/copy-tags-all-instances.py @@ -185,7 +185,12 @@ def main(): # AFTER tag copy | put Name tag back into apply tags, ie, after_tags retain_tags = select_tags(instance.tags, PRESERVE_TAGS) +<<<<<<< HEAD for tag in (*retain_tags, *filtered_tags): +======= + all_tags = retain_tags + filtered_tags + for tag in all_tags: +>>>>>>> 34de914 (Refactor for python2.7) after_tags.append(tag) logger.info('For InstanceID %s, the AFTER FILTERING list of %d tags is:' % (instance.id, len(after_tags))) logger.info('Tags to apply are:') diff --git a/pyaws/tags/tag_utils.py b/pyaws/tags/tag_utils.py index 150cffa..e1587f4 100644 --- a/pyaws/tags/tag_utils.py +++ b/pyaws/tags/tag_utils.py @@ -8,11 +8,6 @@ from pyaws.session import boto3_session from pyaws import logger -try: - from pyaws oscodes_unix import exit_codes -except Exception: - from pyaws oscodes_win import exit_codes # non-specific os-safe codes - def create_taglist(dict): """ diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..8bfd5a1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[egg_info] +tag_build = +tag_date = 0 + diff --git a/setup.py b/setup.py index 8042e23..adc5b11 100644 --- a/setup.py +++ b/setup.py @@ -79,13 +79,14 @@ def run(self): 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 2.7', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'Operating System :: POSIX :: Linux' ], keywords='Amazon Web Services AWS iam ec2 lambda rds s3 sts', packages=find_packages(exclude=['docs', 'scripts', 'assets']), install_requires=requires, - python_requires='>=3.6, <4', + python_requires='>=2.7, <4', entry_points={ 'console_scripts': [ 'pyconfig=pyaws.cli:option_configure' From 1396f4793590eda7b575abdd8cb73c82bcc3b5a3 Mon Sep 17 00:00:00 2001 From: Claudio Caceres Date: Mon, 12 Jun 2023 15:12:20 -0300 Subject: [PATCH 2/2] Add pathlib to requirements --- build/lib/pyaws/logd.py | 2 +- pyaws/logd.py | 2 +- setup.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/build/lib/pyaws/logd.py b/build/lib/pyaws/logd.py index 5c2cd53..f409222 100644 --- a/build/lib/pyaws/logd.py +++ b/build/lib/pyaws/logd.py @@ -7,7 +7,7 @@ import inspect import logging import logging.handlers -from pathlib import Path +from pathlib2 import Path from pyaws.statics import local_config diff --git a/pyaws/logd.py b/pyaws/logd.py index 5c2cd53..f409222 100644 --- a/pyaws/logd.py +++ b/pyaws/logd.py @@ -7,7 +7,7 @@ import inspect import logging import logging.handlers -from pathlib import Path +from pathlib2 import Path from pyaws.statics import local_config diff --git a/setup.py b/setup.py index adc5b11..968686c 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,8 @@ 'boto3>=1.9.100', 'botocore', 'libtools>=0.2.5', - 'distro>=1.4.0' + 'distro>=1.4.0', + 'pathlib2' ]