From e7296d9c42289a6b5b6fd51189373f5c80a4107f Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Sun, 2 Oct 2016 14:45:02 -0400 Subject: [PATCH 01/16] fixes of read me for 3.3 and paths --- reference-architecture/README.md | 2 +- reference-architecture/aws-ansible/README.md | 2 ++ .../{ => aws-ansible}/images/arch.jpg | Bin 3 files changed, 3 insertions(+), 1 deletion(-) rename reference-architecture/{ => aws-ansible}/images/arch.jpg (100%) diff --git a/reference-architecture/README.md b/reference-architecture/README.md index b2d65d40f..033e082cb 100644 --- a/reference-architecture/README.md +++ b/reference-architecture/README.md @@ -1,2 +1,2 @@ # Reference Architecture for OpenShift -This repository contains a series of directories containing code used to deploy an OpenShift environment on different cloud providers. The code in this repository supplements the reference architecture guides for OpenShift 3.2. Different guides and documentation exists depending on the different providers. Regardless of the provider, the envionment will deploy 3 Masters, 2 infrastructure nodes and 2 applcation nodes. The code also deploys a Docker registry and scales the router to the number of Infrastruture nodes. +This repository contains a series of directories containing code used to deploy an OpenShift environment on different cloud providers. The code in this repository supplements the reference architecture guides for OpenShift 3.3. Different guides and documentation exists depending on the different providers. Regardless of the provider, the envionment will deploy 3 Masters, 2 infrastructure nodes and 2 applcation nodes. The code also deploys a Docker registry and scales the router to the number of Infrastruture nodes. diff --git a/reference-architecture/aws-ansible/README.md b/reference-architecture/aws-ansible/README.md index 6ae636cb9..339bd8512 100644 --- a/reference-architecture/aws-ansible/README.md +++ b/reference-architecture/aws-ansible/README.md @@ -15,10 +15,12 @@ The code in this repository handles all of the AWS specific components except fo ``` $ *subscription-manager repos --enable rhel-7-server-optional-rpms* $ *subscription-manager repos --enable rhel-7-server-ose-3.2-rpms* +$ *subscription-manager repos --enable rhel-7-server-ose-3.3-rpms* $ *rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm* $ *yum -y install atomic-openshift-utils \ * * python2-boto \ * * git \ * + * ansible-2.2.0-0.5.prerelease.el7.noarch \ * * python-netaddr \ * * python-httplib2 * ``` diff --git a/reference-architecture/images/arch.jpg b/reference-architecture/aws-ansible/images/arch.jpg similarity index 100% rename from reference-architecture/images/arch.jpg rename to reference-architecture/aws-ansible/images/arch.jpg From ad054efaa7847ab4f052a5fb7843a506e89a2f09 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Wed, 5 Oct 2016 15:53:00 -0400 Subject: [PATCH 02/16] increase wait for route propagation --- .../aws-ansible/playbooks/roles/validate-app/tasks/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference-architecture/aws-ansible/playbooks/roles/validate-app/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/validate-app/tasks/main.yaml index 431cdfb12..e1cfcfb9a 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/validate-app/tasks/main.yaml +++ b/reference-architecture/aws-ansible/playbooks/roles/validate-app/tasks/main.yaml @@ -25,7 +25,7 @@ - name: Sleep to allow for route propegation pause: - seconds: 5 + seconds: 10 - name: check the status of the page uri: url: "http://cakephp-example-validate.{{ wildcard_zone }}" From 232b70b8bc43857c1ce6357adb80d789e9ee4616 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Wed, 5 Oct 2016 15:56:06 -0400 Subject: [PATCH 03/16] README patchup --- reference-architecture/aws-ansible/README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/reference-architecture/aws-ansible/README.md b/reference-architecture/aws-ansible/README.md index 339bd8512..ac5a828db 100644 --- a/reference-architecture/aws-ansible/README.md +++ b/reference-architecture/aws-ansible/README.md @@ -13,16 +13,16 @@ A registered domain must be added to Route53 as a Hosted Zone before installatio The code in this repository handles all of the AWS specific components except for the installation of OpenShift. We rely on the OpenShift playbooks from the openshift-ansible-playbooks rpm. You will need the rpm installed on the workstation before using ose-on-aws.py. ``` -$ *subscription-manager repos --enable rhel-7-server-optional-rpms* -$ *subscription-manager repos --enable rhel-7-server-ose-3.2-rpms* -$ *subscription-manager repos --enable rhel-7-server-ose-3.3-rpms* -$ *rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm* -$ *yum -y install atomic-openshift-utils \ * - * python2-boto \ * - * git \ * - * ansible-2.2.0-0.5.prerelease.el7.noarch \ * - * python-netaddr \ * - * python-httplib2 * +$ subscription-manager repos --enable rhel-7-server-optional-rpms +$ subscription-manager repos --enable rhel-7-server-ose-3.2-rpms +$ subscription-manager repos --enable rhel-7-server-ose-3.3-rpms +$ rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm +$ yum -y install atomic-openshift-utils \ + python2-boto \ + git \ + ansible-2.2.0-0.5.prerelease.el7.noarch \ + python-netaddr \ + python-httplib2 ``` ## Usage From 3b6fc2135021053123638d1ee4a5cc320b3130c5 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Tue, 11 Oct 2016 15:30:42 -0400 Subject: [PATCH 04/16] WIP: add node playbooks --- .../aws-ansible/add-node.py | 226 +++++ .../aws-ansible/inventory/aws/hosts/ec2.py | 913 ++++++++++++++++-- .../aws-ansible/playbooks/add-node.yaml | 63 ++ .../aws-ansible/playbooks/node-setup.yaml | 44 + .../playbooks/openshift-setup.yaml | 2 +- .../files/add-infra-node.json | 131 +++ .../cloudformation-infra/files/add-node.json | 128 +++ .../files/greenfield.json | 32 +- .../cloudformation-infra/tasks/main.yaml | 51 + .../roles/instance-groups/tasks/main.yaml | 17 +- 10 files changed, 1484 insertions(+), 123 deletions(-) create mode 100755 reference-architecture/aws-ansible/add-node.py create mode 100644 reference-architecture/aws-ansible/playbooks/add-node.yaml create mode 100644 reference-architecture/aws-ansible/playbooks/node-setup.yaml create mode 100644 reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/add-infra-node.json create mode 100644 reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/add-node.json diff --git a/reference-architecture/aws-ansible/add-node.py b/reference-architecture/aws-ansible/add-node.py new file mode 100755 index 000000000..f5b448330 --- /dev/null +++ b/reference-architecture/aws-ansible/add-node.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python +# vim: sw=2 ts=2 + +import click +import os +import sys + +@click.command() + +### Cluster options +@click.option('--console-port', default='443', type=click.IntRange(1,65535), help='OpenShift web console port', + show_default=True) +@click.option('--deployment-type', default='openshift-enterprise', help='OpenShift deployment type', + show_default=True) + +### AWS/EC2 options +@click.option('--region', default='us-east-1', help='ec2 region', + show_default=True) +@click.option('--ami', default='ami-10251c7a', help='ec2 ami', + show_default=True) +@click.option('--node-instance-type', default='t2.medium', help='ec2 instance type', + show_default=True) +@click.option('--keypair', help='ec2 keypair name', + show_default=True) +@click.option('--subnet-id', help='Specify a Private subnet within the existing VPC', + show_default=True) + +### DNS options +@click.option('--public-hosted-zone', help='hosted zone for accessing the environment') +@click.option('--app-dns-prefix', default='apps', help='application dns prefix', + show_default=True) + +### Subscription and Software options +@click.option('--rhsm-user', help='Red Hat Subscription Management User') +@click.option('--rhsm-password', help='Red Hat Subscription Management Password', + hide_input=True,) +@click.option('--rhsm-pool', help='Red Hat Subscription Management Pool ID or Subscription Name') + +### Miscellaneous options +@click.option('--iam-role', help='Specify the name of the existing IAM Role', + show_default=True) +@click.option('--shortname', help='Specify the hostname of the sytem', + show_default=True) +@click.option('--existing-sg', help='Specify the already existing node security group id', + show_default=True) +@click.option('--infra-sg', help='Specify the already existing Infrastructure node security group id', + show_default=True) +@click.option('--node-type', default='app', help='Specify the node label (example: infra or app)', + show_default=True) +@click.option('--infra-elb-name', help='Specify the name of the ELB used for the router and registry') +@click.option('--no-confirm', is_flag=True, + help='Skip confirmation prompt') +@click.help_option('--help', '-h') +@click.option('-v', '--verbose', count=True) + +def launch_refarch_env(region=None, + ami=None, + no_confirm=False, + node_instance_type=None, + keypair=None, + subnet_id=None, + existing_sg=None, + infra_sg=None, + public_hosted_zone=None, + app_dns_prefix=None, + shortname=None, + fqdn=None, + deployment_type=None, + console_port=443, + rhsm_user=None, + rhsm_password=None, + rhsm_pool=None, + node_type=None, + iam_role=None, + infra_elb_name=None, + verbose=0): + + # Need to prompt for the R53 zone: + if public_hosted_zone is None: + public_hosted_zone = click.prompt('Hosted DNS zone for accessing the environment') + + if iam_role is None: + iam_role = click.prompt('Specify the name of the existing IAM Role') + + if existing_sg is None: + existing_sg = click.prompt('Node Security group') + + if node_type in 'infra' and infra_sg is None: + infra_sg = click.prompt('Infra Node Security group') + + if shortname is None: + shortname = click.prompt('Hostname of newly created system') + + # If no keypair is not specified fail: + if keypair is None and create_key in 'no': + click.echo('A SSH keypair must be specified or created') + sys.exit(1) + + # Name the keypair if a path is defined + if keypair is None and create_key in 'yes': + keypair = click.prompt('Specify a name for the keypair') + + # If no subnets are defined prompt: + if subnet_id is None: + subnet_id = click.prompt('Specify a Private subnet within the existing VPC') + + # If the user already provided values, don't bother asking again + if rhsm_user is None: + rhsm_user = click.prompt("RHSM username?") + if rhsm_password is None: + rhsm_password = click.prompt("RHSM password?") + if rhsm_pool is None: + rhsm_pool = click.prompt("RHSM Pool ID or Subscription Name?") + + # Calculate various DNS values + wildcard_zone="%s.%s" % (app_dns_prefix, public_hosted_zone) + + # Calculate various DNS values + fqdn="%s.%s" % (shortname, public_hosted_zone) + + # Ask for ELB if new node is infra + if node_type in 'infra' and infra_elb_name is None: + infra_elb_name = click.prompt("Specify the ELB Name used by the router and registry?") + + # Display information to the user about their choices + click.echo('Configured values:') + click.echo('\tami: %s' % ami) + click.echo('\tregion: %s' % region) + click.echo('\tnode_instance_type: %s' % node_instance_type) + click.echo('\tkeypair: %s' % keypair) + click.echo('\tsubnet_id: %s' % subnet_id) + click.echo('\texisting_sg: %s' % existing_sg) + click.echo('\tconsole port: %s' % console_port) + click.echo('\tdeployment_type: %s' % deployment_type) + click.echo('\tpublic_hosted_zone: %s' % public_hosted_zone) + click.echo('\tapp_dns_prefix: %s' % app_dns_prefix) + click.echo('\tapps_dns: %s' % wildcard_zone) + click.echo('\tshortname: %s' % shortname) + click.echo('\tfqdn: %s' % fqdn) + click.echo('\trhsm_user: %s' % rhsm_user) + click.echo('\trhsm_password: *******') + click.echo('\trhsm_pool: %s' % rhsm_pool) + click.echo('\tnode_type: %s' % node_type) + click.echo('\tiam_role: %s' % iam_role) + click.echo('\tinfra_elb_name: %s' % infra_elb_name) + click.echo("") + + if not no_confirm: + click.confirm('Continue using these values?', abort=True) + + playbooks = ['playbooks/infrastructure.yaml', 'playbooks/add-node.yaml'] + + for playbook in playbooks: + + # hide cache output unless in verbose mode + devnull='> /dev/null' + + if verbose > 0: + devnull='' + + # refresh the inventory cache to prevent stale hosts from + # interferring with re-running + command='inventory/aws/hosts/ec2.py --refresh-cache %s' % (devnull) + os.system(command) + + # remove any cached facts to prevent stale data during a re-run + command='rm -rf .ansible/cached_facts' + os.system(command) + + command='ansible-playbook -i inventory/aws/hosts -e \'region=%s \ + ami=%s \ + keypair=%s \ + add_node=yes \ + create_key=no \ + create_vpc=no \ + subnet_id=%s \ + existing_sg=%s \ + infra_sg=%s \ + node_instance_type=%s \ + public_hosted_zone=%s \ + wildcard_zone=%s \ + shortname=%s \ + fqdn=%s \ + console_port=%s \ + deployment_type=%s \ + rhsm_user=%s \ + rhsm_password=%s \ + rhsm_pool=%s \ + node_type=%s \ + iam_role=%s \ + infra_elb_name=%s \' %s' % (region, + ami, + keypair, + subnet_id, + existing_sg, + infra_sg, + node_instance_type, + public_hosted_zone, + wildcard_zone, + shortname, + fqdn, + console_port, + deployment_type, + rhsm_user, + rhsm_password, + rhsm_pool, + node_type, + iam_role, + infra_elb_name, + playbook) + + if verbose > 0: + command += " -" + "".join(['v']*verbose) + click.echo('We are running: %s' % command) + + status = os.system(command) + if os.WIFEXITED(status) and os.WEXITSTATUS(status) != 0: + return os.WEXITSTATUS(status) + +if __name__ == '__main__': + # check for AWS access info + if os.getenv('AWS_ACCESS_KEY_ID') is None or os.getenv('AWS_SECRET_ACCESS_KEY') is None: + print 'AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY **MUST** be exported as environment variables.' + sys.exit(1) + + launch_refarch_env(auto_envvar_prefix='OSE_REFArch') diff --git a/reference-architecture/aws-ansible/inventory/aws/hosts/ec2.py b/reference-architecture/aws-ansible/inventory/aws/hosts/ec2.py index a94adabd8..3def6037a 100755 --- a/reference-architecture/aws-ansible/inventory/aws/hosts/ec2.py +++ b/reference-architecture/aws-ansible/inventory/aws/hosts/ec2.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python ''' EC2 external inventory script @@ -22,6 +22,12 @@ export EC2_URL=http://hostname_of_your_cc:port/services/Eucalyptus +If you're using boto profiles (requires boto>=2.24.0) you can choose a profile +using the --boto-profile command line argument (e.g. ec2.py --boto-profile prod) or using +the AWS_PROFILE variable: + + AWS_PROFILE=prod ansible-playbook -i ec2.py myplaybook.yml + For more details, see: http://docs.pythonboto.org/en/latest/boto_config_tut.html When run against a specific host, this script returns the following variables: @@ -31,6 +37,7 @@ - ec2_attachTime - ec2_attachment - ec2_attachmentId + - ec2_block_devices - ec2_client_token - ec2_deleteOnTermination - ec2_description @@ -121,8 +128,20 @@ import boto from boto import ec2 from boto import rds +from boto import elasticache from boto import route53 -import ConfigParser +import six + +from ansible.module_utils import ec2 as ec2_utils + +HAS_BOTO3 = False +try: + import boto3 + HAS_BOTO3 = True +except ImportError: + pass + +from six.moves import configparser from collections import defaultdict try: @@ -132,6 +151,7 @@ class Ec2Inventory(object): + def _empty_inventory(self): return {"_meta" : {"hostvars" : {}}} @@ -145,9 +165,21 @@ def __init__(self): # Index of hostname (address) to instance ID self.index = {} + # Boto profile to use (if any) + self.boto_profile = None + + # AWS credentials. + self.credentials = {} + # Read settings and parse CLI arguments - self.read_settings() self.parse_cli_args() + self.read_settings() + + # Make sure that profile_name is not passed at all if not set + # as pre 2.24 boto will fall over otherwise + if self.boto_profile: + if not hasattr(boto.ec2.EC2Connection, 'profile_name'): + self.fail_with_error("boto version must be >= 2.24 to use profile") # Cache if self.args.refresh_cache: @@ -166,7 +198,7 @@ def __init__(self): else: data_to_print = self.json_format_dict(self.inventory, True) - print data_to_print + print(data_to_print) def is_cache_valid(self): @@ -184,10 +216,12 @@ def is_cache_valid(self): def read_settings(self): ''' Reads the settings from the ec2.ini file ''' - - config = ConfigParser.SafeConfigParser() + if six.PY3: + config = configparser.ConfigParser() + else: + config = configparser.SafeConfigParser() ec2_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ec2.ini') - ec2_ini_path = os.environ.get('EC2_INI_PATH', ec2_default_ini_path) + ec2_ini_path = os.path.expanduser(os.path.expandvars(os.environ.get('EC2_INI_PATH', ec2_default_ini_path))) config.read(ec2_ini_path) # is eucalyptus? @@ -204,7 +238,7 @@ def read_settings(self): configRegions_exclude = config.get('ec2', 'regions_exclude') if (configRegions == 'all'): if self.eucalyptus_host: - self.regions.append(boto.connect_euca(host=self.eucalyptus_host).region.name) + self.regions.append(boto.connect_euca(host=self.eucalyptus_host).region.name, **self.credentials) else: for regionInfo in ec2.regions(): if regionInfo.name not in configRegions_exclude: @@ -216,6 +250,11 @@ def read_settings(self): self.destination_variable = config.get('ec2', 'destination_variable') self.vpc_destination_variable = config.get('ec2', 'vpc_destination_variable') + if config.has_option('ec2', 'hostname_variable'): + self.hostname_variable = config.get('ec2', 'hostname_variable') + else: + self.hostname_variable = None + if config.has_option('ec2', 'destination_format') and \ config.has_option('ec2', 'destination_format_tags'): self.destination_format = config.get('ec2', 'destination_format') @@ -236,31 +275,132 @@ def read_settings(self): if config.has_option('ec2', 'rds'): self.rds_enabled = config.getboolean('ec2', 'rds') - # Return all EC2 and RDS instances (if RDS is enabled) + # Include RDS cluster instances? + if config.has_option('ec2', 'include_rds_clusters'): + self.include_rds_clusters = config.getboolean('ec2', 'include_rds_clusters') + else: + self.include_rds_clusters = False + + # Include ElastiCache instances? + self.elasticache_enabled = True + if config.has_option('ec2', 'elasticache'): + self.elasticache_enabled = config.getboolean('ec2', 'elasticache') + + # Return all EC2 instances? if config.has_option('ec2', 'all_instances'): self.all_instances = config.getboolean('ec2', 'all_instances') else: self.all_instances = False + + # Instance states to be gathered in inventory. Default is 'running'. + # Setting 'all_instances' to 'yes' overrides this option. + ec2_valid_instance_states = [ + 'pending', + 'running', + 'shutting-down', + 'terminated', + 'stopping', + 'stopped' + ] + self.ec2_instance_states = [] + if self.all_instances: + self.ec2_instance_states = ec2_valid_instance_states + elif config.has_option('ec2', 'instance_states'): + for instance_state in config.get('ec2', 'instance_states').split(','): + instance_state = instance_state.strip() + if instance_state not in ec2_valid_instance_states: + continue + self.ec2_instance_states.append(instance_state) + else: + self.ec2_instance_states = ['running'] + + # Return all RDS instances? (if RDS is enabled) if config.has_option('ec2', 'all_rds_instances') and self.rds_enabled: self.all_rds_instances = config.getboolean('ec2', 'all_rds_instances') else: self.all_rds_instances = False + # Return all ElastiCache replication groups? (if ElastiCache is enabled) + if config.has_option('ec2', 'all_elasticache_replication_groups') and self.elasticache_enabled: + self.all_elasticache_replication_groups = config.getboolean('ec2', 'all_elasticache_replication_groups') + else: + self.all_elasticache_replication_groups = False + + # Return all ElastiCache clusters? (if ElastiCache is enabled) + if config.has_option('ec2', 'all_elasticache_clusters') and self.elasticache_enabled: + self.all_elasticache_clusters = config.getboolean('ec2', 'all_elasticache_clusters') + else: + self.all_elasticache_clusters = False + + # Return all ElastiCache nodes? (if ElastiCache is enabled) + if config.has_option('ec2', 'all_elasticache_nodes') and self.elasticache_enabled: + self.all_elasticache_nodes = config.getboolean('ec2', 'all_elasticache_nodes') + else: + self.all_elasticache_nodes = False + + # boto configuration profile (prefer CLI argument) + self.boto_profile = self.args.boto_profile + if config.has_option('ec2', 'boto_profile') and not self.boto_profile: + self.boto_profile = config.get('ec2', 'boto_profile') + + # AWS credentials (prefer environment variables) + if not (self.boto_profile or os.environ.get('AWS_ACCESS_KEY_ID') or + os.environ.get('AWS_PROFILE')): + if config.has_option('credentials', 'aws_access_key_id'): + aws_access_key_id = config.get('credentials', 'aws_access_key_id') + else: + aws_access_key_id = None + if config.has_option('credentials', 'aws_secret_access_key'): + aws_secret_access_key = config.get('credentials', 'aws_secret_access_key') + else: + aws_secret_access_key = None + if config.has_option('credentials', 'aws_security_token'): + aws_security_token = config.get('credentials', 'aws_security_token') + else: + aws_security_token = None + if aws_access_key_id: + self.credentials = { + 'aws_access_key_id': aws_access_key_id, + 'aws_secret_access_key': aws_secret_access_key + } + if aws_security_token: + self.credentials['security_token'] = aws_security_token + # Cache related cache_dir = os.path.expanduser(config.get('ec2', 'cache_path')) + if self.boto_profile: + cache_dir = os.path.join(cache_dir, 'profile_' + self.boto_profile) if not os.path.exists(cache_dir): os.makedirs(cache_dir) - self.cache_path_cache = cache_dir + "/ansible-ec2.cache" - self.cache_path_index = cache_dir + "/ansible-ec2.index" + cache_name = 'ansible-ec2' + aws_profile = lambda: (self.boto_profile or + os.environ.get('AWS_PROFILE') or + os.environ.get('AWS_ACCESS_KEY_ID') or + self.credentials.get('aws_access_key_id', None)) + if aws_profile(): + cache_name = '%s-%s' % (cache_name, aws_profile()) + self.cache_path_cache = cache_dir + "/%s.cache" % cache_name + self.cache_path_index = cache_dir + "/%s.index" % cache_name self.cache_max_age = config.getint('ec2', 'cache_max_age') + if config.has_option('ec2', 'expand_csv_tags'): + self.expand_csv_tags = config.getboolean('ec2', 'expand_csv_tags') + else: + self.expand_csv_tags = False + # Configure nested groups instead of flat namespace. if config.has_option('ec2', 'nested_groups'): self.nested_groups = config.getboolean('ec2', 'nested_groups') else: self.nested_groups = False + # Replace dash or not in group names + if config.has_option('ec2', 'replace_dash_in_groups'): + self.replace_dash_in_groups = config.getboolean('ec2', 'replace_dash_in_groups') + else: + self.replace_dash_in_groups = True + # Configure which groups should be created. group_by_options = [ 'group_by_instance_id', @@ -276,6 +416,10 @@ def read_settings(self): 'group_by_route53_names', 'group_by_rds_engine', 'group_by_rds_parameter_group', + 'group_by_elasticache_engine', + 'group_by_elasticache_cluster', + 'group_by_elasticache_parameter_group', + 'group_by_elasticache_replication_group', ] for option in group_by_options: if config.has_option('ec2', option): @@ -290,7 +434,7 @@ def read_settings(self): self.pattern_include = re.compile(pattern_include) else: self.pattern_include = None - except ConfigParser.NoOptionError, e: + except configparser.NoOptionError: self.pattern_include = None # Do we need to exclude hosts that match a pattern? @@ -300,13 +444,16 @@ def read_settings(self): self.pattern_exclude = re.compile(pattern_exclude) else: self.pattern_exclude = None - except ConfigParser.NoOptionError, e: + except configparser.NoOptionError: self.pattern_exclude = None # Instance filters (see boto and EC2 API docs). Ignore invalid filters. self.ec2_instance_filters = defaultdict(list) if config.has_option('ec2', 'instance_filters'): - for instance_filter in config.get('ec2', 'instance_filters', '').split(','): + + filters = [f for f in config.get('ec2', 'instance_filters').split(',') if f] + + for instance_filter in filters: instance_filter = instance_filter.strip() if not instance_filter or '=' not in instance_filter: continue @@ -325,6 +472,8 @@ def parse_cli_args(self): help='Get all the variables about a specific instance') parser.add_argument('--refresh-cache', action='store_true', default=False, help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)') + parser.add_argument('--profile', '--boto-profile', action='store', dest='boto_profile', + help='Use boto profile for connections to EC2') self.args = parser.parse_args() @@ -338,72 +487,267 @@ def do_api_calls_update_cache(self): self.get_instances_by_region(region) if self.rds_enabled: self.get_rds_instances_by_region(region) + if self.elasticache_enabled: + self.get_elasticache_clusters_by_region(region) + self.get_elasticache_replication_groups_by_region(region) + if self.include_rds_clusters: + self.include_rds_clusters_by_region(region) self.write_to_cache(self.inventory, self.cache_path_cache) self.write_to_cache(self.index, self.cache_path_index) + def connect(self, region): + ''' create connection to api server''' + if self.eucalyptus: + conn = boto.connect_euca(host=self.eucalyptus_host, **self.credentials) + conn.APIVersion = '2010-08-31' + else: + conn = self.connect_to_aws(ec2, region) + return conn + + def boto_fix_security_token_in_profile(self, connect_args): + ''' monkey patch for boto issue boto/boto#2100 ''' + profile = 'profile ' + self.boto_profile + if boto.config.has_option(profile, 'aws_security_token'): + connect_args['security_token'] = boto.config.get(profile, 'aws_security_token') + return connect_args + + def connect_to_aws(self, module, region): + connect_args = self.credentials + + # only pass the profile name if it's set (as it is not supported by older boto versions) + if self.boto_profile: + connect_args['profile_name'] = self.boto_profile + self.boto_fix_security_token_in_profile(connect_args) + + conn = module.connect_to_region(region, **connect_args) + # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported + if conn is None: + self.fail_with_error("region name: %s likely not supported, or AWS is down. connection to region failed." % region) + return conn def get_instances_by_region(self, region): ''' Makes an AWS EC2 API call to the list of instances in a particular region ''' try: - if self.eucalyptus: - conn = boto.connect_euca(host=self.eucalyptus_host) - conn.APIVersion = '2010-08-31' - else: - conn = ec2.connect_to_region(region) - - # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported - if conn is None: - print("region name: %s likely not supported, or AWS is down. connection to region failed." % region) - sys.exit(1) - + conn = self.connect(region) reservations = [] if self.ec2_instance_filters: - for filter_key, filter_values in self.ec2_instance_filters.iteritems(): + for filter_key, filter_values in self.ec2_instance_filters.items(): reservations.extend(conn.get_all_instances(filters = { filter_key : filter_values })) else: reservations = conn.get_all_instances() + # Pull the tags back in a second step + # AWS are on record as saying that the tags fetched in the first `get_all_instances` request are not + # reliable and may be missing, and the only way to guarantee they are there is by calling `get_all_tags` + instance_ids = [] + for reservation in reservations: + instance_ids.extend([instance.id for instance in reservation.instances]) + + max_filter_value = 199 + tags = [] + for i in range(0, len(instance_ids), max_filter_value): + tags.extend(conn.get_all_tags(filters={'resource-type': 'instance', 'resource-id': instance_ids[i:i+max_filter_value]})) + + tags_by_instance_id = defaultdict(dict) + for tag in tags: + tags_by_instance_id[tag.res_id][tag.name] = tag.value + for reservation in reservations: for instance in reservation.instances: + instance.tags = tags_by_instance_id[instance.id] self.add_instance(instance, region) - except boto.exception.BotoServerError, e: - if not self.eucalyptus: - print "Looks like AWS is down again:" - print e - sys.exit(1) + except boto.exception.BotoServerError as e: + if e.error_code == 'AuthFailure': + error = self.get_auth_error_message() + else: + backend = 'Eucalyptus' if self.eucalyptus else 'AWS' + error = "Error connecting to %s backend.\n%s" % (backend, e.message) + self.fail_with_error(error, 'getting EC2 instances') def get_rds_instances_by_region(self, region): ''' Makes an AWS API call to the list of RDS instances in a particular region ''' try: - conn = rds.connect_to_region(region) + conn = self.connect_to_aws(rds, region) if conn: - instances = conn.get_all_dbinstances() - for instance in instances: - self.add_rds_instance(instance, region) - except boto.exception.BotoServerError, e: + marker = None + while True: + instances = conn.get_all_dbinstances(marker=marker) + marker = instances.marker + for instance in instances: + self.add_rds_instance(instance, region) + if not marker: + break + except boto.exception.BotoServerError as e: + error = e.reason + + if e.error_code == 'AuthFailure': + error = self.get_auth_error_message() if not e.reason == "Forbidden": - print "Looks like AWS RDS is down: " - print e - sys.exit(1) + error = "Looks like AWS RDS is down:\n%s" % e.message + self.fail_with_error(error, 'getting RDS instances') + + def include_rds_clusters_by_region(self, region): + if not HAS_BOTO3: + self.fail_with_error("Working with RDS clusters requires boto3 - please install boto3 and try again", + "getting RDS clusters") + + client = ec2_utils.boto3_inventory_conn('client', 'rds', region, **self.credentials) + + marker, clusters = '', [] + while marker is not None: + resp = client.describe_db_clusters(Marker=marker) + clusters.extend(resp["DBClusters"]) + marker = resp.get('Marker', None) + + account_id = boto.connect_iam().get_user().arn.split(':')[4] + c_dict = {} + for c in clusters: + # remove these datetime objects as there is no serialisation to json + # currently in place and we don't need the data yet + if 'EarliestRestorableTime' in c: + del c['EarliestRestorableTime'] + if 'LatestRestorableTime' in c: + del c['LatestRestorableTime'] + + if self.ec2_instance_filters == {}: + matches_filter = True + else: + matches_filter = False - def get_instance(self, region, instance_id): - ''' Gets details about a specific instance ''' - if self.eucalyptus: - conn = boto.connect_euca(self.eucalyptus_host) - conn.APIVersion = '2010-08-31' + try: + # arn:aws:rds:::: + tags = client.list_tags_for_resource( + ResourceName='arn:aws:rds:' + region + ':' + account_id + ':cluster:' + c['DBClusterIdentifier']) + c['Tags'] = tags['TagList'] + + if self.ec2_instance_filters: + for filter_key, filter_values in self.ec2_instance_filters.items(): + # get AWS tag key e.g. tag:env will be 'env' + tag_name = filter_key.split(":", 1)[1] + # Filter values is a list (if you put multiple values for the same tag name) + matches_filter = any(d['Key'] == tag_name and d['Value'] in filter_values for d in c['Tags']) + + if matches_filter: + # it matches a filter, so stop looking for further matches + break + + except Exception as e: + if e.message.find('DBInstanceNotFound') >= 0: + # AWS RDS bug (2016-01-06) means deletion does not fully complete and leave an 'empty' cluster. + # Ignore errors when trying to find tags for these + pass + + # ignore empty clusters caused by AWS bug + if len(c['DBClusterMembers']) == 0: + continue + elif matches_filter: + c_dict[c['DBClusterIdentifier']] = c + + self.inventory['db_clusters'] = c_dict + + def get_elasticache_clusters_by_region(self, region): + ''' Makes an AWS API call to the list of ElastiCache clusters (with + nodes' info) in a particular region.''' + + # ElastiCache boto module doesn't provide a get_all_intances method, + # that's why we need to call describe directly (it would be called by + # the shorthand method anyway...) + try: + conn = self.connect_to_aws(elasticache, region) + if conn: + # show_cache_node_info = True + # because we also want nodes' information + response = conn.describe_cache_clusters(None, None, None, True) + + except boto.exception.BotoServerError as e: + error = e.reason + + if e.error_code == 'AuthFailure': + error = self.get_auth_error_message() + if not e.reason == "Forbidden": + error = "Looks like AWS ElastiCache is down:\n%s" % e.message + self.fail_with_error(error, 'getting ElastiCache clusters') + + try: + # Boto also doesn't provide wrapper classes to CacheClusters or + # CacheNodes. Because of that wo can't make use of the get_list + # method in the AWSQueryConnection. Let's do the work manually + clusters = response['DescribeCacheClustersResponse']['DescribeCacheClustersResult']['CacheClusters'] + + except KeyError as e: + error = "ElastiCache query to AWS failed (unexpected format)." + self.fail_with_error(error, 'getting ElastiCache clusters') + + for cluster in clusters: + self.add_elasticache_cluster(cluster, region) + + def get_elasticache_replication_groups_by_region(self, region): + ''' Makes an AWS API call to the list of ElastiCache replication groups + in a particular region.''' + + # ElastiCache boto module doesn't provide a get_all_intances method, + # that's why we need to call describe directly (it would be called by + # the shorthand method anyway...) + try: + conn = self.connect_to_aws(elasticache, region) + if conn: + response = conn.describe_replication_groups() + + except boto.exception.BotoServerError as e: + error = e.reason + + if e.error_code == 'AuthFailure': + error = self.get_auth_error_message() + if not e.reason == "Forbidden": + error = "Looks like AWS ElastiCache [Replication Groups] is down:\n%s" % e.message + self.fail_with_error(error, 'getting ElastiCache clusters') + + try: + # Boto also doesn't provide wrapper classes to ReplicationGroups + # Because of that wo can't make use of the get_list method in the + # AWSQueryConnection. Let's do the work manually + replication_groups = response['DescribeReplicationGroupsResponse']['DescribeReplicationGroupsResult']['ReplicationGroups'] + + except KeyError as e: + error = "ElastiCache [Replication Groups] query to AWS failed (unexpected format)." + self.fail_with_error(error, 'getting ElastiCache clusters') + + for replication_group in replication_groups: + self.add_elasticache_replication_group(replication_group, region) + + def get_auth_error_message(self): + ''' create an informative error message if there is an issue authenticating''' + errors = ["Authentication error retrieving ec2 inventory."] + if None in [os.environ.get('AWS_ACCESS_KEY_ID'), os.environ.get('AWS_SECRET_ACCESS_KEY')]: + errors.append(' - No AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY environment vars found') else: - conn = ec2.connect_to_region(region) + errors.append(' - AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment vars found but may not be correct') - # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported - if conn is None: - print("region name: %s likely not supported, or AWS is down. connection to region failed." % region) - sys.exit(1) + boto_paths = ['/etc/boto.cfg', '~/.boto', '~/.aws/credentials'] + boto_config_found = list(p for p in boto_paths if os.path.isfile(os.path.expanduser(p))) + if len(boto_config_found) > 0: + errors.append(" - Boto configs found at '%s', but the credentials contained may not be correct" % ', '.join(boto_config_found)) + else: + errors.append(" - No Boto config found at any expected location '%s'" % ', '.join(boto_paths)) + + return '\n'.join(errors) + + def fail_with_error(self, err_msg, err_operation=None): + '''log an error to std err for ansible-playbook to consume and exit''' + if err_operation: + err_msg = 'ERROR: "{err_msg}", while: {err_operation}'.format( + err_msg=err_msg, err_operation=err_operation) + sys.stderr.write(err_msg) + sys.exit(1) + + def get_instance(self, region, instance_id): + conn = self.connect(region) reservations = conn.get_all_instances([instance_id]) for reservation in reservations: @@ -414,13 +758,13 @@ def add_instance(self, instance, region): ''' Adds an instance to the inventory and index, as long as it is addressable ''' - # Only want running instances unless all_instances is True - if not self.all_instances and instance.state != 'running': + # Only return instances with desired instance states + if instance.state not in self.ec2_instance_states: return # Select the best destination address if self.destination_format and self.destination_format_tags: - dest = self.destination_format.format(*[ getattr(instance, 'tags').get(tag, 'nil') for tag in self.destination_format_tags ]) + dest = self.destination_format.format(*[ getattr(instance, 'tags').get(tag, '') for tag in self.destination_format_tags ]) elif instance.subnet_id: dest = getattr(instance, self.vpc_destination_variable, None) if dest is None: @@ -434,32 +778,46 @@ def add_instance(self, instance, region): # Skip instances we cannot address (e.g. private VPC subnet) return + # Set the inventory name + hostname = None + if self.hostname_variable: + if self.hostname_variable.startswith('tag_'): + hostname = instance.tags.get(self.hostname_variable[4:], None) + else: + hostname = getattr(instance, self.hostname_variable) + + # If we can't get a nice hostname, use the destination address + if not hostname: + hostname = dest + else: + hostname = self.to_safe(hostname).lower() + # if we only want to include hosts that match a pattern, skip those that don't - if self.pattern_include and not self.pattern_include.match(dest): + if self.pattern_include and not self.pattern_include.match(hostname): return # if we need to exclude hosts that match a pattern, skip those - if self.pattern_exclude and self.pattern_exclude.match(dest): + if self.pattern_exclude and self.pattern_exclude.match(hostname): return # Add to index - self.index[dest] = [region, instance.id] + self.index[hostname] = [region, instance.id] # Inventory: Group by instance ID (always a group of 1) if self.group_by_instance_id: - self.inventory[instance.id] = [dest] + self.inventory[instance.id] = [hostname] if self.nested_groups: self.push_group(self.inventory, 'instances', instance.id) # Inventory: Group by region if self.group_by_region: - self.push(self.inventory, region, dest) + self.push(self.inventory, region, hostname) if self.nested_groups: self.push_group(self.inventory, 'regions', region) # Inventory: Group by availability zone if self.group_by_availability_zone: - self.push(self.inventory, instance.placement, dest) + self.push(self.inventory, instance.placement, hostname) if self.nested_groups: if self.group_by_region: self.push_group(self.inventory, region, instance.placement) @@ -468,28 +826,28 @@ def add_instance(self, instance, region): # Inventory: Group by Amazon Machine Image (AMI) ID if self.group_by_ami_id: ami_id = self.to_safe(instance.image_id) - self.push(self.inventory, ami_id, dest) + self.push(self.inventory, ami_id, hostname) if self.nested_groups: self.push_group(self.inventory, 'images', ami_id) # Inventory: Group by instance type if self.group_by_instance_type: type_name = self.to_safe('type_' + instance.instance_type) - self.push(self.inventory, type_name, dest) + self.push(self.inventory, type_name, hostname) if self.nested_groups: self.push_group(self.inventory, 'types', type_name) # Inventory: Group by key pair if self.group_by_key_pair and instance.key_name: key_name = self.to_safe('key_' + instance.key_name) - self.push(self.inventory, key_name, dest) + self.push(self.inventory, key_name, hostname) if self.nested_groups: self.push_group(self.inventory, 'keys', key_name) # Inventory: Group by VPC if self.group_by_vpc_id and instance.vpc_id: vpc_id_name = self.to_safe('vpc_id_' + instance.vpc_id) - self.push(self.inventory, vpc_id_name, dest) + self.push(self.inventory, vpc_id_name, hostname) if self.nested_groups: self.push_group(self.inventory, 'vpcs', vpc_id_name) @@ -498,41 +856,51 @@ def add_instance(self, instance, region): try: for group in instance.groups: key = self.to_safe("security_group_" + group.name) - self.push(self.inventory, key, dest) + self.push(self.inventory, key, hostname) if self.nested_groups: self.push_group(self.inventory, 'security_groups', key) except AttributeError: - print 'Package boto seems a bit older.' - print 'Please upgrade boto >= 2.3.0.' - sys.exit(1) + self.fail_with_error('\n'.join(['Package boto seems a bit older.', + 'Please upgrade boto >= 2.3.0.'])) # Inventory: Group by tag keys if self.group_by_tag_keys: - for k, v in instance.tags.iteritems(): - key = self.to_safe("tag_" + k + "=" + v) - self.push(self.inventory, key, dest) - if self.nested_groups: - self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k)) - self.push_group(self.inventory, self.to_safe("tag_" + k), key) + for k, v in instance.tags.items(): + if self.expand_csv_tags and v and ',' in v: + values = map(lambda x: x.strip(), v.split(',')) + else: + values = [v] + + for v in values: + if v: + key = self.to_safe("tag_" + k + "=" + v) + else: + key = self.to_safe("tag_" + k) + self.push(self.inventory, key, hostname) + if self.nested_groups: + self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k)) + if v: + self.push_group(self.inventory, self.to_safe("tag_" + k), key) # Inventory: Group by Route53 domain names if enabled if self.route53_enabled and self.group_by_route53_names: route53_names = self.get_instance_route53_names(instance) for name in route53_names: - self.push(self.inventory, name, dest) + self.push(self.inventory, name, hostname) if self.nested_groups: self.push_group(self.inventory, 'route53', name) # Global Tag: instances without tags if self.group_by_tag_none and len(instance.tags) == 0: - self.push(self.inventory, 'tag_none', dest) + self.push(self.inventory, 'tag_none', hostname) if self.nested_groups: self.push_group(self.inventory, 'tags', 'tag_none') # Global Tag: tag all EC2 instances - self.push(self.inventory, 'ec2', dest) + self.push(self.inventory, 'ec2', hostname) - self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance) + self.inventory["_meta"]["hostvars"][hostname] = self.get_host_info_dict_from_instance(instance) + self.inventory["_meta"]["hostvars"][hostname]['ansible_ssh_host'] = dest def add_rds_instance(self, instance, region): @@ -550,24 +918,38 @@ def add_rds_instance(self, instance, region): # Skip instances we cannot address (e.g. private VPC subnet) return + # Set the inventory name + hostname = None + if self.hostname_variable: + if self.hostname_variable.startswith('tag_'): + hostname = instance.tags.get(self.hostname_variable[4:], None) + else: + hostname = getattr(instance, self.hostname_variable) + + # If we can't get a nice hostname, use the destination address + if not hostname: + hostname = dest + + hostname = self.to_safe(hostname).lower() + # Add to index - self.index[dest] = [region, instance.id] + self.index[hostname] = [region, instance.id] # Inventory: Group by instance ID (always a group of 1) if self.group_by_instance_id: - self.inventory[instance.id] = [dest] + self.inventory[instance.id] = [hostname] if self.nested_groups: self.push_group(self.inventory, 'instances', instance.id) # Inventory: Group by region if self.group_by_region: - self.push(self.inventory, region, dest) + self.push(self.inventory, region, hostname) if self.nested_groups: self.push_group(self.inventory, 'regions', region) # Inventory: Group by availability zone if self.group_by_availability_zone: - self.push(self.inventory, instance.availability_zone, dest) + self.push(self.inventory, instance.availability_zone, hostname) if self.nested_groups: if self.group_by_region: self.push_group(self.inventory, region, instance.availability_zone) @@ -576,14 +958,14 @@ def add_rds_instance(self, instance, region): # Inventory: Group by instance type if self.group_by_instance_type: type_name = self.to_safe('type_' + instance.instance_class) - self.push(self.inventory, type_name, dest) + self.push(self.inventory, type_name, hostname) if self.nested_groups: self.push_group(self.inventory, 'types', type_name) # Inventory: Group by VPC if self.group_by_vpc_id and instance.subnet_group and instance.subnet_group.vpc_id: vpc_id_name = self.to_safe('vpc_id_' + instance.subnet_group.vpc_id) - self.push(self.inventory, vpc_id_name, dest) + self.push(self.inventory, vpc_id_name, hostname) if self.nested_groups: self.push_group(self.inventory, 'vpcs', vpc_id_name) @@ -592,32 +974,270 @@ def add_rds_instance(self, instance, region): try: if instance.security_group: key = self.to_safe("security_group_" + instance.security_group.name) - self.push(self.inventory, key, dest) + self.push(self.inventory, key, hostname) if self.nested_groups: self.push_group(self.inventory, 'security_groups', key) except AttributeError: - print 'Package boto seems a bit older.' - print 'Please upgrade boto >= 2.3.0.' - sys.exit(1) + self.fail_with_error('\n'.join(['Package boto seems a bit older.', + 'Please upgrade boto >= 2.3.0.'])) + # Inventory: Group by engine if self.group_by_rds_engine: - self.push(self.inventory, self.to_safe("rds_" + instance.engine), dest) + self.push(self.inventory, self.to_safe("rds_" + instance.engine), hostname) if self.nested_groups: self.push_group(self.inventory, 'rds_engines', self.to_safe("rds_" + instance.engine)) # Inventory: Group by parameter group if self.group_by_rds_parameter_group: - self.push(self.inventory, self.to_safe("rds_parameter_group_" + instance.parameter_group.name), dest) + self.push(self.inventory, self.to_safe("rds_parameter_group_" + instance.parameter_group.name), hostname) if self.nested_groups: self.push_group(self.inventory, 'rds_parameter_groups', self.to_safe("rds_parameter_group_" + instance.parameter_group.name)) # Global Tag: all RDS instances - self.push(self.inventory, 'rds', dest) + self.push(self.inventory, 'rds', hostname) - self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance) + self.inventory["_meta"]["hostvars"][hostname] = self.get_host_info_dict_from_instance(instance) + self.inventory["_meta"]["hostvars"][hostname]['ansible_ssh_host'] = dest + def add_elasticache_cluster(self, cluster, region): + ''' Adds an ElastiCache cluster to the inventory and index, as long as + it's nodes are addressable ''' + + # Only want available clusters unless all_elasticache_clusters is True + if not self.all_elasticache_clusters and cluster['CacheClusterStatus'] != 'available': + return + + # Select the best destination address + if 'ConfigurationEndpoint' in cluster and cluster['ConfigurationEndpoint']: + # Memcached cluster + dest = cluster['ConfigurationEndpoint']['Address'] + is_redis = False + else: + # Redis sigle node cluster + # Because all Redis clusters are single nodes, we'll merge the + # info from the cluster with info about the node + dest = cluster['CacheNodes'][0]['Endpoint']['Address'] + is_redis = True + + if not dest: + # Skip clusters we cannot address (e.g. private VPC subnet) + return + + # Add to index + self.index[dest] = [region, cluster['CacheClusterId']] + + # Inventory: Group by instance ID (always a group of 1) + if self.group_by_instance_id: + self.inventory[cluster['CacheClusterId']] = [dest] + if self.nested_groups: + self.push_group(self.inventory, 'instances', cluster['CacheClusterId']) + + # Inventory: Group by region + if self.group_by_region and not is_redis: + self.push(self.inventory, region, dest) + if self.nested_groups: + self.push_group(self.inventory, 'regions', region) + + # Inventory: Group by availability zone + if self.group_by_availability_zone and not is_redis: + self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest) + if self.nested_groups: + if self.group_by_region: + self.push_group(self.inventory, region, cluster['PreferredAvailabilityZone']) + self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone']) + + # Inventory: Group by node type + if self.group_by_instance_type and not is_redis: + type_name = self.to_safe('type_' + cluster['CacheNodeType']) + self.push(self.inventory, type_name, dest) + if self.nested_groups: + self.push_group(self.inventory, 'types', type_name) + + # Inventory: Group by VPC (information not available in the current + # AWS API version for ElastiCache) + + # Inventory: Group by security group + if self.group_by_security_group and not is_redis: + + # Check for the existence of the 'SecurityGroups' key and also if + # this key has some value. When the cluster is not placed in a SG + # the query can return None here and cause an error. + if 'SecurityGroups' in cluster and cluster['SecurityGroups'] is not None: + for security_group in cluster['SecurityGroups']: + key = self.to_safe("security_group_" + security_group['SecurityGroupId']) + self.push(self.inventory, key, dest) + if self.nested_groups: + self.push_group(self.inventory, 'security_groups', key) + + # Inventory: Group by engine + if self.group_by_elasticache_engine and not is_redis: + self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest) + if self.nested_groups: + self.push_group(self.inventory, 'elasticache_engines', self.to_safe(cluster['Engine'])) + + # Inventory: Group by parameter group + if self.group_by_elasticache_parameter_group: + self.push(self.inventory, self.to_safe("elasticache_parameter_group_" + cluster['CacheParameterGroup']['CacheParameterGroupName']), dest) + if self.nested_groups: + self.push_group(self.inventory, 'elasticache_parameter_groups', self.to_safe(cluster['CacheParameterGroup']['CacheParameterGroupName'])) + + # Inventory: Group by replication group + if self.group_by_elasticache_replication_group and 'ReplicationGroupId' in cluster and cluster['ReplicationGroupId']: + self.push(self.inventory, self.to_safe("elasticache_replication_group_" + cluster['ReplicationGroupId']), dest) + if self.nested_groups: + self.push_group(self.inventory, 'elasticache_replication_groups', self.to_safe(cluster['ReplicationGroupId'])) + + # Global Tag: all ElastiCache clusters + self.push(self.inventory, 'elasticache_clusters', cluster['CacheClusterId']) + + host_info = self.get_host_info_dict_from_describe_dict(cluster) + + self.inventory["_meta"]["hostvars"][dest] = host_info + + # Add the nodes + for node in cluster['CacheNodes']: + self.add_elasticache_node(node, cluster, region) + + def add_elasticache_node(self, node, cluster, region): + ''' Adds an ElastiCache node to the inventory and index, as long as + it is addressable ''' + + # Only want available nodes unless all_elasticache_nodes is True + if not self.all_elasticache_nodes and node['CacheNodeStatus'] != 'available': + return + + # Select the best destination address + dest = node['Endpoint']['Address'] + + if not dest: + # Skip nodes we cannot address (e.g. private VPC subnet) + return + + node_id = self.to_safe(cluster['CacheClusterId'] + '_' + node['CacheNodeId']) + + # Add to index + self.index[dest] = [region, node_id] + + # Inventory: Group by node ID (always a group of 1) + if self.group_by_instance_id: + self.inventory[node_id] = [dest] + if self.nested_groups: + self.push_group(self.inventory, 'instances', node_id) + + # Inventory: Group by region + if self.group_by_region: + self.push(self.inventory, region, dest) + if self.nested_groups: + self.push_group(self.inventory, 'regions', region) + + # Inventory: Group by availability zone + if self.group_by_availability_zone: + self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest) + if self.nested_groups: + if self.group_by_region: + self.push_group(self.inventory, region, cluster['PreferredAvailabilityZone']) + self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone']) + + # Inventory: Group by node type + if self.group_by_instance_type: + type_name = self.to_safe('type_' + cluster['CacheNodeType']) + self.push(self.inventory, type_name, dest) + if self.nested_groups: + self.push_group(self.inventory, 'types', type_name) + + # Inventory: Group by VPC (information not available in the current + # AWS API version for ElastiCache) + + # Inventory: Group by security group + if self.group_by_security_group: + + # Check for the existence of the 'SecurityGroups' key and also if + # this key has some value. When the cluster is not placed in a SG + # the query can return None here and cause an error. + if 'SecurityGroups' in cluster and cluster['SecurityGroups'] is not None: + for security_group in cluster['SecurityGroups']: + key = self.to_safe("security_group_" + security_group['SecurityGroupId']) + self.push(self.inventory, key, dest) + if self.nested_groups: + self.push_group(self.inventory, 'security_groups', key) + + # Inventory: Group by engine + if self.group_by_elasticache_engine: + self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest) + if self.nested_groups: + self.push_group(self.inventory, 'elasticache_engines', self.to_safe("elasticache_" + cluster['Engine'])) + + # Inventory: Group by parameter group (done at cluster level) + + # Inventory: Group by replication group (done at cluster level) + + # Inventory: Group by ElastiCache Cluster + if self.group_by_elasticache_cluster: + self.push(self.inventory, self.to_safe("elasticache_cluster_" + cluster['CacheClusterId']), dest) + + # Global Tag: all ElastiCache nodes + self.push(self.inventory, 'elasticache_nodes', dest) + + host_info = self.get_host_info_dict_from_describe_dict(node) + + if dest in self.inventory["_meta"]["hostvars"]: + self.inventory["_meta"]["hostvars"][dest].update(host_info) + else: + self.inventory["_meta"]["hostvars"][dest] = host_info + + def add_elasticache_replication_group(self, replication_group, region): + ''' Adds an ElastiCache replication group to the inventory and index ''' + + # Only want available clusters unless all_elasticache_replication_groups is True + if not self.all_elasticache_replication_groups and replication_group['Status'] != 'available': + return + + # Select the best destination address (PrimaryEndpoint) + dest = replication_group['NodeGroups'][0]['PrimaryEndpoint']['Address'] + + if not dest: + # Skip clusters we cannot address (e.g. private VPC subnet) + return + + # Add to index + self.index[dest] = [region, replication_group['ReplicationGroupId']] + + # Inventory: Group by ID (always a group of 1) + if self.group_by_instance_id: + self.inventory[replication_group['ReplicationGroupId']] = [dest] + if self.nested_groups: + self.push_group(self.inventory, 'instances', replication_group['ReplicationGroupId']) + + # Inventory: Group by region + if self.group_by_region: + self.push(self.inventory, region, dest) + if self.nested_groups: + self.push_group(self.inventory, 'regions', region) + + # Inventory: Group by availability zone (doesn't apply to replication groups) + + # Inventory: Group by node type (doesn't apply to replication groups) + + # Inventory: Group by VPC (information not available in the current + # AWS API version for replication groups + + # Inventory: Group by security group (doesn't apply to replication groups) + # Check this value in cluster level + + # Inventory: Group by engine (replication groups are always Redis) + if self.group_by_elasticache_engine: + self.push(self.inventory, 'elasticache_redis', dest) + if self.nested_groups: + self.push_group(self.inventory, 'elasticache_engines', 'redis') + + # Global Tag: all ElastiCache clusters + self.push(self.inventory, 'elasticache_replication_groups', replication_group['ReplicationGroupId']) + + host_info = self.get_host_info_dict_from_describe_dict(replication_group) + + self.inventory["_meta"]["hostvars"][dest] = host_info def get_route53_records(self): ''' Get and store the map of resource records to domain names that @@ -666,7 +1286,6 @@ def get_instance_route53_names(self, instance): return list(name_list) - def get_host_info_dict_from_instance(self, instance): instance_vars = {} for key in vars(instance): @@ -683,7 +1302,7 @@ def get_host_info_dict_from_instance(self, instance): instance_vars['ec2_previous_state_code'] = instance.previous_state_code elif type(value) in [int, bool]: instance_vars[key] = value - elif type(value) in [str, unicode]: + elif isinstance(value, six.string_types): instance_vars[key] = value.strip() elif type(value) == type(None): instance_vars[key] = '' @@ -692,7 +1311,9 @@ def get_host_info_dict_from_instance(self, instance): elif key == 'ec2__placement': instance_vars['ec2_placement'] = value.zone elif key == 'ec2_tags': - for k, v in value.iteritems(): + for k, v in value.items(): + if self.expand_csv_tags and ',' in v: + v = map(lambda x: x.strip(), v.split(',')) key = self.to_safe('ec2_tag_' + k) instance_vars[key] = v elif key == 'ec2_groups': @@ -703,6 +1324,10 @@ def get_host_info_dict_from_instance(self, instance): group_names.append(group.name) instance_vars["ec2_security_group_ids"] = ','.join([str(i) for i in group_ids]) instance_vars["ec2_security_group_names"] = ','.join([str(i) for i in group_names]) + elif key == 'ec2_block_device_mapping': + instance_vars["ec2_block_devices"] = {} + for k, v in value.items(): + instance_vars["ec2_block_devices"][ os.path.basename(k) ] = v.volume_id else: pass # TODO Product codes if someone finds them useful @@ -710,11 +1335,93 @@ def get_host_info_dict_from_instance(self, instance): #print type(value) #print value - if self.route53_enabled: - instance_vars["ec2_route53_names"] = self.get_instance_route53_names(instance) - return instance_vars + def get_host_info_dict_from_describe_dict(self, describe_dict): + ''' Parses the dictionary returned by the API call into a flat list + of parameters. This method should be used only when 'describe' is + used directly because Boto doesn't provide specific classes. ''' + + # I really don't agree with prefixing everything with 'ec2' + # because EC2, RDS and ElastiCache are different services. + # I'm just following the pattern used until now to not break any + # compatibility. + + host_info = {} + for key in describe_dict: + value = describe_dict[key] + key = self.to_safe('ec2_' + self.uncammelize(key)) + + # Handle complex types + + # Target: Memcached Cache Clusters + if key == 'ec2_configuration_endpoint' and value: + host_info['ec2_configuration_endpoint_address'] = value['Address'] + host_info['ec2_configuration_endpoint_port'] = value['Port'] + + # Target: Cache Nodes and Redis Cache Clusters (single node) + if key == 'ec2_endpoint' and value: + host_info['ec2_endpoint_address'] = value['Address'] + host_info['ec2_endpoint_port'] = value['Port'] + + # Target: Redis Replication Groups + if key == 'ec2_node_groups' and value: + host_info['ec2_endpoint_address'] = value[0]['PrimaryEndpoint']['Address'] + host_info['ec2_endpoint_port'] = value[0]['PrimaryEndpoint']['Port'] + replica_count = 0 + for node in value[0]['NodeGroupMembers']: + if node['CurrentRole'] == 'primary': + host_info['ec2_primary_cluster_address'] = node['ReadEndpoint']['Address'] + host_info['ec2_primary_cluster_port'] = node['ReadEndpoint']['Port'] + host_info['ec2_primary_cluster_id'] = node['CacheClusterId'] + elif node['CurrentRole'] == 'replica': + host_info['ec2_replica_cluster_address_'+ str(replica_count)] = node['ReadEndpoint']['Address'] + host_info['ec2_replica_cluster_port_'+ str(replica_count)] = node['ReadEndpoint']['Port'] + host_info['ec2_replica_cluster_id_'+ str(replica_count)] = node['CacheClusterId'] + replica_count += 1 + + # Target: Redis Replication Groups + if key == 'ec2_member_clusters' and value: + host_info['ec2_member_clusters'] = ','.join([str(i) for i in value]) + + # Target: All Cache Clusters + elif key == 'ec2_cache_parameter_group': + host_info["ec2_cache_node_ids_to_reboot"] = ','.join([str(i) for i in value['CacheNodeIdsToReboot']]) + host_info['ec2_cache_parameter_group_name'] = value['CacheParameterGroupName'] + host_info['ec2_cache_parameter_apply_status'] = value['ParameterApplyStatus'] + + # Target: Almost everything + elif key == 'ec2_security_groups': + + # Skip if SecurityGroups is None + # (it is possible to have the key defined but no value in it). + if value is not None: + sg_ids = [] + for sg in value: + sg_ids.append(sg['SecurityGroupId']) + host_info["ec2_security_group_ids"] = ','.join([str(i) for i in sg_ids]) + + # Target: Everything + # Preserve booleans and integers + elif type(value) in [int, bool]: + host_info[key] = value + + # Target: Everything + # Sanitize string values + elif isinstance(value, six.string_types): + host_info[key] = value.strip() + + # Target: Everything + # Replace None by an empty string + elif type(value) == type(None): + host_info[key] = '' + + else: + # Remove non-processed complex types + pass + + return host_info + def get_host_info(self): ''' Get variables about a specific host ''' @@ -778,13 +1485,16 @@ def write_to_cache(self, data, filename): cache.write(json_data) cache.close() + def uncammelize(self, key): + temp = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', key) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', temp).lower() def to_safe(self, word): - ''' Converts 'bad' characters in a string to underscores so they can be - used as Ansible groups ''' - - return re.sub("[^A-Za-z0-9\-]", "_", word) - + ''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups ''' + regex = "[^A-Za-z0-9\_" + if not self.replace_dash_in_groups: + regex += "\-" + return re.sub(regex + "]", "_", word) def json_format_dict(self, data, pretty=False): ''' Converts a dict to a JSON object and dumps it as a formatted @@ -798,4 +1508,3 @@ def json_format_dict(self, data, pretty=False): # Run the script Ec2Inventory() - diff --git a/reference-architecture/aws-ansible/playbooks/add-node.yaml b/reference-architecture/aws-ansible/playbooks/add-node.yaml new file mode 100644 index 000000000..55a866093 --- /dev/null +++ b/reference-architecture/aws-ansible/playbooks/add-node.yaml @@ -0,0 +1,63 @@ +- hosts: localhost + connection: local + gather_facts: no + become: no + vars_files: + - vars/main.yaml + roles: + # Group systems + - instance-groups + +- hosts: new_nodes + gather_facts: yes + become: yes + serial: 1 + user: ec2-user + vars_files: + - vars/main.yaml + roles: + - rhsm-subscription + +- hosts: new_nodes + gather_facts: no + become: yes + user: ec2-user + vars_files: + - vars/main.yaml + roles: + - rhsm-repos + - prerequisites + +- include: node-setup.yaml + +- hosts: new_nodes + gather_facts: no + become: yes + vars_files: + - vars/main.yaml + roles: + - openshift-emptydir-quota + +- hosts: localhost + connection: local + gather_facts: no + become: no + vars_files: + - vars/main.yaml + post_tasks: + - name: add instance to ELB if node is infra + ec2_elb: + instance_id: "{{ hostvars[item].ec2_id }}" + ec2_elbs: "{{ infra_elb_name }}" + state: present + with_items: "{{ groups['tag_provision_node'] }}" + when: node_type == "infra" + + - name: tag a resource + ec2_tag: + region: "{{ region }}" + resource: "{{ hostvars[item].ec2_id }}" + state: absent + tags: + provision: node + with_items: "{{ groups['tag_provision_node'] }}" diff --git a/reference-architecture/aws-ansible/playbooks/node-setup.yaml b/reference-architecture/aws-ansible/playbooks/node-setup.yaml new file mode 100644 index 000000000..978c62c26 --- /dev/null +++ b/reference-architecture/aws-ansible/playbooks/node-setup.yaml @@ -0,0 +1,44 @@ +- include: /usr/share/ansible/openshift-ansible/playbooks/byo/openshift-node/scaleup.yml + vars: + debug_level: 2 + openshift_debug_level: "{{ debug_level }}" + openshift_node_debug_level: "{{ node_debug_level | default(debug_level, true) }}" + osm_controller_args: + cloud-provider: + - "aws" + osm_api_server_args: + cloud-provider: + - "aws" + openshift_node_kubelet_args: + cloud-provider: + - "aws" + node-labels: + - "role={{ openshift_node_labels.role }}" + openshift_master_debug_level: "{{ master_debug_level | default(debug_level, true) }}" + openshift_master_access_token_max_seconds: 2419200 + openshift_master_api_port: "{{ console_port }}" + openshift_master_console_port: "{{ console_port }}" + osm_cluster_network_cidr: 172.16.0.0/16 + openshift_registry_selector: "role=infra" + openshift_router_selector: "role=infra" + openshift_hosted_router_replicas: 2 + openshift_hosted_registry_replicas: 2 + openshift_master_cluster_method: native + openshift_cloudprovider_kind: aws + openshift_master_cluster_hostname: "internal-openshift-master.{{ public_hosted_zone }}" + openshift_master_cluster_public_hostname: "openshift-master.{{ public_hosted_zone }}" + osm_default_subdomain: "{{ wildcard_zone }}" + osm_default_node_selector: "role=app" + deployment_type: openshift-enterprise + openshift_master_identity_providers: + - name: github + kind: GitHubIdentityProvider + login: true + challenge: false + mapping_method: claim + clientID: e76865557b0417387b35 + clientSecret: 72b36e28221c1b93089ecf72f1a19963a8532b06 + organizations: + - openshift + osm_use_cockpit: false + containerized: false diff --git a/reference-architecture/aws-ansible/playbooks/openshift-setup.yaml b/reference-architecture/aws-ansible/playbooks/openshift-setup.yaml index f8bd92308..57941e27f 100644 --- a/reference-architecture/aws-ansible/playbooks/openshift-setup.yaml +++ b/reference-architecture/aws-ansible/playbooks/openshift-setup.yaml @@ -37,7 +37,7 @@ challenge: false mapping_method: claim clientID: e76865557b0417387b35 - clientSecret: a2439464495d6b579d25f46dd51eb05a170e7e59 + clientSecret: 72b36e28221c1b93089ecf72f1a19963a8532b06 organizations: - openshift osm_use_cockpit: false diff --git a/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/add-infra-node.json b/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/add-infra-node.json new file mode 100644 index 000000000..d740802a0 --- /dev/null +++ b/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/add-infra-node.json @@ -0,0 +1,131 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": { + "KeyName": { + "Type": "AWS::EC2::KeyPair::KeyName" + }, + "Route53HostedZone": { + "Type": "String" + }, + "AmiId": { + "Type": "AWS::EC2::Image::Id" + }, + "InstanceType": { + "Type": "String", + "Default": "t2.medium" + }, + "NodeRootVolSize": { + "Type": "String", + "Default": "30" + }, + "NodeDockerVolSize": { + "Type": "String", + "Default": "25" + }, + "NodeDockerVolType": { + "Type": "String", + "Default": "gp2" + }, + "NodeUserData": { + "Type": "String" + }, + "NodeEmptyVolSize": { + "Type": "String", + "Default": "25" + }, + "NodeEmptyVolType": { + "Type": "String", + "Default": "gp2" + }, + "NodeRootVolType": { + "Type": "String", + "Default": "gp2" + }, + "Subnet": { + "Type": "String" + }, + "NodeName": { + "Type": "String" + }, + "NodeInstanceProfile": { + "Type": "String" + }, + "NodeType": { + "Type": "String" + }, + "InfraSg": { + "Type": "String" + }, + "NodeSg": { + "Type": "String" + } + }, + "Resources": { + "Route53Records": { + "Type": "AWS::Route53::RecordSetGroup", + "DependsOn": [ + "NewNode" + ], + "Properties": { + "HostedZoneName": { "Ref": "Route53HostedZone" }, + "RecordSets": [ + { + "Name": { "Ref": "NodeName" }, + "Type": "A", + "TTL": "300", + "ResourceRecords": [{ "Fn::GetAtt" : ["NewNode", "PrivateIp"] }] + } + ] + } + }, + "NewNode" : { + "Type" : "AWS::EC2::Instance", + "Properties" : { + "ImageId" : {"Ref": "AmiId"}, + "UserData": {"Ref": "NodeUserData"}, + "KeyName" : {"Ref": "KeyName"}, + "InstanceType": {"Ref": "InstanceType"}, + "SecurityGroupIds": [{"Ref": "NodeSg"}, {"Ref": "InfraSg"}], + "SubnetId" : {"Ref": "Subnet"}, + "IamInstanceProfile": { "Ref": "NodeInstanceProfile" }, + "Tags": [ + { "Key": "Name", + "Value": {"Ref": "NodeName"} + }, + { "Key": "provision", + "Value": "node" + }, + { "Key": "openshift-role", + "Value": {"Ref": "NodeType"} + } + ], + "BlockDeviceMappings" : [ + { + "DeviceName": "/dev/sda1", + "Ebs": { + "DeleteOnTermination": "true", + "VolumeSize": {"Ref": "NodeRootVolSize"}, + "VolumeType": {"Ref": "NodeRootVolType"} + } + }, + { + "DeviceName": "/dev/xvdb", + "Ebs": { + "DeleteOnTermination": "true", + "VolumeSize": {"Ref": "NodeDockerVolSize"}, + "VolumeType": {"Ref": "NodeDockerVolType"} + } + }, + { + "DeviceName": "/dev/xvdc", + "Ebs": { + "DeleteOnTermination": "true", + "VolumeSize": {"Ref": "NodeEmptyVolSize"}, + "VolumeType": {"Ref": "NodeEmptyVolType"} + } + } + ] + } + } + } +} diff --git a/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/add-node.json b/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/add-node.json new file mode 100644 index 000000000..d2fe1c35a --- /dev/null +++ b/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/add-node.json @@ -0,0 +1,128 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": { + "KeyName": { + "Type": "AWS::EC2::KeyPair::KeyName" + }, + "Route53HostedZone": { + "Type": "String" + }, + "AmiId": { + "Type": "AWS::EC2::Image::Id" + }, + "InstanceType": { + "Type": "String", + "Default": "t2.medium" + }, + "NodeRootVolSize": { + "Type": "String", + "Default": "30" + }, + "NodeDockerVolSize": { + "Type": "String", + "Default": "25" + }, + "NodeDockerVolType": { + "Type": "String", + "Default": "gp2" + }, + "NodeUserData": { + "Type": "String" + }, + "NodeEmptyVolSize": { + "Type": "String", + "Default": "25" + }, + "NodeEmptyVolType": { + "Type": "String", + "Default": "gp2" + }, + "NodeRootVolType": { + "Type": "String", + "Default": "gp2" + }, + "Subnet": { + "Type": "String" + }, + "NodeName": { + "Type": "String" + }, + "NodeInstanceProfile": { + "Type": "String" + }, + "NodeType": { + "Type": "String" + }, + "NodeSg": { + "Type": "String" + } + }, + "Resources": { + "Route53Records": { + "Type": "AWS::Route53::RecordSetGroup", + "DependsOn": [ + "NewNode" + ], + "Properties": { + "HostedZoneName": { "Ref": "Route53HostedZone" }, + "RecordSets": [ + { + "Name": { "Ref": "NodeName" }, + "Type": "A", + "TTL": "300", + "ResourceRecords": [{ "Fn::GetAtt" : ["NewNode", "PrivateIp"] }] + } + ] + } + }, + "NewNode" : { + "Type" : "AWS::EC2::Instance", + "Properties" : { + "ImageId" : {"Ref": "AmiId"}, + "UserData": {"Ref": "NodeUserData"}, + "KeyName" : {"Ref": "KeyName"}, + "InstanceType": {"Ref": "InstanceType"}, + "SecurityGroupIds": {"Ref": "NodeSg"}, + "SubnetId" : {"Ref": "Subnet"}, + "IamInstanceProfile": { "Ref": "NodeInstanceProfile" }, + "Tags": [ + { "Key": "Name", + "Value": {"Ref": "NodeName"} + }, + { "Key": "provision", + "Value": "node" + }, + { "Key": "openshift-role", + "Value": {"Ref": "NodeType"} + } + ], + "BlockDeviceMappings" : [ + { + "DeviceName": "/dev/sda1", + "Ebs": { + "DeleteOnTermination": "true", + "VolumeSize": {"Ref": "NodeRootVolSize"}, + "VolumeType": {"Ref": "NodeRootVolType"} + } + }, + { + "DeviceName": "/dev/xvdb", + "Ebs": { + "DeleteOnTermination": "true", + "VolumeSize": {"Ref": "NodeDockerVolSize"}, + "VolumeType": {"Ref": "NodeDockerVolType"} + } + }, + { + "DeviceName": "/dev/xvdc", + "Ebs": { + "DeleteOnTermination": "true", + "VolumeSize": {"Ref": "NodeEmptyVolSize"}, + "VolumeType": {"Ref": "NodeEmptyVolType"} + } + } + ] + } + } + } +} diff --git a/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/greenfield.json b/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/greenfield.json index f76323427..bdba27d06 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/greenfield.json +++ b/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/greenfield.json @@ -1010,8 +1010,8 @@ "ImageId" : {"Ref": "AmiId"}, "UserData": {"Ref": "MasterUserData"}, "KeyName" : {"Ref": "KeyName"}, - "InstanceType": {"Ref": "MasterInstanceType"}, - "SecurityGroupIds": [{ "Fn::GetAtt" : ["NodeSG", "GroupId"] }, { "Fn::GetAtt" : ["MasterSG", "GroupId"] }, { "Fn::GetAtt" : ["EtcdSG", "GroupId"] }], + "InstanceType": {"Ref": "MasterInstanceType"}, + "SecurityGroupIds": [{ "Fn::GetAtt" : ["NodeSG", "GroupId"] }, { "Fn::GetAtt" : ["MasterSG", "GroupId"] }, { "Fn::GetAtt" : ["EtcdSG", "GroupId"] }], "SubnetId" : {"Ref": "PrivateSubnet2"}, "IamInstanceProfile": { "Ref": "MasterInstanceProfile" }, "Tags": [ @@ -1057,8 +1057,8 @@ "ImageId" : {"Ref": "AmiId"}, "UserData": {"Ref": "MasterUserData"}, "KeyName" : {"Ref": "KeyName"}, - "InstanceType": {"Ref": "MasterInstanceType"}, - "SecurityGroupIds": [{ "Fn::GetAtt" : ["NodeSG", "GroupId"] }, { "Fn::GetAtt" : ["MasterSG", "GroupId"] }, { "Fn::GetAtt" : ["EtcdSG", "GroupId"] }], + "InstanceType": {"Ref": "MasterInstanceType"}, + "SecurityGroupIds": [{ "Fn::GetAtt" : ["NodeSG", "GroupId"] }, { "Fn::GetAtt" : ["MasterSG", "GroupId"] }, { "Fn::GetAtt" : ["EtcdSG", "GroupId"] }], "SubnetId" : {"Ref": "PrivateSubnet3"}, "IamInstanceProfile": { "Ref": "MasterInstanceProfile" }, "Tags": [ @@ -1104,10 +1104,10 @@ "ImageId" : {"Ref": "AmiId"}, "UserData": {"Ref": "NodeUserData"}, "KeyName" : {"Ref": "KeyName"}, - "InstanceType": {"Ref": "InfraInstanceType"}, - "SecurityGroupIds": [{ "Fn::GetAtt" : ["NodeSG", "GroupId"] }, { "Fn::GetAtt" : ["InfraSG", "GroupId"] }], + "InstanceType": {"Ref": "InfraInstanceType"}, + "SecurityGroupIds": [{ "Fn::GetAtt" : ["NodeSG", "GroupId"] }, { "Fn::GetAtt" : ["InfraSG", "GroupId"] }], "SubnetId" : {"Ref": "PrivateSubnet1"}, - "IamInstanceProfile": { "Ref": "NodeInstanceProfile" }, + "IamInstanceProfile": { "Ref": "NodeInstanceProfile" }, "Tags": [ { "Key": "Name", "Value": {"Fn::Join": [".", ["ose-infra-node01",{"Ref": "PublicHostedZone"}]]} @@ -1151,10 +1151,10 @@ "ImageId" : {"Ref": "AmiId"}, "UserData": {"Ref": "NodeUserData"}, "KeyName" : {"Ref": "KeyName"}, - "InstanceType": {"Ref": "InfraInstanceType"}, - "SecurityGroupIds": [{ "Fn::GetAtt" : ["NodeSG", "GroupId"] }, { "Fn::GetAtt" : ["InfraSG", "GroupId"] }], + "InstanceType": {"Ref": "InfraInstanceType"}, + "SecurityGroupIds": [{ "Fn::GetAtt" : ["NodeSG", "GroupId"] }, { "Fn::GetAtt" : ["InfraSG", "GroupId"] }], "SubnetId" : {"Ref": "PrivateSubnet2"}, - "IamInstanceProfile": { "Ref": "NodeInstanceProfile" }, + "IamInstanceProfile": { "Ref": "NodeInstanceProfile" }, "Tags": [ { "Key": "Name", "Value": {"Fn::Join": [".", ["ose-infra-node02",{"Ref": "PublicHostedZone"}]]} @@ -1198,10 +1198,10 @@ "ImageId" : {"Ref": "AmiId"}, "UserData": {"Ref": "NodeUserData"}, "KeyName" : {"Ref": "KeyName"}, - "InstanceType": {"Ref": "AppNodeInstanceType"}, - "SecurityGroupIds": [{ "Fn::GetAtt" : ["NodeSG", "GroupId"] }], + "InstanceType": {"Ref": "AppNodeInstanceType"}, + "SecurityGroupIds": [{ "Fn::GetAtt" : ["NodeSG", "GroupId"] }], "SubnetId" : {"Ref": "PrivateSubnet1"}, - "IamInstanceProfile": { "Ref": "NodeInstanceProfile" }, + "IamInstanceProfile": { "Ref": "NodeInstanceProfile" }, "Tags": [ { "Key": "Name", "Value": {"Fn::Join": [".", ["ose-app-node01",{"Ref": "PublicHostedZone"}]]} @@ -1245,10 +1245,10 @@ "ImageId" : {"Ref": "AmiId"}, "UserData": {"Ref": "NodeUserData"}, "KeyName" : {"Ref": "KeyName"}, - "InstanceType": {"Ref": "AppNodeInstanceType"}, - "SecurityGroupIds": [{ "Fn::GetAtt" : ["NodeSG", "GroupId"] }], + "InstanceType": {"Ref": "AppNodeInstanceType"}, + "SecurityGroupIds": [{ "Fn::GetAtt" : ["NodeSG", "GroupId"] }], "SubnetId" : {"Ref": "PrivateSubnet2"}, - "IamInstanceProfile": { "Ref": "NodeInstanceProfile" }, + "IamInstanceProfile": { "Ref": "NodeInstanceProfile" }, "Tags": [ { "Key": "Name", "Value": {"Fn::Join": [".", ["ose-app-node02",{"Ref": "PublicHostedZone"}]]} diff --git a/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/tasks/main.yaml index 28242601e..8eb79839f 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/tasks/main.yaml +++ b/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/tasks/main.yaml @@ -141,3 +141,54 @@ NodeDockerVolSize: "{{ docker_storage }}" NodeDockerVolType: gp2 when: create_vpc == "no" and byo_bastion == "yes" + +- name: Add App Node + cloudformation: + stack_name: "{{ shortname }}" + state: "present" + region: "{{ region }}" + template: "roles/cloudformation-infra/files/add-node.json" + template_parameters: + NodeName: "{{ fqdn }}" + NodeSg: "{{ existing_sg }}" + Subnet: "{{ subnet_id }}" + Route53HostedZone: "{{ public_hosted_zone }}." + KeyName: "{{ keypair }}" + AmiId: "{{ ami }}" + InstanceType: "{{ node_instance_type }}" + NodeEmptyVolSize: "{{ emptydir_storage }}" + NodeEmptyVolType: gp2 + NodeUserData: "{{ lookup('file', 'user_data_node.yml') | b64encode }}" + NodeRootVolSize: 25 + NodeRootVolType: gp2 + NodeDockerVolSize: "{{ docker_storage }}" + NodeDockerVolType: gp2 + NodeInstanceProfile: "{{ iam_role }}" + NodeType: "{{ node_type }}" + when: create_vpc == "no" and add_node == "yes" and node_type == "app" + +- name: Add Infra Node + cloudformation: + stack_name: "{{ shortname }}" + state: "present" + region: "{{ region }}" + template: "roles/cloudformation-infra/files/add-infra-node.json" + template_parameters: + NodeName: "{{ fqdn }}" + NodeSg: "{{ existing_sg }}" + InfraSg: "{{ infra_sg }}" + Subnet: "{{ subnet_id }}" + Route53HostedZone: "{{ public_hosted_zone }}." + KeyName: "{{ keypair }}" + AmiId: "{{ ami }}" + InstanceType: "{{ node_instance_type }}" + NodeEmptyVolSize: "{{ emptydir_storage }}" + NodeEmptyVolType: gp2 + NodeUserData: "{{ lookup('file', 'user_data_node.yml') | b64encode }}" + NodeRootVolSize: 25 + NodeRootVolType: gp2 + NodeDockerVolSize: "{{ docker_storage }}" + NodeDockerVolType: gp2 + NodeInstanceProfile: "{{ iam_role }}" + NodeType: "{{ node_type }}" + when: create_vpc == "no" and add_node == "yes" and node_type == "infra" diff --git a/reference-architecture/aws-ansible/playbooks/roles/instance-groups/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/instance-groups/tasks/main.yaml index 7f7fe4d06..160162cad 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/instance-groups/tasks/main.yaml +++ b/reference-architecture/aws-ansible/playbooks/roles/instance-groups/tasks/main.yaml @@ -11,7 +11,7 @@ groups: masters, etcd, nodes, cluster_hosts openshift_node_labels: role: master - with_items: "{{ groups['tag_openshift-role_master'] }}" + with_items: "{{ groups['tag_openshift_role_master'] }}" - name: Add a master to the primary masters group add_host: @@ -19,7 +19,7 @@ groups: primary_master openshift_node_labels: role: master - with_items: "{{ groups['tag_openshift-role_master'].0 }}" + with_items: "{{ groups['tag_openshift_role_master'].0 }}" - name: Add infra instances to host group add_host: @@ -27,7 +27,7 @@ groups: nodes, cluster_hosts, schedulable_nodes openshift_node_labels: role: infra - with_items: "{{ groups['tag_openshift-role_infra'] }}" + with_items: "{{ groups['tag_openshift_role_infra'] }}" - name: Add app instances to host group add_host: @@ -35,4 +35,13 @@ groups: nodes, cluster_hosts, schedulable_nodes openshift_node_labels: role: app - with_items: "{{ groups['tag_openshift-role_app'] }}" + with_items: "{{ groups['tag_openshift_role_app'] }}" + +- name: Add app instances to host group + add_host: + name: "{{ hostvars[item].ec2_tag_Name }}" + groups: new_nodes + openshift_node_labels: + role: "{{ node_type }}" + with_items: "{{ groups['tag_provision_node'] }}" + when: add_node is defined From da3619a634bc7743ea0b125deeeb53dac932e05d Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Tue, 11 Oct 2016 16:02:02 -0400 Subject: [PATCH 05/16] syntax error --- .../playbooks/roles/cloudformation-infra/files/add-node.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/add-node.json b/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/add-node.json index d2fe1c35a..5e916f7c1 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/add-node.json +++ b/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/files/add-node.json @@ -82,7 +82,7 @@ "UserData": {"Ref": "NodeUserData"}, "KeyName" : {"Ref": "KeyName"}, "InstanceType": {"Ref": "InstanceType"}, - "SecurityGroupIds": {"Ref": "NodeSg"}, + "SecurityGroupIds": [{"Ref": "NodeSg"}], "SubnetId" : {"Ref": "Subnet"}, "IamInstanceProfile": { "Ref": "NodeInstanceProfile" }, "Tags": [ From f9a2ea9c264d571eaf32371d04dcb4fa8b1e26b6 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Tue, 11 Oct 2016 22:22:17 -0400 Subject: [PATCH 06/16] fix up of host up playbook --- reference-architecture/aws-ansible/playbooks/add-node.yaml | 1 + .../aws-ansible/playbooks/openshift-install.yaml | 6 +++--- .../aws-ansible/playbooks/roles/host-up/tasks/main.yaml | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/reference-architecture/aws-ansible/playbooks/add-node.yaml b/reference-architecture/aws-ansible/playbooks/add-node.yaml index 55a866093..0e95fe697 100644 --- a/reference-architecture/aws-ansible/playbooks/add-node.yaml +++ b/reference-architecture/aws-ansible/playbooks/add-node.yaml @@ -49,6 +49,7 @@ ec2_elb: instance_id: "{{ hostvars[item].ec2_id }}" ec2_elbs: "{{ infra_elb_name }}" + region: "{{ region }}" state: present with_items: "{{ groups['tag_provision_node'] }}" when: node_type == "infra" diff --git a/reference-architecture/aws-ansible/playbooks/openshift-install.yaml b/reference-architecture/aws-ansible/playbooks/openshift-install.yaml index 978aa7f97..4553146ad 100644 --- a/reference-architecture/aws-ansible/playbooks/openshift-install.yaml +++ b/reference-architecture/aws-ansible/playbooks/openshift-install.yaml @@ -8,10 +8,10 @@ # Group systems - instance-groups -- hosts: bastion +- hosts: localhost + connection: local gather_facts: no - become: yes - user: ec2-user + become: no vars_files: - vars/main.yaml roles: diff --git a/reference-architecture/aws-ansible/playbooks/roles/host-up/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/host-up/tasks/main.yaml index e8ff6f237..ee90e7cbb 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/host-up/tasks/main.yaml +++ b/reference-architecture/aws-ansible/playbooks/roles/host-up/tasks/main.yaml @@ -2,6 +2,7 @@ - name: check to see if host is available wait_for: port: 22 + host: "bastion.{{ public_hosted_zone }}" state: started delay: 20 - when: "'bastion' in inventory_hostname" + when: byo_bastion == "no" From 73f4936c6f2f3aff908a411cbfeb4d3fa9afc0a4 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Tue, 11 Oct 2016 23:14:35 -0400 Subject: [PATCH 07/16] scale the router/registry --- .../aws-ansible/playbooks/add-node.yaml | 8 ++++++++ .../library/openshift_facts.py | 1 + .../roles/infra-node-scaleup/tasks/main.yaml | 17 +++++++++++++++++ 3 files changed, 26 insertions(+) create mode 120000 reference-architecture/aws-ansible/playbooks/roles/infra-node-scaleup/library/openshift_facts.py create mode 100644 reference-architecture/aws-ansible/playbooks/roles/infra-node-scaleup/tasks/main.yaml diff --git a/reference-architecture/aws-ansible/playbooks/add-node.yaml b/reference-architecture/aws-ansible/playbooks/add-node.yaml index 0e95fe697..c58d80bde 100644 --- a/reference-architecture/aws-ansible/playbooks/add-node.yaml +++ b/reference-architecture/aws-ansible/playbooks/add-node.yaml @@ -38,6 +38,14 @@ roles: - openshift-emptydir-quota +- hosts: primary_master + gather_facts: yes + become: yes + vars_files: + - vars/main.yaml + roles: + - infra-node-scaleup + - hosts: localhost connection: local gather_facts: no diff --git a/reference-architecture/aws-ansible/playbooks/roles/infra-node-scaleup/library/openshift_facts.py b/reference-architecture/aws-ansible/playbooks/roles/infra-node-scaleup/library/openshift_facts.py new file mode 120000 index 000000000..e0061bb77 --- /dev/null +++ b/reference-architecture/aws-ansible/playbooks/roles/infra-node-scaleup/library/openshift_facts.py @@ -0,0 +1 @@ +/usr/share/ansible/openshift-ansible/roles/openshift_facts/library/openshift_facts.py \ No newline at end of file diff --git a/reference-architecture/aws-ansible/playbooks/roles/infra-node-scaleup/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/infra-node-scaleup/tasks/main.yaml new file mode 100644 index 000000000..720f80521 --- /dev/null +++ b/reference-architecture/aws-ansible/playbooks/roles/infra-node-scaleup/tasks/main.yaml @@ -0,0 +1,17 @@ +--- +- name: Gather facts + openshift_facts: + role: common + +- name: Count the infrastructure nodes + shell: "{{ openshift.common.client_binary }} get nodes --show-labels | grep role=infra | wc -l" + register: nodes + when: node_type == "infra" + +- name: Scale the router + shell: "{{ openshift.common.client_binary }} scale dc/router --replicas={{ nodes.stdout }}" + when: node_type == "infra" + +- name: Scale the registry + shell: "{{ openshift.common.client_binary }} scale dc/docker-registry --replicas={{ nodes.stdout }}" + when: node_type == "infra" From 3fabcb703039635b8b0d04c92fd93bbb7d5b74d7 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Wed, 12 Oct 2016 14:25:53 -0400 Subject: [PATCH 08/16] change of wording in add-node.py --- reference-architecture/aws-ansible/add-node.py | 4 ++-- .../docker-storage-setup/files/docker-storage-setup | 4 ---- .../roles/docker-storage-setup/tasks/main.yaml | 13 ------------- 3 files changed, 2 insertions(+), 19 deletions(-) delete mode 100644 reference-architecture/aws-ansible/playbooks/roles/docker-storage-setup/files/docker-storage-setup delete mode 100644 reference-architecture/aws-ansible/playbooks/roles/docker-storage-setup/tasks/main.yaml diff --git a/reference-architecture/aws-ansible/add-node.py b/reference-architecture/aws-ansible/add-node.py index f5b448330..eeebe2609 100755 --- a/reference-architecture/aws-ansible/add-node.py +++ b/reference-architecture/aws-ansible/add-node.py @@ -37,7 +37,7 @@ @click.option('--rhsm-pool', help='Red Hat Subscription Management Pool ID or Subscription Name') ### Miscellaneous options -@click.option('--iam-role', help='Specify the name of the existing IAM Role', +@click.option('--iam-role', help='Specify the name of the existing IAM Instance profile', show_default=True) @click.option('--shortname', help='Specify the hostname of the sytem', show_default=True) @@ -80,7 +80,7 @@ def launch_refarch_env(region=None, public_hosted_zone = click.prompt('Hosted DNS zone for accessing the environment') if iam_role is None: - iam_role = click.prompt('Specify the name of the existing IAM Role') + iam_role = click.prompt('Specify the name of the existing IAM Instance Profile') if existing_sg is None: existing_sg = click.prompt('Node Security group') diff --git a/reference-architecture/aws-ansible/playbooks/roles/docker-storage-setup/files/docker-storage-setup b/reference-architecture/aws-ansible/playbooks/roles/docker-storage-setup/files/docker-storage-setup deleted file mode 100644 index 66269fdc4..000000000 --- a/reference-architecture/aws-ansible/playbooks/roles/docker-storage-setup/files/docker-storage-setup +++ /dev/null @@ -1,4 +0,0 @@ -DEVS=/dev/xvdb -VG=docker-vol -DATA_SIZE=95%VG -EXTRA_DOCKER_STORAGE_OPTIONS="--storage-opt dm.basesize=3G" diff --git a/reference-architecture/aws-ansible/playbooks/roles/docker-storage-setup/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/docker-storage-setup/tasks/main.yaml deleted file mode 100644 index 4b082391b..000000000 --- a/reference-architecture/aws-ansible/playbooks/roles/docker-storage-setup/tasks/main.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -- name: create the docker-storage-setup config file - copy: - src: docker-storage-setup - dest: /etc/sysconfig/docker-storage-setup - owner: root - group: root - mode: 0644 -- name: start docker - service: - name: docker - state: started - enabled: true From 3e095b7972a2ceeaeb631b6a44dc659b1b625015 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Wed, 12 Oct 2016 15:26:40 -0400 Subject: [PATCH 09/16] change of trigger name --- reference-architecture/aws-ansible/add-node.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/reference-architecture/aws-ansible/add-node.py b/reference-architecture/aws-ansible/add-node.py index eeebe2609..55eb701f7 100755 --- a/reference-architecture/aws-ansible/add-node.py +++ b/reference-architecture/aws-ansible/add-node.py @@ -41,7 +41,7 @@ show_default=True) @click.option('--shortname', help='Specify the hostname of the sytem', show_default=True) -@click.option('--existing-sg', help='Specify the already existing node security group id', +@click.option('--node-sg', help='Specify the already existing node security group id', show_default=True) @click.option('--infra-sg', help='Specify the already existing Infrastructure node security group id', show_default=True) @@ -59,7 +59,7 @@ def launch_refarch_env(region=None, node_instance_type=None, keypair=None, subnet_id=None, - existing_sg=None, + node_sg=None, infra_sg=None, public_hosted_zone=None, app_dns_prefix=None, @@ -82,8 +82,8 @@ def launch_refarch_env(region=None, if iam_role is None: iam_role = click.prompt('Specify the name of the existing IAM Instance Profile') - if existing_sg is None: - existing_sg = click.prompt('Node Security group') + if node_sg is None: + node_sg = click.prompt('Node Security group') if node_type in 'infra' and infra_sg is None: infra_sg = click.prompt('Infra Node Security group') @@ -129,7 +129,7 @@ def launch_refarch_env(region=None, click.echo('\tnode_instance_type: %s' % node_instance_type) click.echo('\tkeypair: %s' % keypair) click.echo('\tsubnet_id: %s' % subnet_id) - click.echo('\texisting_sg: %s' % existing_sg) + click.echo('\tnode_sg: %s' % node_sg) click.echo('\tconsole port: %s' % console_port) click.echo('\tdeployment_type: %s' % deployment_type) click.echo('\tpublic_hosted_zone: %s' % public_hosted_zone) @@ -174,7 +174,7 @@ def launch_refarch_env(region=None, create_key=no \ create_vpc=no \ subnet_id=%s \ - existing_sg=%s \ + node_sg=%s \ infra_sg=%s \ node_instance_type=%s \ public_hosted_zone=%s \ @@ -192,7 +192,7 @@ def launch_refarch_env(region=None, ami, keypair, subnet_id, - existing_sg, + node_sg, infra_sg, node_instance_type, public_hosted_zone, From 4d1fcedc13ae26156353e18dd51e4eb04ae736d2 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Wed, 12 Oct 2016 15:29:11 -0400 Subject: [PATCH 10/16] fix of existing_sg to node_sg --- reference-architecture/aws-ansible/add-node.py | 1 + .../playbooks/roles/cloudformation-infra/tasks/main.yaml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/reference-architecture/aws-ansible/add-node.py b/reference-architecture/aws-ansible/add-node.py index 55eb701f7..4f3968a4f 100755 --- a/reference-architecture/aws-ansible/add-node.py +++ b/reference-architecture/aws-ansible/add-node.py @@ -130,6 +130,7 @@ def launch_refarch_env(region=None, click.echo('\tkeypair: %s' % keypair) click.echo('\tsubnet_id: %s' % subnet_id) click.echo('\tnode_sg: %s' % node_sg) + click.echo('\tinfra_sg: %s' % infra_sg) click.echo('\tconsole port: %s' % console_port) click.echo('\tdeployment_type: %s' % deployment_type) click.echo('\tpublic_hosted_zone: %s' % public_hosted_zone) diff --git a/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/tasks/main.yaml index 8eb79839f..42c66da41 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/tasks/main.yaml +++ b/reference-architecture/aws-ansible/playbooks/roles/cloudformation-infra/tasks/main.yaml @@ -150,7 +150,7 @@ template: "roles/cloudformation-infra/files/add-node.json" template_parameters: NodeName: "{{ fqdn }}" - NodeSg: "{{ existing_sg }}" + NodeSg: "{{ node_sg }}" Subnet: "{{ subnet_id }}" Route53HostedZone: "{{ public_hosted_zone }}." KeyName: "{{ keypair }}" @@ -175,7 +175,7 @@ template: "roles/cloudformation-infra/files/add-infra-node.json" template_parameters: NodeName: "{{ fqdn }}" - NodeSg: "{{ existing_sg }}" + NodeSg: "{{ node_sg }}" InfraSg: "{{ infra_sg }}" Subnet: "{{ subnet_id }}" Route53HostedZone: "{{ public_hosted_zone }}." From 6ac7ad9ebbad8f9c3bffd536dd27773239aa811a Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Wed, 12 Oct 2016 15:51:32 -0400 Subject: [PATCH 11/16] needed to switch to the default project --- .../playbooks/roles/infra-node-scaleup/tasks/main.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/reference-architecture/aws-ansible/playbooks/roles/infra-node-scaleup/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/infra-node-scaleup/tasks/main.yaml index 720f80521..a44abac26 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/infra-node-scaleup/tasks/main.yaml +++ b/reference-architecture/aws-ansible/playbooks/roles/infra-node-scaleup/tasks/main.yaml @@ -3,6 +3,9 @@ openshift_facts: role: common +- name: use the default project + shell: "{{ openshift.common.client_binary }} project default" + - name: Count the infrastructure nodes shell: "{{ openshift.common.client_binary }} get nodes --show-labels | grep role=infra | wc -l" register: nodes From 6c3bb73cf8c42e3129036d435bab596b78c97351 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Wed, 12 Oct 2016 16:24:29 -0400 Subject: [PATCH 12/16] moving things around for the load balancer --- .../aws-ansible/playbooks/add-node.yaml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/reference-architecture/aws-ansible/playbooks/add-node.yaml b/reference-architecture/aws-ansible/playbooks/add-node.yaml index c58d80bde..d14d9df34 100644 --- a/reference-architecture/aws-ansible/playbooks/add-node.yaml +++ b/reference-architecture/aws-ansible/playbooks/add-node.yaml @@ -30,21 +30,21 @@ - include: node-setup.yaml -- hosts: new_nodes - gather_facts: no +- hosts: primary_master + gather_facts: yes become: yes vars_files: - vars/main.yaml roles: - - openshift-emptydir-quota + - infra-node-scaleup -- hosts: primary_master - gather_facts: yes +- hosts: new_nodes + gather_facts: no become: yes vars_files: - vars/main.yaml roles: - - infra-node-scaleup + - openshift-emptydir-quota - hosts: localhost connection: local @@ -58,6 +58,7 @@ instance_id: "{{ hostvars[item].ec2_id }}" ec2_elbs: "{{ infra_elb_name }}" region: "{{ region }}" + wait_timeout: 300 state: present with_items: "{{ groups['tag_provision_node'] }}" when: node_type == "infra" From 26a96098fd12bf55e2419f744d90d43af7366ff8 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Thu, 13 Oct 2016 11:23:09 -0400 Subject: [PATCH 13/16] dont wait on elb and suggestion for removing changed in regards to subscription --- reference-architecture/aws-ansible/playbooks/add-node.yaml | 2 +- .../aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml | 1 - .../playbooks/roles/rhsm-subscription/tasks/main.yaml | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/reference-architecture/aws-ansible/playbooks/add-node.yaml b/reference-architecture/aws-ansible/playbooks/add-node.yaml index d14d9df34..848715333 100644 --- a/reference-architecture/aws-ansible/playbooks/add-node.yaml +++ b/reference-architecture/aws-ansible/playbooks/add-node.yaml @@ -58,7 +58,7 @@ instance_id: "{{ hostvars[item].ec2_id }}" ec2_elbs: "{{ infra_elb_name }}" region: "{{ region }}" - wait_timeout: 300 + wait: no state: present with_items: "{{ groups['tag_provision_node'] }}" when: node_type == "infra" diff --git a/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml index c5c1fde81..259ef8175 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml +++ b/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml @@ -1,7 +1,6 @@ --- - name: disable unneeded repos command: subscription-manager repos --disable='*' - when: register_result | changed - name: ensure proper repos are assigned command: subscription-manager repos --enable={{ item }} diff --git a/reference-architecture/aws-ansible/playbooks/roles/rhsm-subscription/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/rhsm-subscription/tasks/main.yaml index 9fbe9772e..c22bcc7fe 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/rhsm-subscription/tasks/main.yaml +++ b/reference-architecture/aws-ansible/playbooks/roles/rhsm-subscription/tasks/main.yaml @@ -5,5 +5,4 @@ password: "{{ rhsm_password }}" state: present pool: "^{{ rhsm_pool }}" - register: register_result when: ansible_os_family == "RedHat" From f905298e780886a4aeefcbc118e766181dafe749 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Thu, 13 Oct 2016 12:45:10 -0400 Subject: [PATCH 14/16] rhui wasn't being disabled using subscription-manager solved using file --- .../playbooks/roles/rhsm-repos/tasks/main.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml index 259ef8175..d74cc2962 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml +++ b/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml @@ -8,3 +8,12 @@ - rhel-7-server-rpms - rhel-7-server-extras-rpms - rhel-7-server-ose-3.3-rpms + +- name: remove rhui repos + file: + path: "{{ item }}" + state: absent + with_items: + - /etc/yum.repos.d/redhat-rhui-client-config.repo + - /etc/yum.repos.d/redhat-rhui.repo + - /etc/yum.repos.d/rhui-load-balancers.conf From 4db29707ac9defd7d3665504d4ceb7cf1c9eb7bd Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Thu, 13 Oct 2016 13:41:17 -0400 Subject: [PATCH 15/16] disable repos rather than remove --- .../aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml index d74cc2962..15ae7b17c 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml +++ b/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml @@ -10,9 +10,10 @@ - rhel-7-server-ose-3.3-rpms - name: remove rhui repos - file: - path: "{{ item }}" - state: absent + replace: + dest: "{{ item }}" + regexp: 'enabled=1' + replace: 'enabled=0' with_items: - /etc/yum.repos.d/redhat-rhui-client-config.repo - /etc/yum.repos.d/redhat-rhui.repo From 73226da71d06dd167617ec8ca9aea87467c9d832 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Thu, 13 Oct 2016 13:50:05 -0400 Subject: [PATCH 16/16] patchup --- .../aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml b/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml index 15ae7b17c..2053334f3 100644 --- a/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml +++ b/reference-architecture/aws-ansible/playbooks/roles/rhsm-repos/tasks/main.yaml @@ -17,4 +17,3 @@ with_items: - /etc/yum.repos.d/redhat-rhui-client-config.repo - /etc/yum.repos.d/redhat-rhui.repo - - /etc/yum.repos.d/rhui-load-balancers.conf