def get_method_response(self, restid, resourceid, method, statuscode): """ This function returns a method response object :param method: the method that is requested :type method: basestring :param restid: the id of the rest api object :type restid: basestring :param resourceid: id of a single resource object :type resourceid: basestring :param statuscode: the statuscode requested :type statuscode: basestring :return: None if not found, else the object """ try: ret = self.apigateway_client.get_method_response(restApiId=restid, resourceId=resourceid, httpMethod=method, statusCode=statuscode) super(Apigateway, self).query_information(query=ret) logger.debug("We found the method response") except Exception as e: # https://github.com/aws/aws-cli/issues/1620 if e.response['Error']['Code'] == "NotFoundException": logger.warning("Method response %s for resource %s does not exist" % (statuscode, resourceid)) else: logger.error("%s" % e, ) ret = None return ret
def query_information(self, query): ''' This function is used to print debug information about a query, to see if it was succesful or not :param query: The boto3 query :return: Query with removed metadata ''' if query['ResponseMetadata']['HTTPStatusCode'] == 201: logger.debug("Resource was succesfully created") logger.info("Query RequestID: %s, HTTPStatusCode: %s" % (query['ResponseMetadata']['RequestId'], query['ResponseMetadata']['HTTPStatusCode'])) elif query['ResponseMetadata']['HTTPStatusCode'] == 202: logger.debug('Request accepted but processing later.') logger.info("Query RequestID: %s, HTTPStatusCode: %s" % (query['ResponseMetadata']['RequestId'], query['ResponseMetadata']['HTTPStatusCode'])) elif query['ResponseMetadata']['HTTPStatusCode'] == 204: logger.debug('Request done but no content returned.') logger.info("Query RequestID: %s, HTTPStatusCode: %s" % (query['ResponseMetadata']['RequestId'], query['ResponseMetadata']['HTTPStatusCode'])) elif query['ResponseMetadata']['HTTPStatusCode'] != 200: logger.warning('There was an issue with request.') logger.warning("Query RequestID: %s, HTTPStatusCode: %s" % (query['ResponseMetadata']['RequestId'], query['ResponseMetadata']['HTTPStatusCode'])) else: logger.debug("Request had no issues") logger.debug("Query RequestID: %s, HTTPStatusCode: %s" % (query['ResponseMetadata']['RequestId'], query['ResponseMetadata']['HTTPStatusCode'])) query.pop('ResponseMetadata') if 'NextToken' in query: logger.error("Token is present. Paging needs to be implemented") return query
def manage_gw_for_route53(self, ips=None, zoneid=None, domain=None, dryrun=None): logger.debug("Checking if need to add IPs to route53") records = self.list_zone_records(zoneid=zoneid) for ip in ips['active']: logger.debug("Checking ip %s" % (ip,)) if record_exists(records=records, name="broker.%s." % (domain,), ip=ip): logger.info("Found IP Entry %s in route53 domain %s" % (ip, domain)) continue else: logger.info("Did not find the IP in the route53, adding them") self.add_gw_to_route53(ips=ips['active'], zoneid=zoneid, domain=domain, dryrun=dryrun) logger.debug("Ips have been updated, no point on checking further") break logger.debug("Checking if need to remove IPs from route53") records = self.list_zone_records(zoneid=zoneid) for ip in ips['deactive']: logger.debug("Checking if deactivate IP %s is active" % (ip,)) if record_exists(records=records, name="broker.%s." % (domain,), ip=ip): logger.info("Need to remove entry %s from route53" % (ip,)) self.add_gw_to_route53(ips=ips['active'], zoneid=zoneid, domain=domain, dryrun=dryrun) logger.debug("Ips have been updated, no point on checking further") break if dryrun: logger.debug("Dryrun configured, no need to test") return True logger.info("Refreshing records to see if change in place") records = self.list_zone_records(zoneid=zoneid) for record in records: if record['Name'] == "broker.%s." % (domain,): if len(record['ResourceRecords']) == len(ips['active']): logger.info("Length of current records equals actice records") else: logger.error("Length does not equal, something went wrong") break
def query_information(self, query): ''' This function is used to print debug information about a query, to see if it was succesful or not :param query: The boto3 query :return: Query with removed metadata ''' if query['ResponseMetadata']['HTTPStatusCode'] == 201: logger.debug("Resource was succesfully created") logger.info("Query RequestID: %s, HTTPStatusCode: %s" % ( query['ResponseMetadata']['RequestId'], query['ResponseMetadata']['HTTPStatusCode'])) elif query['ResponseMetadata']['HTTPStatusCode'] == 202: logger.debug('Request accepted but processing later.') logger.info("Query RequestID: %s, HTTPStatusCode: %s" % ( query['ResponseMetadata']['RequestId'], query['ResponseMetadata']['HTTPStatusCode'])) elif query['ResponseMetadata']['HTTPStatusCode'] == 204: logger.debug('Request done but no content returned.') logger.info("Query RequestID: %s, HTTPStatusCode: %s" % ( query['ResponseMetadata']['RequestId'], query['ResponseMetadata']['HTTPStatusCode'])) elif query['ResponseMetadata']['HTTPStatusCode'] != 200: logger.warning('There was an issue with request.') logger.warning("Query RequestID: %s, HTTPStatusCode: %s" % ( query['ResponseMetadata']['RequestId'], query['ResponseMetadata']['HTTPStatusCode'])) else: logger.debug("Request had no issues") logger.debug("Query RequestID: %s, HTTPStatusCode: %s" % ( query['ResponseMetadata']['RequestId'], query['ResponseMetadata']['HTTPStatusCode'])) query.pop('ResponseMetadata') if 'NextToken' in query: logger.error("Token is present. Paging needs to be implemented") return query
def info(self): logger.info("Starting to gather information") a = awsrequests() parser = argparse.ArgumentParser( description='ec2 tool for devops', usage='''kerrigan.py route53 info [<args>]] ''' + self.global_options) parser.add_argument('--env', action='store', help="Environment to gather information about") args = parser.parse_args(sys.argv[3:]) res = a.route53_info(env=args.env) for r in res: if self.cli['csv']: r['Values'] = Misc.join_list_to_string(list=r['Values']) elif self.cli['table']: r['Values'] = Misc.list_to_multiline_string(r['Values']) else: logger.error( 'There is an unhandled printing. Need to investigate') # Add information if not present: if 'Weight' not in r: r['Weight'] = '-' r['SetIdentifier'] = '-' logger.output(data=res, csvvar=self.cli['csv'], tablevar=self.cli['table'])
def __init__(self): logger.debug("Starting Class for Cloudwatch") try: config_file = open("%s/etc/aws.conf" % (os.environ['KERRIGAN_ROOT'],), 'r') self.yaml = yaml.load(config_file) except IOError as e: logger.error("aws.conf I/O error({0}): {1}".format(e.errno, e.strerror)) self.cloudwatch = boto3.client('cloudwatch', region_name='us-east-1')
def validate_kerrigan_json(stack_data, env): # do some tests logger.debug(msg="Validating if stack can be deployed to env") if 'envs' in stack_data and env in stack_data['envs']: logger.info(msg="Stack can be deployed to environment %s" % (env,)) else: logger.error(msg="Stack should not be deployed to Environment %s" % (env,)) exit(20) return stack_data
def __init__(self): logger.debug("Starting awsrequests") try: config_file = open( "%s/etc/aws.conf" % (os.environ['KERRIGAN_ROOT'], ), 'r') self.yaml = yaml.load(config_file) except IOError as e: logger.error("aws.conf I/O error({0}): {1}".format( e.errno, e.strerror)) self.owner = self.yaml['owner']
def change_record_for_zoneid(self, zoneid=None, changebatch=None, dryrun=None): if dryrun: logger.debug("Dryrun requested") logger.warning("Not running changebatch: %s" % changebatch, ) return True ret = self.boto3.change_resource_record_sets(HostedZoneId=zoneid, ChangeBatch=changebatch) if ret['ResponseMetadata']['HTTPStatusCode'] == 200: logger.info("Change was succesful") else: logger.error("There was an issue with the change")
def __init__(self): logger.debug("Starting Class for Cloudwatch") try: config_file = open( "%s/etc/aws.conf" % (os.environ['KERRIGAN_ROOT'], ), 'r') self.yaml = yaml.load(config_file) except IOError as e: logger.error("aws.conf I/O error({0}): {1}".format( e.errno, e.strerror)) self.cloudwatch = boto3.client('cloudwatch', region_name='us-east-1')
def get_bucket_lifecycle(self, name): try: resp = self.s3.get_bucket_lifecycle(Bucket=name) except Exception as e: if "An error occurred (NoSuchLifecycleConfiguration)" in e: logger.warning("Bucket %s has no Lifecycle configuration" % (name,)) else: logger.error("%s" % e, ) return None return resp['Rules']
def get_bucket_policy(self, name): try: resp = self.s3.get_bucket_policy(Bucket=name) except Exception as e: if "An error occurred (NoSuchBucketPolicy)" in e: logger.warning("Bucket %s has no policy configuration" % (name,)) else: logger.error("%s" % e, ) return None return resp
def get_bucket_replication(self, name): try: resp = self.s3.get_bucket_replication(Bucket=name) except Exception as e: if "An error occurred (ReplicationConfigurationNotFoundError)" in e: logger.warning("Bucket %s has no replication configuration" % (name,)) else: logger.error("%s" % e, ) return None return resp
def generate_elb_name(self, stack=None, facing=None, env=None): if facing == "internal": avail = "int" else: avail = "pub" lb_name = env + "-" + avail + "-" + stack if len(lb_name) > 32: logger.error("ELB name is longer than 32 chars. there will be an issue creating the elb") logger.info("ELB name is going to be: %s" % lb_name, ) return lb_name
def get_bucket_tagging(self, name): try: resp = self.s3.get_bucket_tagging(Bucket=name) except Exception as e: if "An error occurred (NoSuchTagSet)" in e: logger.warning("Bucket %s has no Tag configuration" % (name,)) else: logger.error("%s" % e, ) return None return resp['TagSet']
def rest_api_exists(self, name): try: self.get_rest_api_by_name(name=name) return True except Exception as e: if e.response['Error']['Code'] == "NotFoundException": return False else: logger.error(e) exit(1)
def check_stack_syntax(self,stack=None): ''' This sub is used to validate if stack syntax is correct. :param stack: The stack/puppet_role that needs to be validated :type stack: string :return: Nothing, exit if stack/puppet_role has underscore ''' if "_" in stack: logger.error("Stack name should not contain '_'") exit(666)
def get_object(self, bucket_name, key): try: resp = self.s3.get_object(Bucket=bucket_name, Key=key) except Exception as e: if "An error occurred (NoSuchKey)" in e: logger.warning("Object %s in s3 bucket %s does not exist" % (key, bucket_name)) else: logger.error("%s" % e, ) return None return resp
def get_policy(self, arn=None): try: resp = self.iam.get_policy(PolicyArn=arn) except Exception as e: if "An error occurred (NoSuchEntity) when calling the GetPolicy operation" in e: logger.warning("Policy does not exist %s" % arn, ) else: logger.error("%s" % e, ) return None return resp['Policy']
def get_vpc_from_env(self, env): """ This function returns the vpc object from an environment tag string :param env: The environment that should be returned :return: A boto3.Vpc object with the requested environment """ vpcs = self.get_all_vpcs(filters=[{"Name": "tag:Environment", 'Values': [env]}]) if len(vpcs) == 1: return vpcs[0] else: logger.error("Multiple envs found: %s" % (env,)) raise ValueError
def create_iam(self, name, assume_document): """ This function create an iam role :param name: :param assume_document: :return: """ try: role = self.iam_client.create_role(RoleName=name, AssumeRolePolicyDocument=json.dumps(assume_document)) super(Iam, self).query_information(query=role) return role except Exception as e: logger.error("Exception: %s" % e)
def start(self): logger.info('Invoked starting point for rds') parser = argparse.ArgumentParser(description='ec2 tool for devops', usage='''kerrigan.py rds <command> [<args>] Second level options are: query_parameter_group returns all records of a parameter group ''' + self.global_options) parser.add_argument('command', help='Command to run') args = parser.parse_args(sys.argv[2:3]) if not hasattr(self, args.command): logger.error('Unrecognized command') parser.print_help() exit(1) getattr(self, args.command)()
def start(self): logger.info('Invoked starting point for Route53') parser = argparse.ArgumentParser(description='ec2 tool for devops', usage='''kerrigan.py route53 <command> [<args>] Second level options are: report_route53 ''' + self.global_options) parser.add_argument('command', help='Command to run') args = parser.parse_args(sys.argv[2:3]) if not hasattr(self, args.command): logger.error('Unrecognized command') parser.print_help() exit(1) getattr(self, args.command)()
def get_account_id(self): """ This functions extracts the AWS account id from the user object :return: the AWs account id :rtype: int """ user_data = self.get_user() if 'Arn' in user_data: arn = user_data['Arn'] ret = Misc.parse_arn(arn=arn)['account-id'] else: logger.error("Can not determine the Account ID, User has no ARN") raise ValueError return ret
def start(self): logger.info('Invoked starting point for Cloudformation') parser = argparse.ArgumentParser(description='cloudformation tool for devops', usage='''kerrigan.py cloudformation <command> [<args>] Second level options are: report_cloudformation ''' + self.global_options) parser.add_argument('command', help='Command to run') args = parser.parse_args(sys.argv[2:3]) if not hasattr(self, args.command): logger.error('Unrecognized command') parser.print_help() exit(1) getattr(self, args.command)()
def get_zoneid_from_domain(self, domain=None): zones = self.list_hosted_zones() logger.debug("Searching for domain %s" % (domain,)) concat = domain + '.' logger.debug("Concated domain name is %s" % (concat,)) for zone in zones: if zone['Name'] == concat: if zone['Id'].startswith('/hostedzone/'): tmp = zone['Id'].replace('/hostedzone/', '') return tmp else: return zone['Id'] logger.error("Could not find zone with domain name") return None
def update_role_policy(self, role_name, policy_name, policy_document): """ This function updates a role policy :param role_name: :param policy_name: :param policy_document: :return: """ try: ret = self.iam_client.put_role_policy(RoleName=role_name, PolicyName=policy_name, PolicyDocument=policy_document) super(Iam, self).query_information(query=ret) return ret except Exception as e: logger.error("Exception: %s" % e)
def iam_role_exists(self, name): """ This function tests if iam role exists :param name: :return: """ try: self.get_role(name=name) return True except Exception as e: if e.response['Error']['Code'] == "NoSuchEntity": return False else: logger.error(e) exit(1)
def kinesis_exists(self, name): """ This function tests if kinesis stream exists or not :param name: the name of the stream to test :return: boolean if stream exists """ try: self.describe_stream(streamname=name) return True except Exception as e: if e.response['Error']['Code'] == "ResourceNotFoundException": return False else: logger.error(e) exit(1)
def dynamo_exists(self, name): """ This function checks if a dynamo table exists or not :param name: The name of table to check if exists :return: a boolean if exists or not """ try: self.describe_table(tablename=name) return True except Exception as e: if e.response['Error']['Code'] == "ResourceNotFoundException": return False else: logger.error(e) exit(1)
def get_vpc_from_env(self, env): """ This function returns the vpc object from an environment tag string :param env: The environment that should be returned :return: A boto3.Vpc object with the requested environment """ vpcs = self.get_all_vpcs(filters=[{ "Name": "tag:Environment", 'Values': [env] }]) if len(vpcs) == 1: return vpcs[0] else: logger.error("Multiple envs found: %s" % (env, )) raise ValueError
def create_iam(self, name, assume_document): """ This function create an iam role :param name: :param assume_document: :return: """ try: role = self.iam_client.create_role( RoleName=name, AssumeRolePolicyDocument=json.dumps(assume_document)) super(Iam, self).query_information(query=role) return role except Exception as e: logger.error("Exception: %s" % e)
def lambda_exists(self, name): """ This function tests if lambda exists :param name: :return: """ try: self.get_function(name=name) return True except Exception as e: if e.response['Error']['Code'] == "ResourceNotFoundException": return False else: logger.error(e) exit(1)
def info_certs(self): logger.info("Going to list all certs") parser = argparse.ArgumentParser(description='ec2 tool for devops', usage='''kerrigan.py iam info_all [<args>]] ''' + self.global_options) args = parser.parse_args(sys.argv[3:]) a = awsrequests() res = a.server_certificates_info_all() for r in res: if self.cli['csv']: r['ELB'] = Misc.join_list_to_string(list=r['ELB']) elif self.cli['table']: r['ELB'] = Misc.list_to_multiline_string(r['ELB']) else: logger.error('There is an unhandled printing. Need to investigate') logger.output(data=res, csvvar=self.cli['csv'], tablevar=self.cli['table'])
def info_s3_bucket(self, name, choice): ret = [] s = S3() region = s.get_bucket_location(name=name) s = S3(region=region) if choice == "acl": acl = s.get_bucket_acl(name=name) owner = { 'Owner_Displayname': acl['Owner']['DisplayName'], 'Owner_ID': acl['Owner']['ID'] } for grant in acl['Grants']: info = {'Permission': grant['Permission']} info.update(owner) for key in [ 'DisplayName', 'EmailAddress', 'ID', 'Type', 'URI' ]: if key in grant['Grantee']: value = grant['Grantee'][key] else: value = "" info.update({key: value}) ret.append(info) elif choice == 'lifecycle': lifecycle = s.get_bucket_lifecycle(name=name) print lifecycle # FIXME need to test and configure elif choice == 'region': ret.append({'Region': region}) elif choice == 'logging': logging = s.get_bucket_logging(name=name) print logging # FIXME need to test and configure elif choice == 'policy': policy = s.get_bucket_policy(name=name) print policy # FIXME need to test and configure elif choice == 'replication': replication = s.get_bucket_replication(name=name) print replication # FIXME need to test and configure elif choice == 'tagging': tags = s.get_bucket_tagging(name=name) ret = tags else: logger.error("Unknown choice request: %s" % choice, ) return ret
def update_role_policy(self, role_name, policy_name, policy_document): """ This function updates a role policy :param role_name: :param policy_name: :param policy_document: :return: """ try: ret = self.iam_client.put_role_policy( RoleName=role_name, PolicyName=policy_name, PolicyDocument=policy_document) super(Iam, self).query_information(query=ret) return ret except Exception as e: logger.error("Exception: %s" % e)
def parse_arn(arn): ''' This function parses an arn and returns a dict with items seperated :param arn: A aws arn :type arn: string :return: A dict with the arn splitted :rtype: dict ''' splitted_arn = arn.split(':') if len(splitted_arn) < 6: logger.error("Invalid arn: %s" % arn, ) return None ret = {'arn': splitted_arn[0], 'partition': splitted_arn[1], 'service': splitted_arn[2], 'region': splitted_arn[3], 'account-id': splitted_arn[4], 'resource': splitted_arn[5]} if len(splitted_arn) > 6: ret['resource_sub'] = splitted_arn[6] return ret
def update_stack(self, stackname, parameters, dryrun, templatebody=None, templateurl=None): if dryrun: logger.warning("Dryrun requested not updateing stack: %s" % (stackname,)) return None args = {'StackName': stackname, "Capabilities": ['CAPABILITY_IAM' ]} if parameters: args['Parameters'] = parameters if templatebody: args['TemplateBody'] = json.dumps(templatebody) elif templateurl: args['TemplateUrl'] = templateurl else: logger.error("No body or URL given for stack") raise ValueError resp = self.cloudformation_client.update_stack(**args) super(Cloudformation, self).query_information(query=resp) return resp['StackId']
def start_instances(self, environment=None, puppet_role=None, xively_service=None, dry_run=None): filters = [{ 'Name': 'tag:Environment', 'Values': [environment] }, { 'Name': 'tag:Puppet_role', 'Values': [puppet_role] }] if xively_service: filters.append({ 'Name': 'tag:Xively_service', 'Values': [xively_service] }) e = Ec2() res = e.get_all_instances(filters=filters) candidate_ids = [] for instance in res: if instance['State']['Name'] == 'stopped': candidate_ids.append(instance['InstanceId']) if len(candidate_ids) == 0: logger.warning("Couldn't find any instances to start") return [] if not dry_run: logger.info("Instances would start: {0}".format(candidate_ids)) res = e.start_instances(instance_ids=candidate_ids) if not ('StartingInstances' in res): logger.error( "No instance started, error happened, result:{0}".format( res)) return [] result = [] for instance in res['StartingInstances']: result.append({ 'InstanceId': instance['InstanceId'], 'State': instance['CurrentState']['Name'] }) return result else: logger.info("Instances would start: {0}".format(candidate_ids)) return []
def parse_dynamo_value(value): """ This function parses the value to convert it to simple value :param value: :return: """ key = value.keys()[0] ret = None if key == "S": ret = value[key] elif key == "L": ret = [] for item in value[key]: ret.append(parse_dynamo_value(item)) elif key == "M": ret = convert_dict(item=value[key]) else: logger.error("Key in dynamodb is not handled by kerrigan parser: %s" % (key,)) return ret
def info(self): logger.info("Starting to gather information") a = awsrequests() parser = argparse.ArgumentParser(description='ec2 tool for devops', usage='''kerrigan.py route53 info [<args>]] ''' + self.global_options) parser.add_argument('--env', action='store', help="Environment to gather information about") args = parser.parse_args(sys.argv[3:]) res = a.route53_info(env=args.env) for r in res: if self.cli['csv']: r['Values'] = Misc.join_list_to_string(list=r['Values']) elif self.cli['table']: r['Values'] = Misc.list_to_multiline_string(r['Values']) else: logger.error('There is an unhandled printing. Need to investigate') # Add information if not present: if 'Weight' not in r: r['Weight'] = '-' r['SetIdentifier'] = '-' logger.output(data=res, csvvar=self.cli['csv'], tablevar=self.cli['table'])
def start(self): logger.info('Invoked starting point for iam') parser = argparse.ArgumentParser(description='ec2 tool for devops', usage='''kerrigan.py iam <command> [<args>] Second level options are: info_certs delete_server_cert upload_server_cert compare_certs update list_users compare_iam list_user_credentials ''' + self.global_options) parser.add_argument('command', help='Command to run') args = parser.parse_args(sys.argv[2:3]) if not hasattr(self, args.command): logger.error('Unrecognized command') parser.print_help() exit(1) getattr(self, args.command)()
def get_integration(self, restid, resourceid, method): """ This function returns an integration object :param method: the method that is requested :type method: basestring :param restid: the id of the rest api object :type restid: basestring :param resourceid: id of a single resource object :type resourceid: basestring :return: None if not found, else the object """ try: ret = self.apigateway_client.get_integration(restApiId=restid, resourceId=resourceid, httpMethod=method) super(Apigateway, self).query_information(query=ret) logger.debug("Found the integration object") except Exception as e: if e.response['Error']['Code'] == "NotFoundException": logger.warning("Method integration for %s method does not exist" % (method)) else: logger.error("%s" % e, ) ret = None return ret
def get_supported_columns(service): """ This function returns the supported services dicts :param service: the service which dict should be returned :type service: basestring :return: a dict containing data :rtype: dict """ if service == "ec2": ret = ec2_columns elif service == "elb": ret = elb_columns elif service == "apigateway": ret = apigateway_columns elif service == "ami": ret = ami_columns elif service == "vpc": ret = vpc_columns elif service == "iam": ret = iam_columns elif service == "rds": ret = rds_columns elif service == "route53": ret = route53_columns elif service == "s3": ret = s3_columns elif service == "sg": ret = sg_columns elif service == "autoscale": ret = autoscale_columns elif service == "cloudformation": ret = cloudformation_columns elif service == "kinesis": ret = kinesis_columns else: logger.error("No service provided for supported columns") ret = None return ret
sg_columns = {} s3_columns = {} route53_columns = {} apigateway_columns = {'id': 'id', 'name': 'name', 'description': 'description', 'createdate': 'createdDate'} """ This variable is used to define the dynamo table for kerrigan. It should be same in all envs """ landscape_dynamo_table_name = "kerrigan" try: import yaml except ImportError: logger.error("Could not import yaml, decide what to do later") def cli_argument_parse(): # FIXME test this, need to mock sysargv somehow ''' This function parses and removes cli arguments that the argparse should not handle :return: an array with logger option and aws account options :rtype: array ''' logger.info("Parsing CLI arguments for global options") ret_logger = {'table': True, 'csv': False} ret = {} i = 0 while i < len(sys.argv): if i >= len(sys.argv):