Tok
2019-08-12 723576904f93248ce80c048ffbf8e19c7fd84931
Merge branch 'ansible-tower-config' into development - new babylon
driven config
55 files added
12 files modified
5348 ■■■■■ changed files
README.adoc 1 ●●●● patch | view | raw | blame | history
ansible/configs/ans-tower-prod/env_vars.yml 99 ●●●● patch | view | raw | blame | history
ansible/configs/ans-tower-prod/files/cloud_providers/ec2_cloud_template.j2 529 ●●●●● patch | view | raw | blame | history
ansible/configs/ans-tower-prod/files/hosts_template.j2 37 ●●●● patch | view | raw | blame | history
ansible/configs/ans-tower-prod/files/tower_cli.j2 2 ●●● patch | view | raw | blame | history
ansible/configs/ans-tower-prod/post_software.yml 1 ●●●● patch | view | raw | blame | history
ansible/configs/ans-tower-prod/sample_vars.yml 59 ●●●● patch | view | raw | blame | history
ansible/configs/ans-tower-prod/sample_vars_babylon.yml 169 ●●●●● patch | view | raw | blame | history
ansible/configs/ans-tower-prod/tower_workloads.yml 61 ●●●● patch | view | raw | blame | history
ansible/configs/ans-tower-prod/tower_workloads_workaround.yml 156 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-cicd-lab/files/tower_hosts_template.j2 4 ●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/README.adoc 74 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/destroy_env.yml 18 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/env_vars.yml 450 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/files/cloud_providers/ec2_cloud_template.j2 529 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/files/hosts_template.j2 51 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/files/repos_template.j2 38 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/files/tower_cli.j2 5 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/files/tower_template_inventory.j2 54 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/post_infra.yml 24 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/post_software.yml 33 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/pre_infra.yml 21 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/pre_software.yml 61 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/requirements.yml 6 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/sample_vars.yml 80 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/sample_vars_babylon.yml 169 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/software.yml 20 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/tower_workloads.yml 55 ●●●●● patch | view | raw | blame | history
ansible/configs/ansible-tower/tower_workloads_workaround.yml 156 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-example/sample_vars.yml 4 ●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/README.adoc 35 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/deploy_stack.yml 77 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/destroy_env.yml 41 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/env_vars.yml 463 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/files/cloud_providers/default.j2 369 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/files/cloud_providers/worker.j2 369 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/files/hosts_template.j2 50 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/files/hosts_template.j2.back 15 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/files/repos_template.j2 38 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/files/tower_cli.j2 5 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/multi-region-example.svg 2 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/post_infra.yml 79 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/post_software.yml 21 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/pre_infra.yml 30 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/pre_software.yml 50 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/sample_vars.yml 114 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/sample_vars_babylon.yml 117 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/software.yml 43 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/start.yml 33 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/stop.yml 33 ●●●●● patch | view | raw | blame | history
ansible/configs/multi-region-tower/tower_workloads.yml 49 ●●●●● patch | view | raw | blame | history
ansible/roles/cleanup-tower-default/tasks/main.yml 37 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-babylon-job-runner/tasks/main.yml 15 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-copy-ssh/tasks/main.yml 35 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-credential-create/tasks/main.yml 19 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-inventory-create/tasks/main.yml 24 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-jobtemplate-create/tasks/main.yml 23 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-license-injector/tasks/main.yml 20 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-license-injector/templates/tower_cli.j2 2 ●●● patch | view | raw | blame | history
ansible/roles/tower-org-create/tasks/main.yml 11 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-pip-packages/tasks/main.yml 36 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-pip-packages/templates/requirements.j2 3 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-project-create/tasks/main.yml 19 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-settings-update/tasks/main.yml 14 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-user-create/tasks/main.yml 25 ●●●●● patch | view | raw | blame | history
ansible/roles/tower-virtual-environment/tasks/main.yml 23 ●●●●● patch | view | raw | blame | history
ansible/software_playbooks/tower.yml 43 ●●●●● patch | view | raw | blame | history
README.adoc
@@ -5,6 +5,7 @@
  to fully configured running application environments running on either public
   Cloud Providers or OpenShift clusters.
*AgnosticD* is not an OpenShift Deployer, though it can and does that, it is
 however also a deployer that just happens to be used to deploy a lot of
  OpenShift and OpenShift workloads, amongst other things. 
ansible/configs/ans-tower-prod/env_vars.yml
@@ -277,21 +277,6 @@
        value: "linux"
    subnet: PublicSubnet
  - name: "worker"
    count: "{{worker_instance_count}}"
    security_groups:
      - HostSG
      - TowerSG
    public_dns: false
    dns_loadbalancer: false
    flavor:
      "ec2": "{{worker_instance_type}}"
    tags:
      - key: "AnsibleGroup"
        value: "workers"
      - key: "ostype"
        value: "linux"
    subnet: PublicSubnet
  - name: "support"
    count: "{{support_instance_count}}"
    public_dns: true
@@ -307,6 +292,64 @@
        value: "rhel"
    key_name: "{{key_name}}"
    subnet: PublicSubnet
######### Worker instances #########
instances_worker:
  - name: "worker"
    count: "{{worker_instance_count}}"
    security_groups:
      - HostSG
      - TowerSG
    public_dns: false
    dns_loadbalancer: false
    flavor:
      "ec2": "{{worker_instance_type}}"
    tags:
      - key: "AnsibleGroup"
        value: "workers"
      - key: "ostype"
        value: "linux"
    subnet: PublicSubnet
#########
  # - name: "worker{{target_regions[1].name}}"
  #   count: "{{worker_instance_count}}"
  #   security_groups:
  #     - HostSG
  #     - TowerSG
  #   public_dns: false
  #   dns_loadbalancer: false
  #   flavor:
  #     "ec2": "{{worker_instance_type}}"
  #   tags:
  #     - key: "AnsibleGroup"
  #       value: "workers"
  #     - key: "ostype"
  #       value: "linux"
  #     - key: Worker_region
  #       value: "{{ target_regions[1].name }}"
  #   subnet: PublicSubnet
  #   #########
  # - name: "worker{{target_regions[2].name}}"
  #   count: "{{worker_instance_count}}"
  #   security_groups:
  #     - HostSG
  #     - TowerSG
  #   public_dns: false
  #   dns_loadbalancer: false
  #   flavor:
  #     "ec2": "{{worker_instance_type}}"
  #   tags:
  #     - key: "AnsibleGroup"
  #       value: "workers"
  #     - key: "ostype"
  #       value: "linux"
  #     - key: Worker_region
  #       value: "{{ target_regions[2].name }}"
  #   subnet: PublicSubnet
    #######*************#############
###### VARIABLES YOU SHOULD ***NOT*** CONFIGURE FOR YOUR DEPLOYEMNT
###### You can, but you usually wouldn't need to.
@@ -332,6 +375,7 @@
  - rhel-7-server-rh-common-rpms
  - rhel-7-server-extras-rpms
  - rhel-7-server-optional-rpms
  - rhel-7-server-rhscl-rpms
  - epel-release-latest-7
@@ -379,3 +423,28 @@
#
# cf_template_description: "{{ env_type }}-{{ guid }} Ansible Agnostic Deployer"
tower_run: false
default_workloads:
  - tower-copy-ssh
  - tower-license-injector
  - cleanup-tower-default
  - tower-settings-update
  - tower-pip-packages
  - tower-user-create
  - tower-org-create
  - tower-credential-create
  - tower-project-create
  - tower-inventory-create
  - tower-jobtemplate-create
  - tower-babylon-job-runner
# infra_workloads|:
#   - tower-settings-update
#   - tower-pip-packages
#   - tower-user-create
#   - tower-org-create
#   - tower-project-create
#   - tower-inventory-create
#   - tower-jobtemplate-create
ansible/configs/ans-tower-prod/files/cloud_providers/ec2_cloud_template.j2
New file
@@ -0,0 +1,529 @@
#jinja2: lstrip_blocks: "True"
---
AWSTemplateFormatVersion: "2010-09-09"
Mappings:
  RegionMapping: {{ aws_ami_region_mapping | to_json }}
Resources:
  Vpc:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "{{ aws_vpc_cidr }}"
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: "{{ aws_vpc_name }}"
        - Key: Hostlication
          Value:
            Ref: "AWS::StackId"
  VpcInternetGateway:
    Type: "AWS::EC2::InternetGateway"
  VpcRouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId:
        Ref: Vpc
  VPCRouteInternetGateway:
    DependsOn: VpcGA
    Type: "AWS::EC2::Route"
    Properties:
      GatewayId:
        Ref: VpcInternetGateway
      DestinationCidrBlock: "0.0.0.0/0"
      RouteTableId:
        Ref: VpcRouteTable
  VpcGA:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      InternetGatewayId:
        Ref: VpcInternetGateway
      VpcId:
        Ref: Vpc
  PublicSubnet:
    Type: "AWS::EC2::Subnet"
    DependsOn:
      - Vpc
    Properties:
    {% if aws_availability_zone is defined %}
      AvailabilityZone: {{ aws_availability_zone }}
    {% endif %}
      CidrBlock: "{{ aws_public_subnet_cidr }}"
      Tags:
        - Key: Name
          Value: "{{project_tag}}"
        - Key: Hostlication
          Value:
            Ref: "AWS::StackId"
      MapPublicIpOnLaunch: true
      VpcId:
        Ref: Vpc
  PublicSubnetRTA:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId:
        Ref: VpcRouteTable
      SubnetId:
        Ref: PublicSubnet
{% for security_group in security_groups|list + default_security_groups|list %}
  {{security_group['name']}}:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: Host
      VpcId:
        Ref: Vpc
      Tags:
        - Key: Name
          Value: "{{security_group['name']}}"
{% endfor %}
{% for security_group in default_security_groups|list + security_groups|list %}
{% for rule in security_group.rules %}
  {{security_group['name']}}{{rule['name']}}:
    Type: "AWS::EC2::SecurityGroup{{rule['rule_type']}}"
    Properties:
     GroupId:
       Fn::GetAtt:
         - "{{security_group['name']}}"
         - GroupId
     IpProtocol: {{rule['protocol']}}
     FromPort: {{rule['from_port']}}
     ToPort: {{rule['to_port']}}
  {% if rule['cidr'] is defined %}
     CidrIp: "{{rule['cidr']}}"
  {% endif  %}
  {% if rule['from_group'] is defined %}
     SourceSecurityGroupId:
       Fn::GetAtt:
        - "{{rule['from_group']}}"
        - GroupId
  {% endif  %}
{% endfor %}
{% endfor %}
  DnsZonePrivate:
    Type: "AWS::Route53::HostedZone"
    Properties:
      Name: "{{ aws_dns_zone_private }}"
      VPCs:
        - VPCId:
            Ref: Vpc
          VPCRegion:
            Ref: "AWS::Region"
      HostedZoneConfig:
        Comment: "{{ aws_comment }}"
  {% if secondary_stack is not defined %}
  DnsZonePublic:
    Type: "AWS::Route53::HostedZone"
    Properties:
      Name: "{{ aws_dns_zone_public }}"
      HostedZoneConfig:
        Comment: "{{ aws_comment }}"
  DnsPublicDelegation:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
      - DnsZonePublic
    Properties:
    {% if HostedZoneId is defined %}
      HostedZoneId: "{{ HostedZoneId }}"
    {% else %}
      HostedZoneName: "{{ aws_dns_zone_root }}"
    {% endif %}
      RecordSets:
        - Name: "{{ aws_dns_zone_public }}"
          Type: NS
          TTL: {{ aws_dns_ttl_public }}
          ResourceRecords:
            "Fn::GetAtt":
              - DnsZonePublic
              - NameServers
    {% endif %}
{% for instance in instances %}
{% if instance['dns_loadbalancer'] | d(false) | bool
  and not instance['unique'] | d(false) | bool %}
  {{instance['name']}}DnsLoadBalancer:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
    {% for c in range(1, (instance['count']|int)+1) %}
      - {{instance['name']}}{{c}}
      {% if instance['public_dns'] %}
      - {{instance['name']}}{{c}}EIP
      {% endif %}
    {% endfor %}
    Properties:
      {% if secondary_stack is defined %}
      HostedZoneName: "{{ aws_dns_zone_public }}"
      {% else %}
      HostedZoneId:
        Ref: DnsZonePublic
      {% endif %}
      RecordSets:
      - Name: "{{instance['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
        Type: A
        TTL: {{ aws_dns_ttl_public }}
        ResourceRecords:
{% for c in range(1,(instance['count'] |int)+1) %}
          - "Fn::GetAtt":
            - {{instance['name']}}{{c}}
            - PublicIp
{% endfor %}
{% endif %}
{% for c in range(1,(instance['count'] |int)+1) %}
  {{instance['name']}}{{loop.index}}:
    Type: "AWS::EC2::Instance"
    Properties:
{% if custom_image is defined %}
      ImageId: {{ custom_image.image_id }}
{% else %}
      ImageId:
        Fn::FindInMap:
        - RegionMapping
        - Ref: AWS::Region
        - {{ instance.image | default(aws_default_image) }}
{% endif %}
      InstanceType: "{{instance['flavor'][cloud_provider]}}"
      KeyName: "{{instance.key_name | default(key_name)}}"
    {% if instance['UserData'] is defined %}
      {{instance['UserData']}}
    {% endif %}
    {% if instance['security_groups'] is defined %}
      SecurityGroupIds:
      {% for sg in instance.security_groups %}
        - Ref: {{ sg }}
      {% endfor %}
    {% else %}
      SecurityGroupIds:
        - Ref: DefaultSG
    {% endif %}
      SubnetId:
        Ref: PublicSubnet
      Tags:
    {% if instance['unique'] | d(false) | bool %}
        - Key: Name
          Value: {{instance['name']}}
        - Key: internaldns
          Value: {{instance['name']}}.{{aws_dns_zone_private_chomped}}
    {% else %}
        - Key: Name
          Value: {{instance['name']}}{{loop.index}}
        - Key: internaldns
          Value: {{instance['name']}}{{loop.index}}.{{aws_dns_zone_private_chomped}}
    {% endif %}
        - Key: "owner"
          Value: "{{ email | default('unknownuser') }}"
        - Key: "Project"
          Value: "{{project_tag}}"
        - Key: "{{project_tag}}"
          Value: "{{ instance['name'] }}"
    {% for tag in instance['tags'] %}
        - Key: {{tag['key']}}
          Value: {{tag['value']}}
    {% endfor %}
      BlockDeviceMappings:
    {% if '/dev/sda1' not in instance.volumes|d([])|json_query('[].device_name')
      and '/dev/sda1' not in instance.volumes|d([])|json_query('[].name')
%}
        - DeviceName: "/dev/sda1"
          Ebs:
            VolumeSize: "{{ instance['rootfs_size'] | default(aws_default_rootfs_size) }}"
            VolumeType: "{{ aws_default_volume_type }}"
    {% endif %}
    {% for vol in instance.volumes|default([]) if vol.enable|d(true) %}
        - DeviceName: "{{ vol.name | default(vol.device_name) }}"
          Ebs:
          {% if cloud_provider in vol and 'type' in vol.ec2 %}
            VolumeType: "{{ vol[cloud_provider].type }}"
          {% else %}
            VolumeType: "{{ aws_default_volume_type }}"
          {% endif %}
            VolumeSize: "{{ vol.size }}"
    {% endfor %}
  {{instance['name']}}{{loop.index}}InternalDns:
    Type: "AWS::Route53::RecordSetGroup"
    Properties:
      HostedZoneId:
        Ref: DnsZonePrivate
      RecordSets:
    {% if instance['unique'] | d(false) | bool %}
        - Name: "{{instance['name']}}.{{aws_dns_zone_private}}"
    {% else %}
        - Name: "{{instance['name']}}{{loop.index}}.{{aws_dns_zone_private}}"
    {% endif %}
          Type: A
          TTL: {{ aws_dns_ttl_private }}
          ResourceRecords:
            - "Fn::GetAtt":
              - {{instance['name']}}{{loop.index}}
              - PrivateIp
{% if instance['public_dns'] %}
  {{instance['name']}}{{loop.index}}EIP:
    Type: "AWS::EC2::EIP"
    DependsOn:
    - VpcGA
    Properties:
      InstanceId:
        Ref: {{instance['name']}}{{loop.index}}
  {{instance['name']}}{{loop.index}}PublicDns:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
      - {{instance['name']}}{{loop.index}}EIP
    Properties:
      {% if secondary_stack is defined %}
      HostedZoneName: "{{ aws_dns_zone_public }}"
      {% else %}
      HostedZoneId:
        Ref: DnsZonePublic
      {% endif %}
      RecordSets:
      {% if instance['unique'] | d(false) | bool %}
        - Name: "{{instance['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
      {% else %}
        - Name: "{{instance['name']}}{{loop.index}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
      {% endif %}
          Type: A
          TTL: {{ aws_dns_ttl_public }}
          ResourceRecords:
          - "Fn::GetAtt":
            - {{instance['name']}}{{loop.index}}
            - PublicIp
{% endif %}
{% endfor %}
{% endfor %}
{% for worker_region in target_regions %}
{% for instance in instances_worker %}
{% if instance['dns_loadbalancer'] | d(false) | bool
  and not instance['unique'] | d(false) | bool %}
  {{instance['name']}}{{worker_region['name']}}DnsLoadBalancer:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
    {% for c in range(1, (instance['count']|int)+1) %}
      - {{instance['name']}}{{c}}{{worker_region['name']}}
      {% if instance['public_dns'] %}
      - {{instance['name']}}{{c}}{{worker_region['name']}}EIP
      {% endif %}
    {% endfor %}
    Properties:
      {% if secondary_stack is defined %}
      HostedZoneName: "{{ aws_dns_zone_public }}"
      {% else %}
      HostedZoneId:
        Ref: DnsZonePublic
      {% endif %}
      RecordSets:
      - Name: "{{instance['name']}}{{worker_region['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
        Type: A
        TTL: {{ aws_dns_ttl_public }}
        ResourceRecords:
{% for c in range(1,(instance['count'] |int)+1) %}
          - "Fn::GetAtt":
            - {{instance['name']}}{{c}}.{{worker_region['name']}}
            - PublicIp
{% endfor %}
{% endif %}
{% for c in range(1,(instance['count'] |int)+1) %}
  {{instance['name']}}{{loop.index}}{{worker_region['name']}}:
    Type: "AWS::EC2::Instance"
    Properties:
{% if custom_image is defined %}
      ImageId: {{ custom_image.image_id }}
{% else %}
      ImageId:
        Fn::FindInMap:
        - RegionMapping
        - Ref: AWS::Region
        - {{ instance.image | default(aws_default_image) }}
{% endif %}
      InstanceType: "{{instance['flavor'][cloud_provider]}}"
      KeyName: "{{instance.key_name | default(key_name)}}"
    {% if instance['UserData'] is defined %}
      {{instance['UserData']}}
    {% endif %}
    {% if instance['security_groups'] is defined %}
      SecurityGroupIds:
      {% for sg in instance.security_groups %}
        - Ref: {{ sg }}
      {% endfor %}
    {% else %}
      SecurityGroupIds:
        - Ref: DefaultSG
    {% endif %}
      SubnetId:
        Ref: PublicSubnet
      Tags:
    {% if instance['unique'] | d(false) | bool %}
        - Key: Name
          Value: {{instance['name']}}
        - Key: internaldns
          Value: {{instance['name']}}{{worker_region['name']}}.{{aws_dns_zone_private_chomped}}
    {% else %}
        - Key: Name
          Value: {{instance['name']}}{{loop.index}}.{{worker_region['name']}}
        - Key: internaldns
          Value: {{instance['name']}}{{loop.index}}.{{worker_region['name']}}.{{aws_dns_zone_private_chomped}}
    {% endif %}
        - Key: "owner"
          Value: "{{ email | default('unknownuser') }}"
        - Key: "Project"
          Value: "{{project_tag}}"
        - Key: "{{project_tag}}"
          Value: "{{ instance['name'] }}{{loop.index}}.{{worker_region['name']}}"
    {% for tag in instance['tags'] %}
        - Key: {{tag['key']}}
          Value: {{tag['value']}}
    {% endfor %}
      BlockDeviceMappings:
    {% if '/dev/sda1' not in instance.volumes|d([])|json_query('[].device_name')
      and '/dev/sda1' not in instance.volumes|d([])|json_query('[].name')
%}
        - DeviceName: "/dev/sda1"
          Ebs:
            VolumeSize: "{{ instance['rootfs_size'] | default(aws_default_rootfs_size) }}"
            VolumeType: "{{ aws_default_volume_type }}"
    {% endif %}
    {% for vol in instance.volumes|default([]) if vol.enable|d(true) %}
        - DeviceName: "{{ vol.name | default(vol.device_name) }}"
          Ebs:
          {% if cloud_provider in vol and 'type' in vol.ec2 %}
            VolumeType: "{{ vol[cloud_provider].type }}"
          {% else %}
            VolumeType: "{{ aws_default_volume_type }}"
          {% endif %}
            VolumeSize: "{{ vol.size }}"
    {% endfor %}
  {{instance['name']}}{{loop.index}}{{worker_region['name']}}InternalDns:
    Type: "AWS::Route53::RecordSetGroup"
    Properties:
      HostedZoneId:
        Ref: DnsZonePrivate
      RecordSets:
    {% if instance['unique'] | d(false) | bool %}
        - Name: "{{instance['name']}}{{worker_region['name']}}.{{aws_dns_zone_private}}"
    {% else %}
        - Name: "{{instance['name']}}{{loop.index}}.{{worker_region['name']}}.{{aws_dns_zone_private}}"
    {% endif %}
          Type: A
          TTL: {{ aws_dns_ttl_private }}
          ResourceRecords:
            - "Fn::GetAtt":
              - {{instance['name']}}{{loop.index}}{{worker_region['name']}}
              - PrivateIp
{% if instance['public_dns'] %}
  {{instance['name']}}{{loop.index}}{{worker_region['name']}}EIP:
    Type: "AWS::EC2::EIP"
    DependsOn:
    - VpcGA
    Properties:
      InstanceId:
        Ref: {{instance['name']}}{{loop.index}}{{worker_region['name']}}
  {{instance['name']}}{{loop.index}}{{worker_region['name']}}PublicDns:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
      - {{instance['name']}}{{loop.index}}{{worker_region['name']}}EIP
    Properties:
      {% if secondary_stack is defined %}
      HostedZoneName: "{{ aws_dns_zone_public }}"
      {% else %}
      HostedZoneId:
        Ref: DnsZonePublic
      {% endif %}
      RecordSets:
      {% if instance['unique'] | d(false) | bool %}
        - Name: "{{instance['name']}}{{worker_region['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
      {% else %}
        - Name: "{{instance['name']}}{{loop.index}}.{{worker_region['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
      {% endif %}
          Type: A
          TTL: {{ aws_dns_ttl_public }}
          ResourceRecords:
          - "Fn::GetAtt":
            - {{instance['name']}}{{loop.index}}.{{worker_region['name']}}
            - PublicIp
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
  {% if secondary_stack is not defined %}
  Route53User:
    Type: AWS::IAM::User
    Properties:
      Policies:
        - PolicyName: Route53Access
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action: route53:GetHostedZone
                Resource: arn:aws:route53:::change/*
              - Effect: Allow
                Action: route53:ListHostedZones
                Resource: "*"
              - Effect: Allow
                Action:
                  - route53:ChangeResourceRecordSets
                  - route53:ListResourceRecordSets
                  - route53:GetHostedZone
                Resource:
                  Fn::Join:
                    - ""
                    - - "arn:aws:route53:::hostedzone/"
                      - Ref: DnsZonePublic
              - Effect: Allow
                Action: route53:GetChange
                Resource: arn:aws:route53:::change/*
  Route53UserAccessKey:
      DependsOn: Route53User
      Type: AWS::IAM::AccessKey
      Properties:
        UserName:
          Ref: Route53User
  {% endif %}
Outputs:
  Route53internalzoneOutput:
    Description: The ID of the internal route 53 zone
    Value:
      Ref: DnsZonePrivate
  {% if secondary_stack is not defined %}
  Route53User:
    Value:
      Ref: Route53User
    Description: IAM User for Route53 (Let's Encrypt)
  Route53UserAccessKey:
    Value:
      Ref: Route53UserAccessKey
    Description: IAM User for Route53 (Let's Encrypt)
  Route53UserSecretAccessKey:
    Value:
      Fn::GetAtt:
        - Route53UserAccessKey
        - SecretAccessKey
    Description: IAM User for Route53 (Let's Encrypt)
  {% endif %}
ansible/configs/ans-tower-prod/files/hosts_template.j2
@@ -1,20 +1,28 @@
[tower]
{% for host in groups['towers'] %}
tower{{loop.index}}.{{chomped_zone_internal_dns}} public_host_name=tower{{loop.index}}.{{ guid }}{{subdomain_base_suffix}} ssh_host={{host}}
{{ host }}
{% endfor %}
[database]
## This should be replaced by supports[0] name
support1.{{chomped_zone_internal_dns}}
## Add isolated if needed, we should have an "IF" statement, only if worker groups exist and have instances.
{% if worker == 'yes' %}
[isolated_group_{{region}}]
{% if target_regions is defined %}
{%for i_region in target_regions %}
[isolated_group_{{i_region.name}}]
{% for host in groups['workers'] %}
worker{{loop.index}}.{{chomped_zone_internal_dns}} public_host_name=worker{{loop.index}}.{{ guid }}{{subdomain_base_suffix}} ssh_host={{host}}
{% if '.' + i_region['name'] + '.' in host %}
{{ host }}
{% endif %}
{% endfor %}
[isolated_group_{{region}}:vars]
[isolated_group_{{i_region.name}}:vars]
controller=tower
{% endfor %}
{% endif %}
@@ -22,33 +30,22 @@
ansible_become=true
admin_password={{tower_admin_password}}
## This should be replaced by supports[0] name
pg_host='support1.{{guid}}.internal'
pg_host='support1.{{chomped_zone_internal_dns}}'
pg_port='5432'
pg_database='awx'
pg_username='awx'
pg_password={{tower_admin_password}}
rabbitmq_port=5672
rabbitmq_vhost=tower
rabbitmq_username=tower
rabbitmq_password={{ tower_admin_password | regex_replace('[^a-zA-Z0-9]') }}
rabbitmq_cookie=cookiemonster
rabbitmq_use_long_name=true
### For our use, not Tower install use (so you can run ansible command line)
[supports]
{% for host in groups['support'] %}
support{{loop.index}}.{{chomped_zone_internal_dns}} public_host_name=support{{loop.index}}.{{ guid }}{{subdomain_base_suffix}} ssh_host={{host}}
{{ host }}
{% endfor %}
{% if worker == 'yes' %}
[workers]
{% for host in groups['workers'] %}
worker{{loop.index}}.{{chomped_zone_internal_dns}} public_host_name=worker{{loop.index}}.{{ guid }}{{subdomain_base_suffix}} ssh_host={{host}}
{% endfor %}
{% endif %}
ansible/configs/ans-tower-prod/files/tower_cli.j2
@@ -1,5 +1,5 @@
[general]
host = tower1.{{guid}}.example.opentlc.com
host = {{ tower_hostname }}
username = admin
password = {{tower_admin_password}}
verify_ssl = False
ansible/configs/ans-tower-prod/post_software.yml
@@ -20,6 +20,7 @@
- name: PostSoftware flight-check
  hosts: localhost
  connection: local
ansible/configs/ans-tower-prod/sample_vars.yml
@@ -33,23 +33,48 @@
tower_version: 3.5.0-1                 # tower version you want to install 
region: apac                           # region can not be with special characters in case of isolated node group
software_to_deploy: tower              # Define tower to install tower or none to have only infra ready.
worker: no                            # Set yes to add isolated node group.
worker_instance_count: 0              # Set 0 to not to provision worker(isolated) nodes.
worker: yes                            # Set yes to add isolated node group.
worker_instance_count: 1             # Set 0 to not to provision worker(isolated) nodes.
tower_license: >
  {
    "eula_accepted": true,
    "company_name": "Red Hat",
    "contact_email": "name@redhat.com",
    "contact_name": "some person"
    "hostname": "70a415ef832159a36413fa599",
    "instance_count": 50,
    "license_date": 16581423619,
    "license_key":
    "eea1b84d1e39cfEXAMPLE5739066069e60c6d0aEXAMPLE2c29cc61b2aEXAMPLE",
    "license_type": "enterprise",
    "subscription_name": "Ansible Tower by Red Hat (50 Managed Nodes), RHT Internal",
    "trial": true
  }
# tower_license: >                     #Set the tower licencse in the same format. Do not forget to add "eula_accepted: true".
#   {
#     "eula_accepted": true,
#     "company_name": "Red Hat",
#     "contact_email": "name@redhat.com",
#     "contact_name": "some person"
#     "hostname": "70a415ef832159a36413fa599",
#     "instance_count": 50,
#     "license_date": 16581423619,
#     "license_key":
#     "eea1b84d1e39cfEXAMPLE5739066069e60c6d0aEXAMPLE2c29cc61b2aEXAMPLE",
#     "license_type": "enterprise",
#     "subscription_name": "Ansible Tower by Red Hat (50 Managed Nodes), RHT Internal",
#     "trial": true
#   }
# accounts:                                      #Define users you want to create. Set superuser: yes to make user system wide System Administrator
#   - user: test1
#     password: changeme
#     email: babylon@example.com
#     firstname: test1
#     lastname: one
#     superuser: yes
#   - user: test2
#     password: changeme
#     email: babylon1@example.com
#     firstname: test2
#     lastname: two
#   - user: test3
#   - user: test4
#     lastname: four
# tower_organization:
#   - name: gpte
#   - name: BU
target_regions:
  - name: na
  - name: emea
  - name: na
ansible/configs/ans-tower-prod/sample_vars_babylon.yml
New file
@@ -0,0 +1,169 @@
---
cloudformation_retries: 0
# ## Environment size
# bastion_instance_type: "t2.medium"
# tower_instance_type: "t2.medium"
# worker_instance_type: "t2.medium"
# support_instance_type: "t2.medium"
root_filesystem_size: 20                #Size of the root filesystem
# Env config basics
env_type: ans-tower-prod                 # Name of config to deploy
output_dir: /opt/workdir               # Writable working scratch directory
email: name@example.com                 # User info for notifications
#guid: hwtest2                          # Unique string used in FQDN
# AWS specific
subdomain_base_suffix: .example.opentlc.com      # Your domain used in FQDN
# Path to yum repos (Will be overwritten by the last -e@ file, such as ../secrets.yml)
own_repo_path: http://admin.example.com/repos/product
# Cloud specfic settings - example given here for AWS
cloud_provider: ec2                     # Which AgnosticD Cloud Provider to use
aws_region: ap-southeast-2                  # AWS Region to deploy in
HostedZoneId: Z3IHLWJZOU9SRT            # You will need to change this
key_name: ocpkey                       # Keyname must exist in AWS
#Ansible Tower related vars
tower_version: 3.5.0-1                 # tower version you want to install
region: apac                           # region can not be with special characters in case of isolated node group
software_to_deploy: tower              # Define tower to install tower or none to have only infra ready.
tower_instance_count: 2
support_instance_count: 2
worker_instance_count: 2              # Set 0 to not to provision worker(isolated) nodes.
# tower_license: >                     #Set the tower licencse in the same format. Do not forget to add "eula_accepted: true".
#   {
#     "eula_accepted": true,
#     "company_name": "Red Hat",
#     "contact_email": "name@redhat.com",
#     "contact_name": "some person"
#     "hostname": "70a415ef832159a36413fa599",
#     "instance_count": 50,
#     "license_date": 16581423619,
#     "license_key":
#     "eea1b84d1e39cfEXAMPLE5739066069e60c6d0aEXAMPLE2c29cc61b2aEXAMPLE",
#     "license_type": "enterprise",
#     "subscription_name": "Ansible Tower by Red Hat (50 Managed Nodes), RHT Internal",
#     "trial": true
#   }
tower_host_name: "tower1.{{guid}}{{subdomain_base_suffix}}"
tower_user_accounts:                                      #Define users you want to create. Set superuser: yes to make user system wide System Administrator
  - user: babylon
    password: changeme
    email: babylon@example.com
    firstname: Baby
    lastname: Lon
    superuser: yes
  - user: babylon-viewer
    password: changeme
    email: babylon1@example.com
    firstname: Babylon
    lastname: Viewer
#   - user: test3
#   - user: test4
#     lastname: four
tower_organization:
  - name: gpte
  - name: BU
target_regions:
  - name: emea
  - name: apac
### tower project roles
tower_projects:
  - name: babylon-dev
    description: "babylon dev project"
    organization: "gpte"
    scm_url: "https://github.com/redhat-gpte-devopsautomation/babylon.git"
    #scm_type:
    #scm_credential:
    scm_branch:  dev
    scm_update_on_launch: true
tower_inventories:
  - name: empty-inventory-emea
    description: emea
    organization: gpte
    instance_group: emea
  - name: empty-inventory-apac
    description: apac
    organization: gpte
    instance_group: apac
  - name: empty-inventory
    description: "Empty inventory"
    organization: gpte
    # instance_group: ""
tower_job_templates:
  - name: babylon_job_runner
    description: "babylon job runner"
    job_type: run
    #vault_credential:
    project: babylon
    playbook: job-runner.yml
    become: yes
# Tower settings
tower_setting_params:
  AWX_PROOT_BASE_PATH: "/tmp"
  AWX_PROOT_SHOW_PATHS: "'/var/lib/awx/projects/', '/tmp'"
# List of virtual environment which will be created
# restart of tower service is required
# ansible-tower-service restart
# https://docs.ansible.com/ansible-tower/latest/html/userguide/security.html
tower_virtual_environment:
  - /var/lib/awx/venv/ansible
  - /var/lib/awx/venv/test1
# Path of Virtual Env for update
tower_update_venv: /var/lib/awx/venv/ansible
# Pip packages with version which needs to be updated for venv
pip_requirements:
  - boto==2.49.0
  - boto3==1.9.200
  - awscli==1.16.210
  - ansible-tower-cli==3.3.6
key_local_path: "~/.ssh/{{key_name}}.pem"
tower_job_templates:
  - name: job-runner-dev
    description: "babylon job runner dev"
    job_type: run
    #vault_credential:
    project: babylon-dev
    playbook: job-runner.yml
    inventory: empty-inventory
    become: yes
# Tower settings
tower_setting_params:
  AWX_PROOT_BASE_PATH: "/tmp"
  AWX_PROOT_SHOW_PATHS: "'/var/lib/awx/projects/', '/tmp'"
# List of virtual environment which will be created (WIP)
# tower_virtual_environment:
#   - /var/lib/awx/venv/ansible
#   - /var/lib/awx/venv/test1
# Path of Virtual Env for update
tower_update_venv: /var/lib/awx/venv/ansible
key_local_path: "~/.ssh/{{key_name}}.pem"
ansible/configs/ans-tower-prod/tower_workloads.yml
@@ -1,12 +1,55 @@
- hosts: bastions
---
- name: Install workloads
  hosts: bastions
  gather_facts: false
  become: yes
  run_once: true
  become: true
  tasks:
  - name: Include workload roles
    include_role:
      name: tower-license-injector
    vars:
      tower_license: "{{ tower_license }}"
    when: tower_license is defined
  - set_fact:
      tower_hostname: "{{ item | first }}"
    loop:
      - "{{ query('inventory_hostnames', 'towers') }}"
  - name: Install tower-default workloads
    when:
    - default_workloads | d("") | length > 0
    tags:
      - tower-license-injector
    - default_workloads
    block:
    - name: Install tower-default-workloads
      when:
      - default_workloads | d("") | length >0
      block:
      - name: Deploy tower-default workloads
        include_role:
          name: "{{ workload_loop_var }}"
        vars:
          tower_username: "admin"
        loop: "{{ default_workloads }}"
        loop_control:
          loop_var: workload_loop_var
  - name: Install tower-infra workloads
    when:
    - infra_workloads|d("")|length > 0
    tags:
      - infra_workloads
    block:
    - name: Check if admin_user is set
      fail:
        msg: admin_user must be set for tower-infra workloads
      when:
      - not admin_user is defined or admin_user|length == 0
    - name: Install tower-infra-workloads
      when:
      - infra_workloads|d("")|length >0
      block:
      - name: Deploy tower-infra workloads
        include_role:
          name: "{{ workload_loop_var }}"
        vars:
          tower_username: admin
          ACTION: "provision"
        loop: "{{ infra_workloads.split(',')|list }}"
        loop_control:
          loop_var: workload_loop_var
ansible/configs/ans-tower-prod/tower_workloads_workaround.yml
New file
@@ -0,0 +1,156 @@
- hosts: bastions
  gather_facts: false
  become: yes
  tasks:
  - name: Inject License
    include_role:
      name: tower-license-injector
    when: tower_license is defined
    tags:
      - tower-license-injector
###### delete demo stuff #######
  - name: Delete Demo Job Template
    tower_job_template:
      name: "Demo Job Template"
      state: absent
      job_type: run
      playbook: "hello_world.yml"
      project: "Demo Project"
      inventory: "Demo Inventory"
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    ignore_errors: yes
  - name: Delete Demo Credential
    command: tower-cli credential delete -n "Demo Credential"
    ignore_errors: yes
  - name: Delete Demo Project
    tower_project:
      name: "Demo Project"
      state: absent
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    ignore_errors: yes
  - name: Delete Demo Inventory
    tower_inventory:
      name: "Demo Inventory"
      organization: Default
      state: absent
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    ignore_errors: yes
###### Create tower users #####
  - name: Add tower user
    tower_user:
      username: "{{ item.user }}"
      password: "{{ item.password | default('change_me') }}"
      email: "{{ item.email | default('rhpds-admins@redhat.com') }}"
      first_name: "{{ item.firstname | default(item.user) }}"
      last_name: "{{ item.lastname | default(item.user) }}"
      superuser: "{{ item.superuser | default('no')}}"
      state: present
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    loop: "{{ tower_accounts }}"
    when: tower_accounts is defined
    tags:
      - tower-user-create
#### Create Tower Organization ####
  - name: Add tower org
    tower_organization:
      name: "{{ item.name }}"
      state: present
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    loop: "{{ tower_organization }}"
    when: tower_organization is defined
    tags:
      - tower-org-create
#### Create tower Project #####
  - name: Add tower project
    tower_project:
      name: "{{ item.name }}"
      description: "{{ item.description }}"
      organization:  "{{ item.organization | default('Default')}}"
      scm_url:  "{{ item.scm_url }}"
      scm_type: "{{ item.scm_type | d('git')}}"
      scm_credential: "{{ item.scm_credential | d('')}}"
      scm_branch:  "{{ item.scm_branch | d('master') }}"
      scm_update_on_launch: "{{ item.scm_update_on_launch | d('false') }}"
      state: present
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    loop: "{{ tower_projects }}"
    when: tower_projects is defined
    tags:
      - tower-project-create
#### Create tower Inventory ####
  - name: Block for Inventory
    when: tower_inventories is defined
    block:
    - name: Add tower inventory
      tower_inventory:
        name: "{{ item.name }}"
        description: "{{ item.description  }}"
        organization: "{{ item.organization | d('gpte') }}"
        state: present
        tower_host: "{{ tower_host_name }}"
        tower_username: admin
        tower_password: "{{tower_admin_password}}"
        tower_verify_ssl: false
      loop: "{{ tower_inventories }}"
      tags:
        - tower-inventory-create
    - name: Associate instance group to inventory
      command: >-
        tower-cli inventory
        associate_ig
        --inventory "{{ item.name }}"
        --instance-group "{{ item.instance_group | d('') }}"
      loop: "{{ tower_inventories }}"
      when:
        - item.instance_group is defined
#### Create Tower Job Template ####
  - name: Add tower JobTemplate
    tower_job_template:
      name: "{{ item.name }}"
      description: "{{ item.description  }}"
      job_type: run
      ask_inventory: Yes
      ask_credential: Yes
      vault_credential: "{{ item.vault_credential | d('') }}"
      ask_extra_vars: Yes
      project: "{{ item.project }}"
      playbook: "{{ item.playbook | d('main.yml') }}"
      become_enabled: "{{ item.become | d('no') }}"
      concurrent_jobs_enabled: Yes
      state: present
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    loop: "{{ tower_job_templates }}"
    when: tower_job_templates is defined
    tags:
      - tower-job-template-create
ansible/configs/ansible-cicd-lab/files/tower_hosts_template.j2
@@ -4,14 +4,14 @@
tower{{loop.index}}.{{chomped_zone_internal_dns}} public_host_name=tower{{loop.index}}.{{ guid }}{{subdomain_base_suffix}} ssh_host={{host}}
{% endfor %}
[database]
tower1.{{chomped_zone_internal_dns}}
tower1.{{target_regions[0].name}}.{{guid}}{{subdomain_base_suffix}}
[all:vars]
ansible_become=true
admin_password={{tower_admin_password}}
pg_host='tower1.{{chomped_zone_internal_dns}}'
pg_host='tower1.{{target_regions[0].name}}.{{guid}}{{subdomain_base_suffix}}'
pg_port='5432'
pg_database='awx'
ansible/configs/ansible-tower/README.adoc
New file
@@ -0,0 +1,74 @@
= ansible-tower config
Author: Prakhar Srivastava, psrivast@redhat.com and Shachar Boresntein, sha@redhat.com
Owner: Prakhar Srivastava, psrivast@redhat.com
Alternative Contacts: Tony Kay, tok@redhat.com,
== Overview
ansible-tower config was initially created for the babylon Project to deploy Ansible Tower and worker ndoes (isolated nodes)
+
[source,yaml]
----
deploy_tower_homework: true
----
== NOTES
. There are 2 sets of deployer scripts:
**
**
. There are 2 sets of secrets files:
**
**
The "homework" lab requires additional secrets over and above the usual cloud (AWS)
credentails and repo path. These are traditionally stored on the admin host and
can be located via the relevant deployer scripts.
== Review the Env_Type variable file
* This file link:./env_vars.yml[./env_vars.yml] contains all the variables you
 need to define to control, or customize, the deployment of your environment. In
normal usage this should not need to be touched or ammended and one-off changes
can be tested by passing vars or var files with `-e` or `-e @my_version_vars.yml`.
== Secrets
As noted above a deploy of the normal lab requires just 3
== Running Ansible Playbook
You can run the playbook with the following arguments to overwrite the default variable values:
From the `ansible_agnostic_deployer/ansible` directory run
`
[source,bash]
----
ansible-playbook ansible/main.yml  \
      -e @./ansible/configs/ans-tower-prod/sample_vars.yml \
      -e @../secret.yml \
      -e "guid=sborenstest2" -vvvv
----
=== To Delete an environment
----
REGION=us-east-1
KEYNAME=ocpkey
GUID=test02
ENVTYPE=ans-tower-lab
CLOUDPROVIDER=ec2
ansible-playbook configs/${ENVTYPE}/destroy_env.yml \
        -e "guid=${GUID}" -e "env_type=${ENVTYPE}" \
        -e "cloud_provider=${CLOUDPROVIDER}" \
        -e "aws_region=${REGION}"  -e "key_name=${KEYNAME}"  \
        -e "subdomain_base_suffix=${BASESUFFIX}" \
        -e @~/secret.yml -vv
----
ansible/configs/ansible-tower/destroy_env.yml
New file
@@ -0,0 +1,18 @@
---
- import_playbook: ../../include_vars.yml
- name: Delete Infrastructure
  hosts: localhost
  connection: local
  gather_facts: False
  become: no
  tasks:
    - name: Run infra-ec2-template-destroy
      include_role:
        name: "infra-{{cloud_provider}}-template-destroy"
      when: cloud_provider == 'ec2'
    - name: Run infra-azure-template-destroy
      include_role:
        name: "infra-{{cloud_provider}}-template-destroy"
      when: cloud_provider == 'azure'
ansible/configs/ansible-tower/env_vars.yml
New file
@@ -0,0 +1,450 @@
---
## TODO: What variables can we strip out of here to build complex variables?
## i.e. what can we add into group_vars as opposed to config_vars?
## Example: We don't really need "subdomain_base_short". If we want to use this,
## should just toss in group_vars/all.
### Also, we should probably just create a variable reference in the README.md
### For now, just tagging comments in line with configuration file.
### Vars that can be removed:
# use_satellite: true
# use_subscription_manager: false
# use_own_repos: false
###### VARIABLES YOU SHOULD CONFIGURE FOR YOUR DEPLOYEMNT
###### OR PASS as "-e" args to ansible-playbook command
### Common Host settings
repo_method: file # Other Options are: file, satellite and rhn
tower_admin_password: 'changeme'
tower_version: "3.4.3-1"
# Do you want to run a full yum update
update_packages: false
#If using repo_method: satellite, you must set these values as well.
# satellite_url: satellite.example.com
# satellite_org: Sat_org_name
# satellite_activationkey: "rhel7basic"
## guid is the deployment unique identifier, it will be appended to all tags,
## files and anything that identifies this environment from another "just like it"
guid: defaultguid
install_bastion: true
install_common: true
install_ipa_client: false
## SB Don't set software_to_deploy from here, always use extra vars (-e) or "none" will be used
software_to_deploy: tower
repo_version: "{{tower_version}}"
### If you want a Key Pair name created and injected into the hosts,
# set `set_env_authorized_key` to true and set the keyname in `env_authorized_key`
# you can use the key used to create the environment or use your own self generated key
# if you set "use_own_key" to false your PRIVATE key will be copied to the bastion. (This is {{key_name}})
use_own_key: true
env_authorized_key: "{{guid}}key"
ansible_ssh_private_key_file: ~/.ssh/{{key_name}}.pem
set_env_authorized_key: true
### AWS EC2 Environment settings
### Route 53 Zone ID (AWS)
# This is the Route53 HostedZoneId where you will create your Public DNS entries
# This only needs to be defined if your CF template uses route53
HostedZoneId: Z3IHLWJZOU9SRT
# The region to be used, if not specified by -e in the command line
aws_region: ap-southeast-2
# The key that is used to
key_name: "default_key_name"
## Networking (AWS)
subdomain_base_short: "{{ guid }}"
subdomain_base_suffix: ".example.opentlc.com"
subdomain_base: "{{subdomain_base_short}}{{subdomain_base_suffix}}"
## Environment Sizing
bastion_instance_type: "t2.medium"
tower_instance_count: 3
tower_instance_type: "t2.medium"
worker_instance_count: 2
worker_instance_type: "t2.medium"
support_instance_count: 2
support_instance_type: "t2.medium"
subnets:
  - name: PublicSubnet
    cidr: "192.168.1.0/24"
    routing_table: true
security_groups:
  - name: BastionSG
    rules:
      - name: BasSSHPublic
        description: "SSH public"
        from_port: 22
        to_port: 22
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
  - name: TowerSG
    rules:
      - name: SSHTower
        description: "SSH public"
        from_port: 22
        to_port: 22
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: HTTPTower
        description: "HTTP public"
        from_port: 80
        to_port: 80
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: HTTPSTower
        description: "HTTP public"
        from_port: 443
        to_port: 443
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: RabbitMq
        description: "RabbitMq"
        from_port: 5672
        to_port: 5672
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: EPMD
        description: "EPMD"
        from_port: 4369
        to_port: 4369
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: RabbitMqCLi
        description: "RabbitMq Cli"
        from_port: 25672
        to_port: 25672
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: RabbitMqAPi
        description: "RabbitMq Api"
        from_port: 15672
        to_port: 15672
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: RabbitMqCliTools
        description: "RabbitMq CLi tools"
        from_port: 35672
        to_port: 35672
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: BasTowerTcp
        description: "ALL from bastion tcp"
        from_port: 0
        to_port: 65535
        protocol: tcp
        group: BastionSG
        rule_type: Ingress
      - name: BasTowerUdp
        description: "ALL from bastion udp"
        from_port: 0
        to_port: 65535
        protocol: udp
        group: BastionSG
        rule_type: Ingress
      - name: AllInternaltcp
        description: "All other nodes tcp"
        from_port: 0
        to_port: 65535
        protocol: tcp
        group: HostSG
        rule_type: Ingress
      - name: AllInternaludp
        description: "All other nodes udp"
        from_port: 0
        to_port: 65535
        protocol: udp
        group: HostSG
        rule_type: Ingress
      - name: AllTowerNodestcp
        description: "All tower nodes tcp"
        from_port: 0
        to_port: 65535
        protocol: tcp
        group: TowerSG
        rule_type: Ingress
      - name: AllTowerNodesudp
        description: "All tower nodes udp"
        from_port: 0
        to_port: 65535
        protocol: udp
        group: TowerSG
        rule_type: Ingress
  - name: HostSG
    rules:
      - name: HostUDPPorts
        description: "Only from Itself udp"
        from_port: 0
        to_port: 65535
        protocol: udp
        group: HostSG
        rule_type: Ingress
      - name: Postgresql
        description: "PostgreSql"
        from_port: 5432
        to_port: 5432
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: HostTCPPorts
        description: "Only from Itself tcp"
        from_port: 0
        to_port: 65535
        protocol: tcp
        group: HostSG
        rule_type: Ingress
      - name: TowerUDPPorts
        description: "Only from tower"
        from_port: 0
        to_port: 65535
        protocol: udp
        group: TowerSG
        rule_type: Ingress
      - name: TowerTCPPorts
        description: "Only from tower"
        from_port: 0
        to_port: 65535
        protocol: tcp
        group: TowerSG
        rule_type: Ingress
      - name: BastionUDPPorts
        description: "Only from bastion"
        from_port: 0
        to_port: 65535
        protocol: udp
        group: BastionSG
        rule_type: Ingress
      - name: BastionTCPPorts
        description: "Only from bastion"
        from_port: 0
        to_port: 65535
        protocol: tcp
        group: BastionSG
        rule_type: Ingress
instances:
  - name: "bastion"
    count: 1
    unique: true
    public_dns: true
    dns_loadbalancer: true
    security_groups:
      - BastionSG
    flavor:
      ec2: "{{bastion_instance_type}}"
      azure: "{{bastion_instance_type}}"
    tags:
      - key: "AnsibleGroup"
        value: "bastions"
      - key: "ostype"
        value: "linux"
    rootfs_size: "{{root_filesystem_size}}"
    subnet: PublicSubnet
  - name: "tower"
    count: "{{tower_instance_count}}"
    security_groups:
      - TowerSG
    public_dns: true
    dns_loadbalancer: true
    flavor:
      "ec2": "{{tower_instance_type}}"
    tags:
      - key: "AnsibleGroup"
        value: "towers"
      - key: "ostype"
        value: "linux"
    subnet: PublicSubnet
  - name: "support"
    count: "{{support_instance_count}}"
    public_dns: true
    security_groups:
      - TowerSG
      - HostSG
    flavor:
      "ec2": "{{support_instance_type}}"
    tags:
      - key: "AnsibleGroup"
        value: "support"
      - key: "ostype"
        value: "rhel"
    key_name: "{{key_name}}"
    subnet: PublicSubnet
######### Worker instances #########
instances_worker:
  - name: "worker"
    count: "{{worker_instance_count}}"
    security_groups:
      - HostSG
      - TowerSG
    public_dns: false
    dns_loadbalancer: false
    flavor:
      "ec2": "{{worker_instance_type}}"
    tags:
      - key: "AnsibleGroup"
        value: "workers"
      - key: "ostype"
        value: "linux"
    subnet: PublicSubnet
#########
  # - name: "worker{{target_regions[1].name}}"
  #   count: "{{worker_instance_count}}"
  #   security_groups:
  #     - HostSG
  #     - TowerSG
  #   public_dns: false
  #   dns_loadbalancer: false
  #   flavor:
  #     "ec2": "{{worker_instance_type}}"
  #   tags:
  #     - key: "AnsibleGroup"
  #       value: "workers"
  #     - key: "ostype"
  #       value: "linux"
  #     - key: Worker_region
  #       value: "{{ target_regions[1].name }}"
  #   subnet: PublicSubnet
  #   #########
  # - name: "worker{{target_regions[2].name}}"
  #   count: "{{worker_instance_count}}"
  #   security_groups:
  #     - HostSG
  #     - TowerSG
  #   public_dns: false
  #   dns_loadbalancer: false
  #   flavor:
  #     "ec2": "{{worker_instance_type}}"
  #   tags:
  #     - key: "AnsibleGroup"
  #       value: "workers"
  #     - key: "ostype"
  #       value: "linux"
  #     - key: Worker_region
  #       value: "{{ target_regions[2].name }}"
  #   subnet: PublicSubnet
    #######*************#############
###### VARIABLES YOU SHOULD ***NOT*** CONFIGURE FOR YOUR DEPLOYEMNT
###### You can, but you usually wouldn't need to.
ansible_user: ec2-user
remote_user: ec2-user
common_packages:
  - python
  - unzip
  - bash-completion
  - tmux
  - bind-utils
  - wget
  - git
  - vim-enhanced
  - at
  - python-pip
  - gcc
  - ansible
rhel_repos:
  - rhel-7-server-rpms
  - rhel-7-server-rh-common-rpms
  - rhel-7-server-extras-rpms
  - rhel-7-server-optional-rpms
  - rhel-7-server-rhscl-rpms
  - epel-release-latest-7
project_tag: "{{ env_type }}-{{ guid }}"
zone_internal_dns: "{{guid}}.internal."
chomped_zone_internal_dns: "{{guid}}.internal"
tower_public_dns: "towerlb.{{subdomain_base}}."
#tower_public_dns: "tower.{{subdomain_base}}."
bastion_public_dns: "bastion.{{subdomain_base}}."
bastion_public_dns_chomped: "bastion.{{subdomain_base}}"
# we don't use this anymore <sborenst>
# activedirectory_public_dns: "ad.{{subdomain_base}}."
# activedirectory_public_dns_chomped: "ad.{{subdomain_base}}"
#
# vpcid_cidr_block: "192.168.0.0/16"
# vpcid_name_tag: "{{subdomain_base}}"
#
# az_1_name: "{{ aws_region }}a"
# az_2_name: "{{ aws_region }}b"
#
# subnet_private_1_cidr_block: "192.168.2.0/24"
# subnet_private_1_az: "{{ az_2_name }}"
# subnet_private_1_name_tag: "{{subdomain_base}}-private"
#
# subnet_private_2_cidr_block: "192.168.1.0/24"
# subnet_private_2_az: "{{ az_1_name }}"
# subnet_private_2_name_tag: "{{subdomain_base}}-private"
#
# subnet_public_1_cidr_block: "192.168.10.0/24"
# subnet_public_1_az: "{{ az_1_name }}"
# subnet_public_1_name_tag: "{{subdomain_base}}-public"
#
# subnet_public_2_cidr_block: "192.168.20.0/24"
# subnet_public_2_az: "{{ az_2_name }}"
# subnet_public_2_name_tag: "{{subdomain_base}}-public"
#
# dopt_domain_name: "{{ aws_region }}.compute.internal"
#
# rtb_public_name_tag: "{{subdomain_base}}-public"
# rtb_private_name_tag: "{{subdomain_base}}-private"
#
#
# cf_template_description: "{{ env_type }}-{{ guid }} Ansible Agnostic Deployer"
tower_run: false
default_workloads:
  - tower-copy-ssh
  - tower-license-injector
  - cleanup-tower-default
  - tower-settings-update
  - tower-pip-packages
  - tower-user-create
  - tower-org-create
  - tower-credential-create
  - tower-project-create
  - tower-inventory-create
  - tower-jobtemplate-create
  - tower-babylon-job-runner
# infra_workloads|:
#   - tower-settings-update
#   - tower-pip-packages
#   - tower-user-create
#   - tower-org-create
#   - tower-project-create
#   - tower-inventory-create
#   - tower-jobtemplate-create
ansible/configs/ansible-tower/files/cloud_providers/ec2_cloud_template.j2
New file
@@ -0,0 +1,529 @@
#jinja2: lstrip_blocks: "True"
---
AWSTemplateFormatVersion: "2010-09-09"
Mappings:
  RegionMapping: {{ aws_ami_region_mapping | to_json }}
Resources:
  Vpc:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "{{ aws_vpc_cidr }}"
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: "{{ aws_vpc_name }}"
        - Key: Hostlication
          Value:
            Ref: "AWS::StackId"
  VpcInternetGateway:
    Type: "AWS::EC2::InternetGateway"
  VpcRouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId:
        Ref: Vpc
  VPCRouteInternetGateway:
    DependsOn: VpcGA
    Type: "AWS::EC2::Route"
    Properties:
      GatewayId:
        Ref: VpcInternetGateway
      DestinationCidrBlock: "0.0.0.0/0"
      RouteTableId:
        Ref: VpcRouteTable
  VpcGA:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      InternetGatewayId:
        Ref: VpcInternetGateway
      VpcId:
        Ref: Vpc
  PublicSubnet:
    Type: "AWS::EC2::Subnet"
    DependsOn:
      - Vpc
    Properties:
    {% if aws_availability_zone is defined %}
      AvailabilityZone: {{ aws_availability_zone }}
    {% endif %}
      CidrBlock: "{{ aws_public_subnet_cidr }}"
      Tags:
        - Key: Name
          Value: "{{project_tag}}"
        - Key: Hostlication
          Value:
            Ref: "AWS::StackId"
      MapPublicIpOnLaunch: true
      VpcId:
        Ref: Vpc
  PublicSubnetRTA:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId:
        Ref: VpcRouteTable
      SubnetId:
        Ref: PublicSubnet
{% for security_group in security_groups|list + default_security_groups|list %}
  {{security_group['name']}}:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: Host
      VpcId:
        Ref: Vpc
      Tags:
        - Key: Name
          Value: "{{security_group['name']}}"
{% endfor %}
{% for security_group in default_security_groups|list + security_groups|list %}
{% for rule in security_group.rules %}
  {{security_group['name']}}{{rule['name']}}:
    Type: "AWS::EC2::SecurityGroup{{rule['rule_type']}}"
    Properties:
     GroupId:
       Fn::GetAtt:
         - "{{security_group['name']}}"
         - GroupId
     IpProtocol: {{rule['protocol']}}
     FromPort: {{rule['from_port']}}
     ToPort: {{rule['to_port']}}
  {% if rule['cidr'] is defined %}
     CidrIp: "{{rule['cidr']}}"
  {% endif  %}
  {% if rule['from_group'] is defined %}
     SourceSecurityGroupId:
       Fn::GetAtt:
        - "{{rule['from_group']}}"
        - GroupId
  {% endif  %}
{% endfor %}
{% endfor %}
  DnsZonePrivate:
    Type: "AWS::Route53::HostedZone"
    Properties:
      Name: "{{ aws_dns_zone_private }}"
      VPCs:
        - VPCId:
            Ref: Vpc
          VPCRegion:
            Ref: "AWS::Region"
      HostedZoneConfig:
        Comment: "{{ aws_comment }}"
  {% if secondary_stack is not defined %}
  DnsZonePublic:
    Type: "AWS::Route53::HostedZone"
    Properties:
      Name: "{{ aws_dns_zone_public }}"
      HostedZoneConfig:
        Comment: "{{ aws_comment }}"
  DnsPublicDelegation:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
      - DnsZonePublic
    Properties:
    {% if HostedZoneId is defined %}
      HostedZoneId: "{{ HostedZoneId }}"
    {% else %}
      HostedZoneName: "{{ aws_dns_zone_root }}"
    {% endif %}
      RecordSets:
        - Name: "{{ aws_dns_zone_public }}"
          Type: NS
          TTL: {{ aws_dns_ttl_public }}
          ResourceRecords:
            "Fn::GetAtt":
              - DnsZonePublic
              - NameServers
    {% endif %}
{% for instance in instances %}
{% if instance['dns_loadbalancer'] | d(false) | bool
  and not instance['unique'] | d(false) | bool %}
  {{instance['name']}}DnsLoadBalancer:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
    {% for c in range(1, (instance['count']|int)+1) %}
      - {{instance['name']}}{{c}}
      {% if instance['public_dns'] %}
      - {{instance['name']}}{{c}}EIP
      {% endif %}
    {% endfor %}
    Properties:
      {% if secondary_stack is defined %}
      HostedZoneName: "{{ aws_dns_zone_public }}"
      {% else %}
      HostedZoneId:
        Ref: DnsZonePublic
      {% endif %}
      RecordSets:
      - Name: "{{instance['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
        Type: A
        TTL: {{ aws_dns_ttl_public }}
        ResourceRecords:
{% for c in range(1,(instance['count'] |int)+1) %}
          - "Fn::GetAtt":
            - {{instance['name']}}{{c}}
            - PublicIp
{% endfor %}
{% endif %}
{% for c in range(1,(instance['count'] |int)+1) %}
  {{instance['name']}}{{loop.index}}:
    Type: "AWS::EC2::Instance"
    Properties:
{% if custom_image is defined %}
      ImageId: {{ custom_image.image_id }}
{% else %}
      ImageId:
        Fn::FindInMap:
        - RegionMapping
        - Ref: AWS::Region
        - {{ instance.image | default(aws_default_image) }}
{% endif %}
      InstanceType: "{{instance['flavor'][cloud_provider]}}"
      KeyName: "{{instance.key_name | default(key_name)}}"
    {% if instance['UserData'] is defined %}
      {{instance['UserData']}}
    {% endif %}
    {% if instance['security_groups'] is defined %}
      SecurityGroupIds:
      {% for sg in instance.security_groups %}
        - Ref: {{ sg }}
      {% endfor %}
    {% else %}
      SecurityGroupIds:
        - Ref: DefaultSG
    {% endif %}
      SubnetId:
        Ref: PublicSubnet
      Tags:
    {% if instance['unique'] | d(false) | bool %}
        - Key: Name
          Value: {{instance['name']}}
        - Key: internaldns
          Value: {{instance['name']}}.{{aws_dns_zone_private_chomped}}
    {% else %}
        - Key: Name
          Value: {{instance['name']}}{{loop.index}}
        - Key: internaldns
          Value: {{instance['name']}}{{loop.index}}.{{aws_dns_zone_private_chomped}}
    {% endif %}
        - Key: "owner"
          Value: "{{ email | default('unknownuser') }}"
        - Key: "Project"
          Value: "{{project_tag}}"
        - Key: "{{project_tag}}"
          Value: "{{ instance['name'] }}"
    {% for tag in instance['tags'] %}
        - Key: {{tag['key']}}
          Value: {{tag['value']}}
    {% endfor %}
      BlockDeviceMappings:
    {% if '/dev/sda1' not in instance.volumes|d([])|json_query('[].device_name')
      and '/dev/sda1' not in instance.volumes|d([])|json_query('[].name')
%}
        - DeviceName: "/dev/sda1"
          Ebs:
            VolumeSize: "{{ instance['rootfs_size'] | default(aws_default_rootfs_size) }}"
            VolumeType: "{{ aws_default_volume_type }}"
    {% endif %}
    {% for vol in instance.volumes|default([]) if vol.enable|d(true) %}
        - DeviceName: "{{ vol.name | default(vol.device_name) }}"
          Ebs:
          {% if cloud_provider in vol and 'type' in vol.ec2 %}
            VolumeType: "{{ vol[cloud_provider].type }}"
          {% else %}
            VolumeType: "{{ aws_default_volume_type }}"
          {% endif %}
            VolumeSize: "{{ vol.size }}"
    {% endfor %}
  {{instance['name']}}{{loop.index}}InternalDns:
    Type: "AWS::Route53::RecordSetGroup"
    Properties:
      HostedZoneId:
        Ref: DnsZonePrivate
      RecordSets:
    {% if instance['unique'] | d(false) | bool %}
        - Name: "{{instance['name']}}.{{aws_dns_zone_private}}"
    {% else %}
        - Name: "{{instance['name']}}{{loop.index}}.{{aws_dns_zone_private}}"
    {% endif %}
          Type: A
          TTL: {{ aws_dns_ttl_private }}
          ResourceRecords:
            - "Fn::GetAtt":
              - {{instance['name']}}{{loop.index}}
              - PrivateIp
{% if instance['public_dns'] %}
  {{instance['name']}}{{loop.index}}EIP:
    Type: "AWS::EC2::EIP"
    DependsOn:
    - VpcGA
    Properties:
      InstanceId:
        Ref: {{instance['name']}}{{loop.index}}
  {{instance['name']}}{{loop.index}}PublicDns:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
      - {{instance['name']}}{{loop.index}}EIP
    Properties:
      {% if secondary_stack is defined %}
      HostedZoneName: "{{ aws_dns_zone_public }}"
      {% else %}
      HostedZoneId:
        Ref: DnsZonePublic
      {% endif %}
      RecordSets:
      {% if instance['unique'] | d(false) | bool %}
        - Name: "{{instance['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
      {% else %}
        - Name: "{{instance['name']}}{{loop.index}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
      {% endif %}
          Type: A
          TTL: {{ aws_dns_ttl_public }}
          ResourceRecords:
          - "Fn::GetAtt":
            - {{instance['name']}}{{loop.index}}
            - PublicIp
{% endif %}
{% endfor %}
{% endfor %}
{% for worker_region in target_regions %}
{% for instance in instances_worker %}
{% if instance['dns_loadbalancer'] | d(false) | bool
  and not instance['unique'] | d(false) | bool %}
  {{instance['name']}}{{worker_region['name']}}DnsLoadBalancer:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
    {% for c in range(1, (instance['count']|int)+1) %}
      - {{instance['name']}}{{c}}{{worker_region['name']}}
      {% if instance['public_dns'] %}
      - {{instance['name']}}{{c}}{{worker_region['name']}}EIP
      {% endif %}
    {% endfor %}
    Properties:
      {% if secondary_stack is defined %}
      HostedZoneName: "{{ aws_dns_zone_public }}"
      {% else %}
      HostedZoneId:
        Ref: DnsZonePublic
      {% endif %}
      RecordSets:
      - Name: "{{instance['name']}}{{worker_region['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
        Type: A
        TTL: {{ aws_dns_ttl_public }}
        ResourceRecords:
{% for c in range(1,(instance['count'] |int)+1) %}
          - "Fn::GetAtt":
            - {{instance['name']}}{{c}}.{{worker_region['name']}}
            - PublicIp
{% endfor %}
{% endif %}
{% for c in range(1,(instance['count'] |int)+1) %}
  {{instance['name']}}{{loop.index}}{{worker_region['name']}}:
    Type: "AWS::EC2::Instance"
    Properties:
{% if custom_image is defined %}
      ImageId: {{ custom_image.image_id }}
{% else %}
      ImageId:
        Fn::FindInMap:
        - RegionMapping
        - Ref: AWS::Region
        - {{ instance.image | default(aws_default_image) }}
{% endif %}
      InstanceType: "{{instance['flavor'][cloud_provider]}}"
      KeyName: "{{instance.key_name | default(key_name)}}"
    {% if instance['UserData'] is defined %}
      {{instance['UserData']}}
    {% endif %}
    {% if instance['security_groups'] is defined %}
      SecurityGroupIds:
      {% for sg in instance.security_groups %}
        - Ref: {{ sg }}
      {% endfor %}
    {% else %}
      SecurityGroupIds:
        - Ref: DefaultSG
    {% endif %}
      SubnetId:
        Ref: PublicSubnet
      Tags:
    {% if instance['unique'] | d(false) | bool %}
        - Key: Name
          Value: {{instance['name']}}
        - Key: internaldns
          Value: {{instance['name']}}{{worker_region['name']}}.{{aws_dns_zone_private_chomped}}
    {% else %}
        - Key: Name
          Value: {{instance['name']}}{{loop.index}}.{{worker_region['name']}}
        - Key: internaldns
          Value: {{instance['name']}}{{loop.index}}.{{worker_region['name']}}.{{aws_dns_zone_private_chomped}}
    {% endif %}
        - Key: "owner"
          Value: "{{ email | default('unknownuser') }}"
        - Key: "Project"
          Value: "{{project_tag}}"
        - Key: "{{project_tag}}"
          Value: "{{ instance['name'] }}{{loop.index}}.{{worker_region['name']}}"
    {% for tag in instance['tags'] %}
        - Key: {{tag['key']}}
          Value: {{tag['value']}}
    {% endfor %}
      BlockDeviceMappings:
    {% if '/dev/sda1' not in instance.volumes|d([])|json_query('[].device_name')
      and '/dev/sda1' not in instance.volumes|d([])|json_query('[].name')
%}
        - DeviceName: "/dev/sda1"
          Ebs:
            VolumeSize: "{{ instance['rootfs_size'] | default(aws_default_rootfs_size) }}"
            VolumeType: "{{ aws_default_volume_type }}"
    {% endif %}
    {% for vol in instance.volumes|default([]) if vol.enable|d(true) %}
        - DeviceName: "{{ vol.name | default(vol.device_name) }}"
          Ebs:
          {% if cloud_provider in vol and 'type' in vol.ec2 %}
            VolumeType: "{{ vol[cloud_provider].type }}"
          {% else %}
            VolumeType: "{{ aws_default_volume_type }}"
          {% endif %}
            VolumeSize: "{{ vol.size }}"
    {% endfor %}
  {{instance['name']}}{{loop.index}}{{worker_region['name']}}InternalDns:
    Type: "AWS::Route53::RecordSetGroup"
    Properties:
      HostedZoneId:
        Ref: DnsZonePrivate
      RecordSets:
    {% if instance['unique'] | d(false) | bool %}
        - Name: "{{instance['name']}}{{worker_region['name']}}.{{aws_dns_zone_private}}"
    {% else %}
        - Name: "{{instance['name']}}{{loop.index}}.{{worker_region['name']}}.{{aws_dns_zone_private}}"
    {% endif %}
          Type: A
          TTL: {{ aws_dns_ttl_private }}
          ResourceRecords:
            - "Fn::GetAtt":
              - {{instance['name']}}{{loop.index}}{{worker_region['name']}}
              - PrivateIp
{% if instance['public_dns'] %}
  {{instance['name']}}{{loop.index}}{{worker_region['name']}}EIP:
    Type: "AWS::EC2::EIP"
    DependsOn:
    - VpcGA
    Properties:
      InstanceId:
        Ref: {{instance['name']}}{{loop.index}}{{worker_region['name']}}
  {{instance['name']}}{{loop.index}}{{worker_region['name']}}PublicDns:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
      - {{instance['name']}}{{loop.index}}{{worker_region['name']}}EIP
    Properties:
      {% if secondary_stack is defined %}
      HostedZoneName: "{{ aws_dns_zone_public }}"
      {% else %}
      HostedZoneId:
        Ref: DnsZonePublic
      {% endif %}
      RecordSets:
      {% if instance['unique'] | d(false) | bool %}
        - Name: "{{instance['name']}}{{worker_region['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
      {% else %}
        - Name: "{{instance['name']}}{{loop.index}}.{{worker_region['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
      {% endif %}
          Type: A
          TTL: {{ aws_dns_ttl_public }}
          ResourceRecords:
          - "Fn::GetAtt":
            - {{instance['name']}}{{loop.index}}.{{worker_region['name']}}
            - PublicIp
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
  {% if secondary_stack is not defined %}
  Route53User:
    Type: AWS::IAM::User
    Properties:
      Policies:
        - PolicyName: Route53Access
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action: route53:GetHostedZone
                Resource: arn:aws:route53:::change/*
              - Effect: Allow
                Action: route53:ListHostedZones
                Resource: "*"
              - Effect: Allow
                Action:
                  - route53:ChangeResourceRecordSets
                  - route53:ListResourceRecordSets
                  - route53:GetHostedZone
                Resource:
                  Fn::Join:
                    - ""
                    - - "arn:aws:route53:::hostedzone/"
                      - Ref: DnsZonePublic
              - Effect: Allow
                Action: route53:GetChange
                Resource: arn:aws:route53:::change/*
  Route53UserAccessKey:
      DependsOn: Route53User
      Type: AWS::IAM::AccessKey
      Properties:
        UserName:
          Ref: Route53User
  {% endif %}
Outputs:
  Route53internalzoneOutput:
    Description: The ID of the internal route 53 zone
    Value:
      Ref: DnsZonePrivate
  {% if secondary_stack is not defined %}
  Route53User:
    Value:
      Ref: Route53User
    Description: IAM User for Route53 (Let's Encrypt)
  Route53UserAccessKey:
    Value:
      Ref: Route53UserAccessKey
    Description: IAM User for Route53 (Let's Encrypt)
  Route53UserSecretAccessKey:
    Value:
      Fn::GetAtt:
        - Route53UserAccessKey
        - SecretAccessKey
    Description: IAM User for Route53 (Let's Encrypt)
  {% endif %}
ansible/configs/ansible-tower/files/hosts_template.j2
New file
@@ -0,0 +1,51 @@
[tower]
{% for host in groups['towers'] %}
{{ host }}
{% endfor %}
[database]
support1.{{chomped_zone_internal_dns}}
{% if target_regions is defined %}
{%for i_region in target_regions %}
[isolated_group_{{i_region.name}}]
{% for host in groups['workers'] %}
{% if '.' + i_region['name'] + '.' in host %}
{{ host }}
{% endif %}
{% endfor %}
[isolated_group_{{i_region.name}}:vars]
controller=tower
{% endfor %}
{% endif %}
[all:vars]
ansible_become=true
admin_password={{tower_admin_password}}
pg_host='support1.{{chomped_zone_internal_dns}}'
pg_port='5432'
pg_database='awx'
pg_username='awx'
pg_password={{tower_admin_password}}
rabbitmq_port=5672
rabbitmq_vhost=tower
rabbitmq_username=tower
rabbitmq_password={{ tower_admin_password | regex_replace('[^a-zA-Z0-9]') }}
rabbitmq_cookie=cookiemonster
rabbitmq_use_long_name=true
### For our use, not Tower install use (so you can run ansible command line)
[supports]
{% for host in groups['support'] %}
{{ host }}
{% endfor %}
ansible/configs/ansible-tower/files/repos_template.j2
New file
@@ -0,0 +1,38 @@
[rhel-7-server-rpms]
name=Red Hat Enterprise Linux 7
baseurl={{own_repo_path}}/{{repo_version}}/rhel-7-server-rpms
enabled=1
gpgcheck=0
[rhel-7-server-rh-common-rpms]
name=Red Hat Enterprise Linux 7 Common
baseurl={{own_repo_path}}/{{repo_version}}/rhel-7-server-rh-common-rpms
enabled=1
gpgcheck=0
[rhel-7-server-extras-rpms]
name=Red Hat Enterprise Linux 7 Extras
baseurl={{own_repo_path}}/{{repo_version}}/rhel-7-server-extras-rpms
enabled=1
gpgcheck=0
[rhel-7-server-optional-rpms]
name=Red Hat Enterprise Linux 7 Optional
baseurl={{own_repo_path}}/{{repo_version}}/rhel-7-server-optional-rpms
enabled=1
gpgcheck=0
[rhel-7-server-rhscl-rpms]
name=Red Hat Enterprise Linux 7 Optional
baseurl={{own_repo_path}}/{{repo_version}}/rhel-server-rhscl-7-rpms
enabled=1
gpgcheck=0
[epel]
name=Extra Packages for Enterprise Linux 7 - $basearch
baseurl=http://download.fedoraproject.org/pub/epel/7/$basearch
mirrorlist=http://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch
failovermethod=priority
enabled=1
gpgcheck=0
#gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7
ansible/configs/ansible-tower/files/tower_cli.j2
New file
@@ -0,0 +1,5 @@
[general]
host = {{ tower_hostname }}
username = admin
password = {{tower_admin_password}}
verify_ssl = False
ansible/configs/ansible-tower/files/tower_template_inventory.j2
New file
@@ -0,0 +1,54 @@
[tower]
{% for host in groups['towers'] %}
tower{{loop.index}}.{{chomped_zone_internal_dns}} public_host_name=tower{{loop.index}}.{{ guid }}{{subdomain_base_suffix}} ssh_host={{host}}
{% endfor %}
[database]
## This should be replaced by supports[0] name
support1.{{chomped_zone_internal_dns}}
## Add isolated if needed, we should have an "IF" statement, only if worker groups exist and have instances.
{% if worker == 'yes' %}
[isolated_group_{{region}}]
{% for host in groups['workers'] %}
worker{{loop.index}}.{{chomped_zone_internal_dns}} public_host_name=worker{{loop.index}}.{{ guid }}{{subdomain_base_suffix}} ssh_host={{host}}
{% endfor %}
[isolated_group_{{region}}:vars]
controller=tower
{% endif %}
[all:vars]
ansible_become=true
admin_password={{tower_admin_password}}
## This should be replaced by supports[0] name
pg_host='support1.{{guid}}.internal'
pg_port='5432'
pg_database='awx'
pg_username='awx'
pg_password={{tower_admin_password}}
rabbitmq_port=5672
rabbitmq_vhost=tower
rabbitmq_username=tower
rabbitmq_password={{ tower_admin_password | regex_replace('[^a-zA-Z0-9]') }}
rabbitmq_cookie=cookiemonster
rabbitmq_use_long_name=true
### For our use, not Tower install use (so you can run ansible command line)
[supports]
{% for host in groups['support'] %}
support{{loop.index}}.{{chomped_zone_internal_dns}} public_host_name=support{{loop.index}}.{{ guid }}{{subdomain_base_suffix}} ssh_host={{host}}
{% endfor %}
{% if worker == 'yes' %}
[workers]
{% for host in groups['workers'] %}
worker{{loop.index}}.{{chomped_zone_internal_dns}} public_host_name=worker{{loop.index}}.{{ guid }}{{subdomain_base_suffix}} ssh_host={{host}}
{% endfor %}
{% endif %}
ansible/configs/ansible-tower/post_infra.yml
New file
@@ -0,0 +1,24 @@
- name: Step 002 Post Infrastructure
  hosts: localhost
  connection: local
  become: false
  tags:
  - step002
  - post_infrastructure
  tasks:
  - name: Job Template to launch a Job Template with update on launch inventory set
    uri:
      url: "https://{{ ansible_tower_ip }}/api/v1/job_templates/{{ job_template_id }}/launch/"
      method: POST
      user: "{{tower_admin}}"
      password: "{{tower_admin_password}}"
      body:
        extra_vars:
          guid: "{{guid}}"
          ipa_host_password: "{{ipa_host_password}}"
      body_format: json
      validate_certs: False
      HEADER_Content-Type: "application/json"
      status_code: 200, 201
    when: tower_run == 'true'
ansible/configs/ansible-tower/post_software.yml
New file
@@ -0,0 +1,33 @@
- name: Step 00xxxxx post software
  hosts: support
  gather_facts: False
  become: yes
  tasks:
    - debug:
        msg: "Post-Software tasks Started"
- name: Step lab post software deployment
  hosts: bastions
  gather_facts: False
  become: yes
  tags:
    - opentlc_bastion_tasks
  tasks:
- name: Setup Workloads on Tower
  import_playbook: tower_workloads.yml
- name: PostSoftware flight-check
  hosts: localhost
  connection: local
  gather_facts: false
  become: false
  tags:
    - post_flight_check
  tasks:
    - debug:
        msg: "Post-Software checks completed successfully"
ansible/configs/ansible-tower/pre_infra.yml
New file
@@ -0,0 +1,21 @@
- name: Step 000 Pre Infrastructure
  hosts: localhost
  connection: local
  become: false
  tags:
    - step001
    - pre_infrastructure
  tasks:
  - debug:
      msg: "Pre-Software Steps starting"
- name: PretSoftware flight-check
  hosts: localhost
  connection: local
  gather_facts: false
  become: false
  tags:
  - pre_flight_check
  tasks:
  - debug:
      msg: "Pre-Software checks completed successfully"
ansible/configs/ansible-tower/pre_software.yml
New file
@@ -0,0 +1,61 @@
---
- name: Step 003 - Create env key
  hosts: localhost
  connection: local
  gather_facts: false
  become: false
  tags:
    - step003
    - generate_env_keys
  tasks:
    - name: Generate SSH keys
      shell: ssh-keygen -b 2048 -t rsa -f "{{output_dir}}/{{env_authorized_key}}" -q -N ""
      args:
        creates: "{{output_dir}}/{{env_authorized_key}}"
      when: set_env_authorized_key
- name: Configure all hosts with Repositories, Common Files and Set environment key
  hosts:
    - all:!windows
  become: true
  gather_facts: False
  tags:
    - step004
    - common_tasks
  roles:
    - { role: "set-repositories", when: 'repo_method is defined' }
    - { role: "common", when: 'install_common' }
    - { role: "set_env_authorized_key", when: 'set_env_authorized_key' }
- name: Configuring Bastion Hosts
  hosts: bastions
  become: true
  roles:
    - { role: "bastion", when: 'install_bastion' }
    - { role: "bastion-opentlc-ipa",  when: 'install_ipa_client' }
  tags:
    - step004
    - bastion_tasks
# - name: Inject and configure FTL on bastion as grader host
#   hosts: bastions
#   become: true
#   tasks:
#     - name: Setup FTL
#       include_role:
#         name: ftl-injector
#   tags:
#     - step004
#     - ftl-injector
- name: PreSoftware flight-check
  hosts: localhost
  connection: local
  gather_facts: false
  become: false
  tags:
    - flight_check
  tasks:
    - debug:
        msg: "Pre-Software checks completed successfully"
ansible/configs/ansible-tower/requirements.yml
New file
@@ -0,0 +1,6 @@
---
# External role to setup grader host virtualenv and FTL grading infra
- src: https://github.com/redhat-gpte-devopsautomation/ftl-injector
  name: ftl-injector
  version: v0.7
ansible/configs/ansible-tower/sample_vars.yml
New file
@@ -0,0 +1,80 @@
---
cloudformation_retries: 0
## Environment size
bastion_instance_type: "t2.medium"
tower_instance_type: "t2.medium"
worker_instance_type: "t2.medium"
support_instance_type: "t2.medium"
root_filesystem_size: 20                #Size of the root filesystem
# Env config basics
env_type: ansble-tower                 # Name of config to deploy
output_dir: /tmp                # Writable working scratch directory
email: name@example.com                 # User info for notifications
#guid: hwtest2                          # Unique string used in FQDN
# AWS specific
subdomain_base_suffix: .example.opentlc.com      # Your domain used in FQDN
# Path to yum repos (Will be overwritten by the last -e@ file, such as ../secrets.yml)
own_repo_path: http://admin.example.com/repos/product
# Cloud specfic settings - example given here for AWS
cloud_provider: ec2                     # Which AgnosticD Cloud Provider to use
aws_region: ap-southeast-2                  # AWS Region to deploy in
HostedZoneId: Z3IHLWJZOU9SRT            # You will need to change this
key_name: ocpkey                       # Keyname must exist in AWS
#Ansible Tower related vars
tower_version: 3.5.0-1                 # tower version you want to install
region: apac                           # region can not be with special characters in case of isolated node group
software_to_deploy: tower              # Define tower to install tower or none to have only infra ready.
worker: yes                            # Set yes to add isolated node group.
worker_instance_count: 1             # Set 0 to not to provision worker(isolated) nodes.
# tower_license: >                     #Set the tower licencse in the same format. Do not forget to add "eula_accepted: true".
#   {
#     "eula_accepted": true,
#     "company_name": "Red Hat",
#     "contact_email": "name@redhat.com",
#     "contact_name": "some person"
#     "hostname": "70a415ef832159a36413fa599",
#     "instance_count": 50,
#     "license_date": 16581423619,
#     "license_key":
#     "eea1b84d1e39cfEXAMPLE5739066069e60c6d0aEXAMPLE2c29cc61b2aEXAMPLE",
#     "license_type": "enterprise",
#     "subscription_name": "Ansible Tower by Red Hat (50 Managed Nodes), RHT Internal",
#     "trial": true
#   }
# accounts:                                      #Define users you want to create. Set superuser: yes to make user system wide System Administrator
#   - user: test1
#     password: changeme
#     email: babylon@example.com
#     firstname: test1
#     lastname: one
#     superuser: yes
#   - user: test2
#     password: changeme
#     email: babylon1@example.com
#     firstname: test2
#     lastname: two
#   - user: test3
#   - user: test4
#     lastname: four
# tower_organization:
#   - name: gpte
#   - name: BU
target_regions:
  - name: na
  - name: emea
  - name: na
ansible/configs/ansible-tower/sample_vars_babylon.yml
New file
@@ -0,0 +1,169 @@
---
cloudformation_retries: 0
# ## Environment size
# bastion_instance_type: "t2.medium"
# tower_instance_type: "t2.medium"
# worker_instance_type: "t2.medium"
# support_instance_type: "t2.medium"
root_filesystem_size: 20                #Size of the root filesystem
# Env config basics
env_type: ansible-tower                 # Name of config to deploy
output_dir: /opt/workdir               # Writable working scratch directory
email: name@example.com                 # User info for notifications
#guid: hwtest2                          # Unique string used in FQDN
# AWS specific
subdomain_base_suffix: .example.opentlc.com      # Your domain used in FQDN
# Path to yum repos (Will be overwritten by the last -e@ file, such as ../secrets.yml)
own_repo_path: http://admin.example.com/repos/product
# Cloud specfic settings - example given here for AWS
cloud_provider: ec2                     # Which AgnosticD Cloud Provider to use
aws_region: ap-southeast-2                  # AWS Region to deploy in
HostedZoneId: Z3IHLWJZOU9SRT            # You will need to change this
key_name: ocpkey                       # Keyname must exist in AWS
#Ansible Tower related vars
tower_version: 3.5.0-1                 # tower version you want to install
region: apac                           # region can not be with special characters in case of isolated node group
software_to_deploy: tower              # Define tower to install tower or none to have only infra ready.
tower_instance_count: 2
support_instance_count: 2
worker_instance_count: 2              # Set 0 to not to provision worker(isolated) nodes.
# tower_license: >                     #Set the tower licencse in the same format. Do not forget to add "eula_accepted: true".
#   {
#     "eula_accepted": true,
#     "company_name": "Red Hat",
#     "contact_email": "name@redhat.com",
#     "contact_name": "some person"
#     "hostname": "70a415ef832159a36413fa599",
#     "instance_count": 50,
#     "license_date": 16581423619,
#     "license_key":
#     "eea1b84d1e39cfEXAMPLE5739066069e60c6d0aEXAMPLE2c29cc61b2aEXAMPLE",
#     "license_type": "enterprise",
#     "subscription_name": "Ansible Tower by Red Hat (50 Managed Nodes), RHT Internal",
#     "trial": true
#   }
tower_host_name: "tower1.{{guid}}{{subdomain_base_suffix}}"
tower_user_accounts:                                      #Define users you want to create. Set superuser: yes to make user system wide System Administrator
  - user: babylon
    password: changeme
    email: babylon@example.com
    firstname: Baby
    lastname: Lon
    superuser: yes
  - user: babylon-viewer
    password: changeme
    email: babylon1@example.com
    firstname: Babylon
    lastname: Viewer
#   - user: test3
#   - user: test4
#     lastname: four
tower_organization:
  - name: gpte
  - name: BU
target_regions:
  - name: emea
  - name: apac
### tower project roles
tower_projects:
  - name: babylon-dev
    description: "babylon dev project"
    organization: "gpte"
    scm_url: "https://github.com/redhat-gpte-devopsautomation/babylon.git"
    #scm_type:
    #scm_credential:
    scm_branch:  dev
    scm_update_on_launch: true
tower_inventories:
  - name: empty-inventory-emea
    description: emea
    organization: gpte
    instance_group: emea
  - name: empty-inventory-apac
    description: apac
    organization: gpte
    instance_group: apac
  - name: empty-inventory
    description: "Empty inventory"
    organization: gpte
    # instance_group: ""
tower_job_templates:
  - name: babylon_job_runner
    description: "babylon job runner"
    job_type: run
    #vault_credential:
    project: babylon
    playbook: job-runner.yml
    become: yes
# Tower settings
tower_setting_params:
  AWX_PROOT_BASE_PATH: "/tmp"
  AWX_PROOT_SHOW_PATHS: "'/var/lib/awx/projects/', '/tmp'"
# List of virtual environment which will be created
# restart of tower service is required
# ansible-tower-service restart
# https://docs.ansible.com/ansible-tower/latest/html/userguide/security.html
tower_virtual_environment:
  - /var/lib/awx/venv/ansible
  - /var/lib/awx/venv/test1
# Path of Virtual Env for update
tower_update_venv: /var/lib/awx/venv/ansible
# Pip packages with version which needs to be updated for venv
pip_requirements:
  - boto==2.49.0
  - boto3==1.9.200
  - awscli==1.16.210
  - ansible-tower-cli==3.3.6
key_local_path: "~/.ssh/{{key_name}}.pem"
tower_job_templates:
  - name: job-runner-dev
    description: "babylon job runner dev"
    job_type: run
    #vault_credential:
    project: babylon-dev
    playbook: job-runner.yml
    inventory: empty-inventory
    become: yes
# Tower settings
tower_setting_params:
  AWX_PROOT_BASE_PATH: "/tmp"
  AWX_PROOT_SHOW_PATHS: "'/var/lib/awx/projects/', '/tmp'"
# List of virtual environment which will be created (WIP)
# tower_virtual_environment:
#   - /var/lib/awx/venv/ansible
#   - /var/lib/awx/venv/test1
# Path of Virtual Env for update
tower_update_venv: /var/lib/awx/venv/ansible
key_local_path: "~/.ssh/{{key_name}}.pem"
ansible/configs/ansible-tower/software.yml
New file
@@ -0,0 +1,20 @@
---
#  - name: Step 00xxxxx software
#    hosts: bastions[0]
#    gather_facts: False
#    become: false
#    tasks:
#      - debug:
#          msg: "Software tasks Started - None"
 - name: Software flight-check
   hosts: localhost
   connection: local
   gather_facts: false
   become: false
   tags:
     - post_flight_check
   tasks:
     - debug:
         msg: "Software checks completed successfully"
ansible/configs/ansible-tower/tower_workloads.yml
New file
@@ -0,0 +1,55 @@
---
- name: Install workloads
  hosts: bastions
  gather_facts: false
  run_once: true
  become: true
  tasks:
  - set_fact:
      tower_hostname: "{{ item | first }}"
    loop:
      - "{{ query('inventory_hostnames', 'towers') }}"
  - name: Install tower-default workloads
    when:
    - default_workloads | d("") | length > 0
    tags:
    - default_workloads
    block:
    - name: Install tower-default-workloads
      when:
      - default_workloads | d("") | length >0
      block:
      - name: Deploy tower-default workloads
        include_role:
          name: "{{ workload_loop_var }}"
        vars:
          tower_username: "admin"
        loop: "{{ default_workloads }}"
        loop_control:
          loop_var: workload_loop_var
  - name: Install tower-infra workloads
    when:
    - infra_workloads|d("")|length > 0
    tags:
      - infra_workloads
    block:
    - name: Check if admin_user is set
      fail:
        msg: admin_user must be set for tower-infra workloads
      when:
      - not admin_user is defined or admin_user|length == 0
    - name: Install tower-infra-workloads
      when:
      - infra_workloads|d("")|length >0
      block:
      - name: Deploy tower-infra workloads
        include_role:
          name: "{{ workload_loop_var }}"
        vars:
          tower_username: admin
          ACTION: "provision"
        loop: "{{ infra_workloads.split(',')|list }}"
        loop_control:
          loop_var: workload_loop_var
ansible/configs/ansible-tower/tower_workloads_workaround.yml
New file
@@ -0,0 +1,156 @@
- hosts: bastions
  gather_facts: false
  become: yes
  tasks:
  - name: Inject License
    include_role:
      name: tower-license-injector
    when: tower_license is defined
    tags:
      - tower-license-injector
###### delete demo stuff #######
  - name: Delete Demo Job Template
    tower_job_template:
      name: "Demo Job Template"
      state: absent
      job_type: run
      playbook: "hello_world.yml"
      project: "Demo Project"
      inventory: "Demo Inventory"
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    ignore_errors: yes
  - name: Delete Demo Credential
    command: tower-cli credential delete -n "Demo Credential"
    ignore_errors: yes
  - name: Delete Demo Project
    tower_project:
      name: "Demo Project"
      state: absent
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    ignore_errors: yes
  - name: Delete Demo Inventory
    tower_inventory:
      name: "Demo Inventory"
      organization: Default
      state: absent
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    ignore_errors: yes
###### Create tower users #####
  - name: Add tower user
    tower_user:
      username: "{{ item.user }}"
      password: "{{ item.password | default('change_me') }}"
      email: "{{ item.email | default('rhpds-admins@redhat.com') }}"
      first_name: "{{ item.firstname | default(item.user) }}"
      last_name: "{{ item.lastname | default(item.user) }}"
      superuser: "{{ item.superuser | default('no')}}"
      state: present
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    loop: "{{ tower_accounts }}"
    when: tower_accounts is defined
    tags:
      - tower-user-create
#### Create Tower Organization ####
  - name: Add tower org
    tower_organization:
      name: "{{ item.name }}"
      state: present
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    loop: "{{ tower_organization }}"
    when: tower_organization is defined
    tags:
      - tower-org-create
#### Create tower Project #####
  - name: Add tower project
    tower_project:
      name: "{{ item.name }}"
      description: "{{ item.description }}"
      organization:  "{{ item.organization | default('Default')}}"
      scm_url:  "{{ item.scm_url }}"
      scm_type: "{{ item.scm_type | d('git')}}"
      scm_credential: "{{ item.scm_credential | d('')}}"
      scm_branch:  "{{ item.scm_branch | d('master') }}"
      scm_update_on_launch: "{{ item.scm_update_on_launch | d('false') }}"
      state: present
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    loop: "{{ tower_projects }}"
    when: tower_projects is defined
    tags:
      - tower-project-create
#### Create tower Inventory ####
  - name: Block for Inventory
    when: tower_inventories is defined
    block:
    - name: Add tower inventory
      tower_inventory:
        name: "{{ item.name }}"
        description: "{{ item.description  }}"
        organization: "{{ item.organization | d('gpte') }}"
        state: present
        tower_host: "{{ tower_host_name }}"
        tower_username: admin
        tower_password: "{{tower_admin_password}}"
        tower_verify_ssl: false
      loop: "{{ tower_inventories }}"
      tags:
        - tower-inventory-create
    - name: Associate instance group to inventory
      command: >-
        tower-cli inventory
        associate_ig
        --inventory "{{ item.name }}"
        --instance-group "{{ item.instance_group | d('') }}"
      loop: "{{ tower_inventories }}"
      when:
        - item.instance_group is defined
#### Create Tower Job Template ####
  - name: Add tower JobTemplate
    tower_job_template:
      name: "{{ item.name }}"
      description: "{{ item.description  }}"
      job_type: run
      ask_inventory: Yes
      ask_credential: Yes
      vault_credential: "{{ item.vault_credential | d('') }}"
      ask_extra_vars: Yes
      project: "{{ item.project }}"
      playbook: "{{ item.playbook | d('main.yml') }}"
      become_enabled: "{{ item.become | d('no') }}"
      concurrent_jobs_enabled: Yes
      state: present
      tower_host: "{{ tower_host_name }}"
      tower_username: admin
      tower_password: "{{tower_admin_password}}"
      tower_verify_ssl: false
    loop: "{{ tower_job_templates }}"
    when: tower_job_templates is defined
    tags:
      - tower-job-template-create
ansible/configs/multi-region-example/sample_vars.yml
@@ -10,7 +10,7 @@
node_instance_count: 2                  # Number of nodes to deploy
email: name@example.com                 # User info for notifications
guid: guid02                             # Unique string used in FQDN
guid: testing                             # Unique string used in FQDN
subdomain_base_suffix: .example.opentlc.com      # Your domain used in FQDN
# Path to yum repos
@@ -19,7 +19,7 @@
# Cloud specfic settings - example given here for AWS
cloud_provider: ec2                     # Which AgnosticD Cloud Provider to use 
aws_region: us-east-1                   # AWS Region to deploy in
#aws_region: us-east-1                   # AWS Region to deploy in
HostedZoneId: Z3IHLWJZOU9SRT            # You will need to change this
key_name: ocpkey                        # Keyname must exist in AWS
ansible/configs/multi-region-tower/README.adoc
New file
@@ -0,0 +1,35 @@
= Multi-region-tower
This config deploys towers in multiple regions.
. You specify the target regions using the `target_regions` variable, for example:
+
[source,yaml]
----
target_regions:
  - region: us-east-1
    stack: default
    name: na
    vpc_cidr: 10.1.0.0/16
    subnet_cidr: 10.1.0.0/24
  - region: eu-central-1
    stack: worker.j2
    name: emea
    vpc_cidr: 10.1.0.0/16
    subnet_cidr: 10.1.0.0/24
  - region: ap-southeast-1
    stack: worker.j2
    name: apac
    vpc_cidr: 10.1.0.0/16
    subnet_cidr: 10.1.0.0/24
----
+
This will deploy the same stack (default) in the specified regions. The default CloudFormation template is used: either in the config dir, or the default cloudformation template if there is none in the config dir.
+
If you want to use a specific template in a region, just specify the file name and store the template in `configs/multi-region-example/files/cloud_providers/FILENAME`.
+
. In this config the first region specified under target_regions dictionary will have master cluster installed which will include tower nodes and database node.
. Respective other regions next specified list under target_regions will be used to create Isolated group i.e. "name: emea" = [isolated_group_emea ] and worker nodes will be added.
Have a look at link:sample_vars.yml[] too for example vars.
ansible/configs/multi-region-tower/deploy_stack.yml
New file
@@ -0,0 +1,77 @@
---
- debug:
    msg: "stack_file is {{stack_file}}"
- when: stack_file == 'default'
  block:
    - name: Run infra-ec2-template-generate Role (target_regions)
      include_role:
        name: infra-ec2-template-generate
      vars:
        cloudformation_template: "{{ output_dir }}/{{ env_type }}.{{ guid }}.{{ cloud_provider }}_cloud_template_{{ aws_region }}"
    - set_fact:
        stack_deployed: false
    - name: Run infra-ec2-template-create Role (target_regions)
      include_role:
        name: infra-ec2-template-create
      vars:
        aws_region_loop: "{{ aws_region }}"
        cloudformation_template: "{{ output_dir }}/{{ env_type }}.{{ guid }}.{{ cloud_provider }}_cloud_template_{{ aws_region }}"
- when: stack_file != 'default'
  block:
    - name: Run infra-ec2-template-generate Role (target_regions)
      include_role:
        name: infra-ec2-template-generate
      vars:
        cloudformation_template_src: "../../configs/{{ env_type }}/files/cloud_providers/{{ stack_file }}"
        cloudformation_template: "{{ output_dir }}/{{ env_type }}.{{ guid }}.{{ cloud_provider }}_cloud_template_{{ aws_region }}"
    - set_fact:
        stack_deployed: false
    - name: Run infra-ec2-template-create Role (target_regions)
      include_role:
        name: infra-ec2-template-create
      vars:
        aws_region_loop: "{{ aws_region }}"
        cloudformation_template: "{{ output_dir }}/{{ env_type }}.{{ guid }}.{{ cloud_provider }}_cloud_template_{{ aws_region }}"
- name: report Cloudformation error (target_regions)
  fail:
    msg: "FAIL {{ project_tag }} Create Cloudformation"
  when: not cloudformation_out is succeeded
  tags:
    - provision_cf_template
- name: Run infra-ec2-create-inventory Role (target_regions)
  include_role:
    name: infra-ec2-create-inventory
##### Task to append env_type ssh_config to add worker nodes deffination for removing dependencies on bastion ###########
- name: Add worker nodes to workdir ssh config file
  blockinfile:
    dest: "{{output_dir}}/{{ env_type }}_{{ guid }}_ssh_conf"
    marker: "##### {mark} ADDED BASTION PROXY HOST {{ item }} {{ env_type }}-{{ guid }} ######"
    content: |
        Host {{ item }} {{ hostvars[item].shortname |d('')}}
          Hostname  {{ hostvars[item].public_dns_name }}
          IdentityFile {{ ssh_key | default(infra_ssh_key) | default(ansible_ssh_private_key_file) | default(default_key_name)}}
          IdentitiesOnly yes
          User ec2-user
          ControlMaster auto
          ControlPath /tmp/{{ guid }}-%r-%h-%p
          ControlPersist 5m
          StrictHostKeyChecking no
          ConnectTimeout 60
          ConnectionAttempts 10
          UserKnownHostsFile {{ansible_known_host}}
  loop: "{{ groups['workers'] }}"
  tags:
    - worker_ssh_config
### End task ####
ansible/configs/multi-region-tower/destroy_env.yml
New file
@@ -0,0 +1,41 @@
---
- import_playbook: ../../include_vars.yml
- name: Delete Infrastructure
  hosts: localhost
  connection: local
  gather_facts: False
  become: no
  environment:
    AWS_ACCESS_KEY_ID: "{{aws_access_key_id}}"
    AWS_SECRET_ACCESS_KEY: "{{aws_secret_access_key}}"
  tasks:
    - name: Find all VPC peering matching the stack name
      ec2_vpc_peering_facts:
        region: "{{ item.region }}"
        filters:
          "tag:stack": "{{ project_tag }}"
          status-code:
            - pending-acceptance
            - expired
            - provisioning
            - active
            - rejected
      register: vpc_peers
      loop: "{{ target_regions }}"
    - name: Delete all peering Connection
      ec2_vpc_peer:
        region: "{{ item.requester_vpc_info.region }}"
        peering_id: "{{ item.vpc_peering_connection_id }}"
        state: absent
      loop: "{{ vpc_peers.results |json_query('[].result[]') }}"
    - name: Run infra-ec2-template-destroy
      include_role:
        name: "infra-{{cloud_provider}}-template-destroy"
      vars:
        aws_region: "{{ _region.region }}"
      loop_control:
        loop_var: _region
      loop: "{{ target_regions | reverse | list }}"
ansible/configs/multi-region-tower/env_vars.yml
New file
@@ -0,0 +1,463 @@
################################################################################
################################################################################
### Environment Structure
################################################################################
################################################################################
## Environment Sizing
# target_regions:
#   - region: us-east-1
#     stack: default
#     name: na
#     vpc_cidr: 10.1.0.0/16
#     subnet_cidr: 10.1.0.0/24
#   - region: eu-central-1
#     stack: default
#     name: emea
#     vpc_cidr: 10.2.0.0/16
#     subnet_cidr: 10.2.0.0/24
#   - region: ap-southeast-1
#     stack: default
#     name: apac
#     vpc_cidr: 10.3.0.0/16
#     subnet_cidr: 10.3.0.0/24
default_key_name: ~/.ssh/{{key_name}}.pem
bastion_instance_type:
  ec2: "t2.medium"
  azure: Standard_A2_V2
bastion_instance_image: RHEL75
tower_instance_type:
  ec2: "t2.medium"
  azure: Standard_A2_V2
tower_instance_image: RHEL75
support_instance_type:
  ec2: "t2.medium"
  azure: Standard_A2_V2
support_instance_image: RHEL75
worker_instance_type:
  ec2: "t2.medium"
  azure: Standard_A2_V2
worker_instance_image: RHEL75
# How many do you want for each instance type
tower_instance_count: 3
support_instance_count: 2
worker_instance_count: 2
# security_groups:
#   - name: LocalSG
#     rules:
#       - name: AllowLocalUdp
#         description: "Local network"
#         from_port: 0
#         to_port: 65535
#         protocol: udp
#         cidr: 10.0.0.0/8
#         rule_type: Ingress
#       - name: AllowLocalTcp
#         description: "Local network"
#         from_port: 0
#         to_port: 65535
#         protocol: tcp
#         cidr: 10.0.0.0/8
#         rule_type: Ingress
#       - name: AllowICMP
#         description: "Local network"
#         from_port: -1
#         to_port: -1
#         protocol: icmp
#         cidr: 10.0.0.0/8
#         rule_type: Ingress
security_groups:
  - name: BastionSG
    rules:
      - name: BasSSHPublic
        description: "SSH public"
        from_port: 22
        to_port: 22
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
  - name: TowerSG
    rules:
      - name: SSHTower
        description: "SSH public"
        from_port: 22
        to_port: 22
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: HTTPTower
        description: "HTTP public"
        from_port: 80
        to_port: 80
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: HTTPSTower
        description: "HTTP public"
        from_port: 443
        to_port: 443
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: RabbitMq
        description: "RabbitMq"
        from_port: 5672
        to_port: 5672
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: EPMD
        description: "EPMD"
        from_port: 4369
        to_port: 4369
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: RabbitMqCLi
        description: "RabbitMq Cli"
        from_port: 25672
        to_port: 25672
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: RabbitMqAPi
        description: "RabbitMq Api"
        from_port: 15672
        to_port: 15672
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: RabbitMqCliTools
        description: "RabbitMq CLi tools"
        from_port: 35672
        to_port: 35672
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: BasTowerTcp
        description: "ALL from bastion tcp"
        from_port: 0
        to_port: 65535
        protocol: tcp
        group: BastionSG
        rule_type: Ingress
      - name: BasTowerUdp
        description: "ALL from bastion udp"
        from_port: 0
        to_port: 65535
        protocol: udp
        group: BastionSG
        rule_type: Ingress
      - name: AllInternaltcp
        description: "All other nodes tcp"
        from_port: 0
        to_port: 65535
        protocol: tcp
        group: HostSG
        rule_type: Ingress
      - name: AllInternaludp
        description: "All other nodes udp"
        from_port: 0
        to_port: 65535
        protocol: udp
        group: HostSG
        rule_type: Ingress
      - name: AllTowerNodestcp
        description: "All tower nodes tcp"
        from_port: 0
        to_port: 65535
        protocol: tcp
        group: TowerSG
        rule_type: Ingress
      - name: AllTowerNodesudp
        description: "All tower nodes udp"
        from_port: 0
        to_port: 65535
        protocol: udp
        group: TowerSG
        rule_type: Ingress
  - name: HostSG
    rules:
      - name: HostUDPPorts
        description: "Only from Itself udp"
        from_port: 0
        to_port: 65535
        protocol: udp
        group: HostSG
        rule_type: Ingress
      - name: Postgresql
        description: "PostgreSql"
        from_port: 5432
        to_port: 5432
        protocol: tcp
        cidr: "0.0.0.0/0"
        rule_type: Ingress
      - name: HostTCPPorts
        description: "Only from Itself tcp"
        from_port: 0
        to_port: 65535
        protocol: tcp
        group: HostSG
        rule_type: Ingress
      - name: TowerUDPPorts
        description: "Only from tower"
        from_port: 0
        to_port: 65535
        protocol: udp
        group: TowerSG
        rule_type: Ingress
      - name: TowerTCPPorts
        description: "Only from tower"
        from_port: 0
        to_port: 65535
        protocol: tcp
        group: TowerSG
        rule_type: Ingress
      - name: BastionUDPPorts
        description: "Only from bastion"
        from_port: 0
        to_port: 65535
        protocol: udp
        group: BastionSG
        rule_type: Ingress
      - name: BastionTCPPorts
        description: "Only from bastion"
        from_port: 0
        to_port: 65535
        protocol: tcp
        group: BastionSG
        rule_type: Ingress
# Environment Instances
instances:
  - name: "bastion"
    count: 1
    unique: true
    public_dns: true
    dns_loadbalancer: false
    image: "{{ bastion_instance_image }}"
    flavor:
      ec2: "t2.medium"
      azure: Standard_A2_V2
    tags:
      - key: "AnsibleGroup"
        value: "bastions"
      - key: "ostype"
        value: "linux"
      - key: "instance_filter"
        value: "{{ env_type }}-{{ email }}"
    volumes:
      - name: '/dev/sda1'
        size: 20
    security_groups:
      - BastionSG
  - name: "tower"
    count: "{{tower_instance_count}}"
    public_dns: true
    dns_loadbalancer: false
    image: "{{ tower_instance_image }}"
    security_groups:
      - TowerSG
      - HostSG
    flavor:
      ec2: "t2.medium"
      azure: Standard_A2_V2
    tags:
      - key: "AnsibleGroup"
        value: "towers"
      - key: "ostype"
        value: "linux"
      - key: "instance_filter"
        value: "{{ env_type }}-{{ email }}"
  - name: "support"
    count: "{{support_instance_count}}"
    public_dns: true
    dns_loadbalancer: false
    image: "{{ support_instance_image }}"
    security_groups:
      - TowerSG
      - HostSG
    flavor:
      ec2: "t2.medium"
      azure: Standard_A2_V2
    tags:
      - key: "AnsibleGroup"
        value: "support"
      - key: "ostype"
        value: "linux"
      - key: "instance_filter"
        value: "{{ env_type }}-{{ email }}"
instances_worker:
#   - name: "bastion"
#     count: 1
#     unique: true
#     public_dns: true
#     dns_loadbalancer: false
#     image: "{{ bastion_instance_image }}"
#     flavor:
#       ec2: "t2.medium"
#       azure: Standard_A2_V2
#     tags:
#       - key: "AnsibleGroup"
#         value: "bastions"
#       - key: "ostype"
#         value: "linux"
#       - key: "instance_filter"
#         value: "{{ env_type }}-{{ email }}"
#     volumes:
#       - name: '/dev/sda1'
#         size: 20
#     security_groups:
#       - BastionSG
#       - LocalSG
  - name: "worker"
    count: "{{worker_instance_count}}"
    public_dns: true
    dns_loadbalancer: false
    image: "{{ worker_instance_image }}"
    security_groups:
      - TowerSG
      - HostSG
    flavor:
      ec2: "t2.medium"
      azure: Standard_A2_V2
    tags:
      - key: "AnsibleGroup"
        value: "workers"
      - key: "ostype"
        value: "linux"
      - key: "instance_filter"
        value: "{{ env_type }}-{{ email }}"
# DNS settings for environmnet
subdomain_base_short: "{{ guid }}"
subdomain_base_suffix: ".example.opentlc.com"
subdomain_base: "{{subdomain_base_short}}{{subdomain_base_suffix}}"
zone_internal_dns: "{{guid}}.internal."
chomped_zone_internal_dns: "{{guid}}.internal"
# Stuff that only GPTE cares about:
install_ipa_client: false
################################################################################
################################################################################
### Common Host settings
################################################################################
################################################################################
# Other Options are: file, satellite and rhn
#If using repo_method: satellite, you must set these values as well.
# satellite_url: satellite.example.com
# satellite_org: Sat_org_name
# satellite_activationkey: "rhel7basic"
repo_method: file
repo_version: "{{ tower_version }}"
# Do you want to run a full yum update
update_packages: false
common_packages:
  - python
  - unzip
  - bash-completion
  - tmux
  - wget
  - git
  - vim-enhanced
  - at
  - ansible
  - gcc
  - python-pip
rhel_repos:
  - rhel-7-server-rpms
  - rhel-7-server-rh-common-rpms
  - rhel-7-server-extras-rpms
  - rhel-7-server-optional-rpms
  - rhel-7-server-rhscl-rpms
  - epel-release-latest-7
###V2WORK, these should just be set as default listed in the documentation
install_bastion: true
install_common: true
## SB Don't set software_to_deploy from here, always use extra vars (-e) or "none" will be used
#software_to_deploy: none
## guid is the deployment unique identifier, it will be appended to all tags,
## files and anything that identifies this environment from another.
# Using GUID is required, if it is not passed in the command line or uncommented
# here the deployment will fail
#guid: defaultguid
###V2WORK, these should just be set as default listed in the documentation
# This is where the ssh_config file will be created, this file is used to
# define the communication method to all the hosts in the deployment
deploy_local_ssh_config_location: "{{output_dir}}/"
### If you want a Key Pair name created and injected into the hosts,
# set `set_env_authorized_key` to true and set the keyname in `env_authorized_key`
# you can use the key used to create the environment or use your own self generated key
# if you set "use_own_key" to false your PRIVATE key will be copied to the bastion. (This is {{key_name}})
###V2WORK, these should just be set as default listed in the documentation
use_own_key: true
env_authorized_key: "{{guid}}key"
set_env_authorized_key: true
################################################################################
################################################################################
### AWS EC2 Specific Variables
################################################################################
################################################################################
### Route 53 Zone ID (AWS)
# This is the Route53 HostedZoneId where you will create your Public DNS entries
# This only needs to be defined if your CF template uses route53
HostedZoneId: Z3IHLWJZOU9SRT
# The key that is used to connect to the AWS instance initially, it should
# exist in your aws account and the private key should exist on the local machine
# you are provisioning from.
#key_name: "default_key_name"
###V2WORK THIS SHOULD MOVE INTO THE ROLE
# This var is used to identify stack (cloudformation, azure resourcegroup, ...)
project_tag: "{{ env_type }}-{{ guid }}"
################################################################################
################################################################################
### Azure Specific Variables
################################################################################
################################################################################
# Create a dedicated resourceGroup for this deployment
az_destroy_method: resource_group
az_resource_group: "{{ project_tag }}"
# you can operate differently: if you share on resourceGroup for all you deployments,
# you can specify a different resourceGroup and method:
#az_destroy_method: deployment
#az_resource_group: my-shared-resource-group
ansible/configs/multi-region-tower/files/cloud_providers/default.j2
New file
@@ -0,0 +1,369 @@
#jinja2: lstrip_blocks: "True"
---
AWSTemplateFormatVersion: "2010-09-09"
Mappings:
  RegionMapping: {{ aws_ami_region_mapping | to_json }}
Resources:
  Vpc:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "{{ aws_vpc_cidr }}"
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: "{{ aws_vpc_name }}"
        - Key: Hostlication
          Value:
            Ref: "AWS::StackId"
  VpcInternetGateway:
    Type: "AWS::EC2::InternetGateway"
  VpcRouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId:
        Ref: Vpc
  VPCRouteInternetGateway:
    DependsOn: VpcGA
    Type: "AWS::EC2::Route"
    Properties:
      GatewayId:
        Ref: VpcInternetGateway
      DestinationCidrBlock: "0.0.0.0/0"
      RouteTableId:
        Ref: VpcRouteTable
  VpcGA:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      InternetGatewayId:
        Ref: VpcInternetGateway
      VpcId:
        Ref: Vpc
  PublicSubnet:
    Type: "AWS::EC2::Subnet"
    DependsOn:
      - Vpc
    Properties:
    {% if aws_availability_zone is defined %}
      AvailabilityZone: {{ aws_availability_zone }}
    {% endif %}
      CidrBlock: "{{ aws_public_subnet_cidr }}"
      Tags:
        - Key: Name
          Value: "{{project_tag}}"
        - Key: Hostlication
          Value:
            Ref: "AWS::StackId"
      MapPublicIpOnLaunch: true
      VpcId:
        Ref: Vpc
  PublicSubnetRTA:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId:
        Ref: VpcRouteTable
      SubnetId:
        Ref: PublicSubnet
{% for security_group in security_groups|list + default_security_groups|list %}
  {{security_group['name']}}:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: Host
      VpcId:
        Ref: Vpc
      Tags:
        - Key: Name
          Value: "{{security_group['name']}}"
{% endfor %}
{% for security_group in default_security_groups|list + security_groups|list %}
{% for rule in security_group.rules %}
  {{security_group['name']}}{{rule['name']}}:
    Type: "AWS::EC2::SecurityGroup{{rule['rule_type']}}"
    Properties:
     GroupId:
       Fn::GetAtt:
         - "{{security_group['name']}}"
         - GroupId
     IpProtocol: {{rule['protocol']}}
     FromPort: {{rule['from_port']}}
     ToPort: {{rule['to_port']}}
  {% if rule['cidr'] is defined %}
     CidrIp: "{{rule['cidr']}}"
  {% endif  %}
  {% if rule['from_group'] is defined %}
     SourceSecurityGroupId:
       Fn::GetAtt:
        - "{{rule['from_group']}}"
        - GroupId
  {% endif  %}
{% endfor %}
{% endfor %}
  DnsZonePrivate:
    Type: "AWS::Route53::HostedZone"
    Properties:
      Name: "{{ aws_dns_zone_private }}"
      VPCs:
        - VPCId:
            Ref: Vpc
          VPCRegion:
            Ref: "AWS::Region"
      HostedZoneConfig:
        Comment: "{{ aws_comment }}"
  {% if secondary_stack is not defined %}
  DnsZonePublic:
    Type: "AWS::Route53::HostedZone"
    Properties:
      Name: "{{ aws_dns_zone_public }}"
      HostedZoneConfig:
        Comment: "{{ aws_comment }}"
  DnsPublicDelegation:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
      - DnsZonePublic
    Properties:
    {% if HostedZoneId is defined %}
      HostedZoneId: "{{ HostedZoneId }}"
    {% else %}
      HostedZoneName: "{{ aws_dns_zone_root }}"
    {% endif %}
      RecordSets:
        - Name: "{{ aws_dns_zone_public }}"
          Type: NS
          TTL: {{ aws_dns_ttl_public }}
          ResourceRecords:
            "Fn::GetAtt":
              - DnsZonePublic
              - NameServers
    {% endif %}
{% for instance in instances %}
{% if instance['dns_loadbalancer'] | d(false) | bool
  and not instance['unique'] | d(false) | bool %}
  {{instance['name']}}DnsLoadBalancer:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
    {% for c in range(1, (instance['count']|int)+1) %}
      - {{instance['name']}}{{c}}
      {% if instance['public_dns'] %}
      - {{instance['name']}}{{c}}EIP
      {% endif %}
    {% endfor %}
    Properties:
      {% if secondary_stack is defined %}
      HostedZoneName: "{{ aws_dns_zone_public }}"
      {% else %}
      HostedZoneId:
        Ref: DnsZonePublic
      {% endif %}
      RecordSets:
      - Name: "{{instance['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
        Type: A
        TTL: {{ aws_dns_ttl_public }}
        ResourceRecords:
{% for c in range(1,(instance['count'] |int)+1) %}
          - "Fn::GetAtt":
            - {{instance['name']}}{{c}}
            - PublicIp
{% endfor %}
{% endif %}
{% for c in range(1,(instance['count'] |int)+1) %}
  {{instance['name']}}{{loop.index}}:
    Type: "AWS::EC2::Instance"
    Properties:
{% if custom_image is defined %}
      ImageId: {{ custom_image.image_id }}
{% else %}
      ImageId:
        Fn::FindInMap:
        - RegionMapping
        - Ref: AWS::Region
        - {{ instance.image | default(aws_default_image) }}
{% endif %}
      InstanceType: "{{instance['flavor'][cloud_provider]}}"
      KeyName: "{{instance.key_name | default(key_name)}}"
    {% if instance['UserData'] is defined %}
      {{instance['UserData']}}
    {% endif %}
    {% if instance['security_groups'] is defined %}
      SecurityGroupIds:
      {% for sg in instance.security_groups %}
        - Ref: {{ sg }}
      {% endfor %}
    {% else %}
      SecurityGroupIds:
        - Ref: DefaultSG
    {% endif %}
      SubnetId:
        Ref: PublicSubnet
      Tags:
    {% if instance['unique'] | d(false) | bool %}
        - Key: Name
          Value: {{instance['name']}}
        - Key: internaldns
          Value: {{instance['name']}}.{{aws_dns_zone_private_chomped}}
    {% else %}
        - Key: Name
          Value: {{instance['name']}}{{loop.index}}
        - Key: internaldns
          Value: {{instance['name']}}{{loop.index}}.{{aws_dns_zone_private_chomped}}
    {% endif %}
        - Key: "owner"
          Value: "{{ email | default('unknownuser') }}"
        - Key: "Project"
          Value: "{{project_tag}}"
        - Key: "{{project_tag}}"
          Value: "{{ instance['name'] }}"
    {% for tag in instance['tags'] %}
        - Key: {{tag['key']}}
          Value: {{tag['value']}}
    {% endfor %}
      BlockDeviceMappings:
    {% if '/dev/sda1' not in instance.volumes|d([])|json_query('[].device_name')
      and '/dev/sda1' not in instance.volumes|d([])|json_query('[].name')
%}
        - DeviceName: "/dev/sda1"
          Ebs:
            VolumeSize: "{{ instance['rootfs_size'] | default(aws_default_rootfs_size) }}"
            VolumeType: "{{ aws_default_volume_type }}"
    {% endif %}
    {% for vol in instance.volumes|default([]) if vol.enable|d(true) %}
        - DeviceName: "{{ vol.name | default(vol.device_name) }}"
          Ebs:
          {% if cloud_provider in vol and 'type' in vol.ec2 %}
            VolumeType: "{{ vol[cloud_provider].type }}"
          {% else %}
            VolumeType: "{{ aws_default_volume_type }}"
          {% endif %}
            VolumeSize: "{{ vol.size }}"
    {% endfor %}
  {{instance['name']}}{{loop.index}}InternalDns:
    Type: "AWS::Route53::RecordSetGroup"
    Properties:
      HostedZoneId:
        Ref: DnsZonePrivate
      RecordSets:
    {% if instance['unique'] | d(false) | bool %}
        - Name: "{{instance['name']}}.{{aws_dns_zone_private}}"
    {% else %}
        - Name: "{{instance['name']}}{{loop.index}}.{{aws_dns_zone_private}}"
    {% endif %}
          Type: A
          TTL: {{ aws_dns_ttl_private }}
          ResourceRecords:
            - "Fn::GetAtt":
              - {{instance['name']}}{{loop.index}}
              - PrivateIp
{% if instance['public_dns'] %}
  {{instance['name']}}{{loop.index}}EIP:
    Type: "AWS::EC2::EIP"
    DependsOn:
    - VpcGA
    Properties:
      InstanceId:
        Ref: {{instance['name']}}{{loop.index}}
  {{instance['name']}}{{loop.index}}PublicDns:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
      - {{instance['name']}}{{loop.index}}EIP
    Properties:
      {% if secondary_stack is defined %}
      HostedZoneName: "{{ aws_dns_zone_public }}"
      {% else %}
      HostedZoneId:
        Ref: DnsZonePublic
      {% endif %}
      RecordSets:
      {% if instance['unique'] | d(false) | bool %}
        - Name: "{{instance['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
      {% else %}
        - Name: "{{instance['name']}}{{loop.index}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
      {% endif %}
          Type: A
          TTL: {{ aws_dns_ttl_public }}
          ResourceRecords:
          - "Fn::GetAtt":
            - {{instance['name']}}{{loop.index}}
            - PublicIp
{% endif %}
{% endfor %}
{% endfor %}
  {% if secondary_stack is not defined %}
  Route53User:
    Type: AWS::IAM::User
    Properties:
      Policies:
        - PolicyName: Route53Access
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action: route53:GetHostedZone
                Resource: arn:aws:route53:::change/*
              - Effect: Allow
                Action: route53:ListHostedZones
                Resource: "*"
              - Effect: Allow
                Action:
                  - route53:ChangeResourceRecordSets
                  - route53:ListResourceRecordSets
                  - route53:GetHostedZone
                Resource:
                  Fn::Join:
                    - ""
                    - - "arn:aws:route53:::hostedzone/"
                      - Ref: DnsZonePublic
              - Effect: Allow
                Action: route53:GetChange
                Resource: arn:aws:route53:::change/*
  Route53UserAccessKey:
      DependsOn: Route53User
      Type: AWS::IAM::AccessKey
      Properties:
        UserName:
          Ref: Route53User
  {% endif %}
Outputs:
  Route53internalzoneOutput:
    Description: The ID of the internal route 53 zone
    Value:
      Ref: DnsZonePrivate
  {% if secondary_stack is not defined %}
  Route53User:
    Value:
      Ref: Route53User
    Description: IAM User for Route53 (Let's Encrypt)
  Route53UserAccessKey:
    Value:
      Ref: Route53UserAccessKey
    Description: IAM User for Route53 (Let's Encrypt)
  Route53UserSecretAccessKey:
    Value:
      Fn::GetAtt:
        - Route53UserAccessKey
        - SecretAccessKey
    Description: IAM User for Route53 (Let's Encrypt)
  {% endif %}
ansible/configs/multi-region-tower/files/cloud_providers/worker.j2
New file
@@ -0,0 +1,369 @@
#jinja2: lstrip_blocks: "True"
---
AWSTemplateFormatVersion: "2010-09-09"
Mappings:
  RegionMapping: {{ aws_ami_region_mapping | to_json }}
Resources:
  Vpc:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "{{ aws_vpc_cidr }}"
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: "{{ aws_vpc_name }}"
        - Key: Hostlication
          Value:
            Ref: "AWS::StackId"
  VpcInternetGateway:
    Type: "AWS::EC2::InternetGateway"
  VpcRouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId:
        Ref: Vpc
  VPCRouteInternetGateway:
    DependsOn: VpcGA
    Type: "AWS::EC2::Route"
    Properties:
      GatewayId:
        Ref: VpcInternetGateway
      DestinationCidrBlock: "0.0.0.0/0"
      RouteTableId:
        Ref: VpcRouteTable
  VpcGA:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      InternetGatewayId:
        Ref: VpcInternetGateway
      VpcId:
        Ref: Vpc
  PublicSubnet:
    Type: "AWS::EC2::Subnet"
    DependsOn:
      - Vpc
    Properties:
    {% if aws_availability_zone is defined %}
      AvailabilityZone: {{ aws_availability_zone }}
    {% endif %}
      CidrBlock: "{{ aws_public_subnet_cidr }}"
      Tags:
        - Key: Name
          Value: "{{project_tag}}"
        - Key: Hostlication
          Value:
            Ref: "AWS::StackId"
      MapPublicIpOnLaunch: true
      VpcId:
        Ref: Vpc
  PublicSubnetRTA:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId:
        Ref: VpcRouteTable
      SubnetId:
        Ref: PublicSubnet
{% for security_group in security_groups|list + default_security_groups|list %}
  {{security_group['name']}}:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: Host
      VpcId:
        Ref: Vpc
      Tags:
        - Key: Name
          Value: "{{security_group['name']}}"
{% endfor %}
{% for security_group in default_security_groups|list + security_groups|list %}
{% for rule in security_group.rules %}
  {{security_group['name']}}{{rule['name']}}:
    Type: "AWS::EC2::SecurityGroup{{rule['rule_type']}}"
    Properties:
     GroupId:
       Fn::GetAtt:
         - "{{security_group['name']}}"
         - GroupId
     IpProtocol: {{rule['protocol']}}
     FromPort: {{rule['from_port']}}
     ToPort: {{rule['to_port']}}
  {% if rule['cidr'] is defined %}
     CidrIp: "{{rule['cidr']}}"
  {% endif  %}
  {% if rule['from_group'] is defined %}
     SourceSecurityGroupId:
       Fn::GetAtt:
        - "{{rule['from_group']}}"
        - GroupId
  {% endif  %}
{% endfor %}
{% endfor %}
  DnsZonePrivate:
    Type: "AWS::Route53::HostedZone"
    Properties:
      Name: "{{ aws_dns_zone_private }}"
      VPCs:
        - VPCId:
            Ref: Vpc
          VPCRegion:
            Ref: "AWS::Region"
      HostedZoneConfig:
        Comment: "{{ aws_comment }}"
  {% if secondary_stack is not defined %}
  DnsZonePublic:
    Type: "AWS::Route53::HostedZone"
    Properties:
      Name: "{{ aws_dns_zone_public }}"
      HostedZoneConfig:
        Comment: "{{ aws_comment }}"
  DnsPublicDelegation:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
      - DnsZonePublic
    Properties:
    {% if HostedZoneId is defined %}
      HostedZoneId: "{{ HostedZoneId }}"
    {% else %}
      HostedZoneName: "{{ aws_dns_zone_root }}"
    {% endif %}
      RecordSets:
        - Name: "{{ aws_dns_zone_public }}"
          Type: NS
          TTL: {{ aws_dns_ttl_public }}
          ResourceRecords:
            "Fn::GetAtt":
              - DnsZonePublic
              - NameServers
    {% endif %}
{% for instance in instances_worker %}
{% if instance['dns_loadbalancer'] | d(false) | bool
  and not instance['unique'] | d(false) | bool %}
  {{instance['name']}}DnsLoadBalancer:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
    {% for c in range(1, (instance['count']|int)+1) %}
      - {{instance['name']}}{{c}}
      {% if instance['public_dns'] %}
      - {{instance['name']}}{{c}}EIP
      {% endif %}
    {% endfor %}
    Properties:
      {% if secondary_stack is defined %}
      HostedZoneName: "{{ aws_dns_zone_public }}"
      {% else %}
      HostedZoneId:
        Ref: DnsZonePublic
      {% endif %}
      RecordSets:
      - Name: "{{instance['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
        Type: A
        TTL: {{ aws_dns_ttl_public }}
        ResourceRecords:
{% for c in range(1,(instance['count'] |int)+1) %}
          - "Fn::GetAtt":
            - {{instance['name']}}{{c}}
            - PublicIp
{% endfor %}
{% endif %}
{% for c in range(1,(instance['count'] |int)+1) %}
  {{instance['name']}}{{loop.index}}:
    Type: "AWS::EC2::Instance"
    Properties:
{% if custom_image is defined %}
      ImageId: {{ custom_image.image_id }}
{% else %}
      ImageId:
        Fn::FindInMap:
        - RegionMapping
        - Ref: AWS::Region
        - {{ instance.image | default(aws_default_image) }}
{% endif %}
      InstanceType: "{{instance['flavor'][cloud_provider]}}"
      KeyName: "{{instance.key_name | default(key_name)}}"
    {% if instance['UserData'] is defined %}
      {{instance['UserData']}}
    {% endif %}
    {% if instance['security_groups'] is defined %}
      SecurityGroupIds:
      {% for sg in instance.security_groups %}
        - Ref: {{ sg }}
      {% endfor %}
    {% else %}
      SecurityGroupIds:
        - Ref: DefaultSG
    {% endif %}
      SubnetId:
        Ref: PublicSubnet
      Tags:
    {% if instance['unique'] | d(false) | bool %}
        - Key: Name
          Value: {{instance['name']}}
        - Key: internaldns
          Value: {{instance['name']}}.{{aws_dns_zone_private_chomped}}
    {% else %}
        - Key: Name
          Value: {{instance['name']}}{{loop.index}}
        - Key: internaldns
          Value: {{instance['name']}}{{loop.index}}.{{aws_dns_zone_private_chomped}}
    {% endif %}
        - Key: "owner"
          Value: "{{ email | default('unknownuser') }}"
        - Key: "Project"
          Value: "{{project_tag}}"
        - Key: "{{project_tag}}"
          Value: "{{ instance['name'] }}"
    {% for tag in instance['tags'] %}
        - Key: {{tag['key']}}
          Value: {{tag['value']}}
    {% endfor %}
      BlockDeviceMappings:
    {% if '/dev/sda1' not in instance.volumes|d([])|json_query('[].device_name')
      and '/dev/sda1' not in instance.volumes|d([])|json_query('[].name')
%}
        - DeviceName: "/dev/sda1"
          Ebs:
            VolumeSize: "{{ instance['rootfs_size'] | default(aws_default_rootfs_size) }}"
            VolumeType: "{{ aws_default_volume_type }}"
    {% endif %}
    {% for vol in instance.volumes|default([]) if vol.enable|d(true) %}
        - DeviceName: "{{ vol.name | default(vol.device_name) }}"
          Ebs:
          {% if cloud_provider in vol and 'type' in vol.ec2 %}
            VolumeType: "{{ vol[cloud_provider].type }}"
          {% else %}
            VolumeType: "{{ aws_default_volume_type }}"
          {% endif %}
            VolumeSize: "{{ vol.size }}"
    {% endfor %}
  {{instance['name']}}{{loop.index}}InternalDns:
    Type: "AWS::Route53::RecordSetGroup"
    Properties:
      HostedZoneId:
        Ref: DnsZonePrivate
      RecordSets:
    {% if instance['unique'] | d(false) | bool %}
        - Name: "{{instance['name']}}.{{aws_dns_zone_private}}"
    {% else %}
        - Name: "{{instance['name']}}{{loop.index}}.{{aws_dns_zone_private}}"
    {% endif %}
          Type: A
          TTL: {{ aws_dns_ttl_private }}
          ResourceRecords:
            - "Fn::GetAtt":
              - {{instance['name']}}{{loop.index}}
              - PrivateIp
{% if instance['public_dns'] %}
  {{instance['name']}}{{loop.index}}EIP:
    Type: "AWS::EC2::EIP"
    DependsOn:
    - VpcGA
    Properties:
      InstanceId:
        Ref: {{instance['name']}}{{loop.index}}
  {{instance['name']}}{{loop.index}}PublicDns:
    Type: "AWS::Route53::RecordSetGroup"
    DependsOn:
      - {{instance['name']}}{{loop.index}}EIP
    Properties:
      {% if secondary_stack is defined %}
      HostedZoneName: "{{ aws_dns_zone_public }}"
      {% else %}
      HostedZoneId:
        Ref: DnsZonePublic
      {% endif %}
      RecordSets:
      {% if instance['unique'] | d(false) | bool %}
        - Name: "{{instance['name']}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
      {% else %}
        - Name: "{{instance['name']}}{{loop.index}}.{{aws_dns_zone_public_prefix|d('')}}{{ aws_dns_zone_public }}"
      {% endif %}
          Type: A
          TTL: {{ aws_dns_ttl_public }}
          ResourceRecords:
          - "Fn::GetAtt":
            - {{instance['name']}}{{loop.index}}
            - PublicIp
{% endif %}
{% endfor %}
{% endfor %}
  {% if secondary_stack is not defined %}
  Route53User:
    Type: AWS::IAM::User
    Properties:
      Policies:
        - PolicyName: Route53Access
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action: route53:GetHostedZone
                Resource: arn:aws:route53:::change/*
              - Effect: Allow
                Action: route53:ListHostedZones
                Resource: "*"
              - Effect: Allow
                Action:
                  - route53:ChangeResourceRecordSets
                  - route53:ListResourceRecordSets
                  - route53:GetHostedZone
                Resource:
                  Fn::Join:
                    - ""
                    - - "arn:aws:route53:::hostedzone/"
                      - Ref: DnsZonePublic
              - Effect: Allow
                Action: route53:GetChange
                Resource: arn:aws:route53:::change/*
  Route53UserAccessKey:
      DependsOn: Route53User
      Type: AWS::IAM::AccessKey
      Properties:
        UserName:
          Ref: Route53User
  {% endif %}
Outputs:
  Route53internalzoneOutput:
    Description: The ID of the internal route 53 zone
    Value:
      Ref: DnsZonePrivate
  {% if secondary_stack is not defined %}
  Route53User:
    Value:
      Ref: Route53User
    Description: IAM User for Route53 (Let's Encrypt)
  Route53UserAccessKey:
    Value:
      Ref: Route53UserAccessKey
    Description: IAM User for Route53 (Let's Encrypt)
  Route53UserSecretAccessKey:
    Value:
      Fn::GetAtt:
        - Route53UserAccessKey
        - SecretAccessKey
    Description: IAM User for Route53 (Let's Encrypt)
  {% endif %}
ansible/configs/multi-region-tower/files/hosts_template.j2
New file
@@ -0,0 +1,50 @@
[tower]
{% for host in groups['towers'] %}
{{ host }}
{% endfor %}
[database]
support1.{{target_regions[0].name}}.{{chomped_zone_internal_dns}}
{% if target_regions is defined %}
{%for i_region in target_regions[1:] %}
[isolated_group_{{i_region.name}}]
{% for host in groups['workers'] %}
    {% if '.' + i_region.name + '.' in host %}
{{ host }}
    {% endif %}
{% endfor %}
[isolated_group_{{i_region.name}}:vars]
controller=tower
{% endfor %}
{% endif %}
[all:vars]
ansible_become=true
admin_password={{tower_admin_password}}
## This should be replaced by supports[0] name
pg_host='support1.{{target_regions[0].name}}.{{chomped_zone_internal_dns}}'
pg_port='5432'
pg_database='awx'
pg_username='awx'
pg_password={{tower_admin_password}}
rabbitmq_port=5672
rabbitmq_vhost=tower
rabbitmq_username=tower
rabbitmq_password={{ tower_admin_password | regex_replace('[^a-zA-Z0-9]') }}
rabbitmq_cookie=cookiemonster
rabbitmq_use_long_name=true
[support]
{% for host in groups['support'] %}
{{ host }}
{% endfor %}
[workers]
{% for host in groups['workers'] %}
{{ host }}
{% endfor %}
ansible/configs/multi-region-tower/files/hosts_template.j2.back
New file
@@ -0,0 +1,15 @@
[all:vars]
###########################################################################
### Ansible Vars
###########################################################################
timeout=60
ansible_become=yes
ansible_user={{remote_user}}
ansible_ssh_private_key_file="~/.ssh/{{guid}}key.pem"
ansible_ssh_common_args="-o StrictHostKeyChecking=no"
[nodes]
## These are the frontends
{% for host in groups['nodes']|d([]) %}
node{{loop.index}}.{{chomped_zone_internal_dns}} ansible_ssh_host=frontend{{loop.index}}.{{subdomain_base}}
{% endfor %}
ansible/configs/multi-region-tower/files/repos_template.j2
New file
@@ -0,0 +1,38 @@
[rhel-7-server-rpms]
name=Red Hat Enterprise Linux 7
baseurl={{own_repo_path}}/{{repo_version}}/rhel-7-server-rpms
enabled=1
gpgcheck=0
[rhel-7-server-rh-common-rpms]
name=Red Hat Enterprise Linux 7 Common
baseurl={{own_repo_path}}/{{repo_version}}/rhel-7-server-rh-common-rpms
enabled=1
gpgcheck=0
[rhel-7-server-extras-rpms]
name=Red Hat Enterprise Linux 7 Extras
baseurl={{own_repo_path}}/{{repo_version}}/rhel-7-server-extras-rpms
enabled=1
gpgcheck=0
[rhel-7-server-optional-rpms]
name=Red Hat Enterprise Linux 7 Optional
baseurl={{own_repo_path}}/{{repo_version}}/rhel-7-server-optional-rpms
enabled=1
gpgcheck=0
[rhel-7-server-rhscl-rpms]
name=Red Hat Enterprise Linux 7 Optional
baseurl={{own_repo_path}}/{{repo_version}}/rhel-server-rhscl-7-rpms
enabled=1
gpgcheck=0
[epel]
name=Extra Packages for Enterprise Linux 7 - $basearch
baseurl=http://download.fedoraproject.org/pub/epel/7/$basearch
mirrorlist=http://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch
failovermethod=priority
enabled=1
gpgcheck=0
#gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7
ansible/configs/multi-region-tower/files/tower_cli.j2
New file
@@ -0,0 +1,5 @@
[general]
host = tower1.{{target_regions[0].name}}.{{guid}}{{subdomain_base_suffix}}
username = admin
password = {{tower_admin_password}}
verify_ssl = False
ansible/configs/multi-region-tower/multi-region-example.svg
New file
@@ -0,0 +1,2 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="858px" height="642px" viewBox="-0.5 -0.5 858 642" content="&lt;mxfile modified=&quot;2019-02-15T10:37:20.274Z&quot; host=&quot;www.draw.io&quot; agent=&quot;Mozilla/5.0 (X11; Fedora; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36&quot; etag=&quot;loChwW8icn2UyFmw7MHl&quot; version=&quot;10.2.2&quot; type=&quot;google&quot;&gt;&lt;diagram name=&quot;Page-1&quot; id=&quot;75ae5057-2f1f-a65a-41a6-c58fb5237df7&quot;&gt;7V1dc6O2Gv41ntlexANIAnwZe5PT7dnuZJp2TntudmSQbVqMfAAnTn/9kbD40Ae2Y4OTTPFerCVkIfQ875ekl4zAbL37V4o3q59pSOKRY4W7Efg8chwb2pD9x2teRA2auPuaZRqFoq6ueIz+JqLSErXbKCSZ1DCnNM6jjVwZ0CQhQS7V4TSlz3KzBY3lu27wkmgVjwGO9dr/RGG+ErW2ZdUXfiTRciVu7SNxYY6Dv5Yp3SbifiMHLIrP/vIal32J9tkKh/S5UQXuRmCWUprvv613MxLzyS2nbf+7+5ar1bhTkuSn/MCP86+TL49f/vw+ffgD3+K7f/8+vakGl7+UE0JCNj+iSNN8RZc0wfFdXTstHprwbi1WWuXrmH212VeShLccElYMYpxlUbCvvI/issmfJM9fBAvwNqesqr7JV0o3ol2Wp/SvChHAahY0ye/xOoo502Z0m0YkZSP/Rp7FRdGrA0V5RmOaFs8E3OLD69lIGvUhIn4Iq9s1rvjOHBS/ILso/718Pvb9D/7YY28iip93YhqKwkuj8EDSaE1ykoq6/UTz2W0FUFRl7OEC0er+Hj/Nfsu8ZPnr3U8/3nhguvvvDRSo5ThdkvwAvLZfEY1JMKFsPOkL+2FKYpxHT/JIsJCkZdVO/JRhil8aDTY0SvKs0fMDr2ANhFYALtr3WOoElZlKe7cUqTPby83Zl/2Ay1LjyeuqQjheIyhikp5wvBXT9tsmxDkZOW7MIJjOGRndZV4Ava/hHJREy/3flpYXbrKCrbesge1sdvXFspdP3x4LoAKahj80b3Juh2Uf7PH3IyurFfnPyS6XxTolrG88LxpwJgv8WWs0HaHPrAbH0TLhUs+ozAk/fSJpHjEleysurKMwLHRHjOcknlaqsyFxQnlKsmw7pwi+SdAFWmwYZCexXNgR8Ti1em7K4QFVqcuS6N4awwn0JF7eOPvi66RNFye5U1vpgC4WGVMCsgR0w3mkkeN9q/6KLh2p/qOWsGedbmz4Niodyiy00WEV7TnWZe39yRV0uqvp9FlKWnT6oCVP05LooJa8scYORBMJa3CZlryCHpy08sT6myYDXXqjizV2LU92JruxqTcAKvqpB6NqVN+6H3nAyHKiPK+inDxucGE9nlkULvNIN1/3/J9mR2GFn8E6aZC2IuK4sqJG1lhM3XMdPYOy0aoRODvAakdJmuXXTmmpxzsKYkXAxzQVasR85SVzwHeqb9MUQ1eXNMtyXcs6y1mR0eYiHmzTp4pFJzkmRhH1X+OXaM7QtR0V1V2G1mHHA6DL2gP7cHvfOti+J8fG0ZRMFaziNVcgyTzbNGLC2nJ9uq2izuyHwbCdYtgO6KRDfhC7u92JLfPs8dWsl06sLqxX00a127MOrJetrBwBpBsvxzMZL8/ty3h5B2bwNNNUWaNuTVMHJoghlb7sR4zKYmVMeaEeclF6aZbUBdTjNp3E82IFotQnZxlBc3T+VkbwIm7Bj+1repbs+oNSeo9La1+uJtJm9GccJcWUMiv1ymVa17xMe9TCyjAkPAKVxVVUnW5nTbjLWuhUx7UD2MFECTE8DXW7XJpsot5bfKEvEz0yBykJcTFqDfl3j2NPuDH5lMXVhYbY0LYNAtsbdJ4GXTZAZ9C0vgqdAThk0rR9AedrwN0lT1FKkzUp9q4UyB628zgK1PDmF7rNCQ/YWpbpHnDK/Wi6GPH9erMeb6EE6zmOEsaA8lQCRyLE2aqCq0T6K49hHmgW5RGVEG+lQkmamCxU2q3whg9gvVvy4xhj/JyBccqf8jsCus92fy/iG9Y6jEhNH8HOWBlauge2E0ape8Au0BjlGZQ4cnoilA2OO0PngzqneU7XBlBz7l/rauAYpiRwDHAiHwHYDmcXttebjBXggK8BZ9IEqC9NYJ/gxQ7AQeS8O+B0Z3kATvealCUJCHV397qw6T7Tl4SZxSQg2YCfhp+vbIhAyzN4vddFUHee2hF8H75tFxrQ1jTgxNGAgNd0Yg2Hxv4BQDhIhgFZbwwDcDQY5jgr9FDr4b1eXP+udV4kyPR6xddczikXf6XYkn86ooOvHO0xSKVJPULYFx30SGCgw9Xo4DlqhIHKE41H9APsKzQEeoQxEOJqhLA9xYFCE4MDZSREX/sm4GNFLjEna/A92rxrnKtF3urUPjzJDvTnFujr+gPKF0uzcpAXupM3RlkPZweUL0XZ1ZYJUTmtb4azfkj3HeMcxHQbLmi6xvsbqFh7aDLjD/8OsHbUXXgXmI58mnb1+nLYSmdgwLpjrIGyhYv0TRto2rTpS6ihvoAyAN1JFOapQq0frTGKNOgL6Q/ljD1Fab7F8fdNGj3hnHwvkNcB9xEzlJdY7O52+JGNFIvtWLpnVpt1Sbz7WomBH8o3+3igew5UQIfAMRjv/mA3H0/XQD6QVloeIB01jo82TpO2HCA99dBrI21VwFPnrBryMIgdIuKNDCchJ64HsH4Itj4J2U2SqCEZw5z4b+bFlY6d6vtPg1h3eUDSUYXafXuhBhrEneRgnX7O/Zgon5d+Ds9SAueJfLskn3DyHLYknPSbboXU7Ez/cDrUkfb9pEPBPqgpvw8GfRhqNlIyLCQlZRy3qsa0jLfguvcWXHcdOWcYulfgrh4APxAGQLLUOK1Bjm6BNeWQyhl4UzbqmcUz8ZwZL/FcIrlCLXtyha2XeB9yhVr25Apb7d5W7m+rA2xUaCWpe0u5v9UYYJF+aHQ9FHN91NrLcldlI/btwTS9DFSWBQf4dZxt9s+0iHZ8HCaXB45TshfFL0GxTMGK+29yq40g2oH0SE3I2+NP1Wex9fXiyUT3V8q6zv0V0xaf9N6moIK3TtFhKtS35r6etfOI1/xtEMKHZN8Skj/T1HiSvOU1TB8kC6ELLlhqWIqgvvxkm7Z8u0gUMdJBX4n4hSyLEwA8gfs1aV3A+phpXcCgBuuXJyl65mCWdQcUUdMHnIlp3cIx5RL5fVFEj2orijgDRa5OEfXciDN5a4LoO5AVQewzCFIwY+DIJRxRV0pu9JSI61LkVdn+vUSNzrFpP/N9egs/IEFgCj3nPoKo60jxVW+lucJCqBlt3WSEJCZLbD5b+Onb4/DKlQveJdayKtB45YqtvL3nwhew9P/qOUMqx69MsFvzX61USXntbNn9A6StytalCyfUVTNK9DClryRWM8X1o+t1yKmzQs1/frd7MN1xobplJ96Dpcap5ZGmBgFcXydAuQPWPQH0o+oDAXojgO/KL3RGtu4/XhV+Q6bCAH9v8CNHht+2TamdHRGAFeu/BbF3H+q/uAHu/g8=&lt;/diagram&gt;&lt;/mxfile&gt;" style="background-color: rgb(255, 255, 255);"><defs/><g><path d="M 260 101 L 245 101 L 245 128 L 530 128 L 530 28 L 636.4 28" fill="none" stroke="#82b366" stroke-width="3" stroke-miterlimit="10" pointer-events="none"/><path d="M 643.15 28 L 634.15 32.5 L 636.4 28 L 634.15 23.5 Z" fill="#82b366" stroke="#82b366" stroke-width="3" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(495.5,25.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="79" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Courier New&quot;; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Update<br /><font style="font-size: 12px">(NS record)<br style="font-size: 12px" /></font></div></div></foreignObject><text x="40" y="19" fill="#666666" text-anchor="middle" font-size="12px" font-family="Courier New">[Not supported by viewer]</text></switch></g><path d="M 223 111 L 223 158 L 600 158 L 600 197 L 634.9 197" fill="none" stroke="#82b366" stroke-width="3" stroke-miterlimit="10" pointer-events="none"/><path d="M 641.65 197 L 632.65 201.5 L 634.9 197 L 632.65 192.5 Z" fill="#82b366" stroke="#82b366" stroke-width="3" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(346.5,149.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="43" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Courier New&quot;; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Create<br /></div></div></foreignObject><text x="22" y="12" fill="#666666" text-anchor="middle" font-size="12px" font-family="Courier New">Create&lt;br&gt;</text></switch></g><g transform="translate(510.5,165.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="79" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Courier New&quot;; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Create zone<br /></div></div></foreignObject><text x="40" y="12" fill="#666666" text-anchor="middle" font-size="12px" font-family="Courier New">Create zone&lt;br&gt;</text></switch></g><rect x="140" y="58.5" width="360" height="230" rx="34.5" ry="34.5" fill="#ffffff" stroke="#8f8f8f" stroke-width="4" pointer-events="none"/><path d="M 103 383 Q 103 408 168 408 Q 233 408 233 363 Q 233 318 458 318 Q 683 318 683 231.24" fill="none" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 683 225.24 L 687 233.24 L 683 231.24 L 679 233.24 Z" fill="#82b366" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(270.5,305.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="79" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Courier New&quot;; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">Update <br />(A records)<br /></div></div></foreignObject><text x="40" y="19" fill="#666666" text-anchor="middle" font-size="12px" font-family="Courier New">Update &lt;br&gt;(A records)&lt;br&gt;</text></switch></g><rect x="30" y="363" width="270" height="276" rx="40.5" ry="40.5" fill="#ffffff" stroke="#8f8f8f" stroke-width="4" pointer-events="none"/><path d="M 653 358 Q 653 291 668 291 Q 683 291 683 231.24" fill="none" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 683 225.24 L 687 233.24 L 683 231.24 L 679 233.24 Z" fill="#82b366" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><rect x="585" y="358" width="270" height="270" rx="40.5" ry="40.5" fill="#ffffff" stroke="#8f8f8f" stroke-width="4" pointer-events="none"/><g transform="translate(293.5,71.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="77" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(0, 102, 0); line-height: 1.2; vertical-align: top; width: 77px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Main stack<br style="font-size: 16px" /></div></div></foreignObject><text x="39" y="17" fill="#006600" text-anchor="middle" font-size="16px" font-family="Helvetica">[Not supported by viewer]</text></switch></g><g transform="translate(125.5,381.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="88" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 102, 0); line-height: 1.2; vertical-align: top; width: 90px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Secondary stack<br /></div></div></foreignObject><text x="44" y="12" fill="#006600" text-anchor="middle" font-size="12px" font-family="Helvetica">Secondary stack&lt;br&gt;</text></switch></g><g transform="translate(696.5,376.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="86" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 102, 0); line-height: 1.2; vertical-align: top; width: 88px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">secondary stack<br /></div></div></foreignObject><text x="43" y="12" fill="#006600" text-anchor="middle" font-size="12px" font-family="Helvetica">secondary stack&lt;br&gt;</text></switch></g><path d="M 682.38 223 L 666.58 218.42 L 666.58 190.14 L 645 188.75 L 645 181.75 L 649.82 180.36 L 666.58 182.68 L 666.58 175.58 L 682.38 171 L 698.3 175.58 L 698.3 197 L 720 197 L 720 203.1 L 716.01 203.58 L 698.3 202.68 L 698.3 218.42 Z" fill="#ff6666" stroke="none" pointer-events="none"/><path d="M 649.82 187.98 L 645 188.75 L 645 181.75 L 649.82 180.36 Z M 682.38 223 L 666.58 218.42 L 666.58 190.14 L 670.92 190.41 L 676.46 189.92 L 676.46 184.03 L 666.58 182.68 L 666.58 175.58 L 682.38 171 Z M 716.01 203.58 L 688.28 202.13 L 688.28 197 L 716.01 197 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 670.92 190.41 L 645 188.75 L 649.82 187.98 L 676.46 189.92 Z" fill-opacity="0.5" fill="#000000" stroke="none" pointer-events="none"/><g transform="translate(722.5,176.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="111" height="40" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Environment<br />Public Route53 zone<br />Part of main stack<br /></div></div></foreignObject><text x="56" y="26" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text></switch></g><path d="M 284.49 196 L 279.51 193.94 L 279.51 190.78 L 275.11 192.12 L 271.31 190.55 L 271.31 188.28 L 267.86 189.09 L 264.78 187.81 L 264.78 186.14 L 261.99 186.66 L 259.5 185.63 L 259.5 156.38 L 261.99 155.35 L 264.78 155.86 L 264.78 154.2 L 267.86 152.92 L 271.31 153.72 L 271.31 151.46 L 275.11 149.88 L 279.51 151.22 L 279.51 148.06 L 284.49 146 L 309.5 156.37 L 309.5 185.63 Z" fill="#f58534" stroke="none" pointer-events="none"/><path d="M 261.99 186.66 L 259.5 185.63 L 259.5 156.38 L 261.99 155.35 Z M 267.86 189.09 L 264.78 187.81 L 264.78 154.2 L 267.86 152.92 Z M 275.11 192.12 L 271.31 190.55 L 271.31 151.46 L 275.11 149.88 Z M 279.51 193.94 L 279.51 148.06 L 284.49 146 L 284.49 196 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 357.49 196 L 352.51 193.94 L 352.51 190.78 L 348.11 192.12 L 344.31 190.55 L 344.31 188.28 L 340.86 189.09 L 337.78 187.81 L 337.78 186.14 L 334.99 186.66 L 332.5 185.63 L 332.5 156.38 L 334.99 155.35 L 337.78 155.86 L 337.78 154.2 L 340.86 152.92 L 344.31 153.72 L 344.31 151.46 L 348.11 149.88 L 352.51 151.22 L 352.51 148.06 L 357.49 146 L 382.5 156.37 L 382.5 185.63 Z" fill="#f58534" stroke="none" pointer-events="none"/><path d="M 334.99 186.66 L 332.5 185.63 L 332.5 156.38 L 334.99 155.35 Z M 340.86 189.09 L 337.78 187.81 L 337.78 154.2 L 340.86 152.92 Z M 348.11 192.12 L 344.31 190.55 L 344.31 151.46 L 348.11 149.88 Z M 352.51 193.94 L 352.51 148.06 L 357.49 146 L 357.49 196 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 154.99 505 L 150.01 502.94 L 150.01 499.78 L 145.61 501.12 L 141.81 499.55 L 141.81 497.28 L 138.36 498.09 L 135.28 496.81 L 135.28 495.14 L 132.49 495.66 L 130 494.63 L 130 465.38 L 132.49 464.35 L 135.28 464.86 L 135.28 463.2 L 138.36 461.92 L 141.81 462.72 L 141.81 460.46 L 145.61 458.88 L 150.01 460.22 L 150.01 457.06 L 154.99 455 L 180 465.37 L 180 494.63 Z" fill="#f58534" stroke="none" pointer-events="none"/><path d="M 132.49 495.66 L 130 494.63 L 130 465.38 L 132.49 464.35 Z M 138.36 498.09 L 135.28 496.81 L 135.28 463.2 L 138.36 461.92 Z M 145.61 501.12 L 141.81 499.55 L 141.81 460.46 L 145.61 458.88 Z M 150.01 502.94 L 150.01 457.06 L 154.99 455 L 154.99 505 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 764.99 465.5 L 760.01 463.44 L 760.01 460.28 L 755.61 461.62 L 751.81 460.05 L 751.81 457.78 L 748.36 458.59 L 745.28 457.31 L 745.28 455.64 L 742.49 456.16 L 740 455.13 L 740 425.88 L 742.49 424.85 L 745.28 425.36 L 745.28 423.7 L 748.36 422.42 L 751.81 423.22 L 751.81 420.96 L 755.61 419.38 L 760.01 420.72 L 760.01 417.56 L 764.99 415.5 L 790 425.87 L 790 455.13 Z" fill="#f58534" stroke="none" pointer-events="none"/><path d="M 742.49 456.16 L 740 455.13 L 740 425.88 L 742.49 424.85 Z M 748.36 458.59 L 745.28 457.31 L 745.28 423.7 L 748.36 422.42 Z M 755.61 461.62 L 751.81 460.05 L 751.81 420.96 L 755.61 419.38 Z M 760.01 463.44 L 760.01 417.56 L 764.99 415.5 L 764.99 465.5 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><g transform="translate(739.5,473.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="51" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Instances</div></div></foreignObject><text x="26" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Instances</text></switch></g><g transform="translate(286.5,203.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="52" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 52px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Instances</div></div></foreignObject><text x="26" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Instances</text></switch></g><g transform="translate(128.5,513.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="52" height="12" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 52px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Instances</div></div></foreignObject><text x="26" y="12" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">Instances</text></switch></g><path d="M 168.58 244 C 163.62 244 160 240.43 160 236.91 L 160 207.01 C 160 203.71 163.42 200 168.75 200 L 201.54 200 C 206.1 200 210 203.27 210 207.12 L 210 236.8 C 210 240.89 206.01 244 201.39 244 Z" fill="#f58534" stroke="none" pointer-events="none"/><path d="M 168.58 244 C 163.62 244 160 240.44 160 236.91 L 160 234.89 C 160 239.04 164.14 242.09 168.65 242.09 L 201.45 242.09 C 206.01 242.09 210 238.93 210 234.87 L 210 236.8 C 210 240.89 206.03 244 201.39 244 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><g transform="translate(167.5,251.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="35" height="24" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">bastion<br /><br /></div></div></foreignObject><text x="18" y="18" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">bastion&lt;br&gt;&lt;br&gt;</text></switch></g><path d="M 616.36 561 C 612.4 561 609.5 557.59 609.5 554.23 L 609.5 525.69 C 609.5 522.54 612.24 519 616.5 519 L 642.73 519 C 646.38 519 649.5 522.12 649.5 525.8 L 649.5 554.13 C 649.5 558.03 646.31 561 642.61 561 Z" fill="#f58534" stroke="none" pointer-events="none"/><path d="M 616.36 561 C 612.4 561 609.5 557.6 609.5 554.23 L 609.5 552.31 C 609.5 556.26 612.81 559.18 616.42 559.18 L 642.66 559.18 C 646.31 559.18 649.5 556.16 649.5 552.29 L 649.5 554.13 C 649.5 558.03 646.32 561 642.61 561 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><g transform="translate(611.5,568.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="35" height="24" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">bastion<br /><br /></div></div></foreignObject><text x="18" y="18" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">bastion&lt;br&gt;&lt;br&gt;</text></switch></g><path d="M 56.86 508.5 C 52.9 508.5 50 505.17 50 501.89 L 50 474.03 C 50 470.96 52.74 467.5 57 467.5 L 83.23 467.5 C 86.88 467.5 90 470.54 90 474.14 L 90 501.79 C 90 505.61 86.81 508.5 83.11 508.5 Z" fill="#f58534" stroke="none" pointer-events="none"/><path d="M 56.86 508.5 C 52.9 508.5 50 505.18 50 501.89 L 50 500.01 C 50 503.88 53.31 506.72 56.92 506.72 L 83.16 506.72 C 86.81 506.72 90 503.78 90 499.99 L 90 501.79 C 90 505.61 86.82 508.5 83.11 508.5 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><g transform="translate(52.5,516.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="35" height="24" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">bastion<br /><br /></div></div></foreignObject><text x="18" y="18" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">bastion&lt;br&gt;&lt;br&gt;</text></switch></g><path d="M 120.9 225.74 C 120.07 229.24 117.84 231.2 115.65 231.2 C 112.38 231.2 110 227.47 110 223.45 L 110 220.48 C 110 216.52 112.53 212.83 115.52 212.83 C 118.06 212.83 120.15 215.09 120.9 218.3 L 143.58 218.3 L 143.58 212 L 160 220.69 L 160 223.28 L 143.58 232 L 143.58 225.74 Z" fill="#f58534" stroke="none" pointer-events="none"/><path d="M 143.58 229.42 L 160 220.69 L 160 223.28 L 143.58 232 Z M 143.58 225.74 L 120.9 225.74 C 120.07 229.23 117.83 231.2 115.65 231.2 C 112.39 231.2 110 227.45 110 223.45 L 110 220.48 C 110 225.45 112.72 228.59 115.65 228.59 C 118.09 228.59 120.18 226.22 120.89 223.13 L 143.58 223.13 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 10.9 490.74 C 10.07 494.24 7.84 496.2 5.65 496.2 C 2.38 496.2 0 492.47 0 488.45 L 0 485.48 C 0 481.52 2.53 477.83 5.52 477.83 C 8.06 477.83 10.15 480.09 10.9 483.3 L 33.58 483.3 L 33.58 477 L 50 485.69 L 50 488.28 L 33.58 497 L 33.58 490.74 Z" fill="#f58534" stroke="none" pointer-events="none"/><path d="M 33.58 494.42 L 50 485.69 L 50 488.28 L 33.58 497 Z M 33.58 490.74 L 10.9 490.74 C 10.07 494.23 7.83 496.2 5.65 496.2 C 2.39 496.2 0 492.45 0 488.45 L 0 485.48 C 0 490.45 2.72 493.59 5.65 493.59 C 8.09 493.59 10.18 491.22 10.89 488.13 L 33.58 488.13 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 570.4 543.74 C 569.57 547.24 567.34 549.2 565.15 549.2 C 561.88 549.2 559.5 545.47 559.5 541.45 L 559.5 538.48 C 559.5 534.52 562.03 530.83 565.02 530.83 C 567.56 530.83 569.65 533.09 570.4 536.3 L 593.08 536.3 L 593.08 530 L 609.5 538.69 L 609.5 541.28 L 593.08 550 L 593.08 543.74 Z" fill="#f58534" stroke="none" pointer-events="none"/><path d="M 593.08 547.42 L 609.5 538.69 L 609.5 541.28 L 593.08 550 Z M 593.08 543.74 L 570.4 543.74 C 569.57 547.23 567.33 549.2 565.15 549.2 C 561.89 549.2 559.5 545.45 559.5 541.45 L 559.5 538.48 C 559.5 543.45 562.22 546.59 565.15 546.59 C 567.59 546.59 569.68 544.22 570.39 541.13 L 593.08 541.13 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 95.6 394.15 L 95.6 380.98 L 91.12 381.88 L 91.12 393.16 Z M 104.4 394.15 L 108.92 393.15 L 108.92 381.88 L 104.4 380.97 Z M 100 403.5 L 85 396.85 L 85 378.11 L 100 371.5 L 115 378.19 L 115 396.93 Z" fill="#759c3e" stroke="none" pointer-events="none"/><path d="M 113.05 393.66 L 108.92 393.15 L 108.92 381.88 L 113.05 381.4 Z M 86.95 393.62 L 95.6 395.92 L 95.6 379.08 L 86.95 381.38 Z M 100 403.5 L 85 396.85 L 85 378.11 L 100 371.5 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 104.4 395.96 L 104.4 394.15 L 108.92 393.15 L 113.05 393.66 Z M 95.6 395.92 L 86.95 393.62 L 91.12 393.16 L 95.6 394.15 Z" fill-opacity="0.3" fill="#ffffff" stroke="none" pointer-events="none"/><path d="M 104.4 380.97 L 104.4 379.12 L 113.05 381.4 L 108.92 381.88 Z M 91.12 381.88 L 86.95 381.38 L 95.6 379.08 L 95.6 380.98 Z" fill-opacity="0.5" fill="#000000" stroke="none" pointer-events="none"/><path d="M 230.91 96.39 L 230.91 75.81 L 224.19 77.22 L 224.19 94.84 Z M 244.09 96.39 L 250.88 94.83 L 250.88 77.22 L 244.09 75.79 Z M 237.5 111 L 215 100.61 L 215 71.33 L 237.5 61 L 260 71.46 L 260 100.73 Z" fill="#759c3e" stroke="none" pointer-events="none"/><path d="M 257.07 95.62 L 250.88 94.83 L 250.88 77.22 L 257.07 76.47 Z M 217.93 95.57 L 230.91 99.16 L 230.91 72.84 L 217.93 76.44 Z M 237.5 111 L 215 100.61 L 215 71.33 L 237.5 61 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 244.09 99.22 L 244.09 96.39 L 250.88 94.83 L 257.07 95.62 Z M 230.91 99.16 L 217.93 95.57 L 224.19 94.84 L 230.91 96.39 Z" fill-opacity="0.3" fill="#ffffff" stroke="none" pointer-events="none"/><path d="M 244.09 75.79 L 244.09 72.91 L 257.07 76.47 L 250.88 77.22 Z M 224.19 77.22 L 217.93 76.44 L 230.91 72.84 L 230.91 75.81 Z" fill-opacity="0.5" fill="#000000" stroke="none" pointer-events="none"/><path d="M 665.6 391.35 L 665.6 377.77 L 661.12 378.7 L 661.12 390.33 Z M 674.4 391.35 L 678.92 390.33 L 678.92 378.7 L 674.4 377.76 Z M 670 401 L 655 394.15 L 655 374.82 L 670 368 L 685 374.9 L 685 394.22 Z" fill="#759c3e" stroke="none" pointer-events="none"/><path d="M 683.05 390.85 L 678.92 390.33 L 678.92 378.7 L 683.05 378.21 Z M 656.95 390.81 L 665.6 393.18 L 665.6 375.82 L 656.95 378.19 Z M 670 401 L 655 394.15 L 655 374.82 L 670 368 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 674.4 393.23 L 674.4 391.35 L 678.92 390.33 L 683.05 390.85 Z M 665.6 393.18 L 656.95 390.81 L 661.12 390.33 L 665.6 391.35 Z" fill-opacity="0.3" fill="#ffffff" stroke="none" pointer-events="none"/><path d="M 674.4 377.76 L 674.4 375.86 L 683.05 378.21 L 678.92 378.7 Z M 661.12 378.7 L 656.95 378.19 L 665.6 375.82 L 665.6 377.77 Z" fill-opacity="0.5" fill="#000000" stroke="none" pointer-events="none"/><path d="M 412.13 271 C 403.14 271 396.2 264.15 396.2 257.79 L 396.2 255.14 C 395.5 247.11 401.92 240.8 406.38 239.56 C 406.13 233.87 408.6 227.39 413.84 223.07 C 421.42 217 430.33 217.92 435.71 220.8 C 439.5 222.72 442.91 226.11 444.88 230.59 C 448.93 227.85 453.26 228.67 455.73 230.09 C 459.08 232.01 460.85 235.41 460.9 238.76 C 464.34 239.12 468.43 240.66 471.97 245.28 C 473.82 247.74 474.96 252.13 475 254.75 L 475 257.47 C 475 264.34 467.7 271 459.12 271 Z" fill="#856794" stroke="none" pointer-events="none"/><path d="M 475 254.75 L 475 257.47 C 475 264.33 467.7 271 459.12 271 L 412.13 271 C 403.14 271 396.2 264.13 396.2 257.79 L 396.2 255.14 C 396.2 260.92 402.78 268.17 411.99 268.17 L 458.42 268.17 C 468.25 268.17 475 261.29 475 254.75 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><rect x="395.5" y="217" width="0" height="0" fill="none" stroke="#000000" pointer-events="none"/><path d="M 423.26 261.06 L 419.57 261.06 L 415.2 242.39 L 418.33 242.39 L 421.47 257.38 L 424.83 242.39 L 427.97 242.39 Z M 432.78 251.13 L 434.96 251.13 C 436.14 251.13 437.05 250.56 437.5 249.61 C 437.84 248.86 437.94 247.47 437.57 246.32 C 437.11 245.02 435.87 244.66 434.91 244.66 L 432.78 244.66 Z M 432.78 261.06 L 429.87 261.06 L 429.87 242.39 L 435.75 242.39 C 437.36 242.39 439.18 242.92 440.08 244.73 C 440.81 246.17 440.81 248.15 440.59 249.3 C 440.35 250.53 439.73 251.78 438.53 252.57 C 437.91 252.92 437.18 253.34 435.45 253.4 L 432.78 253.4 Z M 454.61 248.18 L 451.7 248.18 C 451.75 247.06 451.55 246.05 451.15 245.35 C 450.73 244.6 450.06 244.11 448.75 244.22 C 447.97 244.31 446.96 244.45 446.39 246.31 C 445.73 248.82 445.81 252.36 446.02 254.9 C 446.24 257.07 446.75 258.11 447.5 258.68 C 448.32 259.21 449.76 259.23 450.54 258.58 C 451.45 257.82 451.75 256.26 451.7 254.25 L 454.61 254.25 C 454.63 255.67 454.48 257.23 453.82 258.56 C 453.08 260.05 451.76 261.1 449.64 261.32 C 446.93 261.5 445.29 260.73 444.35 259.35 C 443.45 258.12 442.88 255.81 442.85 252.12 C 442.81 248.73 443.11 245.78 444.29 243.99 C 445.12 242.74 446.51 241.77 449.24 241.83 C 451.66 241.87 453.2 242.83 453.97 244.37 C 454.51 245.52 454.64 246.75 454.61 248.18 Z" fill="#ffffff" stroke="none" pointer-events="none"/><path d="M 621.13 494.5 C 612.14 494.5 605.2 487.65 605.2 481.29 L 605.2 478.64 C 604.5 470.61 610.92 464.3 615.38 463.06 C 615.13 457.37 617.6 450.89 622.84 446.57 C 630.42 440.5 639.33 441.42 644.71 444.3 C 648.5 446.22 651.91 449.61 653.88 454.09 C 657.93 451.35 662.26 452.17 664.73 453.59 C 668.08 455.51 669.85 458.91 669.9 462.26 C 673.34 462.62 677.43 464.16 680.97 468.78 C 682.82 471.24 683.96 475.63 684 478.25 L 684 480.97 C 684 487.84 676.7 494.5 668.12 494.5 Z" fill="#856794" stroke="none" pointer-events="none"/><path d="M 684 478.25 L 684 480.97 C 684 487.83 676.7 494.5 668.12 494.5 L 621.13 494.5 C 612.14 494.5 605.2 487.63 605.2 481.29 L 605.2 478.64 C 605.2 484.42 611.78 491.67 620.99 491.67 L 667.42 491.67 C 677.25 491.67 684 484.79 684 478.25 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><rect x="604.5" y="440.5" width="0" height="0" fill="none" stroke="#000000" pointer-events="none"/><path d="M 632.26 484.56 L 628.57 484.56 L 624.2 465.89 L 627.33 465.89 L 630.47 480.88 L 633.83 465.89 L 636.97 465.89 Z M 641.78 474.63 L 643.96 474.63 C 645.14 474.63 646.05 474.06 646.5 473.11 C 646.84 472.36 646.94 470.97 646.57 469.82 C 646.11 468.52 644.87 468.16 643.91 468.16 L 641.78 468.16 Z M 641.78 484.56 L 638.87 484.56 L 638.87 465.89 L 644.75 465.89 C 646.36 465.89 648.18 466.42 649.08 468.23 C 649.81 469.67 649.81 471.65 649.59 472.8 C 649.35 474.03 648.73 475.28 647.53 476.07 C 646.91 476.42 646.18 476.84 644.45 476.9 L 641.78 476.9 Z M 663.61 471.68 L 660.7 471.68 C 660.75 470.56 660.55 469.55 660.15 468.85 C 659.73 468.1 659.06 467.61 657.75 467.72 C 656.97 467.81 655.96 467.95 655.39 469.81 C 654.73 472.32 654.81 475.86 655.02 478.4 C 655.24 480.57 655.75 481.61 656.5 482.18 C 657.32 482.71 658.76 482.73 659.54 482.08 C 660.45 481.32 660.75 479.76 660.7 477.75 L 663.61 477.75 C 663.63 479.17 663.48 480.73 662.82 482.06 C 662.08 483.55 660.76 484.6 658.64 484.82 C 655.93 485 654.29 484.23 653.35 482.85 C 652.45 481.62 651.88 479.31 651.85 475.62 C 651.81 472.23 652.11 469.28 653.29 467.49 C 654.12 466.24 655.51 465.27 658.24 465.33 C 660.66 465.37 662.2 466.33 662.97 467.87 C 663.51 469.02 663.64 470.25 663.61 471.68 Z" fill="#ffffff" stroke="none" pointer-events="none"/><path d="M 284.5 471.13 L 405 470" fill="none" stroke="#9673a6" stroke-width="4" stroke-miterlimit="10" pointer-events="none"/><path d="M 221.63 498.5 C 212.64 498.5 205.7 491.65 205.7 485.29 L 205.7 482.64 C 205 474.61 211.42 468.3 215.88 467.06 C 215.63 461.37 218.1 454.89 223.34 450.57 C 230.92 444.5 239.83 445.42 245.21 448.3 C 249 450.22 252.41 453.61 254.38 458.09 C 258.43 455.35 262.76 456.17 265.23 457.59 C 268.58 459.51 270.35 462.91 270.4 466.26 C 273.84 466.62 277.93 468.16 281.47 472.78 C 283.32 475.24 284.46 479.63 284.5 482.25 L 284.5 484.97 C 284.5 491.84 277.2 498.5 268.62 498.5 Z" fill="#856794" stroke="none" pointer-events="none"/><path d="M 284.5 482.25 L 284.5 484.97 C 284.5 491.83 277.2 498.5 268.62 498.5 L 221.63 498.5 C 212.64 498.5 205.7 491.63 205.7 485.29 L 205.7 482.64 C 205.7 488.42 212.28 495.67 221.49 495.67 L 267.92 495.67 C 277.75 495.67 284.5 488.79 284.5 482.25 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><rect x="205" y="444.5" width="0" height="0" fill="none" stroke="#000000" pointer-events="none"/><path d="M 232.76 488.56 L 229.07 488.56 L 224.7 469.89 L 227.83 469.89 L 230.97 484.88 L 234.33 469.89 L 237.47 469.89 Z M 242.28 478.63 L 244.46 478.63 C 245.64 478.63 246.55 478.06 247 477.11 C 247.34 476.36 247.44 474.97 247.07 473.82 C 246.61 472.52 245.37 472.16 244.41 472.16 L 242.28 472.16 Z M 242.28 488.56 L 239.37 488.56 L 239.37 469.89 L 245.25 469.89 C 246.86 469.89 248.68 470.42 249.58 472.23 C 250.31 473.67 250.31 475.65 250.09 476.8 C 249.85 478.03 249.23 479.28 248.03 480.07 C 247.41 480.42 246.68 480.84 244.95 480.9 L 242.28 480.9 Z M 264.11 475.68 L 261.2 475.68 C 261.25 474.56 261.05 473.55 260.65 472.85 C 260.23 472.1 259.56 471.61 258.25 471.72 C 257.47 471.81 256.46 471.95 255.89 473.81 C 255.23 476.32 255.31 479.86 255.52 482.4 C 255.74 484.57 256.25 485.61 257 486.18 C 257.82 486.71 259.26 486.73 260.04 486.08 C 260.95 485.32 261.25 483.76 261.2 481.75 L 264.11 481.75 C 264.13 483.17 263.98 484.73 263.32 486.06 C 262.58 487.55 261.26 488.6 259.14 488.82 C 256.43 489 254.79 488.23 253.85 486.85 C 252.95 485.62 252.38 483.31 252.35 479.62 C 252.31 476.23 252.61 473.28 253.79 471.49 C 254.62 470.24 256.01 469.27 258.74 469.33 C 261.16 469.37 262.7 470.33 263.47 471.87 C 264.01 473.02 264.14 474.25 264.11 475.68 Z" fill="#ffffff" stroke="none" pointer-events="none"/><path d="M 455 420 L 455 288 L 455 271" fill="none" stroke="#9673a6" stroke-width="4" stroke-miterlimit="10" pointer-events="none"/><path d="M 504 470 L 504 468 L 609 468" fill="none" stroke="#9673a6" stroke-width="4" stroke-miterlimit="10" pointer-events="none"/><path d="M 405 420 L 504 420 L 504 519 L 405 519 Z" fill="#ffffff" stroke="#5a30b5" stroke-miterlimit="10" pointer-events="none"/><path d="M 454.65 437.23 C 454.25 437.23 453.88 437.38 453.6 437.66 L 446 445.35 L 448.11 447.44 L 453.16 442.32 L 453.16 455.77 L 456.13 455.77 L 456.13 442.34 L 461.09 447.4 L 463.21 445.32 L 455.72 437.67 C 455.44 437.38 455.05 437.22 454.65 437.23 Z M 451.99 458.55 C 451.79 458.54 451.58 458.55 451.38 458.56 C 448.98 458.67 447.05 459.77 445.86 460.99 C 445.83 461.01 445.81 461.03 445.79 461.06 C 444.25 462.85 443.68 464.69 443.67 466.63 C 442.55 466.95 441.55 467.47 440.92 468.31 C 440.05 469.48 439.7 470.92 439.7 472.32 C 439.7 475.69 442.71 478.7 446.48 478.7 L 462.79 478.7 C 464.21 478.7 465.77 478.45 467.09 477.51 C 468.42 476.58 469.35 474.92 469.46 472.8 C 469.46 472.79 469.46 472.79 469.46 472.79 C 469.61 469.36 467.39 467.02 464.84 466.5 C 464.71 465.39 464.33 464.26 463.27 463.36 C 463.2 463.3 463.12 463.25 463.04 463.2 C 461.78 462.48 460.45 462.37 459.43 462.72 C 459.24 462.79 459.2 462.91 459.03 463 C 457.94 461.1 456.12 459.5 454.02 458.84 C 453.99 458.83 453.96 458.82 453.93 458.82 C 453.25 458.65 452.61 458.57 451.99 458.55 Z M 478.81 460.73 L 476.69 462.81 L 481.67 467.91 L 472.37 467.91 L 472.37 470.88 L 481.67 470.88 L 476.66 476.01 L 478.78 478.08 L 486.25 470.43 C 486.81 469.85 486.81 468.94 486.25 468.36 Z M 430.44 460.8 L 422.89 468.35 C 422.31 468.93 422.31 469.87 422.89 470.45 L 430.46 478.01 L 432.56 475.92 L 427.52 470.89 L 436.8 470.89 L 436.8 467.92 L 427.52 467.92 L 432.55 462.9 Z M 451.88 461.51 C 452.28 461.53 452.71 461.58 453.15 461.69 C 454.55 462.14 456.58 464.27 457 465.64 C 457.16 466.14 457.56 466.52 458.07 466.64 C 458.58 466.77 459.11 466.61 459.48 466.24 C 459.87 465.86 460.18 465.6 460.41 465.52 C 460.61 465.45 460.91 465.49 461.44 465.75 C 462.03 466.33 462.11 466.96 462 467.46 C 461.9 467.93 462.03 468.42 462.35 468.77 C 462.67 469.13 463.14 469.3 463.62 469.25 C 464.94 469.1 466.6 470 466.49 472.65 C 466.42 474.06 465.98 474.65 465.38 475.07 C 464.78 475.5 463.84 475.73 462.79 475.73 L 446.48 475.73 C 444.42 475.73 442.67 473.69 442.67 472.32 C 442.67 471.42 442.92 470.59 443.3 470.08 C 443.68 469.57 444.11 469.28 445.05 469.28 C 445.49 469.28 445.91 469.09 446.19 468.75 C 446.48 468.41 446.59 467.96 446.51 467.52 C 446.17 465.73 446.44 464.88 448.01 463.04 C 448.72 462.32 449.89 461.59 451.48 461.52 C 451.61 461.51 451.74 461.51 451.88 461.52 Z M 453.13 481.88 L 453.13 496.56 L 448.1 491.51 L 446.01 493.6 L 453.57 501.2 C 453.85 501.48 454.23 501.64 454.62 501.64 C 455.02 501.64 455.4 501.48 455.68 501.2 L 463.21 493.65 L 461.11 491.55 L 456.1 496.56 L 456.1 481.88 Z M 454.5 433.8 C 474.23 433.8 490.2 449.77 490.2 469.5 C 490.2 489.23 474.23 505.2 454.5 505.2 C 434.77 505.2 418.8 489.23 418.8 469.5 C 418.8 449.77 434.77 433.8 454.5 433.8 Z M 454.5 430.83 C 433.17 430.83 415.83 448.17 415.83 469.5 C 415.83 490.84 433.17 508.17 454.5 508.17 C 475.84 508.17 493.17 490.84 493.17 469.5 C 493.17 448.17 475.84 430.83 454.5 430.83 Z" fill="#5a30b5" stroke="none" pointer-events="none"/><g transform="translate(426.5,526.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="55" height="16" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 15px; font-family: Helvetica; color: rgb(133, 103, 148); line-height: 1.2; vertical-align: top; white-space: nowrap; font-weight: bold; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Peering</div></div></foreignObject><text x="28" y="16" fill="#856794" text-anchor="middle" font-size="15px" font-family="Helvetica" font-weight="bold">Peering</text></switch></g><g transform="translate(396.5,549.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="116" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 118px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;"><font color="#a680b8">Same private network<br /></font><br /></div></div></foreignObject><text x="58" y="19" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text></switch></g><g transform="translate(677.5,323.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="144" height="34" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 30px; font-family: &quot;Courier New&quot;; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 146px; white-space: nowrap; overflow-wrap: normal; font-weight: bold; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Region 3<br style="font-size: 30px" /></div></div></foreignObject><text x="72" y="32" fill="#666666" text-anchor="middle" font-size="30px" font-family="Courier New" font-weight="bold">[Not supported by viewer]</text></switch></g><g transform="translate(82.5,325.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="144" height="34" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 30px; font-family: &quot;Courier New&quot;; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 146px; white-space: nowrap; overflow-wrap: normal; font-weight: bold; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Region 2<br style="font-size: 30px" /></div></div></foreignObject><text x="72" y="32" fill="#666666" text-anchor="middle" font-size="30px" font-family="Courier New" font-weight="bold">[Not supported by viewer]</text></switch></g><g transform="translate(237.5,22.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="144" height="34" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 30px; font-family: &quot;Courier New&quot;; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 146px; white-space: nowrap; overflow-wrap: normal; font-weight: bold; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Region 1<br style="font-size: 30px" /></div></div></foreignObject><text x="72" y="32" fill="#666666" text-anchor="middle" font-size="30px" font-family="Courier New" font-weight="bold">[Not supported by viewer]</text></switch></g><path d="M 683.75 70 L 682.75 171" fill="none" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(648.5,92.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="72" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: &quot;Courier New&quot;; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;background-color:#ffffff;">delegation<br />(NS)<br /></div></div></foreignObject><text x="36" y="19" fill="#666666" text-anchor="middle" font-size="12px" font-family="Courier New">delegation&lt;br&gt;(NS)&lt;br&gt;</text></switch></g><path d="M 683.88 70 L 668.08 65.42 L 668.08 37.14 L 646.5 35.75 L 646.5 28.75 L 651.32 27.36 L 668.08 29.68 L 668.08 22.58 L 683.88 18 L 699.8 22.58 L 699.8 44 L 721.5 44 L 721.5 50.1 L 717.51 50.58 L 699.8 49.68 L 699.8 65.42 Z" fill="#ff6666" stroke="none" pointer-events="none"/><path d="M 651.32 34.98 L 646.5 35.75 L 646.5 28.75 L 651.32 27.36 Z M 683.88 70 L 668.08 65.42 L 668.08 37.14 L 672.42 37.41 L 677.96 36.92 L 677.96 31.03 L 668.08 29.68 L 668.08 22.58 L 683.88 18 Z M 717.51 50.58 L 689.78 49.13 L 689.78 44 L 717.51 44 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 672.42 37.41 L 646.5 35.75 L 651.32 34.98 L 677.96 36.92 Z" fill-opacity="0.5" fill="#000000" stroke="none" pointer-events="none"/><g transform="translate(724.5,30.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="113" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; font-weight: bold;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Top<br />Public route53 zone</div></div></foreignObject><text x="57" y="19" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica" font-weight="bold">Top&lt;br&gt;Public route53 zone</text></switch></g><path d="M 234.39 588 L 220.06 583.78 L 220.06 557.66 L 200.5 556.38 L 200.5 549.92 L 204.87 548.64 L 220.06 550.78 L 220.06 544.22 L 234.39 540 L 248.82 544.22 L 248.82 564 L 268.5 564 L 268.5 569.63 L 264.88 570.07 L 248.82 569.24 L 248.82 583.78 Z" fill="#ff6666" stroke="none" pointer-events="none"/><path d="M 204.87 555.68 L 200.5 556.38 L 200.5 549.92 L 204.87 548.64 Z M 234.39 588 L 220.06 583.78 L 220.06 557.66 L 224 557.92 L 229.03 557.47 L 229.03 552.03 L 220.06 550.78 L 220.06 544.22 L 234.39 540 Z M 264.88 570.07 L 239.74 568.73 L 239.74 564 L 264.88 564 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 224 557.92 L 200.5 556.38 L 204.87 555.68 L 229.03 557.47 Z" fill-opacity="0.5" fill="#000000" stroke="none" pointer-events="none"/><g transform="translate(196.5,595.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="75" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">private <br />Route53 zone</div></div></foreignObject><text x="38" y="19" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">private &lt;br&gt;Route53 zone</text></switch></g><path d="M 780.89 574 L 766.56 569.78 L 766.56 543.66 L 747 542.38 L 747 535.92 L 751.37 534.64 L 766.56 536.78 L 766.56 530.22 L 780.89 526 L 795.32 530.22 L 795.32 550 L 815 550 L 815 555.63 L 811.38 556.07 L 795.32 555.24 L 795.32 569.78 Z" fill="#ff6666" stroke="none" pointer-events="none"/><path d="M 751.37 541.68 L 747 542.38 L 747 535.92 L 751.37 534.64 Z M 780.89 574 L 766.56 569.78 L 766.56 543.66 L 770.5 543.92 L 775.53 543.47 L 775.53 538.03 L 766.56 536.78 L 766.56 530.22 L 780.89 526 Z M 811.38 556.07 L 786.24 554.73 L 786.24 550 L 811.38 550 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 770.5 543.92 L 747 542.38 L 751.37 541.68 L 775.53 543.47 Z" fill-opacity="0.5" fill="#000000" stroke="none" pointer-events="none"/><g transform="translate(743.5,581.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="75" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">private <br />Route53 zone</div></div></foreignObject><text x="38" y="19" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">private &lt;br&gt;Route53 zone</text></switch></g><path d="M 440.89 173.5 L 426.56 169.28 L 426.56 143.16 L 407 141.88 L 407 135.42 L 411.37 134.14 L 426.56 136.28 L 426.56 129.72 L 440.89 125.5 L 455.32 129.72 L 455.32 149.5 L 475 149.5 L 475 155.13 L 471.38 155.57 L 455.32 154.74 L 455.32 169.28 Z" fill="#ff6666" stroke="none" pointer-events="none"/><path d="M 411.37 141.18 L 407 141.88 L 407 135.42 L 411.37 134.14 Z M 440.89 173.5 L 426.56 169.28 L 426.56 143.16 L 430.5 143.42 L 435.53 142.97 L 435.53 137.53 L 426.56 136.28 L 426.56 129.72 L 440.89 125.5 Z M 471.38 155.57 L 446.24 154.23 L 446.24 149.5 L 471.38 149.5 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="none"/><path d="M 430.5 143.42 L 407 141.88 L 411.37 141.18 L 435.53 142.97 Z" fill-opacity="0.5" fill="#000000" stroke="none" pointer-events="none"/><g transform="translate(403.5,181.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="75" height="26" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">private <br />Route53 zone</div></div></foreignObject><text x="38" y="19" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">private &lt;br&gt;Route53 zone</text></switch></g></g></svg>
ansible/configs/multi-region-tower/post_infra.yml
New file
@@ -0,0 +1,79 @@
- name: Step 002 Post Infrastructure
  hosts: localhost
  gather_facts: false
  become: false
  tasks:
    - debug:
        msg: "Step 002 Post Infrastructure"
    - name: Deploy secondary stacks
      loop: "{{ target_regions[1:] }}"
      vars:
        stack_file: "{{ _region.stack }}"
        aws_region: "{{ _region.region }}"
        aws_vpc_cidr: "{{ _region.vpc_cidr }}"
        aws_public_subnet_cidr: "{{ _region.subnet_cidr }}"
        aws_dns_zone_private: "{{ _region.name }}.{{ guid }}.internal."
        aws_dns_zone_public_prefix: "{{ _region.name }}."
        secondary_stack: yes
      loop_control:
        loop_var: _region
      include_tasks: deploy_stack.yml
- import_playbook: ../../include_vars.yml
  tags:
    - create_inventory
    - must
- name: Step 001.3 Configure Linux Hosts and Wait for Connection
  hosts:
    - all:!windows:!network
  gather_facts: false
  any_errors_fatal: true
  ignore_errors: false
  become: true
  tags:
    - step001
    - step001.3
    - wait_ssh
    - set_hostname
  tasks:
    - name: set facts for remote access
      tags:
        - create_inventory
      set_fact:
        aws_region_final: "{{hostvars['localhost'].aws_region_final}}"
        ansible_ssh_extra_args: "{{ ansible_ssh_extra_args|d() }} -F {{output_dir}}/{{ env_type }}_{{ guid }}_ssh_conf"
    - name: Run infra-ec2-wait_for_linux_hosts Role
      import_role:
        name: infra-ec2-wait_for_linux_hosts
    - name: Run infra-ec2-linux-set-hostname Role
      import_role:
        name: infra-ec2-linux-set-hostname
- name: Step 002 Create VPC peering
  hosts: localhost
  gather_facts: false
  become: false
  tags: vpc_peering
  environment:
    AWS_ACCESS_KEY_ID: "{{aws_access_key_id}}"
    AWS_SECRET_ACCESS_KEY: "{{aws_secret_access_key}}"
  tasks:
    - include_role:
        name: infra-ec2-vpc-peering
      tags: vpcpeering
      vars:
        vpc_region: "{{ _region[0].region }}"
        peer_region: "{{ _region[1].region }}"
        vpc_private_zone: "{{ _region[0].name }}.{{ guid }}.internal."
        peer_private_zone: "{{ _region[1].name }}.{{ guid }}.internal."
      loop: "{{ target_regions | product(target_regions) | list }}"
      loop_control:
        loop_var: _region
      when:
        - _region[0].region != _region[1].region
        - _region[0].region < _region[1].region
ansible/configs/multi-region-tower/post_software.yml
New file
@@ -0,0 +1,21 @@
- name: Step 005 Post Software
  hosts: localhost
  gather_facts: false
  become: false
  tasks:
    - debug:
        msg: "Step 005 Post Software"
- name: Setup Workloads on Tower
  import_playbook: tower_workloads.yml
- name: PostSoftware flight-check
  hosts: localhost
  connection: local
  gather_facts: false
  become: false
  tags:
    - post_flight_check
  tasks:
    - debug:
        msg: "Post-Software checks completed successfully"
ansible/configs/multi-region-tower/pre_infra.yml
New file
@@ -0,0 +1,30 @@
- name: Step 000 Pre Infrastructure
  hosts: localhost
  gather_facts: false
  become: false
  tasks:
    - debug:
        msg: "Step 000 Pre Infrastructure"
    - fail:
        msg: |-
          'aws_region' must not be defined.
          This is multi-region example, please use the 'target_regions' list.
      when: aws_region is defined
    - fail:
        msg: "'target_regions' is not defined"
      when: target_regions is not defined
    - fail:
        msg: "'target_regions' must contain at least 1 region."
      when: target_regions | length < 1
    - name: set aws_region as the first
      set_fact:
        aws_region: "{{ target_regions[0].region }}"
        aws_vpc_cidr: "{{ target_regions[0].vpc_cidr }}"
        aws_public_subnet_cidr: "{{ target_regions[0].subnet_cidr }}"
        aws_dns_zone_private: "{{ target_regions[0].name }}.{{ guid }}.internal."
        aws_dns_zone_public_prefix: "{{ target_regions[0].name }}."
ansible/configs/multi-region-tower/pre_software.yml
New file
@@ -0,0 +1,50 @@
- name: Step 003 Pre Software
  hosts: localhost
  gather_facts: false
  become: false
  tasks:
    - debug:
        msg: "Step 003 Pre Software"
    - import_role:
        name: infra-local-create-ssh_key
      when: set_env_authorized_key | bool
- name: Configure all hosts with Repositories, Common Files and Set environment key
  hosts:
    - all:!windows
  become: true
  gather_facts: False
  tags:
    - common_tasks
  roles:
    - role: set-repositories
      when: repo_method is defined
    - role: common
      when: install_common | bool
    - role: set_env_authorized_key
      when: set_env_authorized_key | bool
- name: Configuring Bastion Hosts
  hosts: bastions
  become: true
  gather_facts: False
  roles:
    -  role: bastion
       when: install_bastion | bool
  tags:
    - bastion_tasks
- name: PreSoftware flight-check
  hosts: localhost
  connection: local
  gather_facts: false
  become: false
  tags:
    - presoftware_flight_check
  tasks:
    - debug:
        msg: "Pre-Software checks completed successfully"
ansible/configs/multi-region-tower/sample_vars.yml
New file
@@ -0,0 +1,114 @@
---
# sample configuration file
#
# Usage: ansible-playbook main.yml -e @configs/multi-region-example/sample.yml
#
# Ideally keep your copy OUTSIDE your repo, especially if using Cloud Credentials
env_type: multi-region-tower       # Name of config to deploy
output_dir: /tmp/workdir                # Writable working scratch directory
email: name@example.com                 # User info for notifications
#guid: guid02                             # Unique string used in FQDN
subdomain_base_suffix: .example.opentlc.com      # Your domain used in FQDN
# Path to yum repos
#own_repo_path: http://...
# Cloud specfic settings - example given here for AWS
cloud_provider: ec2                     # Which AgnosticD Cloud Provider to use
HostedZoneId: Z3IHLWJZOU9SRT            # You will need to change this
key_name: ocpkey                        # Keyname must exist in AWS
tower_version: 3.5.0-1                 # tower version you want to install
software_to_deploy: tower
# target_regions:                         ### Set dictionaries for installing ansible tower and isolated node.
#   - region: us-east-1                   ### First region will be the one where master tower cluster will be installed
#     stack: default                      ### Other regions will be place where isolated nodes will be installed.
#     vpc_cidr: 10.1.0.0/16
#     subnet_cidr: 10.1.0.0/24
#     name: na
#   - region: eu-central-1
#     stack: worker.j2
#     name: frankfurt
#     vpc_cidr: 10.2.0.0/16
#     subnet_cidr: 10.2.0.0/24
#   - region: ap-southeast-2
#     stack: worker.j2
#     name: sydney
#     vpc_cidr: 10.3.0.0/16
#     subnet_cidr: 10.3.0.0/24
#####Ansible Tower related variables
tower_admin_password: change_me
#worker_instance_count: 0              # Set 0 to not to provision worker(isolated) nodes.
# tower_license: >                     #Set the tower licencse in the same format. Do not forget to add "eula_accepted: true".
#   {
#     "eula_accepted": true,
#     "company_name": "Red Hat",
#     "contact_email": "name@redhat.com",
#     "contact_name": "some person"
#     "hostname": "70a415ef832159a36413fa599",
#     "instance_count": 50,
#     "license_date": 16581423619,
#     "license_key":
#     "eea1b84d1e39cfEXAMPLE5739066069e60c6d0aEXAMPLE2c29cc61b2aEXAMPLE",
#     "license_type": "enterprise",
#     "subscription_name": "Ansible Tower by Red Hat (50 Managed Nodes), RHT Internal",
#     "trial": true
#   }
# tower_accounts:                                      #Define users you want to create. Set superuser: yes to make user system wide System Administrator
#   - user: babylon
#     password: changeme
#     email: babylon@example.com
#     firstname: test1
#     lastname: one
#     superuser: yes
#   - user: babylon-viewer
#     password: changeme
#     email: babylon1@example.com
#     firstname: Babylon
#     lastname: Viewer
# tower_organization:
#   - name: gpte
#   - name: BU
# tower_projects:
#   - name: project1
#     description: Project1 Deployer
#     organization: test
#     scm_url: https://github.com/<your>/<scm>.git
#     scm_update_on_launch: true
# tower_inventories:
#   - name: null-inventory-sydney
#     description: Sydney Region
#     instance_group: sydney
#   - name: null-inventory-frankfurt
#     description: Frankfurt Region
#     instance_group: frankfurt
#   - name: null-inventory-default
#     description: Default Region
# tower_job_templates:
#   - name: job_template name
#     description: description
#     project: project1
#     playbook: playbook.yml
#     become: yes
# AWS Credentials. These are required (don't sync them to your fork)
# aws_access_key_id:
# aws_secret_access_key:
...
ansible/configs/multi-region-tower/sample_vars_babylon.yml
New file
@@ -0,0 +1,117 @@
---
# sample configuration file
#
# Usage: ansible-playbook main.yml -e @configs/multi-region-example/sample.yml
#
# Ideally keep your copy OUTSIDE your repo, especially if using Cloud Credentials
env_type: multi-region-tower       # Name of config to deploy
output_dir: /tmp/workdir                # Writable working scratch directory
email: name@example.com                 # User info for notifications
#guid: guid02                             # Unique string used in FQDN
subdomain_base_suffix: .example.opentlc.com      # Your domain used in FQDN
# Path to yum repos
#own_repo_path: http://...
# Cloud specfic settings - example given here for AWS
cloud_provider: ec2                     # Which AgnosticD Cloud Provider to use
HostedZoneId: Z3IHLWJZOU9SRT            # You will need to change this
key_name: ocpkey                        # Keyname must exist in AWS
tower_version: 3.5.0-1                 # tower version you want to install
software_to_deploy: tower
target_regions:                         ### Set dictionaries for installing ansible tower and isolated node.
  - region: us-east-1                   ### First region will be the one where master tower cluster will be installed
    stack: default                      ### Other regions will be place where isolated nodes will be installed.
    vpc_cidr: 10.1.0.0/16
    subnet_cidr: 10.1.0.0/24
    name: na
#  - region: eu-central-1
#    stack: worker.j2
#    name: frankfurt
#    vpc_cidr: 10.2.0.0/16
#    subnet_cidr: 10.2.0.0/24
  - region: ap-southeast-2
    stack: worker.j2
    name: sydney
    vpc_cidr: 10.3.0.0/16
    subnet_cidr: 10.3.0.0/24
#####Ansible Tower related variables
tower_admin_password: change_me
tower_instance_count: 1              # Set 0 to not to provision worker(isolated) nodes.
support_instance_count: 1              # Set 0 to not to provision worker(isolated) nodes.
worker_instance_count: 1             # Set 0 to not to provision worker(isolated) nodes.
# tower_license: >                     #Set the tower licencse in the same format. Do not forget to add "eula_accepted: true".
#   {
#     "eula_accepted": true,
#     "company_name": "Red Hat",
#     "contact_email": "name@redhat.com",
#     "contact_name": "some person"
#     "hostname": "70a415ef832159a36413fa599",
#     "instance_count": 50,
#     "license_date": 16581423619,
#     "license_key":
#     "eea1b84d1e39cfEXAMPLE5739066069e60c6d0aEXAMPLE2c29cc61b2aEXAMPLE",
#     "license_type": "enterprise",
#     "subscription_name": "Ansible Tower by Red Hat (50 Managed Nodes), RHT Internal",
#     "trial": true
#   }
tower_accounts:                                      #Define users you want to create. Set superuser: yes to make user system wide System Administrator
  - user: babylon
    password: changeme
    email: babylon@example.com
    firstname: test1
    lastname: one
    superuser: yes
  - user: babylon-viewer
    password: changeme
    email: babylon1@example.com
    firstname: Babylon
    lastname: Viewer
tower_organization:
  - name: gpte
  - name: BU
tower_projects:
  - name: babylon
    description: Babylon Deployer
    organization: gpte
    scm_url: https://github.com/redhat-gpte-devopsautomation/babylon.git
    scm_update_on_launch: true
tower_inventories:
  - name: null-inventory-sydney
    description: Sydney Region
    instance_group: sydney
  - name: null-inventory-frankfurt
    description: Frankfurt Region
    instance_group: frankfurt
  - name: null-inventory-default
    description: Default Region
tower_job_templates:
  - name: run_deployer_babylon
    description: Babylon Deployer
    project: babylon
    playbook: babylon-tower/ansible/run-deployer.yml
    become: yes
# AWS Credentials. These are required (don't sync them to your fork)
# aws_access_key_id:
# aws_secret_access_key:
...
ansible/configs/multi-region-tower/software.yml
New file
@@ -0,0 +1,43 @@
---
- name: Step 004 Environment specific Software
  hosts: localhost
  gather_facts: False
  become: false
  tasks:
    - debug:
        msg: "Software tasks Started"
- name: Deploy Roles if infra_workloads defined
  hosts:
    - nodes
  gather_facts: false
  run_once: false
  become: yes
  tags:
    - infra_workloads
  tasks:
  - name: apply infra workloads roles on nodes
    when:
    - infra_workloads|d("")|length > 0
    block:
      - name: Apply role "{{ workload_loop_var }}" on nodes
        include_role:
          name: "{{ workload_loop_var }}"
        vars:
          ACTION: "provision"
        loop: "{{ infra_workloads.split(',')|list }}"
        loop_control:
          loop_var: workload_loop_var
- name: Software flight-check
  hosts: localhost
  connection: local
  gather_facts: false
  become: false
  tags:
    - post_flight_check
  tasks:
    - debug:
        msg: "Software checks completed successfully"
ansible/configs/multi-region-tower/start.yml
New file
@@ -0,0 +1,33 @@
---
- import_playbook: ../../include_vars.yml
- name: Stop instances in all regions
  hosts: localhost
  gather_facts: false
  become: false
  environment:
    AWS_ACCESS_KEY_ID: "{{aws_access_key_id}}"
    AWS_SECRET_ACCESS_KEY: "{{aws_secret_access_key}}"
  tasks:
    - debug:
        msg: "Step 002 Post Infrastructure"
    - loop: "{{ target_regions | json_query('[].region') }}"
      loop_control:
        loop_var: _region
      ec2:
        instance_tags:
          "aws:cloudformation:stack-name": "{{ project_tag }}"
        state: running
        region: "{{ _region }}"
      # Shell equivalent
      # shell: >-
      #   aws ec2 start-instances
      #   --region {{ _region }}
      #   --instance-ids $(
      #     aws ec2 describe-instances
      #     --filters "Name=tag:aws:cloudformation:stack-name,Values={{ project_tag }}
      #     --query Reservations[*].Instances[*].InstanceId
      #     --region {{_region }}
      #     --output text
      #   )
ansible/configs/multi-region-tower/stop.yml
New file
@@ -0,0 +1,33 @@
---
- import_playbook: ../../include_vars.yml
- name: Stop instances in all regions
  hosts: localhost
  gather_facts: false
  become: false
  environment:
    AWS_ACCESS_KEY_ID: "{{aws_access_key_id}}"
    AWS_SECRET_ACCESS_KEY: "{{aws_secret_access_key}}"
  tasks:
    - debug:
        msg: "Step 002 Post Infrastructure"
    - loop: "{{ target_regions | json_query('[].region') }}"
      loop_control:
        loop_var: _region
      ec2:
        instance_tags:
          "aws:cloudformation:stack-name": "{{ project_tag }}"
        state: stopped
        region: "{{ _region }}"
      # Shell equivalent
      # shell: >-
      #   aws ec2 stop-instances
      #   --region {{ _region }}
      #   --instance-ids $(
      #     aws ec2 describe-instances
      #     --filters "Name=tag:aws:cloudformation:stack-name,Values={{ project_tag }}
      #     --query Reservations[*].Instances[*].InstanceId
      #     --region {{_region }}
      #     --output text
      #   )
ansible/configs/multi-region-tower/tower_workloads.yml
New file
@@ -0,0 +1,49 @@
- hosts: bastions
  gather_facts: false
  become: yes
  tasks:
  - set_fact:
      tower_hostname: "{{ item | first }}"
    loop:
      - "{{ query('inventory_hostnames', 'towers') }}"
  - name: Inject License
    include_role:
      name: tower-license-injector
    when: tower_license is defined
    tags:
      - tower-license-injector
  - name: Delete Demo suff
    include_role:
      name: cleanup-tower-default
    tags:
      - tower-clean-default
  - name: Create tower users
    include_role:
      name: tower-user-create
    when: tower_accounts is defined
    tags:
      - tower-user-create
  - name: Create tower org
    include_role:
      name: tower-org-create
    when: tower_organization is defined
    tags:
      - tower-org-create
  - name: Create Inventories
    include_role:
      name: tower-inventory-create
    when: tower_inventories is defined
    tags:
      - tower-inventory-create
  - name: Create Projects
    include_role:
      name: tower-project-create
    when: tower_projects is defined
    tags:
      - tower-project-create
  - name: Create Job Templates
    include_role:
      name: tower-jobtemplate-create
    when: tower_job_templates is defined
    tags:
      - tower-jobtempalte-create
ansible/roles/cleanup-tower-default/tasks/main.yml
New file
@@ -0,0 +1,37 @@
- name: Delete Demo Job Template
  tower_job_template:
     name: "Demo Job Template"
     state: absent
     job_type: run
     playbook: "hello_world.yml"
     project: "Demo Project"
     inventory: "Demo Inventory"
     tower_host: "{{ tower_hostname }}"
     tower_username: admin
     tower_password: "{{tower_admin_password}}"
     tower_verify_ssl: false
  ignore_errors: yes
- name: Delete Demo Credential
  command: tower-cli credential delete -n "Demo Credential"
  ignore_errors: yes
- name: Delete Demo Project
  tower_project:
     name: "Demo Project"
     state: absent
     tower_host: "{{ tower_hostname }}"
     tower_username: admin
     tower_password: "{{tower_admin_password}}"
     tower_verify_ssl: false
  ignore_errors: yes
- name: Delete Demo Inventory
  tower_inventory:
     name: "Demo Inventory"
     organization: Default
     state: absent
     tower_host: "{{ tower_hostname }}"
     tower_username: admin
     tower_password: "{{tower_admin_password}}"
     tower_verify_ssl: false
  ignore_errors: yes
ansible/roles/tower-babylon-job-runner/tasks/main.yml
New file
@@ -0,0 +1,15 @@
---
- name: Add Ansible Tower babylon credentials to jobs
  command: >-
    tower-cli
    job_template
    associate_credential
    --job-template {{ job.name }}
    --credential babylon-tower-credential
    --insecure
  loop: "{{ tower_job_templates }}"
  loop_control:
    loop_var: job
  tags:
    - tower-babylon-job-runner
...
ansible/roles/tower-copy-ssh/tasks/main.yml
New file
@@ -0,0 +1,35 @@
---
- name: Create .ssh directory on towers
  file:
    path:     /var/lib/awx/.ssh
    state:    directory
    owner:    awx
  delegate_to: "{{ item }}"
  loop: "{{ query('inventory_hostnames', 'towers') }}"
- name: Copy private key from local machine to towers awx
  copy:
    src:      "{{ key_local_path }}"
    dest:     /var/lib/awx/.ssh
    mode:     0400
    owner:    awx
  delegate_to: "{{ item }}"
  loop: "{{ query('inventory_hostnames', 'towers') }}"
- name: Create awx .ssh directory on workers
  file:
    path:     /var/lib/awx/.ssh
    state:    directory
    owner:    awx
  delegate_to: "{{ item }}"
  loop: "{{ query('inventory_hostnames', 'workers') }}"
- name: Copy private key from local machine on workers
  copy:
    src:      "{{ key_local_path }}"
    dest:     /var/lib/awx/.ssh
    mode:     0400
    owner:    awx
  delegate_to: "{{ item }}"
  loop: "{{ query('inventory_hostnames', 'workers') }}"
...
ansible/roles/tower-credential-create/tasks/main.yml
New file
@@ -0,0 +1,19 @@
---
- name: Add tower credential
  tower_credential:
    name:               "{{ credential.name }}"
    username:           "{{ credential.username }}"
    password:           "{{ credential.password }}"
    description:        "{{ credential.description }}"
    organization:       "{{ credential.organization | default('Default') }}"
    kind:               "{{ credential.type }}"
    state:              "{{ credential.state }}"
    host:               "{{ credential.host }}"
    tower_host:         "{{ tower_hostname }}"
    tower_username:     admin
    tower_password:     "{{ tower_admin_password }}"
    tower_verify_ssl:   false
  loop: "{{ tower_credentials }}"
  loop_control:
    loop_var: credential
...
ansible/roles/tower-inventory-create/tasks/main.yml
New file
@@ -0,0 +1,24 @@
- name: Add tower inventory
  tower_inventory:
    name: "{{ item.name }}"
    description: "{{ item.description  }}"
    organization: "{{ item.organization | d('gpte') }}"
    state: present
    tower_host: "{{ tower_hostname }}"
    tower_username: admin
    tower_password: "{{tower_admin_password}}"
    tower_verify_ssl: false
  loop: "{{ tower_inventories }}"
  tags:
    - tower-inventory-create
- name: Associate instance group to inventory
  command: >-
    tower-cli inventory
    associate_ig
    --inventory "{{ item.name }}"
    --instance-group "{{ item.instance_group }}"
  loop: "{{ tower_inventories }}"
  when:
     - item.instance_group is defined
ansible/roles/tower-jobtemplate-create/tasks/main.yml
New file
@@ -0,0 +1,23 @@
- name: Add tower JobTemplate
  tower_job_template:
    name: "{{ item.name }}"
    description: "{{ item.description  }}"
    job_type: run
    # ask_inventory: Yes
    inventory: "{{ item.inventory | d('empty-inventory')}}"
    # ask_credential: Yes
    vault_credential: "{{ item.vault_credential | d('') }}"
    credential: "{{ item.credential | d('') }}"
    ask_extra_vars: Yes
    project: "{{ item.project }}"
    playbook: "{{ item.playbook | d('main.yml') }}"
    become_enabled: "{{ item.become | d('no') }}"
    concurrent_jobs_enabled: Yes
    state: present
    tower_host: "{{ tower_hostname }}"
    tower_username: admin
    tower_password: "{{tower_admin_password}}"
    tower_verify_ssl: false
  loop: "{{ tower_job_templates }}"
  tags:
    - tower-job-template-create
ansible/roles/tower-license-injector/tasks/main.yml
@@ -1,18 +1,14 @@
- name: Copy Tower License File
  copy:
    content: "{{ tower_license | from_json }}"
    content: "{{ tower_license | from_json }}"
    dest: /root/license.txt
- name: Install tower-cli
  pip:
    name: ansible-tower-cli
    state: latest
- name: Configure the tower cli file
  template:
    src: "tower_cli.j2"
    dest: "/root/.tower_cli.cfg"
    mode: 0600
  tags:
      - tower-license-injector
- name: Add the tower license
  command: tower-cli setting modify LICENSE @/root/license.txt --insecure
  command: tower-cli setting modify LICENSE @/root/license.txt --insecure
  tags:
      - tower-license-injector
ansible/roles/tower-license-injector/templates/tower_cli.j2
@@ -1,5 +1,5 @@
[general]
host = tower1.{{guid}}.example.opentlc.com
host = "{{ tower_hostname }}"
username = admin
password = {{tower_admin_password}}
verify_ssl = False
ansible/roles/tower-org-create/tasks/main.yml
New file
@@ -0,0 +1,11 @@
- name: Add tower org
  tower_organization:
     name: "{{ item.name }}"
     state: present
     tower_host: "{{ tower_hostname }}"
     tower_username: admin
     tower_password: "{{tower_admin_password}}"
     tower_verify_ssl: false
  loop: "{{ tower_organization }}"
  tags:
    - tower-org-create
ansible/roles/tower-pip-packages/tasks/main.yml
New file
@@ -0,0 +1,36 @@
---
- name: Copying pip requirement file onto towers
  template:
      src: requirements.j2
      dest: /tmp/requirements.txt
  delegate_to: "{{item}}"
  loop: "{{ query('inventory_hostnames', 'towers') }}"
- name: Updating/Installing pip packages on towers
  shell: >-
    source activate &&
    pip install -r /tmp/requirements.txt -U
  args:
    chdir: "{{tower_update_venv}}/bin"
    executable: /bin/bash
  delegate_to: "{{item}}"
  loop:  "{{ query('inventory_hostnames', 'towers') }}"
- name: Copying pip requirement file onto workers
  template:
      src: requirements.j2
      dest: /tmp/requirements.txt
  delegate_to: "{{item}}"
  loop: "{{ query('inventory_hostnames', 'workers') }}"
- name: Updating/Installing pip packages on workers
  shell: >-
    source activate &&
    pip install -r /tmp/requirements.txt -U
  args:
    chdir: "{{tower_update_venv}}/bin"
    executable: /bin/bash
  delegate_to: "{{item}}"
  loop:  "{{ query('inventory_hostnames', 'workers') }}"
ansible/roles/tower-pip-packages/templates/requirements.j2
New file
@@ -0,0 +1,3 @@
{% for i_packages in pip_requirements %}
{{ i_packages }}
{% endfor %}
ansible/roles/tower-project-create/tasks/main.yml
New file
@@ -0,0 +1,19 @@
- name: Add tower project
  tower_project:
    name: "{{ item.name }}"
    description: "{{ item.description }}"
    organization:  "{{ item.organization | default('Default')}}"
    scm_url:  "{{ item.scm_url }}"
    scm_type: "{{ item.scm_type | d('git')}}"
    scm_credential: "{{ item.scm_credential | d('')}}"
    scm_branch:  "{{ item.scm_branch | d('master') }}"
    scm_update_on_launch: "{{ item.scm_update_on_launch | d('false') }}"
    state: present
    tower_host: "{{ tower_hostname }}"
    tower_username: admin
    tower_password: "{{tower_admin_password}}"
    tower_verify_ssl: false
  loop: "{{ tower_projects }}"
  tags:
    - tower-project-create
ansible/roles/tower-settings-update/tasks/main.yml
New file
@@ -0,0 +1,14 @@
#update tower setting path
- name: Set the value of AWX_PROOT_BASE_PATH
  tower_settings:
    name: "{{ item.key }}"
    value: "{{ item.value }}"
    tower_host: "{{ tower_hostname }}"
    tower_username: admin
    tower_password: "{{tower_admin_password}}"
  when: tower_setting_params is defined
  loop: "{{ lookup('dict', tower_setting_params) }}"
ansible/roles/tower-user-create/tasks/main.yml
New file
@@ -0,0 +1,25 @@
---
- name: Check if variable is defined
  debug:
    msg: "tower_user_accounts  is not defined"
  when:
      - tower_user_accounts is not defined
- name: Add tower user
  tower_user:
     username: "{{ item.user }}"
     password: "{{ item.password | default('change_me') }}"
     email: "{{ item.email | default('rhpds-admins@redhat.com') }}"
     first_name: "{{ item.firstname | default(item.user) }}"
     last_name: "{{ item.lastname | default(item.user) }}"
     superuser: "{{ item.superuser | default('no')}}"
     state: present
     tower_host: "{{ tower_hostname }}"
     tower_username: admin
     tower_password: "{{tower_admin_password}}"
     tower_verify_ssl: false
  loop: "{{ tower_user_accounts }}"
  when: tower_user_accounts is defined
  tags:
    - tower-user-create
ansible/roles/tower-virtual-environment/tasks/main.yml
New file
@@ -0,0 +1,23 @@
---
- name: Finding existing environment
  stat:
    path: "{{ item }}"
  register: stat_output
  loop: "{{ tower_virtual_environment }}"
# - debug: var=stat_output.
# - debug:
#     msg: "{{ item.0 }} {{ item.1.stat.exists }}"
#   when: item.1.stat.exists == False
#   with_together:
#     - "{{ tower_virtual_environment }}"
#     - "{{ stat_output.results }}"
- name: Creating virtual environment
  command: /bin/virtualenv "{{ item.0 }}"
  when: item.1.stat.exists == False
  with_together:
    - "{{ tower_virtual_environment }}"
    - "{{ stat_output.results }}"
ansible/software_playbooks/tower.yml
@@ -6,14 +6,6 @@
  tasks:
    - debug:
        msg: "Software tasks Started"
    # This probably needs to get removed.
    # - name: Place Tower License from env_secret_vars on bastion
    #   blockinfile:
    #     create: yes
    #     path: /root/tower_license.txt
    #     block: "{{tower_license}}"
    #   when: tower_license is defined
    - name: Download Tower Setup file
      unarchive:
        src: "https://releases.ansible.com/ansible-tower/setup/ansible-tower-setup-{{tower_version}}.tar.gz"
@@ -22,22 +14,13 @@
      tags:
        - install-tower
    # - name: Remove directory if it exists
    #   file:
    #    path: /root/ansible-tower-setup-{{tower_version}}
    #    state: absent
    #It's not latest, it's whatever we installed, this should be removed.
    # - name: Rename the ansible tower setup directory name
    #   shell: cp -r /root/ansible-tower-setup-* /root/ansible-tower-setup-{{tower_version}}
    #we can put this as /etc/ansible/hosts
    - name: Create Tower Inventory file
      template:
        src: "../configs/{{ env_type }}/files/hosts_template.j2"
        dest: /etc/ansible/hosts
      tags:
        - create_tower_inventory
    # when we move inventory to /etc/ansible/hosts we can add -i to the setup.sh file
    - name: Run Ansible Tower setup
      shell: ./setup.sh -i /etc/ansible/hosts
      args:
@@ -47,8 +30,30 @@
      tags:
        - install-tower
    - name: Install tower-cli
      pip:
        name: ansible-tower-cli
        state: latest
      tags:
          - tower-cli
    - set_fact:
        tower_hostname: "{{ item | first }}"
      loop:
        - "{{ query('inventory_hostnames', 'towers') }}"
      tags:
          - tower-cli
    - name: Configure the tower cli file
      template:
        src: "../configs/{{env_type}}/files/tower_cli.j2"
        dest: "/root/.tower_cli.cfg"
        mode: 0600
      tags:
          - tower-cli
          - install-tower
- name: Software flight-check
  hosts: localhost
  connection: local