├── .gitignore
├── LICENSE
├── README.md
├── ansible.cfg
├── callback_plugins
├── profile_tasks.py
└── profile_tasks.pyc
├── group_vars
└── all.yml
├── hosts
├── install-all.sh
├── install-gateway.sh
├── inventory
├── ec2.ini
├── ec2.py
└── hosts.static
├── play-010-create-servers.yml
├── play-020-baseline.yml
├── play-030-dockerize.yml
├── play-035-wipe-out-containers.yml
├── play-040-consul-servers.yml
├── play-050-microservices.yml
├── play-060-consul-agents.yml
├── play-070-launch-registrators.yml
├── play-080-ca-gateway.yml
├── play-all.yml
├── play-zz-kill-servers.yml
└── roles
├── aws-server-creation
└── tasks
│ ├── main.yml
│ ├── security-setup.yml
│ ├── server-clusters.yml
│ └── vpc-creation.yml
├── ca-gateway
├── tasks
│ └── main.yml
└── vars
│ ├── vault.yml
│ └── vault.yml.sample
├── common-baseline
└── tasks
│ ├── add-layerfs-support.yml
│ └── main.yml
├── consul-clients
└── tasks
│ └── main.yml
├── consul-servers
└── tasks
│ └── main.yml
├── docker-hosts
└── tasks
│ ├── install-docker.yml
│ └── main.yml
├── install-microservices
└── tasks
│ └── main.yml
└── launch-registrators
└── tasks
└── main.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | ssh
2 | .idea
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015- CA Technologies.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Microservices Demo
2 |
3 | Demo of Automated Microservices Infrastructure Setup using Ansible, Docker, Consul and the friends. Demo is tested and uses AWS cloud, but can be adapted for other clouds.
4 |
5 | ## License
6 |
7 | [MIT](LICENSE)
8 |
9 | ## Installation
10 |
11 | 1. Install Ansible
12 | 1. On OS-X you can just run: `sudo -H pip install --ignore-installed ansible`
13 | 1. For other platforms consult with [Ansible installation notes](http://docs.ansible.com/ansible/intro_installation.html)
14 | 1. Install docker-py:
15 |
16 | ```
17 | sudo -H pip install docker-py
18 | ```
19 |
20 | 1. Install jq (sed for JSON):
21 | 1. On OS-X you can just run: `brew install jq`
22 | 1. For other platforms consult with [jq installation notes](https://stedolan.github.io/jq/download/)
23 | 3. Install and configure [AWS CLI Tools](http://docs.aws.amazon.com/cli/latest/userguide/installing.html)
24 | 2. Install and configure Boto:
25 | 1. Installation: http://boto.readthedocs.org/en/latest/getting_started.html
26 |
27 | On OS-X El Capitan, you will have to ignore `six` that is pre-installed and causes issues:
28 |
29 | ```console
30 | sudo -H pip install --ignore-installed boto
31 | ```
32 |
33 | 2. Configuration of Boto/AWS credentials: http://boto.readthedocs.org/en/latest/getting_started.html#configuring-boto-credentials
34 |
35 | 3. Please note that there's currently a [major bug](https://github.com/ansible/ansible-modules-core/issues/2355) in Ansible/`epc_vpc` which ignores profile parameter and uses default credentials, so you need to make sure that your **default** AWS credentials point to the environment where you need things created.
36 |
37 | 3. Clone this repo with:
38 |
39 | ```console
40 | git clone https://github.com/apiacademy/microservices-deployment.git
41 | ```
42 | 4. Go into the cloned repo and run the included [ec2 script](http://docs.ansible.com/ansible/intro_dynamic_inventory.html#example-aws-ec2-external-inventory-script), to make sure you properly installed and configured Boto:
43 |
44 | ```console
45 | cd inventory && ./ec2.py --list --boto-profile irakli-aws && cd --
46 | ```
47 |
48 | Make sure to replace `irakli-aws` with your AWS profile name from `~/.aws/credentials`. You can omit the option if you are using default profile from that file.
49 |
50 | Please also note that in the version of ec2.ini file we ship, for the sake of speed, we restrict the AWS regions the script works with to North America ones. If you need access to any other zone, or all zones: please edit the ec2.ini file accordingly.
51 |
52 | 1. Save a private SSH key that you use/will use the root user on your AWS servers under: `ssh/private-key.pem`. For security reasons, `ssh` folder is .gitignore-d in the demo repo, and you should ignore it, as well, if you build on top of the demo. NEVER check-in SSH keys into repos!
53 |
54 | **ATTENTION:** corresponding public key must be placed under `roles/aws-server-creation/ssh/public-key.pem`!!!
55 |
56 | 1. Make sure your private key permissions are valid:
57 |
58 | ```consul
59 | chmod 700 ssh
60 | chmod 600 ssh/*
61 | ```
62 | 1. If you have some EC2 servers tagged with [key=Name, value=cademo_consuls], and the AWS credentials profile you are using is still `irakli-aws, then you can ping the tagged servers from Ansible with a command like:
63 |
64 | ```
65 | AWS_PROFILE=irakli-aws ansible -i ec2.py tag_Name_cademo_consuls -m ping
66 | ```
67 |
68 | You can find more information about ec2 script and using dynamic EC2 inventories at:
69 |
70 | 1. Please make sure you set up proper AWS Profile in `group_vars/all.yml` under `aws_profile` variabe and then you can create required servers as easily as running:
71 |
72 | ```
73 |
74 | ```
75 |
76 | ## Quickstart
77 |
78 | To create all the servers:
79 |
80 | ```console
81 | AWS_PROFILE=irakli-aws ansible-playbook play-010-create-servers.yml
82 | ```
83 |
84 | To run the entire thing:
85 |
86 | ```console
87 | AWS_PROFILE=irakli-aws ansible-playbook play-all.yml
88 | ```
89 |
90 | ## Debugging
91 |
92 | Consul logs are under: `/var/log/upstart/consul.log`
93 |
94 | To see current members of Consul cluster:
95 |
96 | ```
97 | consul members
98 | ```
99 |
100 | To make sure that consul leadership election succeeded (bootstrapping),
101 | you can run the following on a consul server:
102 |
103 | ```
104 | consul info
105 | ```
106 |
107 | and analyze the `raft:` section of the response.
108 | ## Troubleshooting
109 |
110 | If you are on a network that doesn't allow access to custom port you can create an SSH proxy:
111 |
112 | ```
113 | ssh -D 12345 myuser@remote_ssh_server
114 | ```
115 |
116 | and then in your browser proxu settings indicate SOCKS5 proxy with hostname: localhost, port: 12345.
117 |
--------------------------------------------------------------------------------
/ansible.cfg:
--------------------------------------------------------------------------------
1 | [defaults]
2 | host_key_checking = False
3 | hostfile = inventory
4 | timeout = 60
5 |
6 | [ssh_connection]
7 | control_path = %(directory)s/%%h-%%r
8 |
--------------------------------------------------------------------------------
/callback_plugins/profile_tasks.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import os
3 | import time
4 |
5 |
6 | class CallbackModule(object):
7 | """
8 | A plugin for timing tasks
9 | """
10 | def __init__(self):
11 | self.stats = {}
12 | self.current = None
13 |
14 | def playbook_on_task_start(self, name, is_conditional):
15 | """
16 | Logs the start of each task
17 | """
18 |
19 | if os.getenv("ANSIBLE_PROFILE_DISABLE") is not None:
20 | return
21 |
22 | if self.current is not None:
23 | # Record the running time of the last executed task
24 | self.stats[self.current] = time.time() - self.stats[self.current]
25 |
26 | # Record the start time of the current task
27 | self.current = name
28 | self.stats[self.current] = time.time()
29 |
30 | def playbook_on_stats(self, stats):
31 | """
32 | Prints the timings
33 | """
34 |
35 | if os.getenv("ANSIBLE_PROFILE_DISABLE") is not None:
36 | return
37 |
38 | # Record the timing of the very last task
39 | if self.current is not None:
40 | self.stats[self.current] = time.time() - self.stats[self.current]
41 |
42 | # Sort the tasks by their running time
43 | results = sorted(
44 | self.stats.items(),
45 | key=lambda value: value[1],
46 | reverse=True,
47 | )
48 |
49 | # Just keep the top 10
50 | results = results[:10]
51 |
52 | # Print the timings
53 | for name, elapsed in results:
54 | print(
55 | "{0:-<70}{1:->9}".format(
56 | '{0} '.format(name),
57 | ' {0:.02f}s'.format(elapsed),
58 | )
59 | )
60 |
61 | total_seconds = sum([x[1] for x in self.stats.items()])
62 | print("\nPlaybook finished: {0}, {1} total tasks. {2} elapsed. \n".format(
63 | time.asctime(),
64 | len(self.stats.items()),
65 | datetime.timedelta(seconds=(int(total_seconds)))
66 | )
67 | )
68 |
--------------------------------------------------------------------------------
/callback_plugins/profile_tasks.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apiacademy/microservices-deployment/8ddbaa5ebfb14d6ef6aa2a088b5603f058646e02/callback_plugins/profile_tasks.pyc
--------------------------------------------------------------------------------
/group_vars/all.yml:
--------------------------------------------------------------------------------
1 | ### AWS Settings
2 |
3 | # name of the AWS profile to use from ~/.aws/credentials
4 | aws_profile: "ca-irakli-aws"
5 |
6 | aws_region: "us-east-1"
7 | aws_az: "us-east-1b"
8 | aws_az_alt: "us-east-1c"
9 | aws_image: ami-d05e75b8 # NoVa: Ubuntu Server 14.04 LTS (HVM), SSD Volume Type
10 |
11 | #aws_region: "us-west-1"
12 | #aws_az: "us-west-1a"
13 | #aws_az_alt: "us-west-1b"
14 | #aws_image: "ami-df6a8b9b" # Northern Cali: Ubuntu Server 14.04 LTS (HVM), SSD Volume Type
15 |
16 | project_name: "API Academy MSA DEMO"
17 | project_key: "msa_demo_{{ aws_region }}" # May NOT contain spaces!
18 |
19 | key_name: ssh/private-key.pem
20 | aws_instance_type: m3.medium
21 |
22 | aws_security_group_web: "{{ project_key }}_web"
23 | aws_security_group_consul: "{{ project_key }}_consul"
24 | aws_security_group_services: "{{ project_key }}_microservices"
25 | aws_security_group_db: "{{ project_key }}_db"
26 | aws_security_group_cagateway: "{{ project_key }}_ca_gateway"
27 |
28 | aws_ssh_key_name: "{{ project_name }} SSH Keys"
29 |
30 | # This needs to be hardened once we set up VPN
31 | aws_consul_security_cidr_ip: "0.0.0.0/0"
32 | #aws_consul_security_cidr_ip: "10.0.0.0/16"
33 |
34 | # You definitely want to modify this. Use 'consul keygen' on command line.
35 | consul_secretkey: "lpzYJ7BNjrLBdqP6nRc9hQ=="
36 |
37 | # microservices to install from DockerHub
38 | # @see: roles/aws-server-creation/security-setup.yml -> {{ project_name }} Microservices Security Group
39 | dockerhub_microservices:
40 | - "image" : "theapiacademy/shippinginc-customer-ms"
41 | "name" : "shippinginc-customer-ms"
42 | "ports" : "3000"
43 |
44 | - "image" : "theapiacademy/shippinginc-shipments-ms"
45 | "name" : "shippinginc-shipments-ms"
46 | "ports" : "3000"
47 |
48 |
49 |
50 | # Feel free to edit "exact_count" numbers but editing anything else here may require
51 | # corresponding changes in the code, which is not advisable. This is not a config file, per se.
52 |
53 | server_clusters:
54 | - zone: "{{ aws_az }}"
55 | exact_count: 3
56 | assign_public_ip: no
57 | tags:
58 | cluster: "{{ project_key }}"
59 | class: consul_servers
60 | subnet_tag: "{{ project_key }}_consul"
61 | groups:
62 | - "{{ aws_security_group_web }}"
63 | - "{{ aws_security_group_consul }}"
64 |
65 | - zone: "{{ aws_az }}"
66 | exact_count: 1
67 | assign_public_ip: yes
68 | tags:
69 | cluster: "{{ project_key }}"
70 | class: gateway_servers
71 | subnet_tag: "{{ project_key }}_web"
72 | groups:
73 | - "{{ aws_security_group_web }}"
74 | - "{{ aws_security_group_cagateway }}"
75 |
76 | - zone: "{{ aws_az }}"
77 | exact_count: 3
78 | assign_public_ip: no
79 | tags:
80 | cluster: "{{ project_key }}"
81 | class: services_servers
82 | subnet_tag: "{{ project_key }}_web"
83 | groups:
84 | - "{{ aws_security_group_web }}"
85 | - "{{ aws_security_group_services }}"
86 |
87 | ansible_ssh_private_key_file: ssh/private-key.pem
88 | ansible_ssh_user: ubuntu
--------------------------------------------------------------------------------
/hosts:
--------------------------------------------------------------------------------
1 | #############################################################################
2 | ##
3 | ## ATTENTION: typically all IPs here must be public IPs of your servers.
4 | ##
5 | #############################################################################
6 |
7 | [local]
8 | 127.0.0.1 ansible_connection=local
9 |
10 | #[consulservers]
11 | #54.163.97.231 consul_host=consul1
12 | #54.166.92.45 consul_host=consul2
13 | #54.158.180.91 consul_host=consul3
14 |
15 | #[webheads]
16 | #23.22.28.177 consul_host=web1
17 | #54.147.228.231 consul_host=web2
18 |
--------------------------------------------------------------------------------
/install-all.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ansible-playbook play-all.yml --ask-vault-pass
4 |
--------------------------------------------------------------------------------
/install-gateway.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ansible-playbook play-080-ca-gateway.yml --ask-vault-pass
--------------------------------------------------------------------------------
/inventory/ec2.ini:
--------------------------------------------------------------------------------
1 | # Ansible EC2 external inventory script settings
2 | #
3 |
4 | [ec2]
5 |
6 | # to talk to a private eucalyptus instance uncomment these lines
7 | # and edit edit eucalyptus_host to be the host name of your cloud controller
8 | #eucalyptus = True
9 | #eucalyptus_host = clc.cloud.domain.org
10 |
11 | # AWS regions to make calls to. Set this to 'all' to make request to all regions
12 | # in AWS and merge the results together. Alternatively, set this to a comma
13 | # separated list of regions. E.g. 'us-east-1,us-west-1,us-west-2'
14 | regions=us-east-1,us-west-1,us-west-2
15 | # regions = all
16 | regions_exclude = us-gov-west-1,cn-north-1
17 |
18 | # When generating inventory, Ansible needs to know how to address a server.
19 | # Each EC2 instance has a lot of variables associated with it. Here is the list:
20 | # http://docs.pythonboto.org/en/latest/ref/ec2.html#module-boto.ec2.instance
21 | # Below are 2 variables that are used as the address of a server:
22 | # - destination_variable
23 | # - vpc_destination_variable
24 |
25 | # This is the normal destination variable to use. If you are running Ansible
26 | # from outside EC2, then 'public_dns_name' makes the most sense. If you are
27 | # running Ansible from within EC2, then perhaps you want to use the internal
28 | # address, and should set this to 'private_dns_name'. The key of an EC2 tag
29 | # may optionally be used; however the boto instance variables hold precedence
30 | # in the event of a collision.
31 | destination_variable = public_dns_name
32 |
33 | # For server inside a VPC, using DNS names may not make sense. When an instance
34 | # has 'subnet_id' set, this variable is used. If the subnet is public, setting
35 | # this to 'ip_address' will return the public IP address. For instances in a
36 | # private subnet, this should be set to 'private_ip_address', and Ansible must
37 | # be run from with EC2. The key of an EC2 tag may optionally be used; however
38 | # the boto instance variables hold precedence in the event of a collision.
39 | vpc_destination_variable = ip_address
40 |
41 | # To tag instances on EC2 with the resource records that point to them from
42 | # Route53, uncomment and set 'route53' to True.
43 | route53 = False
44 |
45 | # To exclude RDS instances from the inventory, uncomment and set to False.
46 | #rds = False
47 |
48 | # Additionally, you can specify the list of zones to exclude looking up in
49 | # 'route53_excluded_zones' as a comma-separated list.
50 | # route53_excluded_zones = samplezone1.com, samplezone2.com
51 |
52 | # By default, only EC2 instances in the 'running' state are returned. Set
53 | # 'all_instances' to True to return all instances regardless of state.
54 | all_instances = False
55 |
56 | # By default, only RDS instances in the 'available' state are returned. Set
57 | # 'all_rds_instances' to True return all RDS instances regardless of state.
58 | all_rds_instances = False
59 |
60 | # API calls to EC2 are slow. For this reason, we cache the results of an API
61 | # call. Set this to the path you want cache files to be written to. Two files
62 | # will be written to this directory:
63 | # - ansible-ec2.cache
64 | # - ansible-ec2.index
65 | cache_path = ~/.ansible/tmp
66 |
67 | # The number of seconds a cache file is considered valid. After this many
68 | # seconds, a new API call will be made, and the cache file will be updated.
69 | # To disable the cache, set this value to 0
70 | cache_max_age = 0
71 |
72 | # Organize groups into a nested/hierarchy instead of a flat namespace.
73 | nested_groups = False
74 |
75 | # The EC2 inventory output can become very large. To manage its size,
76 | # configure which groups should be created.
77 | group_by_instance_id = False
78 | group_by_region = True
79 | group_by_availability_zone = True
80 | group_by_ami_id = False
81 | group_by_instance_type = False
82 | group_by_key_pair = False
83 | group_by_vpc_id = True
84 | group_by_security_group = True
85 | group_by_tag_keys = True
86 | group_by_tag_none = True
87 | group_by_route53_names = True
88 | group_by_rds_engine = True
89 | group_by_rds_parameter_group = True
90 |
91 | # If you only want to include hosts that match a certain regular expression
92 | # pattern_include = stage-*
93 |
94 | # If you want to exclude any hosts that match a certain regular expression
95 | # pattern_exclude = stage-*
96 |
97 | # Instance filters can be used to control which instances are retrieved for
98 | # inventory. For the full list of possible filters, please read the EC2 API
99 | # docs: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html#query-DescribeInstances-filters
100 | # Filters are key/value pairs separated by '=', to list multiple filters use
101 | # a list separated by commas. See examples below.
102 |
103 | # Retrieve only instances with (key=value) env=stage tag
104 | # instance_filters = tag:env=stage
105 |
106 | # Retrieve only instances with role=webservers OR role=dbservers tag
107 | # instance_filters = tag:role=webservers,tag:role=dbservers
108 |
109 | # Retrieve only t1.micro instances OR instances with tag env=stage
110 | # instance_filters = instance-type=t1.micro,tag:env=stage
111 |
112 | # You can use wildcards in filter values also. Below will list instances which
113 | # tag Name value matches webservers1*
114 | # (ex. webservers15, webservers1a, webservers123 etc)
115 | # instance_filters = tag:Name=webservers1*
116 |
--------------------------------------------------------------------------------
/inventory/ec2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | EC2 external inventory script
5 | =================================
6 |
7 | Generates inventory that Ansible can understand by making API request to
8 | AWS EC2 using the Boto library.
9 |
10 | NOTE: This script assumes Ansible is being executed where the environment
11 | variables needed for Boto have already been set:
12 | export AWS_ACCESS_KEY_ID='AK123'
13 | export AWS_SECRET_ACCESS_KEY='abc123'
14 |
15 | This script also assumes there is an ec2.ini file alongside it. To specify a
16 | different path to ec2.ini, define the EC2_INI_PATH environment variable:
17 |
18 | export EC2_INI_PATH=/path/to/my_ec2.ini
19 |
20 | If you're using eucalyptus you need to set the above variables and
21 | you need to define:
22 |
23 | export EC2_URL=http://hostname_of_your_cc:port/services/Eucalyptus
24 |
25 | If you're using boto profiles (requires boto>=2.24.0) you can choose a profile
26 | using the --boto-profile command line argument (e.g. ec2.py --boto-profile prod) or using
27 | the AWS_PROFILE variable:
28 |
29 | AWS_PROFILE=prod ansible-playbook -i ec2.py myplaybook.yml
30 |
31 | For more details, see: http://docs.pythonboto.org/en/latest/boto_config_tut.html
32 |
33 | When run against a specific host, this script returns the following variables:
34 | - ec2_ami_launch_index
35 | - ec2_architecture
36 | - ec2_association
37 | - ec2_attachTime
38 | - ec2_attachment
39 | - ec2_attachmentId
40 | - ec2_client_token
41 | - ec2_deleteOnTermination
42 | - ec2_description
43 | - ec2_deviceIndex
44 | - ec2_dns_name
45 | - ec2_eventsSet
46 | - ec2_group_name
47 | - ec2_hypervisor
48 | - ec2_id
49 | - ec2_image_id
50 | - ec2_instanceState
51 | - ec2_instance_type
52 | - ec2_ipOwnerId
53 | - ec2_ip_address
54 | - ec2_item
55 | - ec2_kernel
56 | - ec2_key_name
57 | - ec2_launch_time
58 | - ec2_monitored
59 | - ec2_monitoring
60 | - ec2_networkInterfaceId
61 | - ec2_ownerId
62 | - ec2_persistent
63 | - ec2_placement
64 | - ec2_platform
65 | - ec2_previous_state
66 | - ec2_private_dns_name
67 | - ec2_private_ip_address
68 | - ec2_publicIp
69 | - ec2_public_dns_name
70 | - ec2_ramdisk
71 | - ec2_reason
72 | - ec2_region
73 | - ec2_requester_id
74 | - ec2_root_device_name
75 | - ec2_root_device_type
76 | - ec2_security_group_ids
77 | - ec2_security_group_names
78 | - ec2_shutdown_state
79 | - ec2_sourceDestCheck
80 | - ec2_spot_instance_request_id
81 | - ec2_state
82 | - ec2_state_code
83 | - ec2_state_reason
84 | - ec2_status
85 | - ec2_subnet_id
86 | - ec2_tenancy
87 | - ec2_virtualization_type
88 | - ec2_vpc_id
89 |
90 | These variables are pulled out of a boto.ec2.instance object. There is a lack of
91 | consistency with variable spellings (camelCase and underscores) since this
92 | just loops through all variables the object exposes. It is preferred to use the
93 | ones with underscores when multiple exist.
94 |
95 | In addition, if an instance has AWS Tags associated with it, each tag is a new
96 | variable named:
97 | - ec2_tag_[Key] = [Value]
98 |
99 | Security groups are comma-separated in 'ec2_security_group_ids' and
100 | 'ec2_security_group_names'.
101 | '''
102 |
103 | # (c) 2012, Peter Sankauskas
104 | #
105 | # This file is part of Ansible,
106 | #
107 | # Ansible is free software: you can redistribute it and/or modify
108 | # it under the terms of the GNU General Public License as published by
109 | # the Free Software Foundation, either version 3 of the License, or
110 | # (at your option) any later version.
111 | #
112 | # Ansible is distributed in the hope that it will be useful,
113 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
114 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
115 | # GNU General Public License for more details.
116 | #
117 | # You should have received a copy of the GNU General Public License
118 | # along with Ansible. If not, see .
119 |
120 | ######################################################################
121 |
122 | import sys
123 | import os
124 | import argparse
125 | import re
126 | from time import time
127 | import boto
128 | from boto import ec2
129 | from boto import rds
130 | from boto import elasticache
131 | from boto import route53
132 | import six
133 |
134 | from six.moves import configparser
135 | from collections import defaultdict
136 |
137 | try:
138 | import json
139 | except ImportError:
140 | import simplejson as json
141 |
142 |
143 | class Ec2Inventory(object):
144 | def _empty_inventory(self):
145 | return {"_meta" : {"hostvars" : {}}}
146 |
147 | def __init__(self):
148 | ''' Main execution path '''
149 |
150 | # Inventory grouped by instance IDs, tags, security groups, regions,
151 | # and availability zones
152 | self.inventory = self._empty_inventory()
153 |
154 | # Index of hostname (address) to instance ID
155 | self.index = {}
156 |
157 | # Boto profile to use (if any)
158 | self.boto_profile = None
159 |
160 | # Read settings and parse CLI arguments
161 | self.parse_cli_args()
162 | self.read_settings()
163 |
164 | # Make sure that profile_name is not passed at all if not set
165 | # as pre 2.24 boto will fall over otherwise
166 | if self.boto_profile:
167 | if not hasattr(boto.ec2.EC2Connection, 'profile_name'):
168 | self.fail_with_error("boto version must be >= 2.24 to use profile")
169 |
170 | # Cache
171 | if self.args.refresh_cache:
172 | self.do_api_calls_update_cache()
173 | elif not self.is_cache_valid():
174 | self.do_api_calls_update_cache()
175 |
176 | # Data to print
177 | if self.args.host:
178 | data_to_print = self.get_host_info()
179 |
180 | elif self.args.list:
181 | # Display list of instances for inventory
182 | if self.inventory == self._empty_inventory():
183 | data_to_print = self.get_inventory_from_cache()
184 | else:
185 | data_to_print = self.json_format_dict(self.inventory, True)
186 |
187 | print(data_to_print)
188 |
189 |
190 | def is_cache_valid(self):
191 | ''' Determines if the cache files have expired, or if it is still valid '''
192 |
193 | if os.path.isfile(self.cache_path_cache):
194 | mod_time = os.path.getmtime(self.cache_path_cache)
195 | current_time = time()
196 | if (mod_time + self.cache_max_age) > current_time:
197 | if os.path.isfile(self.cache_path_index):
198 | return True
199 |
200 | return False
201 |
202 |
203 | def read_settings(self):
204 | ''' Reads the settings from the ec2.ini file '''
205 | if six.PY3:
206 | config = configparser.ConfigParser()
207 | else:
208 | config = configparser.SafeConfigParser()
209 | ec2_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ec2.ini')
210 | ec2_ini_path = os.path.expanduser(os.path.expandvars(os.environ.get('EC2_INI_PATH', ec2_default_ini_path)))
211 | config.read(ec2_ini_path)
212 |
213 | # is eucalyptus?
214 | self.eucalyptus_host = None
215 | self.eucalyptus = False
216 | if config.has_option('ec2', 'eucalyptus'):
217 | self.eucalyptus = config.getboolean('ec2', 'eucalyptus')
218 | if self.eucalyptus and config.has_option('ec2', 'eucalyptus_host'):
219 | self.eucalyptus_host = config.get('ec2', 'eucalyptus_host')
220 |
221 | # Regions
222 | self.regions = []
223 | configRegions = config.get('ec2', 'regions')
224 | configRegions_exclude = config.get('ec2', 'regions_exclude')
225 | if (configRegions == 'all'):
226 | if self.eucalyptus_host:
227 | self.regions.append(boto.connect_euca(host=self.eucalyptus_host).region.name)
228 | else:
229 | for regionInfo in ec2.regions():
230 | if regionInfo.name not in configRegions_exclude:
231 | self.regions.append(regionInfo.name)
232 | else:
233 | self.regions = configRegions.split(",")
234 |
235 | # Destination addresses
236 | self.destination_variable = config.get('ec2', 'destination_variable')
237 | self.vpc_destination_variable = config.get('ec2', 'vpc_destination_variable')
238 |
239 | # Route53
240 | self.route53_enabled = config.getboolean('ec2', 'route53')
241 | self.route53_excluded_zones = []
242 | if config.has_option('ec2', 'route53_excluded_zones'):
243 | self.route53_excluded_zones.extend(
244 | config.get('ec2', 'route53_excluded_zones', '').split(','))
245 |
246 | # Include RDS instances?
247 | self.rds_enabled = True
248 | if config.has_option('ec2', 'rds'):
249 | self.rds_enabled = config.getboolean('ec2', 'rds')
250 |
251 | # Include ElastiCache instances?
252 | self.elasticache_enabled = True
253 | if config.has_option('ec2', 'elasticache'):
254 | self.elasticache_enabled = config.getboolean('ec2', 'elasticache')
255 |
256 | # Return all EC2 instances?
257 | if config.has_option('ec2', 'all_instances'):
258 | self.all_instances = config.getboolean('ec2', 'all_instances')
259 | else:
260 | self.all_instances = False
261 |
262 | # Instance states to be gathered in inventory. Default is 'running'.
263 | # Setting 'all_instances' to 'yes' overrides this option.
264 | ec2_valid_instance_states = [
265 | 'pending',
266 | 'running',
267 | 'shutting-down',
268 | 'terminated',
269 | 'stopping',
270 | 'stopped'
271 | ]
272 | self.ec2_instance_states = []
273 | if self.all_instances:
274 | self.ec2_instance_states = ec2_valid_instance_states
275 | elif config.has_option('ec2', 'instance_states'):
276 | for instance_state in config.get('ec2', 'instance_states').split(','):
277 | instance_state = instance_state.strip()
278 | if instance_state not in ec2_valid_instance_states:
279 | continue
280 | self.ec2_instance_states.append(instance_state)
281 | else:
282 | self.ec2_instance_states = ['running']
283 |
284 | # Return all RDS instances? (if RDS is enabled)
285 | if config.has_option('ec2', 'all_rds_instances') and self.rds_enabled:
286 | self.all_rds_instances = config.getboolean('ec2', 'all_rds_instances')
287 | else:
288 | self.all_rds_instances = False
289 |
290 | # Return all ElastiCache replication groups? (if ElastiCache is enabled)
291 | if config.has_option('ec2', 'all_elasticache_replication_groups') and self.elasticache_enabled:
292 | self.all_elasticache_replication_groups = config.getboolean('ec2', 'all_elasticache_replication_groups')
293 | else:
294 | self.all_elasticache_replication_groups = False
295 |
296 | # Return all ElastiCache clusters? (if ElastiCache is enabled)
297 | if config.has_option('ec2', 'all_elasticache_clusters') and self.elasticache_enabled:
298 | self.all_elasticache_clusters = config.getboolean('ec2', 'all_elasticache_clusters')
299 | else:
300 | self.all_elasticache_clusters = False
301 |
302 | # Return all ElastiCache nodes? (if ElastiCache is enabled)
303 | if config.has_option('ec2', 'all_elasticache_nodes') and self.elasticache_enabled:
304 | self.all_elasticache_nodes = config.getboolean('ec2', 'all_elasticache_nodes')
305 | else:
306 | self.all_elasticache_nodes = False
307 |
308 | # boto configuration profile (prefer CLI argument)
309 | self.boto_profile = self.args.boto_profile
310 | if config.has_option('ec2', 'boto_profile') and not self.boto_profile:
311 | self.boto_profile = config.get('ec2', 'boto_profile')
312 |
313 | # Cache related
314 | cache_dir = os.path.expanduser(config.get('ec2', 'cache_path'))
315 | if self.boto_profile:
316 | cache_dir = os.path.join(cache_dir, 'profile_' + self.boto_profile)
317 | if not os.path.exists(cache_dir):
318 | os.makedirs(cache_dir)
319 |
320 | self.cache_path_cache = cache_dir + "/ansible-ec2.cache"
321 | self.cache_path_index = cache_dir + "/ansible-ec2.index"
322 | self.cache_max_age = config.getint('ec2', 'cache_max_age')
323 |
324 | # Configure nested groups instead of flat namespace.
325 | if config.has_option('ec2', 'nested_groups'):
326 | self.nested_groups = config.getboolean('ec2', 'nested_groups')
327 | else:
328 | self.nested_groups = False
329 |
330 | # Configure which groups should be created.
331 | group_by_options = [
332 | 'group_by_instance_id',
333 | 'group_by_region',
334 | 'group_by_availability_zone',
335 | 'group_by_ami_id',
336 | 'group_by_instance_type',
337 | 'group_by_key_pair',
338 | 'group_by_vpc_id',
339 | 'group_by_security_group',
340 | 'group_by_tag_keys',
341 | 'group_by_tag_none',
342 | 'group_by_route53_names',
343 | 'group_by_rds_engine',
344 | 'group_by_rds_parameter_group',
345 | 'group_by_elasticache_engine',
346 | 'group_by_elasticache_cluster',
347 | 'group_by_elasticache_parameter_group',
348 | 'group_by_elasticache_replication_group',
349 | ]
350 | for option in group_by_options:
351 | if config.has_option('ec2', option):
352 | setattr(self, option, config.getboolean('ec2', option))
353 | else:
354 | setattr(self, option, True)
355 |
356 | # Do we need to just include hosts that match a pattern?
357 | try:
358 | pattern_include = config.get('ec2', 'pattern_include')
359 | if pattern_include and len(pattern_include) > 0:
360 | self.pattern_include = re.compile(pattern_include)
361 | else:
362 | self.pattern_include = None
363 | except configparser.NoOptionError:
364 | self.pattern_include = None
365 |
366 | # Do we need to exclude hosts that match a pattern?
367 | try:
368 | pattern_exclude = config.get('ec2', 'pattern_exclude');
369 | if pattern_exclude and len(pattern_exclude) > 0:
370 | self.pattern_exclude = re.compile(pattern_exclude)
371 | else:
372 | self.pattern_exclude = None
373 | except configparser.NoOptionError:
374 | self.pattern_exclude = None
375 |
376 | # Instance filters (see boto and EC2 API docs). Ignore invalid filters.
377 | self.ec2_instance_filters = defaultdict(list)
378 | if config.has_option('ec2', 'instance_filters'):
379 | for instance_filter in config.get('ec2', 'instance_filters', '').split(','):
380 | instance_filter = instance_filter.strip()
381 | if not instance_filter or '=' not in instance_filter:
382 | continue
383 | filter_key, filter_value = [x.strip() for x in instance_filter.split('=', 1)]
384 | if not filter_key:
385 | continue
386 | self.ec2_instance_filters[filter_key].append(filter_value)
387 |
388 | def parse_cli_args(self):
389 | ''' Command line argument processing '''
390 |
391 | parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on EC2')
392 | parser.add_argument('--list', action='store_true', default=True,
393 | help='List instances (default: True)')
394 | parser.add_argument('--host', action='store',
395 | help='Get all the variables about a specific instance')
396 | parser.add_argument('--refresh-cache', action='store_true', default=False,
397 | help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)')
398 | parser.add_argument('--boto-profile', action='store',
399 | help='Use boto profile for connections to EC2')
400 | self.args = parser.parse_args()
401 |
402 |
403 | def do_api_calls_update_cache(self):
404 | ''' Do API calls to each region, and save data in cache files '''
405 |
406 | if self.route53_enabled:
407 | self.get_route53_records()
408 |
409 | for region in self.regions:
410 | self.get_instances_by_region(region)
411 | if self.rds_enabled:
412 | self.get_rds_instances_by_region(region)
413 | if self.elasticache_enabled:
414 | self.get_elasticache_clusters_by_region(region)
415 | self.get_elasticache_replication_groups_by_region(region)
416 |
417 | self.write_to_cache(self.inventory, self.cache_path_cache)
418 | self.write_to_cache(self.index, self.cache_path_index)
419 |
420 | def connect(self, region):
421 | ''' create connection to api server'''
422 | if self.eucalyptus:
423 | conn = boto.connect_euca(host=self.eucalyptus_host)
424 | conn.APIVersion = '2010-08-31'
425 | else:
426 | conn = self.connect_to_aws(ec2, region)
427 | return conn
428 |
429 | def boto_fix_security_token_in_profile(self, connect_args):
430 | ''' monkey patch for boto issue boto/boto#2100 '''
431 | profile = 'profile ' + self.boto_profile
432 | if boto.config.has_option(profile, 'aws_security_token'):
433 | connect_args['security_token'] = boto.config.get(profile, 'aws_security_token')
434 | return connect_args
435 |
436 | def connect_to_aws(self, module, region):
437 | connect_args = {}
438 |
439 | # only pass the profile name if it's set (as it is not supported by older boto versions)
440 | if self.boto_profile:
441 | connect_args['profile_name'] = self.boto_profile
442 | self.boto_fix_security_token_in_profile(connect_args)
443 |
444 | conn = module.connect_to_region(region, **connect_args)
445 | # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported
446 | if conn is None:
447 | self.fail_with_error("region name: %s likely not supported, or AWS is down. connection to region failed." % region)
448 | return conn
449 |
450 | def get_instances_by_region(self, region):
451 | ''' Makes an AWS EC2 API call to the list of instances in a particular
452 | region '''
453 |
454 | try:
455 | conn = self.connect(region)
456 | reservations = []
457 | if self.ec2_instance_filters:
458 | for filter_key, filter_values in self.ec2_instance_filters.items():
459 | reservations.extend(conn.get_all_instances(filters = { filter_key : filter_values }))
460 | else:
461 | reservations = conn.get_all_instances()
462 |
463 | for reservation in reservations:
464 | for instance in reservation.instances:
465 | self.add_instance(instance, region)
466 |
467 | except boto.exception.BotoServerError as e:
468 | if e.error_code == 'AuthFailure':
469 | error = self.get_auth_error_message()
470 | else:
471 | backend = 'Eucalyptus' if self.eucalyptus else 'AWS'
472 | error = "Error connecting to %s backend.\n%s" % (backend, e.message)
473 | self.fail_with_error(error, 'getting EC2 instances')
474 |
475 | def get_rds_instances_by_region(self, region):
476 | ''' Makes an AWS API call to the list of RDS instances in a particular
477 | region '''
478 |
479 | try:
480 | conn = self.connect_to_aws(rds, region)
481 | if conn:
482 | instances = conn.get_all_dbinstances()
483 | for instance in instances:
484 | self.add_rds_instance(instance, region)
485 | except boto.exception.BotoServerError as e:
486 | error = e.reason
487 |
488 | if e.error_code == 'AuthFailure':
489 | error = self.get_auth_error_message()
490 | if not e.reason == "Forbidden":
491 | error = "Looks like AWS RDS is down:\n%s" % e.message
492 | self.fail_with_error(error, 'getting RDS instances')
493 |
494 | def get_elasticache_clusters_by_region(self, region):
495 | ''' Makes an AWS API call to the list of ElastiCache clusters (with
496 | nodes' info) in a particular region.'''
497 |
498 | # ElastiCache boto module doesn't provide a get_all_intances method,
499 | # that's why we need to call describe directly (it would be called by
500 | # the shorthand method anyway...)
501 | try:
502 | conn = elasticache.connect_to_region(region)
503 | if conn:
504 | # show_cache_node_info = True
505 | # because we also want nodes' information
506 | response = conn.describe_cache_clusters(None, None, None, True)
507 |
508 | except boto.exception.BotoServerError as e:
509 | error = e.reason
510 |
511 | if e.error_code == 'AuthFailure':
512 | error = self.get_auth_error_message()
513 | if not e.reason == "Forbidden":
514 | error = "Looks like AWS ElastiCache is down:\n%s" % e.message
515 | self.fail_with_error(error, 'getting ElastiCache clusters')
516 |
517 | try:
518 | # Boto also doesn't provide wrapper classes to CacheClusters or
519 | # CacheNodes. Because of that wo can't make use of the get_list
520 | # method in the AWSQueryConnection. Let's do the work manually
521 | clusters = response['DescribeCacheClustersResponse']['DescribeCacheClustersResult']['CacheClusters']
522 |
523 | except KeyError as e:
524 | error = "ElastiCache query to AWS failed (unexpected format)."
525 | self.fail_with_error(error, 'getting ElastiCache clusters')
526 |
527 | for cluster in clusters:
528 | self.add_elasticache_cluster(cluster, region)
529 |
530 | def get_elasticache_replication_groups_by_region(self, region):
531 | ''' Makes an AWS API call to the list of ElastiCache replication groups
532 | in a particular region.'''
533 |
534 | # ElastiCache boto module doesn't provide a get_all_intances method,
535 | # that's why we need to call describe directly (it would be called by
536 | # the shorthand method anyway...)
537 | try:
538 | conn = elasticache.connect_to_region(region)
539 | if conn:
540 | response = conn.describe_replication_groups()
541 |
542 | except boto.exception.BotoServerError as e:
543 | error = e.reason
544 |
545 | if e.error_code == 'AuthFailure':
546 | error = self.get_auth_error_message()
547 | if not e.reason == "Forbidden":
548 | error = "Looks like AWS ElastiCache [Replication Groups] is down:\n%s" % e.message
549 | self.fail_with_error(error, 'getting ElastiCache clusters')
550 |
551 | try:
552 | # Boto also doesn't provide wrapper classes to ReplicationGroups
553 | # Because of that wo can't make use of the get_list method in the
554 | # AWSQueryConnection. Let's do the work manually
555 | replication_groups = response['DescribeReplicationGroupsResponse']['DescribeReplicationGroupsResult']['ReplicationGroups']
556 |
557 | except KeyError as e:
558 | error = "ElastiCache [Replication Groups] query to AWS failed (unexpected format)."
559 | self.fail_with_error(error, 'getting ElastiCache clusters')
560 |
561 | for replication_group in replication_groups:
562 | self.add_elasticache_replication_group(replication_group, region)
563 |
564 | def get_auth_error_message(self):
565 | ''' create an informative error message if there is an issue authenticating'''
566 | errors = ["Authentication error retrieving ec2 inventory."]
567 | if None in [os.environ.get('AWS_ACCESS_KEY_ID'), os.environ.get('AWS_SECRET_ACCESS_KEY')]:
568 | errors.append(' - No AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY environment vars found')
569 | else:
570 | errors.append(' - AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment vars found but may not be correct')
571 |
572 | boto_paths = ['/etc/boto.cfg', '~/.boto', '~/.aws/credentials']
573 | boto_config_found = list(p for p in boto_paths if os.path.isfile(os.path.expanduser(p)))
574 | if len(boto_config_found) > 0:
575 | errors.append(" - Boto configs found at '%s', but the credentials contained may not be correct" % ', '.join(boto_config_found))
576 | else:
577 | errors.append(" - No Boto config found at any expected location '%s'" % ', '.join(boto_paths))
578 |
579 | return '\n'.join(errors)
580 |
581 | def fail_with_error(self, err_msg, err_operation=None):
582 | '''log an error to std err for ansible-playbook to consume and exit'''
583 | if err_operation:
584 | err_msg = 'ERROR: "{err_msg}", while: {err_operation}'.format(
585 | err_msg=err_msg, err_operation=err_operation)
586 | sys.stderr.write(err_msg)
587 | sys.exit(1)
588 |
589 | def get_instance(self, region, instance_id):
590 | conn = self.connect(region)
591 |
592 | reservations = conn.get_all_instances([instance_id])
593 | for reservation in reservations:
594 | for instance in reservation.instances:
595 | return instance
596 |
597 | def add_instance(self, instance, region):
598 | ''' Adds an instance to the inventory and index, as long as it is
599 | addressable '''
600 |
601 | # Only return instances with desired instance states
602 | if instance.state not in self.ec2_instance_states:
603 | return
604 |
605 | # Select the best destination address
606 | if instance.subnet_id:
607 | dest = getattr(instance, self.vpc_destination_variable, None)
608 | if dest is None:
609 | dest = getattr(instance, 'tags').get(self.vpc_destination_variable, None)
610 | else:
611 | dest = getattr(instance, self.destination_variable, None)
612 | if dest is None:
613 | dest = getattr(instance, 'tags').get(self.destination_variable, None)
614 |
615 | if not dest:
616 | # Skip instances we cannot address (e.g. private VPC subnet)
617 | return
618 |
619 | # if we only want to include hosts that match a pattern, skip those that don't
620 | if self.pattern_include and not self.pattern_include.match(dest):
621 | return
622 |
623 | # if we need to exclude hosts that match a pattern, skip those
624 | if self.pattern_exclude and self.pattern_exclude.match(dest):
625 | return
626 |
627 | # Add to index
628 | self.index[dest] = [region, instance.id]
629 |
630 | # Inventory: Group by instance ID (always a group of 1)
631 | if self.group_by_instance_id:
632 | self.inventory[instance.id] = [dest]
633 | if self.nested_groups:
634 | self.push_group(self.inventory, 'instances', instance.id)
635 |
636 | # Inventory: Group by region
637 | if self.group_by_region:
638 | self.push(self.inventory, region, dest)
639 | if self.nested_groups:
640 | self.push_group(self.inventory, 'regions', region)
641 |
642 | # Inventory: Group by availability zone
643 | if self.group_by_availability_zone:
644 | self.push(self.inventory, instance.placement, dest)
645 | if self.nested_groups:
646 | if self.group_by_region:
647 | self.push_group(self.inventory, region, instance.placement)
648 | self.push_group(self.inventory, 'zones', instance.placement)
649 |
650 | # Inventory: Group by Amazon Machine Image (AMI) ID
651 | if self.group_by_ami_id:
652 | ami_id = self.to_safe(instance.image_id)
653 | self.push(self.inventory, ami_id, dest)
654 | if self.nested_groups:
655 | self.push_group(self.inventory, 'images', ami_id)
656 |
657 | # Inventory: Group by instance type
658 | if self.group_by_instance_type:
659 | type_name = self.to_safe('type_' + instance.instance_type)
660 | self.push(self.inventory, type_name, dest)
661 | if self.nested_groups:
662 | self.push_group(self.inventory, 'types', type_name)
663 |
664 | # Inventory: Group by key pair
665 | if self.group_by_key_pair and instance.key_name:
666 | key_name = self.to_safe('key_' + instance.key_name)
667 | self.push(self.inventory, key_name, dest)
668 | if self.nested_groups:
669 | self.push_group(self.inventory, 'keys', key_name)
670 |
671 | # Inventory: Group by VPC
672 | if self.group_by_vpc_id and instance.vpc_id:
673 | vpc_id_name = self.to_safe('vpc_id_' + instance.vpc_id)
674 | self.push(self.inventory, vpc_id_name, dest)
675 | if self.nested_groups:
676 | self.push_group(self.inventory, 'vpcs', vpc_id_name)
677 |
678 | # Inventory: Group by security group
679 | if self.group_by_security_group:
680 | try:
681 | for group in instance.groups:
682 | key = self.to_safe("security_group_" + group.name)
683 | self.push(self.inventory, key, dest)
684 | if self.nested_groups:
685 | self.push_group(self.inventory, 'security_groups', key)
686 | except AttributeError:
687 | self.fail_with_error('\n'.join(['Package boto seems a bit older.',
688 | 'Please upgrade boto >= 2.3.0.']))
689 |
690 | # Inventory: Group by tag keys
691 | if self.group_by_tag_keys:
692 | for k, v in instance.tags.items():
693 | if v:
694 | key = self.to_safe("tag_" + k + "=" + v)
695 | else:
696 | key = self.to_safe("tag_" + k)
697 | self.push(self.inventory, key, dest)
698 | if self.nested_groups:
699 | self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k))
700 | self.push_group(self.inventory, self.to_safe("tag_" + k), key)
701 |
702 | # Inventory: Group by Route53 domain names if enabled
703 | if self.route53_enabled and self.group_by_route53_names:
704 | route53_names = self.get_instance_route53_names(instance)
705 | for name in route53_names:
706 | self.push(self.inventory, name, dest)
707 | if self.nested_groups:
708 | self.push_group(self.inventory, 'route53', name)
709 |
710 | # Global Tag: instances without tags
711 | if self.group_by_tag_none and len(instance.tags) == 0:
712 | self.push(self.inventory, 'tag_none', dest)
713 | if self.nested_groups:
714 | self.push_group(self.inventory, 'tags', 'tag_none')
715 |
716 | # Global Tag: tag all EC2 instances
717 | self.push(self.inventory, 'ec2', dest)
718 |
719 | self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance)
720 |
721 |
722 | def add_rds_instance(self, instance, region):
723 | ''' Adds an RDS instance to the inventory and index, as long as it is
724 | addressable '''
725 |
726 | # Only want available instances unless all_rds_instances is True
727 | if not self.all_rds_instances and instance.status != 'available':
728 | return
729 |
730 | # Select the best destination address
731 | dest = instance.endpoint[0]
732 |
733 | if not dest:
734 | # Skip instances we cannot address (e.g. private VPC subnet)
735 | return
736 |
737 | # Add to index
738 | self.index[dest] = [region, instance.id]
739 |
740 | # Inventory: Group by instance ID (always a group of 1)
741 | if self.group_by_instance_id:
742 | self.inventory[instance.id] = [dest]
743 | if self.nested_groups:
744 | self.push_group(self.inventory, 'instances', instance.id)
745 |
746 | # Inventory: Group by region
747 | if self.group_by_region:
748 | self.push(self.inventory, region, dest)
749 | if self.nested_groups:
750 | self.push_group(self.inventory, 'regions', region)
751 |
752 | # Inventory: Group by availability zone
753 | if self.group_by_availability_zone:
754 | self.push(self.inventory, instance.availability_zone, dest)
755 | if self.nested_groups:
756 | if self.group_by_region:
757 | self.push_group(self.inventory, region, instance.availability_zone)
758 | self.push_group(self.inventory, 'zones', instance.availability_zone)
759 |
760 | # Inventory: Group by instance type
761 | if self.group_by_instance_type:
762 | type_name = self.to_safe('type_' + instance.instance_class)
763 | self.push(self.inventory, type_name, dest)
764 | if self.nested_groups:
765 | self.push_group(self.inventory, 'types', type_name)
766 |
767 | # Inventory: Group by VPC
768 | if self.group_by_vpc_id and instance.subnet_group and instance.subnet_group.vpc_id:
769 | vpc_id_name = self.to_safe('vpc_id_' + instance.subnet_group.vpc_id)
770 | self.push(self.inventory, vpc_id_name, dest)
771 | if self.nested_groups:
772 | self.push_group(self.inventory, 'vpcs', vpc_id_name)
773 |
774 | # Inventory: Group by security group
775 | if self.group_by_security_group:
776 | try:
777 | if instance.security_group:
778 | key = self.to_safe("security_group_" + instance.security_group.name)
779 | self.push(self.inventory, key, dest)
780 | if self.nested_groups:
781 | self.push_group(self.inventory, 'security_groups', key)
782 |
783 | except AttributeError:
784 | self.fail_with_error('\n'.join(['Package boto seems a bit older.',
785 | 'Please upgrade boto >= 2.3.0.']))
786 |
787 |
788 | # Inventory: Group by engine
789 | if self.group_by_rds_engine:
790 | self.push(self.inventory, self.to_safe("rds_" + instance.engine), dest)
791 | if self.nested_groups:
792 | self.push_group(self.inventory, 'rds_engines', self.to_safe("rds_" + instance.engine))
793 |
794 | # Inventory: Group by parameter group
795 | if self.group_by_rds_parameter_group:
796 | self.push(self.inventory, self.to_safe("rds_parameter_group_" + instance.parameter_group.name), dest)
797 | if self.nested_groups:
798 | self.push_group(self.inventory, 'rds_parameter_groups', self.to_safe("rds_parameter_group_" + instance.parameter_group.name))
799 |
800 | # Global Tag: all RDS instances
801 | self.push(self.inventory, 'rds', dest)
802 |
803 | self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance)
804 |
805 | def add_elasticache_cluster(self, cluster, region):
806 | ''' Adds an ElastiCache cluster to the inventory and index, as long as
807 | it's nodes are addressable '''
808 |
809 | # Only want available clusters unless all_elasticache_clusters is True
810 | if not self.all_elasticache_clusters and cluster['CacheClusterStatus'] != 'available':
811 | return
812 |
813 | # Select the best destination address
814 | if 'ConfigurationEndpoint' in cluster and cluster['ConfigurationEndpoint']:
815 | # Memcached cluster
816 | dest = cluster['ConfigurationEndpoint']['Address']
817 | is_redis = False
818 | else:
819 | # Redis sigle node cluster
820 | # Because all Redis clusters are single nodes, we'll merge the
821 | # info from the cluster with info about the node
822 | dest = cluster['CacheNodes'][0]['Endpoint']['Address']
823 | is_redis = True
824 |
825 | if not dest:
826 | # Skip clusters we cannot address (e.g. private VPC subnet)
827 | return
828 |
829 | # Add to index
830 | self.index[dest] = [region, cluster['CacheClusterId']]
831 |
832 | # Inventory: Group by instance ID (always a group of 1)
833 | if self.group_by_instance_id:
834 | self.inventory[cluster['CacheClusterId']] = [dest]
835 | if self.nested_groups:
836 | self.push_group(self.inventory, 'instances', cluster['CacheClusterId'])
837 |
838 | # Inventory: Group by region
839 | if self.group_by_region and not is_redis:
840 | self.push(self.inventory, region, dest)
841 | if self.nested_groups:
842 | self.push_group(self.inventory, 'regions', region)
843 |
844 | # Inventory: Group by availability zone
845 | if self.group_by_availability_zone and not is_redis:
846 | self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest)
847 | if self.nested_groups:
848 | if self.group_by_region:
849 | self.push_group(self.inventory, region, cluster['PreferredAvailabilityZone'])
850 | self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone'])
851 |
852 | # Inventory: Group by node type
853 | if self.group_by_instance_type and not is_redis:
854 | type_name = self.to_safe('type_' + cluster['CacheNodeType'])
855 | self.push(self.inventory, type_name, dest)
856 | if self.nested_groups:
857 | self.push_group(self.inventory, 'types', type_name)
858 |
859 | # Inventory: Group by VPC (information not available in the current
860 | # AWS API version for ElastiCache)
861 |
862 | # Inventory: Group by security group
863 | if self.group_by_security_group and not is_redis:
864 |
865 | # Check for the existence of the 'SecurityGroups' key and also if
866 | # this key has some value. When the cluster is not placed in a SG
867 | # the query can return None here and cause an error.
868 | if 'SecurityGroups' in cluster and cluster['SecurityGroups'] is not None:
869 | for security_group in cluster['SecurityGroups']:
870 | key = self.to_safe("security_group_" + security_group['SecurityGroupId'])
871 | self.push(self.inventory, key, dest)
872 | if self.nested_groups:
873 | self.push_group(self.inventory, 'security_groups', key)
874 |
875 | # Inventory: Group by engine
876 | if self.group_by_elasticache_engine and not is_redis:
877 | self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest)
878 | if self.nested_groups:
879 | self.push_group(self.inventory, 'elasticache_engines', self.to_safe(cluster['Engine']))
880 |
881 | # Inventory: Group by parameter group
882 | if self.group_by_elasticache_parameter_group:
883 | self.push(self.inventory, self.to_safe("elasticache_parameter_group_" + cluster['CacheParameterGroup']['CacheParameterGroupName']), dest)
884 | if self.nested_groups:
885 | self.push_group(self.inventory, 'elasticache_parameter_groups', self.to_safe(cluster['CacheParameterGroup']['CacheParameterGroupName']))
886 |
887 | # Inventory: Group by replication group
888 | if self.group_by_elasticache_replication_group and 'ReplicationGroupId' in cluster and cluster['ReplicationGroupId']:
889 | self.push(self.inventory, self.to_safe("elasticache_replication_group_" + cluster['ReplicationGroupId']), dest)
890 | if self.nested_groups:
891 | self.push_group(self.inventory, 'elasticache_replication_groups', self.to_safe(cluster['ReplicationGroupId']))
892 |
893 | # Global Tag: all ElastiCache clusters
894 | self.push(self.inventory, 'elasticache_clusters', cluster['CacheClusterId'])
895 |
896 | host_info = self.get_host_info_dict_from_describe_dict(cluster)
897 |
898 | self.inventory["_meta"]["hostvars"][dest] = host_info
899 |
900 | # Add the nodes
901 | for node in cluster['CacheNodes']:
902 | self.add_elasticache_node(node, cluster, region)
903 |
904 | def add_elasticache_node(self, node, cluster, region):
905 | ''' Adds an ElastiCache node to the inventory and index, as long as
906 | it is addressable '''
907 |
908 | # Only want available nodes unless all_elasticache_nodes is True
909 | if not self.all_elasticache_nodes and node['CacheNodeStatus'] != 'available':
910 | return
911 |
912 | # Select the best destination address
913 | dest = node['Endpoint']['Address']
914 |
915 | if not dest:
916 | # Skip nodes we cannot address (e.g. private VPC subnet)
917 | return
918 |
919 | node_id = self.to_safe(cluster['CacheClusterId'] + '_' + node['CacheNodeId'])
920 |
921 | # Add to index
922 | self.index[dest] = [region, node_id]
923 |
924 | # Inventory: Group by node ID (always a group of 1)
925 | if self.group_by_instance_id:
926 | self.inventory[node_id] = [dest]
927 | if self.nested_groups:
928 | self.push_group(self.inventory, 'instances', node_id)
929 |
930 | # Inventory: Group by region
931 | if self.group_by_region:
932 | self.push(self.inventory, region, dest)
933 | if self.nested_groups:
934 | self.push_group(self.inventory, 'regions', region)
935 |
936 | # Inventory: Group by availability zone
937 | if self.group_by_availability_zone:
938 | self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest)
939 | if self.nested_groups:
940 | if self.group_by_region:
941 | self.push_group(self.inventory, region, cluster['PreferredAvailabilityZone'])
942 | self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone'])
943 |
944 | # Inventory: Group by node type
945 | if self.group_by_instance_type:
946 | type_name = self.to_safe('type_' + cluster['CacheNodeType'])
947 | self.push(self.inventory, type_name, dest)
948 | if self.nested_groups:
949 | self.push_group(self.inventory, 'types', type_name)
950 |
951 | # Inventory: Group by VPC (information not available in the current
952 | # AWS API version for ElastiCache)
953 |
954 | # Inventory: Group by security group
955 | if self.group_by_security_group:
956 |
957 | # Check for the existence of the 'SecurityGroups' key and also if
958 | # this key has some value. When the cluster is not placed in a SG
959 | # the query can return None here and cause an error.
960 | if 'SecurityGroups' in cluster and cluster['SecurityGroups'] is not None:
961 | for security_group in cluster['SecurityGroups']:
962 | key = self.to_safe("security_group_" + security_group['SecurityGroupId'])
963 | self.push(self.inventory, key, dest)
964 | if self.nested_groups:
965 | self.push_group(self.inventory, 'security_groups', key)
966 |
967 | # Inventory: Group by engine
968 | if self.group_by_elasticache_engine:
969 | self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest)
970 | if self.nested_groups:
971 | self.push_group(self.inventory, 'elasticache_engines', self.to_safe("elasticache_" + cluster['Engine']))
972 |
973 | # Inventory: Group by parameter group (done at cluster level)
974 |
975 | # Inventory: Group by replication group (done at cluster level)
976 |
977 | # Inventory: Group by ElastiCache Cluster
978 | if self.group_by_elasticache_cluster:
979 | self.push(self.inventory, self.to_safe("elasticache_cluster_" + cluster['CacheClusterId']), dest)
980 |
981 | # Global Tag: all ElastiCache nodes
982 | self.push(self.inventory, 'elasticache_nodes', dest)
983 |
984 | host_info = self.get_host_info_dict_from_describe_dict(node)
985 |
986 | if dest in self.inventory["_meta"]["hostvars"]:
987 | self.inventory["_meta"]["hostvars"][dest].update(host_info)
988 | else:
989 | self.inventory["_meta"]["hostvars"][dest] = host_info
990 |
991 | def add_elasticache_replication_group(self, replication_group, region):
992 | ''' Adds an ElastiCache replication group to the inventory and index '''
993 |
994 | # Only want available clusters unless all_elasticache_replication_groups is True
995 | if not self.all_elasticache_replication_groups and replication_group['Status'] != 'available':
996 | return
997 |
998 | # Select the best destination address (PrimaryEndpoint)
999 | dest = replication_group['NodeGroups'][0]['PrimaryEndpoint']['Address']
1000 |
1001 | if not dest:
1002 | # Skip clusters we cannot address (e.g. private VPC subnet)
1003 | return
1004 |
1005 | # Add to index
1006 | self.index[dest] = [region, replication_group['ReplicationGroupId']]
1007 |
1008 | # Inventory: Group by ID (always a group of 1)
1009 | if self.group_by_instance_id:
1010 | self.inventory[replication_group['ReplicationGroupId']] = [dest]
1011 | if self.nested_groups:
1012 | self.push_group(self.inventory, 'instances', replication_group['ReplicationGroupId'])
1013 |
1014 | # Inventory: Group by region
1015 | if self.group_by_region:
1016 | self.push(self.inventory, region, dest)
1017 | if self.nested_groups:
1018 | self.push_group(self.inventory, 'regions', region)
1019 |
1020 | # Inventory: Group by availability zone (doesn't apply to replication groups)
1021 |
1022 | # Inventory: Group by node type (doesn't apply to replication groups)
1023 |
1024 | # Inventory: Group by VPC (information not available in the current
1025 | # AWS API version for replication groups
1026 |
1027 | # Inventory: Group by security group (doesn't apply to replication groups)
1028 | # Check this value in cluster level
1029 |
1030 | # Inventory: Group by engine (replication groups are always Redis)
1031 | if self.group_by_elasticache_engine:
1032 | self.push(self.inventory, 'elasticache_redis', dest)
1033 | if self.nested_groups:
1034 | self.push_group(self.inventory, 'elasticache_engines', 'redis')
1035 |
1036 | # Global Tag: all ElastiCache clusters
1037 | self.push(self.inventory, 'elasticache_replication_groups', replication_group['ReplicationGroupId'])
1038 |
1039 | host_info = self.get_host_info_dict_from_describe_dict(replication_group)
1040 |
1041 | self.inventory["_meta"]["hostvars"][dest] = host_info
1042 |
1043 | def get_route53_records(self):
1044 | ''' Get and store the map of resource records to domain names that
1045 | point to them. '''
1046 |
1047 | r53_conn = route53.Route53Connection()
1048 | all_zones = r53_conn.get_zones()
1049 |
1050 | route53_zones = [ zone for zone in all_zones if zone.name[:-1]
1051 | not in self.route53_excluded_zones ]
1052 |
1053 | self.route53_records = {}
1054 |
1055 | for zone in route53_zones:
1056 | rrsets = r53_conn.get_all_rrsets(zone.id)
1057 |
1058 | for record_set in rrsets:
1059 | record_name = record_set.name
1060 |
1061 | if record_name.endswith('.'):
1062 | record_name = record_name[:-1]
1063 |
1064 | for resource in record_set.resource_records:
1065 | self.route53_records.setdefault(resource, set())
1066 | self.route53_records[resource].add(record_name)
1067 |
1068 |
1069 | def get_instance_route53_names(self, instance):
1070 | ''' Check if an instance is referenced in the records we have from
1071 | Route53. If it is, return the list of domain names pointing to said
1072 | instance. If nothing points to it, return an empty list. '''
1073 |
1074 | instance_attributes = [ 'public_dns_name', 'private_dns_name',
1075 | 'ip_address', 'private_ip_address' ]
1076 |
1077 | name_list = set()
1078 |
1079 | for attrib in instance_attributes:
1080 | try:
1081 | value = getattr(instance, attrib)
1082 | except AttributeError:
1083 | continue
1084 |
1085 | if value in self.route53_records:
1086 | name_list.update(self.route53_records[value])
1087 |
1088 | return list(name_list)
1089 |
1090 | def get_host_info_dict_from_instance(self, instance):
1091 | instance_vars = {}
1092 | for key in vars(instance):
1093 | value = getattr(instance, key)
1094 | key = self.to_safe('ec2_' + key)
1095 |
1096 | # Handle complex types
1097 | # state/previous_state changed to properties in boto in https://github.com/boto/boto/commit/a23c379837f698212252720d2af8dec0325c9518
1098 | if key == 'ec2__state':
1099 | instance_vars['ec2_state'] = instance.state or ''
1100 | instance_vars['ec2_state_code'] = instance.state_code
1101 | elif key == 'ec2__previous_state':
1102 | instance_vars['ec2_previous_state'] = instance.previous_state or ''
1103 | instance_vars['ec2_previous_state_code'] = instance.previous_state_code
1104 | elif type(value) in [int, bool]:
1105 | instance_vars[key] = value
1106 | elif isinstance(value, six.string_types):
1107 | instance_vars[key] = value.strip()
1108 | elif type(value) == type(None):
1109 | instance_vars[key] = ''
1110 | elif key == 'ec2_region':
1111 | instance_vars[key] = value.name
1112 | elif key == 'ec2__placement':
1113 | instance_vars['ec2_placement'] = value.zone
1114 | elif key == 'ec2_tags':
1115 | for k, v in value.items():
1116 | key = self.to_safe('ec2_tag_' + k)
1117 | instance_vars[key] = v
1118 | elif key == 'ec2_groups':
1119 | group_ids = []
1120 | group_names = []
1121 | for group in value:
1122 | group_ids.append(group.id)
1123 | group_names.append(group.name)
1124 | instance_vars["ec2_security_group_ids"] = ','.join([str(i) for i in group_ids])
1125 | instance_vars["ec2_security_group_names"] = ','.join([str(i) for i in group_names])
1126 | else:
1127 | pass
1128 | # TODO Product codes if someone finds them useful
1129 | #print key
1130 | #print type(value)
1131 | #print value
1132 |
1133 | return instance_vars
1134 |
1135 | def get_host_info_dict_from_describe_dict(self, describe_dict):
1136 | ''' Parses the dictionary returned by the API call into a flat list
1137 | of parameters. This method should be used only when 'describe' is
1138 | used directly because Boto doesn't provide specific classes. '''
1139 |
1140 | # I really don't agree with prefixing everything with 'ec2'
1141 | # because EC2, RDS and ElastiCache are different services.
1142 | # I'm just following the pattern used until now to not break any
1143 | # compatibility.
1144 |
1145 | host_info = {}
1146 | for key in describe_dict:
1147 | value = describe_dict[key]
1148 | key = self.to_safe('ec2_' + self.uncammelize(key))
1149 |
1150 | # Handle complex types
1151 |
1152 | # Target: Memcached Cache Clusters
1153 | if key == 'ec2_configuration_endpoint' and value:
1154 | host_info['ec2_configuration_endpoint_address'] = value['Address']
1155 | host_info['ec2_configuration_endpoint_port'] = value['Port']
1156 |
1157 | # Target: Cache Nodes and Redis Cache Clusters (single node)
1158 | if key == 'ec2_endpoint' and value:
1159 | host_info['ec2_endpoint_address'] = value['Address']
1160 | host_info['ec2_endpoint_port'] = value['Port']
1161 |
1162 | # Target: Redis Replication Groups
1163 | if key == 'ec2_node_groups' and value:
1164 | host_info['ec2_endpoint_address'] = value[0]['PrimaryEndpoint']['Address']
1165 | host_info['ec2_endpoint_port'] = value[0]['PrimaryEndpoint']['Port']
1166 | replica_count = 0
1167 | for node in value[0]['NodeGroupMembers']:
1168 | if node['CurrentRole'] == 'primary':
1169 | host_info['ec2_primary_cluster_address'] = node['ReadEndpoint']['Address']
1170 | host_info['ec2_primary_cluster_port'] = node['ReadEndpoint']['Port']
1171 | host_info['ec2_primary_cluster_id'] = node['CacheClusterId']
1172 | elif node['CurrentRole'] == 'replica':
1173 | host_info['ec2_replica_cluster_address_'+ str(replica_count)] = node['ReadEndpoint']['Address']
1174 | host_info['ec2_replica_cluster_port_'+ str(replica_count)] = node['ReadEndpoint']['Port']
1175 | host_info['ec2_replica_cluster_id_'+ str(replica_count)] = node['CacheClusterId']
1176 | replica_count += 1
1177 |
1178 | # Target: Redis Replication Groups
1179 | if key == 'ec2_member_clusters' and value:
1180 | host_info['ec2_member_clusters'] = ','.join([str(i) for i in value])
1181 |
1182 | # Target: All Cache Clusters
1183 | elif key == 'ec2_cache_parameter_group':
1184 | host_info["ec2_cache_node_ids_to_reboot"] = ','.join([str(i) for i in value['CacheNodeIdsToReboot']])
1185 | host_info['ec2_cache_parameter_group_name'] = value['CacheParameterGroupName']
1186 | host_info['ec2_cache_parameter_apply_status'] = value['ParameterApplyStatus']
1187 |
1188 | # Target: Almost everything
1189 | elif key == 'ec2_security_groups':
1190 |
1191 | # Skip if SecurityGroups is None
1192 | # (it is possible to have the key defined but no value in it).
1193 | if value is not None:
1194 | sg_ids = []
1195 | for sg in value:
1196 | sg_ids.append(sg['SecurityGroupId'])
1197 | host_info["ec2_security_group_ids"] = ','.join([str(i) for i in sg_ids])
1198 |
1199 | # Target: Everything
1200 | # Preserve booleans and integers
1201 | elif type(value) in [int, bool]:
1202 | host_info[key] = value
1203 |
1204 | # Target: Everything
1205 | # Sanitize string values
1206 | elif isinstance(value, six.string_types):
1207 | host_info[key] = value.strip()
1208 |
1209 | # Target: Everything
1210 | # Replace None by an empty string
1211 | elif type(value) == type(None):
1212 | host_info[key] = ''
1213 |
1214 | else:
1215 | # Remove non-processed complex types
1216 | pass
1217 |
1218 | return host_info
1219 |
1220 | def get_host_info(self):
1221 | ''' Get variables about a specific host '''
1222 |
1223 | if len(self.index) == 0:
1224 | # Need to load index from cache
1225 | self.load_index_from_cache()
1226 |
1227 | if not self.args.host in self.index:
1228 | # try updating the cache
1229 | self.do_api_calls_update_cache()
1230 | if not self.args.host in self.index:
1231 | # host might not exist anymore
1232 | return self.json_format_dict({}, True)
1233 |
1234 | (region, instance_id) = self.index[self.args.host]
1235 |
1236 | instance = self.get_instance(region, instance_id)
1237 | return self.json_format_dict(self.get_host_info_dict_from_instance(instance), True)
1238 |
1239 | def push(self, my_dict, key, element):
1240 | ''' Push an element onto an array that may not have been defined in
1241 | the dict '''
1242 | group_info = my_dict.setdefault(key, [])
1243 | if isinstance(group_info, dict):
1244 | host_list = group_info.setdefault('hosts', [])
1245 | host_list.append(element)
1246 | else:
1247 | group_info.append(element)
1248 |
1249 | def push_group(self, my_dict, key, element):
1250 | ''' Push a group as a child of another group. '''
1251 | parent_group = my_dict.setdefault(key, {})
1252 | if not isinstance(parent_group, dict):
1253 | parent_group = my_dict[key] = {'hosts': parent_group}
1254 | child_groups = parent_group.setdefault('children', [])
1255 | if element not in child_groups:
1256 | child_groups.append(element)
1257 |
1258 | def get_inventory_from_cache(self):
1259 | ''' Reads the inventory from the cache file and returns it as a JSON
1260 | object '''
1261 |
1262 | cache = open(self.cache_path_cache, 'r')
1263 | json_inventory = cache.read()
1264 | return json_inventory
1265 |
1266 |
1267 | def load_index_from_cache(self):
1268 | ''' Reads the index from the cache file sets self.index '''
1269 |
1270 | cache = open(self.cache_path_index, 'r')
1271 | json_index = cache.read()
1272 | self.index = json.loads(json_index)
1273 |
1274 |
1275 | def write_to_cache(self, data, filename):
1276 | ''' Writes data in JSON format to a file '''
1277 |
1278 | json_data = self.json_format_dict(data, True)
1279 | cache = open(filename, 'w')
1280 | cache.write(json_data)
1281 | cache.close()
1282 |
1283 | def uncammelize(self, key):
1284 | temp = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', key)
1285 | return re.sub('([a-z0-9])([A-Z])', r'\1_\2', temp).lower()
1286 |
1287 | def to_safe(self, word):
1288 | ''' Converts 'bad' characters in a string to underscores so they can be
1289 | used as Ansible groups '''
1290 |
1291 | return re.sub("[^A-Za-z0-9\_]", "_", word)
1292 |
1293 | def json_format_dict(self, data, pretty=False):
1294 | ''' Converts a dict to a JSON object and dumps it as a formatted
1295 | string '''
1296 |
1297 | if pretty:
1298 | return json.dumps(data, sort_keys=True, indent=2)
1299 | else:
1300 | return json.dumps(data)
1301 |
1302 |
1303 | # Run the script
1304 | Ec2Inventory()
1305 |
--------------------------------------------------------------------------------
/inventory/hosts.static:
--------------------------------------------------------------------------------
1 | [localhost]
2 | 127.0.0.1 ansible_connection=local
3 |
4 | # https://github.com/arbabnazar/ansible-openvpn-aws-vpc
--------------------------------------------------------------------------------
/play-010-create-servers.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - hosts: localhost
4 | connection: local
5 | gather_facts: no
6 | roles:
7 | - aws-server-creation
--------------------------------------------------------------------------------
/play-020-baseline.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # We could also use tag_cluster_msa_demo which captures all servers, but
4 | # explicitely naming various server classes felt more expressive here.
5 |
6 | - hosts:
7 | - tag_class_gateway_servers
8 | - tag_class_consul_servers
9 | - tag_class_services_servers
10 | sudo: yes
11 | roles:
12 | - common-baseline
--------------------------------------------------------------------------------
/play-030-dockerize.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # We could also use tag_cluster_msa_demo which captures all servers, but
4 | # explicitely naming various server classes felt more expressive here.
5 |
6 | - hosts:
7 | - tag_class_gateway_servers
8 | - tag_class_consul_servers
9 | - tag_class_services_servers
10 | sudo: yes
11 | roles:
12 | - docker-hosts
--------------------------------------------------------------------------------
/play-035-wipe-out-containers.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - hosts:
4 | - tag_class_gateway_servers
5 | - tag_class_consul_servers
6 | - tag_class_services_servers
7 | sudo: yes
8 | tasks:
9 | - name: "stop and remove all running containers"
10 | shell: "docker rm -f `docker ps -qa`"
11 | ignore_errors: yes
12 |
13 | - name: "stop and remove all docker images"
14 | shell: "docker rmi -f `docker images -qa`"
15 | ignore_errors: yes
--------------------------------------------------------------------------------
/play-040-consul-servers.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - hosts:
4 | - tag_class_consul_servers
5 | sudo: yes
6 | roles:
7 | - consul-servers
--------------------------------------------------------------------------------
/play-050-microservices.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - hosts:
4 | - tag_class_services_servers
5 | sudo: yes
6 | roles:
7 | - install-microservices
--------------------------------------------------------------------------------
/play-060-consul-agents.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - hosts:
4 | - tag_class_services_servers
5 | - tag_class_gateway_servers
6 | sudo: yes
7 | roles:
8 | - consul-clients
--------------------------------------------------------------------------------
/play-070-launch-registrators.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - hosts:
4 | - tag_class_services_servers
5 | - tag_class_gateway_servers
6 | sudo: yes
7 | roles:
8 | - launch-registrators
--------------------------------------------------------------------------------
/play-080-ca-gateway.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - hosts:
4 | - tag_class_gateway_servers
5 | sudo: yes
6 | roles:
7 | - ca-gateway
--------------------------------------------------------------------------------
/play-all.yml:
--------------------------------------------------------------------------------
1 | - include: "play-010-create-servers.yml"
2 | - include: "play-020-baseline.yml"
3 | - include: "play-030-dockerize.yml"
4 |
5 | #- include: "play-035-wipe-out-containers.yml"
6 | - include: "play-040-consul-servers.yml"
7 | - include: "play-050-microservices.yml"
8 | - include: "play-060-consul-agents.yml"
9 | - include: "play-070-launch-registrators.yml"
10 |
--------------------------------------------------------------------------------
/play-zz-kill-servers.yml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apiacademy/microservices-deployment/8ddbaa5ebfb14d6ef6aa2a088b5603f058646e02/play-zz-kill-servers.yml
--------------------------------------------------------------------------------
/roles/aws-server-creation/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - include: "vpc-creation.yml"
4 | - include: "security-setup.yml"
5 | - include: "server-clusters.yml"
--------------------------------------------------------------------------------
/roles/aws-server-creation/tasks/security-setup.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: "{{ project_name }} Web Security Group"
4 | ec2_group:
5 | profile: "{{ aws_profile }}"
6 | name: "{{ aws_security_group_web }}"
7 | description: "security group for web servers"
8 | purge_rules: true
9 | region: "{{ aws_region }}"
10 | vpc_id: "{{ vpc_settings['vpc_id'] }}"
11 | rules:
12 | - proto: tcp
13 | from_port: 80
14 | to_port: 80
15 | cidr_ip: "0.0.0.0/0"
16 | - proto: tcp
17 | from_port: 22
18 | to_port: 22
19 | cidr_ip: "0.0.0.0/0"
20 | - proto: tcp
21 | from_port: 443
22 | to_port: 443
23 | cidr_ip: "0.0.0.0/0"
24 | - proto: tcp
25 | from_port: 53
26 | to_port: 53
27 | cidr_ip: "0.0.0.0/0"
28 |
29 | - name: "{{ project_name }} Microservices Security Group"
30 | ec2_group:
31 | profile: "{{ aws_profile }}"
32 | name: "{{ aws_security_group_services }}"
33 | description: "security group for microservices"
34 | purge_rules: true
35 | region: "{{ aws_region }}"
36 | vpc_id: "{{ vpc_settings['vpc_id'] }}"
37 | rules:
38 | - proto: tcp
39 | from_port: 1024
40 | to_port: 65000
41 | cidr_ip: "0.0.0.0/0"
42 |
43 | - name: "{{ project_name }} CA Gateway Security Group"
44 | ec2_group:
45 | profile: "{{ aws_profile }}"
46 | name: "{{ aws_security_group_cagateway }}"
47 | description: "security group for CA Gateway"
48 | purge_rules: true
49 | region: "{{ aws_region }}"
50 | vpc_id: "{{ vpc_settings['vpc_id'] }}"
51 | rules:
52 | - proto: tcp
53 | from_port: 8443
54 | to_port: 8443
55 | cidr_ip: "0.0.0.0/0"
56 | - proto: tcp
57 | from_port: 8080
58 | to_port: 8080
59 | cidr_ip: "0.0.0.0/0"
60 | - proto: tcp
61 | from_port: 9443
62 | to_port: 9443
63 | cidr_ip: "0.0.0.0/0"
64 | - proto: tcp
65 | from_port: 2124
66 | to_port: 2124
67 | cidr_ip: "0.0.0.0/0"
68 |
69 | - name: "{{ project_name }} DB Security Group"
70 | ec2_group:
71 | profile: "{{ aws_profile }}"
72 | name: "{{ aws_security_group_db }}"
73 | description: "security group for databases"
74 | purge_rules: true
75 | region: "{{ aws_region }}"
76 | vpc_id: "{{ vpc_settings['vpc_id'] }}"
77 | rules:
78 | - proto: tcp
79 | from_port: 3306
80 | to_port: 3306
81 | cidr_ip: "0.0.0.0/0"
82 |
83 | - name: "{{ project_name }} Consul Security Group"
84 | ec2_group:
85 | profile: "{{ aws_profile }}"
86 | name: "{{ aws_security_group_consul }}"
87 | description: "security group for Consul servers"
88 | purge_rules: true
89 | region: "{{ aws_region }}"
90 | vpc_id: "{{ vpc_settings['vpc_id'] }}"
91 | rules:
92 | - proto: tcp
93 | from_port: 3000
94 | to_port: 3000
95 | cidr_ip: "{{ aws_consul_security_cidr_ip }}"
96 | - proto: tcp
97 | from_port: 5000
98 | to_port: 5000
99 | cidr_ip: "{{ aws_consul_security_cidr_ip }}"
100 | - proto: tcp
101 | from_port: 8300
102 | to_port: 8300
103 | cidr_ip: "{{ aws_consul_security_cidr_ip }}"
104 | - proto: tcp
105 | from_port: 8301
106 | to_port: 8301
107 | cidr_ip: "{{ aws_consul_security_cidr_ip }}"
108 | - proto: tcp
109 | from_port: 8302
110 | to_port: 8302
111 | cidr_ip: "{{ aws_consul_security_cidr_ip }}"
112 | - proto: tcp
113 | from_port: 8400
114 | to_port: 8400
115 | cidr_ip: "{{ aws_consul_security_cidr_ip }}"
116 | - proto: tcp
117 | from_port: 8500
118 | to_port: 8500
119 | cidr_ip: "{{ aws_consul_security_cidr_ip }}"
120 | - proto: tcp
121 | from_port: 8600
122 | to_port: 8600
123 | cidr_ip: "{{ aws_consul_security_cidr_ip }}"
124 |
125 | - proto: udp
126 | from_port: 53
127 | to_port: 53
128 | cidr_ip: "{{ aws_consul_security_cidr_ip }}"
129 | - proto: udp
130 | from_port: 8301
131 | to_port: 8301
132 | cidr_ip: "{{ aws_consul_security_cidr_ip }}"
133 | - proto: udp
134 | from_port: 8302
135 | to_port: 8302
136 | cidr_ip: "{{ aws_consul_security_cidr_ip }}"
137 | - proto: udp
138 | from_port: 8600
139 | to_port: 8600
140 | cidr_ip: "{{ aws_consul_security_cidr_ip }}"
141 |
142 | # see: http://docs.ansible.com/ansible/playbooks_lookups.html#more-lookups
143 | #- name: Setup security key (SSH key)
144 | # ec2_key:
145 | # name:"{{ aws_ssh_key_name }}"
146 | # key_material:"{{ lookup('file', './ssh/public-key.pem') }}"
147 | # state: present
148 |
149 | - name: Setup security key (SSH key)
150 | ec2_key:
151 | profile: "{{ aws_profile }}"
152 | region: "{{ aws_region }}"
153 | name: "{{ aws_ssh_key_name }}"
154 | key_material: "{{ item }}"
155 | with_file: "ssh/public-key.pem"
156 |
157 |
158 |
--------------------------------------------------------------------------------
/roles/aws-server-creation/tasks/server-clusters.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # http://docs.ansible.com/ansible/ec2_module.html#requirements
4 |
5 | - name: Launch Server Clusters
6 | ec2:
7 | profile: "{{ aws_profile }}"
8 | key_name: "{{ aws_ssh_key_name }}"
9 | group: "{{ item[0].groups }}"
10 | instance_type: "{{ aws_instance_type }}"
11 | image: "{{ aws_image }}"
12 | wait: true
13 | region: "{{ aws_region }}"
14 | vpc_subnet_id: "{{item[1]['id']}}"
15 | # assign_public_ip: "{{item[0].assign_public_ip}}"
16 | # Trick: create all servers with public_ips so we can easily configure them. We will take public
17 | # IPs away, later using http://docs.ansible.com/ansible/ec2_eip_module.html
18 | assign_public_ip: True
19 | instance_tags: "{{ item[0].tags }}"
20 | exact_count: "{{ item[0].exact_count }}"
21 | count_tag: "{{ item[0].tags }}"
22 | zone: "{{ item[0].zone }}"
23 | when: item[0]['tags']['subnet_tag'] == item[1]['resource_tags']['uid']
24 | with_nested:
25 | - "{{server_clusters}}"
26 | - "{{ vpc_settings['subnets'] }}"
27 | # register: serverzzzz
28 |
29 | # This is nuts, but unfortunately necessary. For whatever reason, tags property
30 | # doesn't get set the first time servers are created, so we have to re-run this. Life!
31 | #
32 | # Thankfully we are idempotent and second iteration runs quickly, since servers
33 | # already exist: we just check that they are there.
34 | - name: Rescan Just-Created Server Clusters
35 | ec2:
36 | profile: "{{ aws_profile }}"
37 | key_name: "{{ aws_ssh_key_name }}"
38 | group: "{{ item[0].groups }}"
39 | instance_type: "{{ aws_instance_type }}"
40 | image: "{{ aws_image }}"
41 | wait: true
42 | region: "{{ aws_region }}"
43 | vpc_subnet_id: "{{item[1]['id']}}"
44 | # assign_public_ip: "{{item[0].assign_public_ip}}"
45 | # Trick: create all servers with public_ips so we can easily configure them. We will take public
46 | # IPs away, later using http://docs.ansible.com/ansible/ec2_eip_module.html
47 | assign_public_ip: True
48 | instance_tags: "{{ item[0].tags }}"
49 | exact_count: "{{ item[0].exact_count }}"
50 | count_tag: "{{ item[0].tags }}"
51 | zone: "{{ item[0].zone }}"
52 | when: item[0]['tags']['subnet_tag'] == item[1]['resource_tags']['uid']
53 | with_nested:
54 | - "{{server_clusters}}"
55 | - "{{ vpc_settings['subnets'] }}"
56 | register: serverzzzz
57 |
58 | # - debug:‚
59 | # msg: "helllooooooo {{item[1]['public_ip']}} {{item[1]['tags']}}"
60 | # with_subelements:
61 | # - serverzzzz.results
62 | # - tagged_instances
63 |
64 | - name: "Building Dynamic Inventory of just-created servers"
65 | add_host: name={{item[1]['public_ip']}} groups="tag_class_{{item[1]['tags']['class']}},tag_cluster_msa_demo"
66 | with_subelements:
67 | - serverzzzz.results
68 | - tagged_instances
69 |
70 | - name: Wait for SSH to come up
71 | wait_for: host={{ item }} port=22 delay=0 timeout=500 state=started
72 | with_items: "{{groups.tag_cluster_msa_demo}}"
73 |
74 | # - shell: "AWS_PROFILE='{{ aws_profile }}' ./inventory/ec2.py"
75 | # register: serverz
76 | #
77 | # - set_fact:
78 | # ec2_servers: "{{ serverz.stdout | from_json }}"
79 | #
80 | # - debug:
81 | # msg: "{{ ec2_servers['tag_cluster_msa_demo'] }}"
82 | #
83 | # - name: "Building Dynamic Inventory of just-created servers"
84 | # #add_host: name={{ ip_from_ec2 }} groups=just_created foo=42
85 | # when: item[0]['tags']['subnet_tag'] == item[1]['resource_tags']['uid']
86 | # with_nested:
87 | # - "{{ server_clusters }}"
88 | # - "{{ ec2_servers }}"
89 |
90 | #-
91 | # - tag_class_gateway_servers
92 | # - tag_class_consul_servers
93 | # - tag_class_services_servers
--------------------------------------------------------------------------------
/roles/aws-server-creation/tasks/vpc-creation.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # see: http://docs.ansible.com/ansible/ec2_vpc_net_module.html
4 |
5 | - ec2_vpc:
6 | profile: "{{ aws_profile }}"
7 | region: "{{ aws_region }}"
8 | state: present
9 | cidr_block: "10.0.0.0/16"
10 | wait: yes
11 | wait_timeout: 300
12 | resource_tags:
13 | "Name": "{{ project_name }} VPC"
14 | subnets:
15 | - cidr: "10.0.1.0/24"
16 | az: "{{aws_az}}"
17 | resource_tags: { "Tier": "web", "Name" : "{{ project_name }} Web", "uid" : "{{ project_key }}_web" }
18 | - cidr: "10.0.2.0/24"
19 | az: "{{aws_az}}"
20 | resource_tags: { "Tier":"consul", "Name" : "{{ project_name }} Consul", "uid" : "{{ project_key }}_consul" }
21 | - cidr: "10.0.3.0/24"
22 | az: "{{aws_az}}"
23 | resource_tags: { "Tier":"database", "Name" : "{{ project_name }} DB", "uid" : "{{ project_key }}_db" }
24 | - cidr: "10.0.4.0/24"
25 | az: "{{aws_az_alt}}"
26 | resource_tags: { "Tier":"database", "Name" : "{{ project_name }} DB", "uid" : "{{ project_key }}_db_alt" }
27 | internet_gateway: True
28 | route_tables:
29 | - subnets:
30 | - "10.0.1.0/24"
31 | - "10.0.2.0/24"
32 | - "10.0.3.0/24"
33 | - "10.0.4.0/24"
34 | routes:
35 | - dest: "0.0.0.0/0"
36 | gw: igw
37 | resource_tags:
38 | "Name": "{{ project_name }} VPC"
39 | register: vpc_settings
40 |
41 | # - debug: var=vpc_settings
42 |
43 | #- set_fact:
44 | # subnets: "{{vpc_settings['subnets']}}"
45 | # other_fact: "{{ local_var * 2 }}"
46 | # another_fact: "{{ some_registered_
47 |
48 | # Mater class: match subnets to clusters. Woohoo
49 | #- name: Launch Server Clusters
50 | # debug: msg=" {{item[0]['tags']['subnet_tag']}} - {{item[1]['resource_tags']['Tier']}} "
51 | # when: item[0]['tags']['subnet_tag'] == item[1]['resource_tags']['Tier']
52 | # with_nested:
53 | # - "{{server_clusters}}"
54 | # - "{{ vpc_settings['subnets'] }}"
--------------------------------------------------------------------------------
/roles/ca-gateway/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - include_vars: "vault.yml"
4 |
5 | #- debug:
6 | # msg: "{{ dockerhub.user }}/{{dockerhub.pwd}} @ {{dockerhub.ca_gateway_uri}}"
7 |
8 | - name: Stop/Removes CA Gateway mySQL docker container
9 | docker:
10 | image: "mysql:5.5"
11 | state: absent
12 |
13 | - set_fact: mysql_host="{{ groups.tag_class_gateway_servers | first }}"
14 |
15 | - debug:
16 | msg: " {{mysql_host}} {{ inventory_hostname }} "
17 |
18 | - name: Install MySQL Container on Gateway1
19 | docker:
20 | name: gateway-database
21 | image: "mysql:5.5"
22 | memory_limit: "1024MB"
23 | detach: true
24 | state: started
25 | env:
26 | MYSQL_ROOT_PASSWORD: "{{ dockerhub.db_pwd }}"
27 | #SERVICE_CHECK_SCRIPT: "nc -vz 0.0.0.0 3306 | grep Succeeded"
28 | #SERVICE_CHECK_INTERVAL: "30s"
29 | #SERVICE_CHECK_TIMEOUT: "5s"
30 | ports:
31 | - 3306:3306
32 | when: inventory_hostname == "{{ mysql_host }}"
33 |
34 | - name: Login to Docker Hub
35 | shell: "docker login -u {{ dockerhub.user }} -p {{dockerhub.pwd}} -e irakli+dockerhub@gmail.com"
36 |
37 | - name: Stop/Removes CA Gateway docker container
38 | docker:
39 | image: "{{dockerhub.ca_gateway_uri}}"
40 | state: absent
41 |
42 | - name: Stop/Removes CA Gateway docker container
43 | docker:
44 | image: "heinrichbutowatca/ssg:9.0.00-1.consul-test4"
45 | state: absent
46 |
47 | - name: Install CA Gateway From Dockerhub
48 | docker:
49 | name: apigateway
50 | net: host
51 | detach: true
52 | memory_limit: "2024MB"
53 | image: "{{dockerhub.ca_gateway_uri}}"
54 | state: started
55 | ports:
56 | - 2124:2124
57 | - 8080:8080
58 | - 8443:8443
59 | - 9443:9443
60 | env:
61 | SSG_CLUSTER_HOST: localhost
62 | SSG_CLUSTER_PASSWORD: "{{ dockerhub.ssg_pwd }}"
63 | SSG_DATABASE_TYPE: mysql
64 | #SSG_DATABASE_HOST: "{{ mysql_host }}"
65 | SSG_DATABASE_HOST: "127.0.0.1"
66 | SSG_DATABASE_PORT: 3306
67 | SSG_DATABASE_NAME: ssg
68 | SSG_DATABASE_USER: gateway
69 | SSG_DATABASE_PASSWORD: "{{dockerhub.db_unprivileged_pwd}}"
70 | SSG_DATABASE_ADMIN_USER: root
71 | SSG_DATABASE_ADMIN_PASS: "{{ dockerhub.db_pwd }}"
72 | SSG_ADMIN_USER: pmadmin
73 | SSG_ADMIN_PASS: "{{ dockerhub.ssg_pwd }}"
74 | SSG_LICENSE: "{{dockerhub.license}}"
75 | SSG_INTERNAL_SERVICES: "restman wsman"
76 | #SERVICE_2124_IGNORE: always
77 | #SERVICE_8443_IGNORE: always
78 | #SERVICE_8080_IGNORE: always
79 | #SERVICE_9443_NAME: "apigateway"
80 | #SERVICE_9443_TAGS: "ca_gateway"
81 | #SERVICE_9443_CHECK_SCRIPT: "nc -vz 127.0.0.1 9443 | grep Succeeded"
82 | #SERVICE_9443_CHECK_INTERVAL: "30s"
83 | #SERVICE_9443_CHECK_TIMEOUT: "5s"
84 |
85 |
--------------------------------------------------------------------------------
/roles/ca-gateway/vars/vault.yml:
--------------------------------------------------------------------------------
1 | $ANSIBLE_VAULT;1.1;AES256
2 | 37623563613534366561386234386232393434376134663033393265323039646563306639373964
3 | 6561313335633365393139653433366163316533326335370a376162383338323730633939316564
4 | 36663532363863656136633164626333353936383261303738396238653664396662626632313463
5 | 3738653461366365310a326666643234663133616362373836633439663261346365643031663866
6 | 39343661653734386132643534316662646462663939303632333136313234623931333966613361
7 | 32323034323864306434633361313737353236633166383133393237383262643665356639633834
8 | 32383134636533373166346366353464666239643633353836323866336233353136346132633733
9 | 37643332626266663432353338616531316266393861633061633930376664373231333538393835
10 | 34633531653938663666353531326464333466376636353664303963323037353132643664356339
11 | 31383936623737616538663463653632393562623339363962636566316561343838363539626263
12 | 63323238653665643833656561343963653565656666303062613038633738393030646665313361
13 | 36663835316532373139313065353930353636646331616462313861363334393065386633303866
14 | 33386431356639333330323835306430313335633335393538313330396631323538633933663037
15 | 63613366616234356661623639376561363964323439386661333634376432656536363564366362
16 | 62313830636539656361626466346664353766343037663435373164616365336133326666376333
17 | 33386632336335623466653330313535333663636634323930653738336363396238333662343933
18 | 33306238623464623065396262373137373064633230373239303831616439393036623631396438
19 | 63386634643566333635666539386162643336626232316664373761306232366431353963643266
20 | 64383163363532626230323236353762343532383132356338653733343961363131633666323662
21 | 30303362326464323063373366356464633632313365633031636362383864323836623164623131
22 | 63613535326335313464386365373032363835646333653133396332323836393437633236663763
23 | 61353266386632623935656236643166303661393337336136363264326638653563326337653734
24 | 34373737636231353335333439656239383963366238613131306439393634313338666462383862
25 | 30383435663561383739343738653530376431333330663066663134616337613534383138393462
26 | 36653961396631626535346539653864396564326362333865643934343036313630633939653531
27 | 36643434616134383031633230386330326662373239623166386262366437363637353032373435
28 | 63613935396266633538306135626563326433663063323238393664326161303932623432616662
29 | 39633336656630393232653937323631333066393730313438363637643736623936323739356237
30 | 63386534356132316537333262356635626132316537653936633261376233623161333064383337
31 | 32653936346239306464306135383930393433303164303566373863356262376662363738323764
32 | 65383136656464383431323131363761333239666639393230623164353030306165336462306265
33 | 34393231343431316532343230623232383866616334343338363534653630616236326464343735
34 | 37363831313939363435666562306535303864613439383066653336643130303764306334626236
35 | 63613162643363326430343636313763326265303630313965326165333730353261663437656664
36 | 35643539393135613661323463663666373238663438353563363262393037663037653762613036
37 | 65346465353834316132306231376563613033303335626236613935663062303034316661373735
38 | 66666265623039663161626630313961663564626238313838306530386565393863613038376238
39 | 34393035653461303836373031383831393262666564366234386631326666393462373937653665
40 | 32646662323138646539383066376630633665626331633338363666383834656362313336623762
41 | 33303233366633656236383366633665353330323135373533396438303463666662663065396464
42 | 37646266343764363863376466396331653334653630346636643765663665653635633033326464
43 | 36326362353163366363666165623134303838306232616639336132366662363934663338393339
44 | 35303664323632656530653462316534643230363139353664343533343963396338616534646637
45 | 35613130326432333933373765316632323865313531343538623130666632636132616638346636
46 | 62623939323133346337643230613338393630313164346232643861656236353834343565346161
47 | 34393261353438353661393731393661666435613934653430643165633535383661393465386662
48 | 63356561666434633566616162393334346563376165363531303930303436396666613535363833
49 | 36626632333965666135613766623031306139356130643734376363303733326666373139613766
50 | 33396238663434623864656663373130363334636631323533633463306364666237383064613430
51 | 30353635353832386530613833336235613938353638343332663830363864313164373036333539
52 | 63386534613434366266613336623061666233376631633532353233363036376133383530303565
53 | 33373431616134623962303433326130313033653133336530343936376334303534646433313562
54 | 30343666633332613937653239643263376431666536383564336362626631636539373739633163
55 | 64373234373935313332656336653931316266326236633066373034646630313635613938626262
56 | 33373838333833356132393334623533616134393562366436306266303632366336303963376433
57 | 31383563393461613961376166663563333232313735616465396465393236376132613635383232
58 | 31343433663437386232326532633632303533336631653166383161653237313033646263353038
59 | 32386263643464363130376166623465323130656164643966396262363334633066643762616436
60 | 63396562353932376534363737313565613333646239346138393862363334643630666338363436
61 | 39306338313831633236373566306334343530623664386431363030323262383165353964363866
62 | 64306161393565356434383765346631333737666436343830326636666137336161333931343464
63 | 65396238643231613866663162613339643434376631336339613233316532336166323038303063
64 | 36356162663236366434366661303933303134326137396663666166663637356433613861656462
65 | 65623838343831303736356437373634336631636631663062306433626638333937313636663037
66 | 64373739633234633263643030616134646262383533376134613162363664613636653938376537
67 | 36333366356439356432323030346666643466646166373965303963396531303664353236336664
68 | 35356130626137613433323066306437653563343435363161366362396166636132383564363930
69 | 63653735393435333764353236396137386161316236633834393334373166373738326661333535
70 | 62373935623131313166346162376633373065363463656639366661616563626161333762363365
71 | 38373462616232383365303964356566323461633162656433373664323136626537363832653861
72 | 64386536626664363235643765656136383764653835316338626631353734303232643864663866
73 | 36646332623838373139613965653266636633313739396265353265373266613337396562323864
74 | 36626231343464346161663835333764613935303561653163336338616561613165383231366437
75 | 64383332613935663763383030623134333632343638663264356635633537626563376135346634
76 | 38333833306361393536383838616439346363326262336333333965346439653339326335616162
77 | 65343839346266653564313063646136363766663938643463616363363636643230353239626339
78 | 35653038636664626534613231623236366437663139613264333963313565613238643264346366
79 | 65343636326265383961303539646432373034343236626232643439346261623035363963316230
80 | 38653035323930316233653262343732656461393630653237393964306132616163313363616462
81 | 33623030316636343234326337383163333362393033346431636164343466333166383832343664
82 | 63323133346166396265636430326632303037323861326430333761323634656233666235343035
83 | 32373261633536623936656635396161363138623230313064333832343139653236626237623066
84 | 30366366383333646339633461333164613236383865643666393139383636323431393362336533
85 | 66353433346562313464336635356661386562333436393261643435323839303265653266633236
86 | 33623963643737346263313738613364363539643831383438313962346635376332393931323831
87 | 63626238306635646231363836616234353466613330363030636339376232616633313763633230
88 | 64643431396665353330356139363834626330353436343838643132396333343266663236613165
89 | 62343330616663636232383164343339333565323239346434623638636361396661386530366465
90 | 63313066633564633238323162306634336666616338636435663437396135373434393566656131
91 | 30333236653165643464623038383961303237613931336537323836303133653538383434366166
92 | 32363462643662386533376431356136376362333565323762363562653265363032383866333038
93 | 35643264353765343432323465393431396338316338653366613432303135643962303133633637
94 | 61386432623765376163653963623733303230346335303865353361633862303235303433363838
95 | 30663634346162333763386363656662363464393436623934303138313037343938363037343462
96 | 61333861656533346234393764613832393738313565656136376662383637613634376661393831
97 | 33303337643364343638613636613738363830393233656261326139666233633737326434643261
98 | 38363730666362663334653938623336653131613735633133646134656566623162646630376135
99 | 66343732373365643538633564313138643132663166623235626536663834346135653065383830
100 | 34646236313833643832363365383333653439393138326333326231383966383739396339656461
101 | 33313264646365333134333339633938333763386238646361643066393363643638386532326234
102 | 33306631653363303865653563303135316630396164313035313862363162333266333263393436
103 | 62323632323339373231663666393738383862336134366137323636336364333631366462353239
104 | 36353732643536663939366338393334653132386335306465326139353435373137333738386435
105 | 65363134353664623930613030303734383736613966373865376561333436363639386565333036
106 | 64303930306234393532316462663130366530373466336137643735373038633633643965666637
107 | 32313863343038373339613433336161633631663135643836383635373738383038336665386337
108 | 61643462323236363932373535613036636133386565323238323132353837616534343865663239
109 | 66336462386438346331393161323938643632626239333136623932333461386338646331613061
110 | 35386134393733373066613039363664316230373963396461633564633063326466663533373630
111 | 38373937636264333165623338633065313931376565323463376434306537303639363161623363
112 | 35396638386239356239353135623962643132363537663236383636346266623732333535653931
113 | 62333039616465353134316334323736663961393165393336373664666533633661363734633464
114 | 31656132316533316264666532643331393831386466373334383638623865303564633563613434
115 | 66663937396636623838303139616161623561313339313833366134373835613562633034303130
116 | 33373935633831623030333339383335636561623934656632613466396635363333376463653830
117 | 38313438623732323237396562633838353230383735333434346638326365323264646261643063
118 | 62383637323830633135303034323963636330356233653865346166333137653431353835653433
119 | 37343737623363393831633963643835303631303130393030366566656534653833336164316438
120 | 36663332326239353436373363646238346664303135323138366161613664313763633630336165
121 | 61366331323633333731616637636632356631663766643633346235326137623239623261313461
122 | 36663638393465336331636166323864616434373036323166313361306463626262393337333662
123 | 37656334656165626431643137616363333263373438323731353531376164623261616334623536
124 | 36353161373361663130316663386361373065326138353962663763306665613331656361613465
125 | 62333861363438333231646664306133303262623431656132373731633239323936353765326464
126 | 37373166653632313839663964653366326630646233636134356334323238396535376563376639
127 | 31653137656462613937333234616631323931313831316364656261383232653262376137383237
128 | 31356666616238373363656132366137373634623731643766626162343031616438313561376462
129 | 35346532333038316531363937633431636665343535616133653363323866343430373038346436
130 | 63613539666264396634346237313864373862316132303862373537396462363733633830613564
131 | 63346264323533313364656565613362343930643966376133643962316634353536396234333764
132 | 39396434323636396431386461343834666561613539383633633836663465613636326236613065
133 | 30643830643231613135313062353336613233643035633432303161663830373765616266656130
134 | 61376432363164663031373466613535316139393861646339336237663164653161656338383937
135 | 37303665366264616332323232343366393863356363386539383863306435323836323461646263
136 | 65303331643633396363376135363535353265376431656237643636666466313530633365383736
137 | 36613864306134646562613062646362386633623334626631343232396266303961333438626461
138 | 30303265383837633534613064326330303733356137303634626564626431376461373036656339
139 | 66363236373337336665616263326563333332316238336632303563306136386464366239303031
140 | 65626563646238356136363461353763343838353436323932383434306139643665653162373962
141 | 38393938653534326338663632633338326431303362303965656237623033656436636238353833
142 | 32343732333333616565646530633433353561303631303037346366396434363238666666653832
143 | 62646131333162663732376639386135393663616635333838343064343866383537666639306662
144 | 30316338366563396438396665366639633932303332643936626461646633633637623063303965
145 | 37626163386262643238336235663934613135653736346339663564636664393665663932363065
146 | 34636633306433333961643138363265333663313036356136633266326638306362386337653235
147 | 39643264343866366632313336303137346663363134653639393430336562643363386661633762
148 | 39626163353962396131376330336538333737666166636464333538356132633034386134313031
149 | 38396332326534613930323939333661306537653062633362646236313962633066366431336233
150 | 30353763663563323062316664373864383434376337616536613635363531653831323761313861
151 | 66316634393562643163323764356664623530643930373135613638623932313062376138356236
152 | 65306438316464653062396533323934383336633666656335376261383537643534356562393964
153 | 61303034646263616232363735666562633664666262633138396664363931376663663934326536
154 | 65306239353762386565616237356564356531383465376566386638666438393764353065636337
155 | 35663138643763383361323062333830333338313965646538303066336161643232373436653965
156 | 37343837343763313065306139343263663633323265363136303662633665383963383537346631
157 | 31376463623266323037623132333665363934643332653039643862626564663631376663666466
158 | 34363931393464636136633063646462393663666461323038313931366261626334393338333637
159 | 36336138613630386165643433636636323264636532383566333263353730303761336139646232
160 | 36343963613836363536346362363233643335653866613162366238323530326631353763343937
161 | 63666439376361633565326631333865356338616437346334376239396131646534353864656164
162 | 31313735643535666233343136383135373837393365316264303465343337346639366637633731
163 | 34306436636632386162393135336665613230653437636264616434396161363333626237383564
164 | 64396661636532363833373331336132613162653534663966303461633861653634666566306633
165 | 36313830616239396436383339383636326134376437366335653864666264396535333538366232
166 | 62633630363938666336626535323630303437313130653935393634653830303166316263353561
167 | 35613232336535623066666566326133623337306666666661376135656262343665346531373636
168 | 62643964366533346465323132633765626335333461373265363337366335353130613132366162
169 | 34643864656532333532653434613133666637373466343464623030666339303434383364636334
170 | 62303964343639383730376435386562646566326137366339623365383639623561666434383834
171 | 65353230663138623835373736623963326537336463323865643131666432636531636634333662
172 | 65373833313038663831666331363735623935613338666565393862396631616237333934386331
173 | 63346537383862393464626138633635373533363235353966323239356534646165356530366634
174 | 63626662383038323532323337356131393165666162636533306534616636616463333863636532
175 | 35653864386530353965353430643733616362633130363761393263306630386638386134323539
176 | 66333136613736313231663330363031376636663932396162346665613061623936653136646137
177 | 31376332663931633238396666373131373630633737326534393732356165663837643732373264
178 | 30613335313339303264303861373865656662316561616639633838326632346265393531626434
179 | 62363935613663653362303164626436323231643030313934386534613363636531373137333563
180 | 64336561363334333130356661646637636434343762333436313165313236306164393866636362
181 | 32333730313536363665353662343733656664353934633761353933363466646532636664376635
182 | 63313837346631366131333933636463363837613865373166656166616232373136616239633461
183 | 31356534366165323432613162353231653762346666323632333437393232303030363262636137
184 | 34366334346334393530653038363861663162656162303766373130386164353139346538633038
185 | 65613933666432306362646636316434316364616331633735653534313162386361393462346637
186 | 36653966396334376264393264303161336665303436343061376639663037373565336633326163
187 | 38343730313635383439326233396530323065313336396532366331346362656239373961613434
188 | 62326133396366636332623465393261336633666432373933366530363637323061633133343231
189 | 61393636343233643235656632626561336334653232373264616330633964646230636637336138
190 | 30613939393630323861313763663065356135663765376462393939356564626666313537373630
191 | 35356530303432336266613735356264636566383134656131663236633537353865363139323865
192 | 36323761343565393834346266626432383165643336313334323333373534313538316637373561
193 | 66303636353064313861323964323966326464346538366531363631623464626634626665326365
194 | 37336339343737366237336234336664363437306165646165323065653561313539646134333031
195 | 30646134356336373933663164336462303238343639633461373734373264306637313739363835
196 | 39656462653030373365343636343064323833333762613833613234336433663533343465333937
197 | 31646434646335363263353861666231366261376464363436356263363739336663396633666631
198 | 62386332376237653363376139643830333436363065343034633739313237666335393735636465
199 | 66666334636338313835616235376261373761646664316539323662356666626363343463643939
200 | 32616234363939386633343437386366343733346665363639323063636565363965376437386565
201 | 31326230306564626139376463333933303666343730386538306263613062386133343530376331
202 | 36616132666362366634306231636465336330623839386462376139633838653737626330313863
203 | 63313630386361616230333830663263643737346165383162306565323139613863613030323538
204 | 36383862356533313532633965383431663231386636643132386535316435623063613763366561
205 | 36613331643636313062393161623063633832356463643265306461373061353139663465323563
206 | 31313562396433333130666130393062393538393334633632393835353539346261656235623333
207 | 31653061623736663031663737343332373563623466356133633864616538333836306530356337
208 | 65343865376530383136343363313134373664633037373161613863386166343138373036363035
209 | 34396531343164373032633132353663303334356330653261393435623066353635366566306336
210 | 36663939373861653162623361313665353035323338376662393536666266323863333838396661
211 | 36383732653839366232333237643661343964653232303962353635303462343930386633353833
212 | 36656133656133613861646265343230623433663431393231363034393039363534663633323030
213 | 66393735383836653632646337633530313761383466613138653363313865316437653435356263
214 | 36313661323431383534646138656330623061626635666434376465363639323534353866343833
215 | 33376338303137393734633430376639336136613866343665323835633132386638356236643737
216 | 32393864613332366364306232376439343566613235353361353531373961316336636538303933
217 | 61346664346139363734343135323337613438353365393266336530633730336231326237303837
218 | 64623832623337646431643964626235373233386539343636643133316463373132646665653361
219 | 35373866646263653037393966333234353830376639666530363962333933653338336665386165
220 | 63373739386338643830323531373530613766343130353634393164303965313865303632663165
221 | 61386163633132396638376464666435346434376439393066363565333839643930333336363237
222 | 31303635326663633731343831333532303436353833626232633038346139343137356431623939
223 | 30326465373063633734366132323239633033623438386537306437623262333166353437363830
224 | 62373262643061613236376161323132613963653466333864393461346665616234656263653638
225 | 39643533366235663730653466333932306261353661393364616636653863363739646233336464
226 | 39616335333132346537663233363435393764326332313664313066636263373365343430623233
227 | 34396633653536323539313633326433323964663431323766326438646132653838323165616664
228 | 33666262396632386539323237303238373163656131346562336663383637383235376363336138
229 | 61646664396233363165373932343036623763613235623261383564646135353231343861626133
230 | 37363433636461616232626166303765323338363437383665333566373739316232313034386639
231 | 65346638303135633130383665323964366530663634353131383666626365376430393232633061
232 | 36343935376164616131373433333566303565623362316362303336376435306433616165386535
233 | 65323461306531323139366630643735306361353234613536333665633764333161386331343238
234 | 35356236613637613462623238376261313037386633663533306561386366333730383139353239
235 | 30383531343061353339303362386466353562343535373233323465356461643662396132633133
236 | 38616534366539643133393534333833343265393432396437623265653332633636393433363663
237 | 39393162373564626266663931306665393061326166663135343731653135633463396630353936
238 | 30663165323639326233643063316162633439373836353632346435616466343963623833323130
239 | 34646162386136353864316664613433366363666439613732353962303432633539663132383331
240 | 62663865326236343630376661373833613639623361643462643537613566356665356534653861
241 | 39636533373961633932663165383031626163383364363066326430336633613563616530653965
242 | 34643431366231626138623739636532366232373164383165373264363333313765653664646231
243 | 33663337376134396637656466306330633633346132313434646331663734643165356139333638
244 | 65376366613238653238396434303432326461333566393966346365353333306563616463316330
245 | 62373737396432626237653731333561303035613165366163393437633536653863653031366538
246 | 33356336626365366565633033626138373831326361396364633066336163323333383939346337
247 | 30663564373930363637393436663138313834326163663331326264383237616637373962393138
248 | 39366430643631633763316666353031613062663231616361333734383635653832613964303533
249 | 35376464336135643230363931343832366433333834333432613639663261356162393436396230
250 | 33313662326162303034643264653461393330383064643330386532663538643063353230373931
251 | 63666631616439316636633335323531653539326131613364316561316431323162633266366532
252 | 38653834366166643861396231663039393634363133313036633465653837323361393161353133
253 | 32323465356632313863326137316638613662383366326465643264386438323436663931666139
254 | 39323431646433616565653231623032383433353437363866303134653232313365333561376431
255 | 65613362663731376234383762386230656137333439386366323738393432616136613465653731
256 | 38633330323934346464393632653561383138323865623262663865363533653935613338336235
257 | 31363039313764313434366532616232323539356664346666613737303935623761666335316165
258 | 38323331323739376538613736343564383364343933646234333137386535383038626433666539
259 | 39366331616233333339356235633437336331343166393830633931633466393435353133356464
260 | 64613633306238613731636364353661343330386664616637356634323933656334346532303463
261 | 63616231383966666431653035373432303862363534383037646239633961643930373565663933
262 | 33643834386361663739613636326134393564323664616231333363623262333036313537636164
263 | 62316266376665646361313836633432643663303162333066613263363730343131366565633032
264 | 62346236663463386539666661313365333630363332383239363561393339653230343035333966
265 | 61346431343265303339653436396235653838623963613634343936663362613337323935393631
266 | 63353035343864306565356263306438613237656266613836386565623964383732363831343633
267 | 66663261323132396530323661656561303436393233316230363539353634663339366135383664
268 | 32306163393637303566633534623232643965343333333263383563633439643965633064623236
269 | 37373063623736333435656463333738376634336165336261343262363466336261353165343564
270 | 30373732326434366639306433663430336231323735393630306630366562333839646466383565
271 | 32383166333831636665386339636638623733643230323134303833633431353535386134343234
272 | 61333965666263323531613964656139386636303963636137323233646433383464346330613936
273 | 33616138353438326434336132623064353637313765303562396365663830313763356139656563
274 | 35393632386638393232313633366338373038613833643238323036663235376238383164616666
275 | 31626162383230623036633036356661383863303431356264643364333063663365616536623566
276 | 39333333366664646433636638383231323563306266353138306663623334323532356331356232
277 | 64383539366461336537316638613161396366363266386161363237313163363235653831306132
278 | 31386332376564656165613964613433663035626631613063393238363233393033626134343436
279 | 61346166623562333330353835333433353634343433653439616535646538353563636433353662
280 | 65303939313166613766333130343534313334376138633234346234356136343964616363383663
281 | 30393538363261626364623161366134346432313533376135643331336439346233396431393830
282 | 34306563383636363664616331343733633264383233363737656361653862306265343537386439
283 | 63373237336131353534303936396166376134663630326664326338663238326635326462343239
284 | 63643061663632316631346663366465313466373563653962346466363837333863303739366238
285 | 31393861336663633538393135663733303233313539666233383533393263353938613763623536
286 | 64393761363431313437396336336431663163626130383736653736376132643734323431323231
287 | 64346266353561373062343464373330306632366566626133333062643064366266396232373664
288 | 61333565393335653231376566646633663839616464313062323038373236663630643230623232
289 | 32343331643163623766373937303531646566316134383834383761303166333635323564623537
290 | 62636333353031363864626633356533303366653061353061663334356431376134396634323034
291 | 62376635376466373565623965373633633366316466396565363238396135366531356437613730
292 | 62653030663038666239343762646235613663633732363235343932326366306238326566373437
293 | 38303039643231653336653365643532353063643237303265373162343661373232343066666439
294 | 38653634633631373532313563653661336135623734633463313433303638616237623139346238
295 | 62303637643136393532313362313736376361353665653138333562363833313633333765343535
296 | 36653538623439376637356464376539306266373034313466616139353837363265613066316466
297 | 34366164373536613666306163343963363137343662653163383030313664373566633134373233
298 | 33303966326663333038613439333235623936623866356437386564373335346363343834356563
299 | 35373339313664333064373636393536656432623066633934623232323664356665313438336466
300 | 32653561656364353231643733343932343864633633323031326665613835643232626639623632
301 | 30373162633365366632636531303266633261396232633036393765613662643537623262656434
302 | 66666438623639373636613333653239316637346561613861363630663038313735313833353432
303 | 30386635653330663835623563326631356336316635393037613333316165386437313431356333
304 | 61323739353239616534336133373834323135336164303263376131623432366166383638623065
305 | 30656637393838376366613335383866663666616564303165633034613561353839646135363163
306 | 66626430363464633766333165313633393536666433383137643035386564643439386437333132
307 | 65396564393335383062373633386464303335666363303038613030356665353334663431363536
308 | 63636263626264386433343836633737383633666161383661636335336533313834303563363431
309 | 63373537306438306134663564376437336635303239343466633662376261303931643462353136
310 | 61623263353163363136326535323563393563316131623832363264376138313332373566343062
311 | 37356533643536633861383661306338623538623364336132343033393338373262613535303430
312 | 32666538303261663932323235633639343636646134323966373639646232653439656634336136
313 | 66626165336138376136373236623530333863313737336663353334346432656261366661316436
314 | 39616239393863306236306161653762333765333833613833373036333439316339306530666265
315 | 34616662663364376661306337653262643537626465633631366233363634623462333235366137
316 | 33396264663762336533643738666166343961373132343062396337323537616461373135663062
317 | 33623634616235663239376635333130613337626334666338623561613134643763353233613866
318 | 35623838613163323230613866303937666430623432366266326661303265656430626635613061
319 | 62323264363332633430313439653063643035656164353338363064623464303131303161646433
320 | 64383735636630643231353833396332623961313036636161383861636335373565666330636238
321 | 30363636343239336565343637323865626162303330646564373133633062613034346332396566
322 | 39306366366564353436353132323036623039383966646362653363666266613930613266383661
323 | 64633263633234646466633836356133393332653938393437366232653862643532303430323562
324 | 35613331623337366631616165373761653933633837636562646464336636356633383235376630
325 | 31623262346236633064336265633462666438393364336234623039333437656433623836346535
326 | 32663661376533343135663131313439643064353163373336666636653938383061376233323135
327 | 65353566623234333835653534306338633637303564613532366534323563393239643838643335
328 | 63633737316537306236663430316633666564393839363966373364613034353331636337356330
329 | 33393061666639653336646230393862333263343532336137343034353962646162303363383961
330 | 34383430653164303166393937373863306438313839346466376130303938346661613537613237
331 | 63383133303630653033623937343531316633643337303134373966323064383334396339386666
332 | 61626165303966336163653534396635623561323035643037633430623761373937383262646465
333 | 32343330333964323437396561373637663333313865366336333063373637623837646439333330
334 | 65393365663532373939616133306561653039623533356533666164333736343532353332616238
335 | 34633631623230333734306535323965356162633535346435313463643464656436636365313161
336 | 65303764393239306364306434333662653939316133626164616237303364346662666536626334
337 | 34616661386132313236663266383235633064316333313861373430636130626536353232326365
338 | 34373265613037323539656234323234663330363337333337623838306663313661366330366533
339 | 64353866643333326635343937376538613734356633653330313131636432353664636461323936
340 | 38623530613937643035393331383337363631363630616635323566353433666335346333376666
341 | 65326335373839623962653937616131343865306564316338633965313436383166386163346665
342 | 35366162356632353034366339646635646135353935393834306436336565333331353331313166
343 | 37316130356363363532623434373738326436313861373836393438386165393064383439626439
344 | 61303663373665626138313165366132363332653465613265626265323765353532656362666165
345 | 38346333636462363039353635626333653964643832393232356634663933646531633466366434
346 | 30303866613761663039303634666436333962663733306462346134306566323233376363623938
347 | 31623064363439613231646163376430663334343033333339383436653732656337306438326630
348 | 63326631663339383636616161646339326163663265303266653763306336333335396637353439
349 | 61343764653439346432383666626630666434613966656663623536623834363638343562303531
350 | 61343032666239633034666133356337306462383531383861303161663962663532313433623430
351 | 37313965313637383465396163616165646130373139323437383331663936386634346433653331
352 | 64363962366439303466393137306539393133376361663166633339323664653562616134386233
353 | 30356266373039633138646561313732616635343066633662653134656562356262383266343663
354 | 62653134393765353234343161316633303737313934643134613130356139623933336136366332
355 | 31616131363731346133323064343465636136313433316438336533353333623234376539633130
356 | 66376339373037316465363562643164353332643335346530366163343366663863303337663231
357 | 30656132333531336136356661613065633262383737353335616130366338366562316135366532
358 | 62326131633036373532653030623961643363643065343730323133306532323662303062303539
359 | 62656366376431613230663139346637343666356664643862666634666632393832386564343566
360 | 39376662656261383732633630663066323032313935306230363461626434396637396538303764
361 | 66333962316535306637396661366538373038353365343331643938376262656531346437623830
362 | 37346135373139313364663935346162366136636131303931383061366264353839353261386136
363 | 62356239613535313566663531613734306461636636336365626536393139653262383330613236
364 | 39663533346431666232396531323063643761663236646233396337643036356663616564626330
365 | 63323930323039313839616364386163613435383133633534643932373439376266643333666166
366 | 61303531333932366331343631396439366432393030636163376264343436656364353566633866
367 | 39623432643433633937316531353935663661653537626535653032366436393032323137643536
368 | 30383032393931333134623639636135306334633038626531353139303062353634373065343462
369 | 38383638353565373863396262323562633039386130636539386139353764313733393535353839
370 | 62336564636532376463313234353138363930366263393331616462343764363161373665343434
371 | 62653661653036636666646532313432643436326438666562653036313834396536363966636465
372 | 65623233326438356536653032303034636365303331623738336237343733326433303261386233
373 | 34313362346132336166303138326536663366376533326131373462356562626264653961353437
374 | 32333464326139376465396231323630663161383036376331396235373932636662623235346632
375 | 65316566373236376532626162306235623062353530363035663332666431306134663962316230
376 | 36366165306566643964326630646136336430356330633039613237313331366334383531373838
377 | 31363431363365613734633731653437666162336462623364616438616539366533333665313930
378 | 32643437626433323234383532303436343230313561643962363931393830383037623062373165
379 | 31313861633432643932306535343561643662333761613139396562623062626435643332323938
380 | 36623262363535616164343034616232323136383834326661336537626236356638326363623435
381 | 31373461353639646563376261616433313862633061666633366633343664316462386333363037
382 | 66386562393764633330373032626132633562613534633137323164366166386432643463316662
383 | 37323665306138396633346166613031306132376638626436313634656331656534616334353738
384 | 61353565623039363262376236643766653766323166643765366339616566303535363162653135
385 | 62343439653937393030303134303538363832653236613036623061363063343535343135303866
386 | 37333334336536333433356535363665366563303032366638643463356464313762356334643734
387 | 62393063376365626566616362373865613035653363393136323233333338626631383264323461
388 | 39613936373836666463393532396332336365663531653132353931653432376439366438623539
389 | 34616661336332346665366632333034633362336261313036386662623539373663343333656631
390 | 66333036383633306336323730663934616431306239343633613133303764363130623133613831
391 | 31353864616634376536653738353364626464666536616332643534383937373231393733616632
392 | 64323837303735326330633132313133626463396635656631326331333231636633626462326638
393 | 63666163363430306137393134376662303766626236353733363664643434353130336466646663
394 | 36353564386265626631646435663036626463616565346535376666376133373936336234613766
395 | 63313035363333613363346637313632656339636564313461393139636233373462303930653435
396 | 39303765663433303035613436323962363536636162633566373437356234653838336166303964
397 | 33366166343230376436346165383739633063646432383332643837303232616431376530383239
398 | 35626137346130316161356637633437666662386664666339376432313336303231323663306664
399 | 34396234653866343565613362323166663965653939653137393665393466346435386639613636
400 | 61363264383364646262653438666563613034633631343536643665303338373839356135376535
401 | 62613465633234336638336266316131316633646632636262333039656137316139636430333466
402 | 30356533373931333036313033663762666539353034633461306332363939326538383466326137
403 | 66613365343563363837663862383834313265333835633961363939316162333434653639613538
404 | 31353962363037653433376333356530383861653166333837663262323166343338663335366561
405 | 35613066373130343038306635643462663931366366323463383636636363366531663032373436
406 | 37383633343335356331376332316639346465363739623238343262346261653534363437376432
407 | 63623265626561383038356433313832316139303865353935643165333937303530346363363138
408 | 34616138613236303238643164653961366366613735623764346662333934316333663462386234
409 | 65396666346161656538613163663732393039326464313163343966633937383233353537663530
410 | 65333238643830336534383937613339663837626133633634333534303465336135643661313735
411 | 35616437663634303963353665613364323830306266353166393763383732373535323261396331
412 | 64363035393861663764313065306361623935663364383134653661653536656439343836303534
413 | 37303639646636366531643131626336383262316632373136363666653861383239333865636630
414 | 37343338303139306138663662356264353636363965306330353063366536306239666165323035
415 | 39333235373233333932393635303065376132613634636234336130613763643863306532356133
416 | 65323439313263303237323263323134326436666262363737663037336237616538366261343135
417 | 61373261633733623865363930326434663233323664343136613834343935613865633738343133
418 | 65383262366665633233663432656539353064326136653136373432663132353937343864333137
419 | 64353365396333366430383266633738303366373034643936386665396137326432626263643331
420 | 62613531383539633633393334626134633431326438303264623935323531653836373664386231
421 | 30343232303239666361663237376666323337386263653339633534353463643363623262633137
422 | 66663532376662663637616633353064396538643135363663623462633539396234613166656365
423 | 36303763646236343135666664353764326661653934343532383630323034353764626132643832
424 | 63323365306537636661653038383535306638643865643537616565313262616438383462393661
425 | 65306564366536303164353033656261616339383964373863346465386365663663646434613232
426 | 35633337646561663733663835656466303839333037613766383830356661626336666133333433
427 | 30633231623538303065653638643437363065316631363236306330343865653966313163646364
428 | 38356464373066666665366263303031643935313466313063313866393838343537376231333936
429 | 35643738343066376662373130653232353766323761626164653830653763376563653530313333
430 | 38626639376530333363333862313232633031663366376136346634323536313265663334663166
431 | 65303763373364613961643034323966346431383066343133313139663266313631326238306131
432 | 30623664643139333265373934303735393361373065363032653665383666316631663638373534
433 | 65383838323939323732326638626564643037656630383730333764306137623135646262633564
434 | 30626531383337323662663433373637393536303465336235643761616132316335376530303064
435 | 63663964326430393861653035653837366463333662343439633136633164323037613231663835
436 | 35663536303933303366653738643662343861383762383530613839633939623362353561623038
437 | 34356432383231623665323362633064373033393536333238336336623666393463343036653532
438 | 31356164373031313464323432333235376232383033336139666231663139373333383838333833
439 | 37663763366664383765623934663537636565393135346331303330366462626163386133393236
440 | 34326332313566663938656165656630343539353562623434326465383139653737323033326265
441 | 63316331666238636330616239373838353530366339636437363232313130383466353663636663
442 | 31326432623862373537336139343466323335343937663439383465323063303835343033323462
443 | 62646531633435363831626339363738306333363432333930643061623737383263653636343564
444 | 65646537356337396538343736376231333933363434663162333765376630613232613438353433
445 | 64393166613131376163343037363463363730633037313263373636366231646333613035323331
446 | 64386663656331363739393763636636316265353538356266623635333962396534373832343461
447 | 61633365353666303839316366356461653963646365396636613138383235633931303032353032
448 | 39326337353963326138393236666163636537666561306236326265623764353935396463663138
449 | 31346362393130353035643738396464306565383138313834336435366461643631643632303361
450 | 65373434333836363135303430396631373233643238303062383562343663653366343934323565
451 | 61343233616233373437663234353963633832373534353764386433363366303932626436666463
452 | 36656264663937353630356666613534393363333162326635326636376339353164326163376261
453 | 31613730333739393566323338316164336161666335323164303636616334333366396462373035
454 | 30353864643166346231303665326463386234663661316264656130326531643734643161313739
455 | 64303836663536623565326430316364666662333564643965366665316134383135613135366535
456 | 34623666656633643565383636333464316665323538373062336239373534353962616161623266
457 | 62333533333163373734393638363565303633613362393035333439663335303834636430303536
458 | 30656363636133626131383239663663633633323963333664326139323031356537343564666164
459 | 61616132303536323138373830383463346366313863393035346161616235633338333434636536
460 | 61623036336135646335313434633831336366343565613734353163616334346232663164383734
461 | 39326134646436666165343064383737393634623962333963393666623631636366643634646536
462 | 33613736616135383535666333313064313032376134383061306666323964626264616631353830
463 | 35336165636362336339346265366463663065323161633939623838653364326636323065323232
464 | 62666365653139656238336432656530613337653765373233656133613832336133343063373533
465 | 36303265613065353963373061336262643533366663313932366637306235313335373664353130
466 | 35653134346432323562343931353134613865653630316334313165373264343465333938613637
467 | 32653937396333336633343466333838393934616330663164343630616463313733363863623666
468 | 33623665336265313461396132643536323838393133316563363533336262303136333164663032
469 | 31316262323230383061313564633834326135316438393430636431633662336635373961306363
470 | 64636163336530653764633962323532616137316663376436623163376366623430646165346438
471 | 62393734396538343534343964346431633539313063666464353262343332303261633639303561
472 | 36613632633736343462363530373735616365306261393363646134373637623935313230323764
473 | 38356662336666363231373437666639313339363265313833356332656639613031353133633061
474 | 62366638373563376237653931343034656662306535623339373838323637336266333065383139
475 | 35623461663162633835626232353062316533343633393236396461343364366131613463613564
476 | 34366137333533303039366533356330656632633132363962396162636230316537316235336431
477 | 34353732363062323535383639313032346661303930613162383031656634333233633830626338
478 | 62303937393262383135306565393438376536366333373562313136393432633961663465386366
479 | 63343836623433326434313538306161646665643865623036373235336563373033376535316530
480 | 39363835393363373135303438626266623664346566333739636438323537626661346536313361
481 | 65643039363635313638343938623932306562393234646435626337646263323631633632386663
482 | 62623962353865363538353338623732306532303465656462313132306138356535643661396535
483 | 38663765363632633464346439396234366366336339316638383138643133316637373533363433
484 | 37376632363933366665633534383666356437333839353363643363376535333136336234663431
485 | 65613664303862396661663766663332336262373533366566633037343662386362653664643561
486 | 63373333336463393232393339363561396134396536336663336161613731326664613265336664
487 | 32316634643030356434333534393666633334613735656665366331633665653066306631376262
488 | 34353463373136326165646431333062323366326431346239623739346463383262643664663630
489 | 36306366326562323536646463333132326464363531646666326336353562643230396563666265
490 | 36373530366537336266303730623937626664343134346461626666316337346530633132663533
491 | 61386634373539346332383361366463333639366236363437353235663939363432353535306634
492 | 32656163326336336564633132616331356336303861353339643533363466336537363961393332
493 | 34303338626130333064346135636365306364323861323634653437313237353934613566326634
494 | 39306238323036353932343839306636323463353263333833656666333537313937313632383438
495 | 62643436646561333032656631343265636664343135646463356333373864663330346634306238
496 | 61346437316665393061326466636533323864663435303039613366333431353231646236356635
497 | 65633966306434663536303839656362323563353733303033636262646439636430643731303630
498 | 37356330366334656464613565623531323238316566353039366430663931326336343238303662
499 | 66346532643863396534343866636564616638633263356264303362383439323461646336376638
500 | 38336665616638346236636562366330343038393539313330363535303330666563636462643936
501 | 62353364343632313362646236306237336532656664383065643033306266623837396438623632
502 | 33313465616464623836623830663030613365376366653662393661306163613163363338613963
503 | 32353865393731356239623265616333333030306165386133396439633638373066396132363635
504 | 39653337636664363135383539303234343830363564663961383862343665323063653434633732
505 | 35643330323665396535383261636661643235363435303837326137343532653939643165363538
506 | 32643032373864373964623134613462306563373032326438333063373038616230633636323036
507 | 33366361346663343231616335386132663933633265326435316365613963306436653164356135
508 | 64636331386364336166656536376538613336653633666237343032353763393338663831356338
509 | 65636362663965613530366239613266366430643438636166313763646630626163646262616234
510 | 65616139376465626631666164326534623238376634636638643434616363316566643635623330
511 | 64343134646434666339386664663939613462323561623466343736366662346463383133373433
512 | 66633937336361626631666434626332646166333331616232376663643435343362343132643631
513 | 31613334396564636530303736343737373834666138613436366138643331636539373137386661
514 | 33383833636232396261363239373739626235613535333636303463363663613563376230336133
515 | 61636565656561323863303430653131653465336637373961373839363630623162613132363132
516 | 39363461303334316464613632343034646537323837653134383139303563373365383962326635
517 | 39346162393562613166623166613661383236333734643438663333643539626435636362356235
518 | 36626534643733393061306635323366643466653334343864646264343561376232653430666366
519 | 66656532663666363465326336326462636439656338326334336639623431353264396365343737
520 | 39636631356636396462353030363363636531656362663837316331333439663861366364366136
521 | 34363865633036316238663936363065343832386336643734373765666439393865386638303539
522 | 61363561616566383634333836363031313233613335633130313036643066613765666331343936
523 | 34636661663662653731393835656233366463393237386662613965366637613235656335366339
524 | 36613234313633393662366235313833653761653432356239643163663962613337313137383938
525 | 37396538616262373232613733383932306536613164643661313561653137313266306231346432
526 | 37373139383466623666376436333766663233643837646239313235643063613063303932633838
527 | 33613937333066633066393536353333313637666565373135666161613363306138613462323164
528 | 63613231333466313336303335333739373037336664663333666461386436353132333531326433
529 | 30303535353636323630366237363534333438646237646665373930633738343233373632613466
530 | 33613236653432386536373437336230636565343265336537656636343538393461306130353039
531 | 35306165306533343364653264333535613963396334356462316331636330386666373634386561
532 | 30373065613637626438633166343032363762613432393934393561626337303463396465396634
533 | 36313634373837353736656435623838393163343737393230613736303437653364633932623966
534 | 62363438323364656331626136346636313062643761333532306635313861633564323263653462
535 | 30626565326238383838613132313838336234303637333537353266646233656635376634616362
536 | 34366230636137393836323536613333623134396163613465313430646637636633356537336262
537 | 36323730636430333530366436633930613663303835356562656463336161306530306337323765
538 | 30333738333363616137313662653935326464396232316437316463373163623534313130383633
539 | 64366235383439636534366265333638663634373763383130353336313931393035336564336336
540 | 37313264383734663638303464323964636132323734396233613337373531663362643265306131
541 | 63653933353264616261363235326432663262646535633065636239666237613235633439393232
542 | 63333430343864313364333135336338663738353535663263393736363032333335653737323564
543 | 65616331346332396135323563363030343736373638663962616565383134306466326263336430
544 | 31386136666264333463303664316236666331346462336236376365396335643332613032353435
545 | 66613064363965643166646632633939333831626530386531613032346138353732636661363031
546 | 36616632353666663432613462343333333866306438633662306138333761303766376439303164
547 | 30353339383332313234313836656566613963333337323230613963353032663765393331336330
548 | 33383462643964356136353332353265646532386339306638653834666566313433373334306333
549 | 32363866306436373066323233663364363464356533623138646664376361383665623434333733
550 | 65623864373139396363646361323231616164663436373830656532626230626132343431353336
551 | 33396138636536323865663038316138346461323135303839386637336464643664336134316634
552 | 63353630373032316137383165323661323036393239333130646261346136643464346237613230
553 | 31366638623065666437616136336365613932336535313365333534373166633761666463333037
554 | 64633834633164616231653534336364643131343765336536376138316461643031373663356237
555 | 64336565333264353330626232623261383834373465646132336137336236303635366335323461
556 | 31343739383937356235396331363839383139333832353936363339313731313034626335383833
557 | 30343364626332653239383237333730376134653766613433363434356233656637356130353237
558 | 62633263313738616664383435613636633363356461633463336436613865636130653361376138
559 | 32623561643838366664346630303034353336313634396161643238636231303334393335306235
560 | 39326266663862643930336333653638613838666132613533333038633064653062656338376534
561 | 35313735313739306237383766363030313265636130646132646634376530626161613738323535
562 | 65613437666431326463623635333430353363616636613333613364643635636166383531653761
563 | 36656533333038666561616161626361316634663036306336663536333430373934343066633035
564 | 63353162303030663632646261333962343436383133373566383433646166646539383336613739
565 | 35376136383833623962356633316262323139366461396235656532306637393437333334333064
566 | 38616531636266326564323361623165666634626565383534376262383266393664346138393539
567 | 37366463666362306637613737643331373737303364326562623134333135386339633735653033
568 | 36663166353038323730653234383864366565393337653265643238653337303338663131633765
569 | 62666435356131633461323130393030386135343031313730656261373862366163333032666138
570 | 39303666393731663339336664663538646537373733393435633966653365633435613165663261
571 | 64396333306539313239356536646634316331653862646231366538666561363561643936363762
572 | 65333037396232656234326331373434636437396639633935633739373466623534393637643434
573 | 30313564313437623836333434393431373235653831643036366334353135653833653936326236
574 | 62316230363964353639386364383039353835303433613866373335633263653039376665623339
575 | 66326665633463323837316664396138346332336332313261633436306338396236366539663461
576 | 39623833323331396162303234396131383866633932323766663266663936653061313861633264
577 | 34313033386562353136363338333564383137306465663031316233353338656364646364376661
578 | 33613234306233376436363361326631353635653036323464303732303137646432363764653332
579 | 38623264626563326337343738613437636435396438303862386332373536316232643334623039
580 | 61366530666137353766363333313161393731373230336134336231323139386234333334306134
581 | 34643133666662356139633733636332333564653532663938376463383762623134303637666566
582 | 35303866343932333166303330343636303466643239663034343537646536636237643737346532
583 | 66393230623838373938303361663162343930326561323636653536623264663738323263663666
584 | 30383733366539666664316261353333383638663666306165643863356561373366353262363232
585 | 34626565353337373239356336666565303662623464363131343534363961343664326532343231
586 | 38646631383938633330393036346533333435363536306665353737633330623234363237383961
587 | 64653237373736376365316266396266656662666434663530353539343461633932346363313235
588 | 31393832306662396466633437323637646230343031363631636136356539353839666664373764
589 | 64316635376238366637346435313436353535356630313665623265316263623764643833353233
590 | 35346337383564396437643763316463343166633965373738363662643161613333316236616561
591 | 30363130343635383761343364643735643134663936306133316264383764643631626530303536
592 | 32633637373636616539613365643366623065613363386663653833656536623833666134316633
593 | 38366436393132303361646635613034353761373164363661386238653832653938626462333238
594 | 38616433646431613164636362353661306431353232383734646165393132653736623330333437
595 | 37383939613836383435333837393738373934323934343335356664353633383330383565336566
596 | 35656130336435633336393033646535333063643131356238353131653832373037346331386334
597 | 64393666643161386264363637363531383864633935393634343565366261366636643563303762
598 | 35393839366364366361303236323036353730303130326434616632313363336436613333323133
599 | 35316463366461313138323930643537313039633037626233373130646431613238356532386462
600 | 38306636386466633935613261396634313465366264396435643031393466323262633465633565
601 | 34343561303135626265303637353265646233663861306239613234323463386539373230393535
602 | 35376136666537363063383930616139636266386333336439303337666266333761323630613331
603 | 31623835623164323731383938363239313936386537646237356663353762326564306433356530
604 | 64663131386335373664303333343837663461323236316261326433346535396561353731356366
605 | 38316136313336346561306533656131623531353364306335303563376131383566323736616638
606 | 61613938626138366563306263323365646463653738343130323339343863613162323863323562
607 | 35383862373738353832323833636165666339636564656636633435646139616137383834656662
608 | 62613939343339613162323537343161353161373134376666303439316135373662366465616438
609 | 38623265323966336334303937303336663761303436323033613535376237303438336334653938
610 | 34616361343363333366393937363565353137323537626337333737356137353634656339356435
611 | 30373339626364356530343363356338646138316634616166353939616265366361636234613132
612 | 36326466623636396238336466613862613166613434326563636634633738343739663033313265
613 | 34383832323738363832393636613731326135326466653465313335356363333165376530623039
614 | 30666531333134336532636166396633383831623739633563623566343238383666366461393364
615 | 37353634343462623437336266306639313231336533313933316238356662653334643938323935
616 | 64306263353664363837333536663933363063623331386333393633643364623464373737313737
617 | 63333132386139613263313265646435363433653866383636363763653831356265303763323132
618 | 35383631366464306336333963383031303734313132393466303661656132393835333134313739
619 | 30353134386338366562626166613464633563356532343936303734316562346537346363343538
620 | 36373161303538366431353963616132313465313561323837336635396161346338313239663463
621 | 65326635316436373761643930633438343936396630646264623535303365353533303636373462
622 | 66626131613139623636396539666136313237353235356636376461313630346662363233393237
623 | 62653035633135616462613737373636346162643434323964666535343732656430633637303038
624 | 61653732353232333534633930366237333539386235613462303235316432383431613262663936
625 | 31666237646166303433363165626137663234393862383139343739656237383638366538653466
626 | 36393136303831663339656133386635333366653334356635306633656437663563303239646339
627 | 62646665616330666532343337373538396633333336323833373938363136356164353062616365
628 | 32633733316537643336336265653264373038643265353462626330303736623031386436323063
629 | 35303766393337616436396434353831383166336362313832613534326236396565643832656161
630 | 31656235343262366532303937313461326137373638366234646336306237316436316235336530
631 | 61393435613566633539336332316633326530316166323634313561333939663433396638333730
632 | 62666632323138316238636634376331633365656335663536633564393131386338343663356236
633 | 30383435643538393434376236646339666264623437343264393639623633613862633636343963
634 | 62363364623136393835373038333931343632616262646432613533653734376533326363373735
635 | 36313532383564656362316364323865363063303430613731326131343837376232333232643535
636 | 61386131363333393137633465336130616636643037653662366164643839323431636532356162
637 | 63343066626536616461396362663839396230613062666433323562303363346531626565643038
638 | 32643030336639666561633061636439356232653562386534386562616438613130663030626636
639 | 62393463356661613139656266626531666432323664363263353130643230373436633539383738
640 | 37653963333737313031363734303638613831643431326165386337353233346431323762396566
641 | 34623131653163353534316662346461316239626632633030326232393037663361373036343062
642 | 38653063396262663037303863376132333465366364346334373531313234333536343437663231
643 | 33653861333364626336343238663039623532396330373631336161666139343363626232303265
644 | 33663761643735336163623534656331616564313239303061666439643836323034636436323437
645 | 35336130353162356461623438383831383563363637366633366334623665393062613762366334
646 | 64613631383135373239656639383965306462323563646332346231303131376637323131346530
647 | 62326662353163373631356564326263393037356433356661306136306562613633313464653461
648 | 37353232343534323732373433616134366264656461353439653165383666313738386339326365
649 | 64326564373537353665613563336535656336393035646233306531656662356638373038383137
650 | 34343966323863636463663565323532393631333566396438646438636431633931393731613861
651 | 39653664396261653736333666633065363061643434316535343032303663663236653537653735
652 | 36626332313538353936626632353332616462653362656133643738613565316466653039343364
653 | 64626665636165386539373563613964386236336237366664633238633239306134376563393865
654 | 32353366613639356137323464643337666566633330386235323161333964626239316133363661
655 | 62396237316531636639333262663966343031376435393132616133373862333338643431356666
656 | 66396539323435363862396332333535316230393138336161306661326131356432663838616635
657 | 31613964373139333831393665663633643765663030303365346363386335326364626333343663
658 | 31356430393139303765306239363963366261633430663236323662323436333135616338333037
659 | 34613065633337616634646534313233623433333230373661326139313635393634626334343365
660 | 36383762386334636239396539373931346534383737643239376630386335333765653838353766
661 | 30663238373937663862373462663865303338366134336162376336393336623132653233376266
662 | 30323465323433356666616333356532303261366565396563336239333636313231643335356565
663 | 30373734356163336635396662316665353135666461353933643130636463393837336636626432
664 | 32613639653530626434353732636537373532623166313839636662613363316162303030393132
665 | 38373339363231373438623866616164353361353238363635353536333830366465383230373563
666 | 31316134326262323966326238363765643835306561333432636434656364616362373831313865
667 | 30396536303630666365623233356264303064386239616130616633393231356666353039623238
668 | 38636665353436656432656461303766323363333437396362376232353935393039666436346432
669 | 66396463313839613738393731666464313264363730356239363430333031373932613639626661
670 | 32383964626563396437323962383363656162336232653764313139613734313130646234396138
671 | 66646432303633346235656261343561663536623962396233666636356436633536326465633634
672 | 32643235656134383038303630653163376264643331336662623463306233336230323332626537
673 | 62613761363063356430376630323331303864343035633933356637373030363530313731636134
674 | 39313636646365643132316239343539336138313634363162646365306161313334393536666163
675 | 63616634656263303831396532623064646461613038653562393235356237313639656563383337
676 | 63653438396134323933323964323035346633343963326263336263346632363562326131653738
677 | 31363666396266303730336437373436656563653738363031363035633739613266656534366266
678 | 32303731346335626637333362643961333363653738613839336439653433616139366531376565
679 | 33393938336439616666636437303033353532626661663635343534343936653066393566373763
680 | 31636234386230336134643430343563323466663834303261323062373330376633643465316232
681 | 34356563646663386261306135393836613266343330643233646538636636633832623935663235
682 | 35343832366337373862353161346565383035346633393562383034643431613065386262306132
683 | 64613066373735313962643662646664646564636135393735383739366232616435343564323062
684 | 34646264623336386236363339303838633436346536313837303465666331633237303263613636
685 | 37343633386161376538626663343831343337336465663630653239306563346564393465396238
686 | 38623430353634313139333933383934366337376138366466633836313235626536336166343131
687 | 66313733333333613438666663663439343566323839666363373731616163306261623565356361
688 | 30333030326663343061363936363438353335383430323964613634393039646563343766326634
689 | 64393835393964626338646335643637653463656634656166643632363664376135333934613435
690 | 31353633323932313562636436393439666661336535363961313734353731663339643932386238
691 | 65373561353464623634333063633464636237623133343762323333613939653739363232646262
692 | 38393737333232316139663363363065336331666234663133613230363733613364323332613634
693 | 31376362666339353363643431656632363665306635393333366162616464353533346433336236
694 | 30353331653530303861663437653036636332303537616634393335383739353763626161623263
695 | 61643930663766303664386430666637313035333234313434313966613134313636306432653132
696 | 66396663383966346332633765306439356166396536623537653665656534383836396635396539
697 | 31313565343035336566396335353431373932396237363834306264626432316231646235303833
698 | 39396333623239306437643338643961616631373834663737636433363131623366303037336664
699 | 62323130363366333637323134303031616535346363633065626437393064643933373133613131
700 | 35343162633239366365383365643835653239316238363363323633303234393232303662386330
701 | 63613630333135336532643737356234393734303863653266326663323066386634623537376231
702 | 34316133326137646439363962316638656565316636646661653530343434303566613238666562
703 | 37306565613932323765326139343031323364343932333266643032383031396463323838353663
704 | 62393763663865633738373035356166336461373462633965613565653562623030333739663662
705 | 64383766633362356137656137613732613434613935336530626435363938383964646139663333
706 | 39376136313533653132613031373165646534303763313561313237383937363033623834646464
707 | 31666130663466653733613435303332383837656466613164353330326432633131393232323166
708 | 35373936626131303565396135363538653465373138373033656366633737353933353264643261
709 | 37646365633465656366666139373937346661663737653032613834326235636533303339383765
710 | 36636664643034343730353064353334306339306662373232333531346532613637343263666666
711 | 37376165356237333839666562333665336162643537336331376164343963623231393237303139
712 | 63376439363564636637383163393765373962396635356132623939346265306138333663376461
713 | 64653561353365356134653936633064333635643230303830386162366563333739623339343763
714 | 66666631343632356330656166373966343232333834383863383633363838363735383230613838
715 | 38626534323536373432653035323732336431383931393230646234303934313966396537343537
716 | 63616536366136333963326137636533363566356231666237393436633465336563633531653439
717 | 64336132633138653064383736366232626239306566626436663632633763623230656432303334
718 | 33633163313730313831323332383964303261643730626265366334333639613434663735336134
719 | 32333066346239313031663866623137623064653239633730666563663139393163663834393739
720 | 66623264653733636436616430326461663363623035353261663064373366323563383238333233
721 | 62616663353662316532663239373064656233626262316535306335313834376533393439316565
722 | 36366638623630306465653265633236316334396562333865613364323739386565383963386363
723 | 35306236633561373434316537646333326261336163623233343864363635663431343131306237
724 | 64643136323039366635326165306134353166353162333136643064333265363930353531633832
725 | 37396263386630613164393339313735633863616233663239633630646534633932656365363964
726 | 64303931393462393563653237396363303232643464636665333439343532623736633665633462
727 | 65303837333036303564396330373338643737633766626333356534303563623039663230623134
728 | 64386234613365373865386335636162303537373536636464623734396632643431376164643662
729 | 64316330616665343461653564393831626238316236303234643965343365626562663233643436
730 | 33666565636566346665646233623466666633383534356338396236356137376334633734373666
731 | 39373336333264303235353461383739313365613033386434356430663338336636323332343533
732 | 39396566333262636132376235323030386335643364613535363138633362316630633762306432
733 | 61383963633032353261633030326231663038646338333134376537636261313833383262313436
734 | 62313138663463626231326162313432326161353937636564633835363839303035663834643231
735 | 33626564366332633130663335336230333566343034366666313437323337656530383635393364
736 | 32393633346330623632313130313837356136383036316338613539613036613030393933383561
737 | 34636463366566373339363630396636373532356430356538303133356262623462363235643861
738 | 35636134643233666661353631333233313931303239613037616138346462663830656530623734
739 | 65626639316230616461663532373461343438303461303365373335366437333730373866306134
740 | 65363866636365376335336334326536363336393766646630653431383563633130303763653735
741 | 63323362336638313563303936646132316663343265343039376339343765393535396266633936
742 | 36366238383564653931366664383866636334366366363037643163633865326534396366303462
743 | 39653736643266616631373762356562623634383930316630616639663364346666346233643638
744 | 34656638356161356663623931623635303538386139613538376461633234613138643861396638
745 | 62666137613230383464336163366166346636616331353638336134613266386336376134646462
746 | 34393030313930643130343736383963613536613636376435393534313235396635386639386332
747 | 66616462313431353065363334323263373133303338643633346234303632363666373765353963
748 | 34636166316531633465646164333336653837353835346466313130313337356462373538383164
749 | 65383438383163646433623461333564376662393330306235353261363562336637336637323735
750 | 63303939666632626265623966316133333235326362393730613761653635333632313833633833
751 | 64613736626461303331313138663336343635333663373565313964316635376333636435383739
752 | 38336261346162356639666431373735323137303833616663366139343838376432643964373338
753 | 31393565623963363466346163643364333830396364373136393538376633656432383835633366
754 | 63376337303535626632663139323837666439623666353163353135393130343331633761396164
755 | 62323234356135376163656437373737656631653761353238653262633631653464666464353565
756 | 64666532326364653761643261376263663138306639636138316535373234326635623334626264
757 | 30666639623830396138386138333062383138653464363839626535363166656565393764373939
758 | 39313933626336393161376164356637303836626532616331363230373837643239303838343264
759 | 66366566376138316532653666356130303261633263356661366365303764383436313765613633
760 | 31343537666564303734633666636233313039646130386164396361373639376538663235616438
761 | 65653266646530653234613666643436306636346365363337633030313634623433356430666462
762 | 39316231306262343666633066623430366237383461326562626634326531613864656635623664
763 | 61336262656666366438363539343763333535363432613738626566363966366563643530633961
764 | 39623834366562353462626631623165356632313533306532333037386232316366303637663539
765 | 66313936336134303265376334663037313464623338633565663935616533366132383364383031
766 | 36633662353030623834633534336538376432353734616261393736373730613736623036623163
767 | 33333130373265313937396264616532663034396462356661373463646464626463363763313332
768 | 34663639303237316535376165353935356561316238623662663866316135666566396434303164
769 | 38353638333361613039643239633030346361353739353733396162323164316330386338323064
770 | 35353063313739363134626266343039303765623566386536333065333631346262303863373539
771 | 35343231663366366561613063366363303131383936386361393539316363666164346532356237
772 | 63376163343631383965343939393936643733616662386665643966333531306435346433343138
773 | 34326461666464663463346162626565393162333336616434656537643032393562373832376435
774 | 64663837386632313363633363656231663737373830626166613832653564636134306665333934
775 | 31336263333335386530323939306532366561313361326230366466383330623061626666386633
776 | 61643539626334623164643137313338623233336136323936666437363134636661326434343034
777 | 32386334653937663566653563306439343138663061363833383638303932613631306466653663
778 | 34316432356237633531353666643837376166626266303466313237303832386531373236343963
779 | 34356534313861643965663136306539646463303136386365653735633562386330323462306537
780 | 36623662613539366134396361623437323134393734373831633435373335643537366232326637
781 | 35326538336563323161313037323834636236363531383935633963376639393235346331653737
782 | 62376337343662626232343861643431396233313962626530323333666235616131373765616536
783 | 64643933303435396132376339643032326330316432613931393461616164613661303337333465
784 | 64383531616439376537663261663536383130376432343830616432383665386437303961633033
785 | 38623366626433376363666161613436396564363438356233636362326330636361373539353732
786 | 63333165343836363331373231353535303664643731383036346330623637373236303765626135
787 | 65303461363331303633336465303634363962313666323662366362356464333966336266343163
788 | 33653561323065636631376163336339346461643634633635323762373061313266616633636132
789 | 30393762313637393738333436616666346664396538653562623566313930336530626137353234
790 | 36373337356161343738363063623434356530396630623761356536353062373263653664323139
791 | 61323163333961313137616165393661656564613763616565656232306632393136386361633435
792 | 30323038636666306662613035373335643435666534333632656134653538313937626131616230
793 | 65323666613062376164326165643065636330306534343761366339313539376538626537393936
794 | 35316239663065303562356136653938363633376166656332343632656139343931386364623932
795 | 32353862633266323765633238663430646365373563663462303261626336643737373732656537
796 | 39373534336265623735353135376166663862323230653136323735333663396261616535613134
797 | 32393831633038613237373433313534333235663033303662663965333830646662313035623335
798 | 35313763646232343033643566363737386663613861636566316431366537363837313339313834
799 | 32623166663861616361303437643966393130646639386234643833326561626135623439636565
800 | 61383765393034643631303565336232366139383864623731356164356664633831303262636361
801 | 31313331306539653263336362363037346266343038383166633939343563393166363866636238
802 | 63626234346235336431366666373438616262313936663433316532303630393239303261613766
803 | 66643531363937323363633631363937313366646337623963326233366462353038636137616362
804 | 33633833653034363738653736343536356262346330653061313135653233373864303762623631
805 | 35393761333231646464316461353532616639626437626463326633383039356461666134373961
806 | 63613635626533613066343931383338633862306136306264636533613166616164646366383038
807 | 63353066633434633231363539376634316633643232646637353864363039636561633363616537
808 | 65646435633935303061313166393132303939343861393832666665353364316634316338336232
809 | 30373231393433616661353566313636343035646266373734383936356630666630343963363331
810 | 32393735393866646135373062363263656662393537643264646262376666646262383937613363
811 | 33626436633931636633306133376630363037353433316533396361663334386462636135666238
812 | 63326435306163356665366630653537393033626539313638393965643637326430313639363565
813 | 38666639613565333665393439616134646466383032353865666265646335633637656231343536
814 | 30646630356236353464613238356334633466373733323161343738396261376234306634613063
815 | 30356339633831373264613538353461323237336433383763383361656561396363306438376362
816 | 65626234306231623965343232353035363935303664316563626632396439633130393334353434
817 | 63363765656631646131623534666265633665326537323234616332313961616161613030323436
818 | 36623438666562386331613263363862346333393161663237323131633335646130366565366561
819 | 33316361663666363737343331323763366336643561653735643339656264333130626235623862
820 | 64386466623731653461383030393865336234353733643434653164323930346233646537343634
821 | 31653130333930396366336662326132313531656139323361386239326262656532626632316134
822 | 38623631343561393836313439616530393265613137386135303563353166656137316562363163
823 | 63616231343537393433323435343464313333356333303738323362633730613534343064376436
824 | 63373163356364663238376165386236663961653265333061393330303661623365626462366365
825 | 65653864323863663063323135353430326562623461303066326437636264363939363333373430
826 | 64383133393230363162393162383462393936366133326135646536633836376465386662356231
827 | 39623033316362373437636163336434323134633962376435613637323466306562646266333235
828 | 31316165653635646230333638656165666439353664396333353331633930643734663437356238
829 | 61336566636134643161633963653035316337633563316163623561323031643930326330626163
830 | 66393336626638313764366131346238323730646663366430303434393662363962333535653863
831 | 37376337373638653039376566633730613233633066306434343136323563383866343466353538
832 | 37343962366235393432336165333530343932656135623936383339393065616131613164633531
833 | 38643263396266326334396438356534343033363032656233663837633539353738656138616465
834 | 33636437633864396332626361623835346639313837313862656235633133636237383230373233
835 | 62653439303835616531626433643666616165303831646537616164336632356135656634313530
836 | 39363235323038306466316461343163346131316636613031613239616261336533633431616130
837 | 31386663313934613634363561366134656339393138306234306336303736323435646366363162
838 | 61633064336239626561336636353732393337643538613466366464656234343534373665653964
839 | 66303933613136366661613861343766366266386461623265623761626165383639636437386161
840 | 63316232623933393131313637376133633932386364353439616265643632656339376365666533
841 | 63383233323162373863383532313434316133323664373535613730653839663536323336353931
842 | 39356636316666393435653636386534633962663166663634613634336534336665316633313838
843 | 34353734316338616464316635623737653735366635396138343264616536313938613834326138
844 | 66663237346530646637356266323164356537356536636632633161393233346139386164616666
845 | 31373634313935336166366135376539653037306439383839326562363033343366616166643838
846 | 63353933373331353930653130613931306233633734333065396366303930376633303039356130
847 | 39303065373565646366613064333865316130353130643233303430333836383234393065373436
848 | 37613233633833623036313432653634613836303733316435616232353665666131663134306330
849 | 64343766613565306238303531376436613764323435313138313266393962636232313031343934
850 | 34623861353536353361303933653765333631373333616663393031623465356337613133633334
851 | 64323063386630613135623731363263316130626238653230326462663566633964346430346233
852 | 31333662656637323166373737653966623434333566613538366635376164356439613337396632
853 | 33343066336561373232353838303930643936616336613238353561383138653833663633333234
854 | 61623564363135643233386163633237663537663533373438343839396531353839623836653835
855 | 37646163333935613531653765366531323339393831393332393865343930633836616663643038
856 | 30353537386531323839353366343839393737386365393632616639613737343666396463626366
857 | 37663239346532623733323364353964366334356537306665386564653438306166613862336565
858 | 34336335386163656265653834373134663238653963656335626236643839666637303637643537
859 | 64633939666535306432646436343261666662356465333561316163373539353562643034333531
860 | 37376534313737383934353432653266396261326332336365383137356465623836343862313566
861 | 37346266633131346532636636366164623965613734613461623337633934376232393765656438
862 | 63356666636438303434373433623434393432613036623462623531333130396263623265656561
863 | 63333239656332386664383431366161333034623261343333323834636239303838646461393533
864 | 62643638656366393436633465636537633062363539663230373762376662633534616263633961
865 | 33373961366439613161633363313134376564323035303764313531633765666332333435613639
866 | 33326532383233656332643164373362356161626361383835633164303766323663306161353632
867 | 62323135663061663562663230373630643433356462623339306536633232303364346334643031
868 | 37346564373362353062373465356230333762316462303161396363376135666139636561353064
869 | 30346464623164343739636362613561376334376230306233306432363130366261653561363266
870 | 66346435376136663233663164366464383337353536636239333832613663623732343064383238
871 | 64656539323130636632356164303061363332343863303936336661616135663564376235303261
872 | 38363437623730613736653732313764646330393432656639346231643165363764373865383264
873 | 62313537646335343734653365383434653536303632623835393865663430653933313964376662
874 | 38646333643532353633316161653133333765363062366261306636636239626138333339383461
875 | 35663061313238333061353261353831333634643930396665346366306131326563656363373034
876 | 61666366663137623965356536343137646138366366306534336462303766343733383761383534
877 | 33396666373766666662373336643030376637396633613539366364373931396361646239343437
878 | 65363632316335363166333462333961623261363835656434383331393363656637363865323733
879 | 66653236616338643762376232663037323262343830623033303266393963663232626231663638
880 | 39613834623966363061623635323139383031333739353633376539636336353235313032653964
881 | 34333636376539366637386135346466363863633733616662643232643230623336626437363033
882 | 35643338356638653566323538623465646637323162626136323162376236373130343964323261
883 | 66333037326636373736623936626337336666326432623730313061383864333730626532333439
884 | 34333561633664613935393036623031386231396337316262343332326431373661633436303739
885 | 35643038316339383432373135313237623331366330643865663363313936663639666433386565
886 | 63383561653035646630623330336631346666613831626532643263646533333434313836303464
887 | 62616264323639646561333035343862633631616561666364636363336362663865333336656133
888 | 37393263636362653664343435613161383039353366653230656465303031303264326131613232
889 | 39666664663939396239383634663565313062646264393830306238323064356234306132323733
890 | 62656135313330393839393863323437666432323630393461333035313730643161383431343131
891 | 36336361326633643935326539653638643934343733326331353561633036653864323535376431
892 | 62633237643664353164386133633138313631326438393032326231366231346264386164646466
893 | 31333137313761376561613439656132376162663162353434343533633137333635363131366635
894 | 62393361666165323430656365636434363136313862383337303237353265393234643763376630
895 | 38363966336338656363653664316232313561303731626363636236636236376439363261613532
896 | 31363533656133636663323830653039323537393764303966666661363464363566353564623035
897 | 33626639646466643230353737636165306337393232366264333363393836393733363530336435
898 | 63636330336331613938373536316238636338356130383834366439306161316438613839346563
899 | 65316437646365643634613132663162616263323433373333353361646134633434393331303539
900 | 38653939316437303062393732343862616363396262343266646235393335383936393336353362
901 | 34396563643339333862616533313666653633346236333338633964623739343730663665343538
902 | 33663965643266326262333131346364396265373133393464633832366134373261376533326566
903 | 61373632343061613535346334386330616136373862616363376336303836336364356531616562
904 | 31623566326261383365353166623761373134613862613137633265633837353738653566633433
905 | 30323938333431373464646364366635626337393534346434343164643837393035653036333930
906 | 34316431306366376263356232323938313338633461663564336464643231373565653532306564
907 | 62626232376633333038326435616165393465336331663230343064616562316262623861366162
908 | 37653238643065643835613631366162346536303036396366616663363265633536356339393962
909 | 32636335363530353035313834643137393264353061383561393732653134396537323063366231
910 | 38623061313333663534383636363966343564653639373865356436613963306561653764643064
911 | 61326134376335363961376463643137363630616136643464333038323638366366306333643465
912 | 30353434373663386564656664383536363231393864643136636138346531396531616365616165
913 | 37366262363766373038356633313961623838666666616535356230613539666431376536356236
914 | 35343735316533343536663732653735666136623432303634363131613163393162373539303965
915 | 30636238646162353230366533393061666434666135306637383931613866616435336131353130
916 | 62313736333531666165383663366564303665336462393164373231393635613633663664313261
917 | 65363564366232333262306365663364383265313262383539613761393435333233666137393530
918 | 35386330633431346433633663636166666164323933343331383031373938313833643035666261
919 | 62366363336630323135623865303665393936313831626339623039326130383733626238353964
920 | 65393735386561333638613038373637353034666436383234663461643834646132306165666466
921 | 62333963653963346632613366643931613937633933646563383133626431646639653364386166
922 | 62613632353138323566396163333661303862346632326562643438643530633431643562643361
923 | 36303335323063613462653731373765383235663937616131616137313530636564653232323439
924 | 62363732323736346530353963613036643639306562643639386134393135356234613236616265
925 | 65303361653531306633333035383633336239366162333934643034326139313731633366663563
926 | 65383831396564653865646166396634643630333834373966356130663639623731653466633439
927 | 39623961366635386363353161326533383334386662353962343762393236326137366539393137
928 | 37383561343164383837623464336165623935386234333566623263326331333333346262303766
929 | 63383530663065663538386634633238306363373861666634353562313561313535663966333464
930 | 31316435666538313962366162616566313462303464383633353866356465343037326132643232
931 | 39373230633266623162666265323632323164613831363236633065623336383261376337336132
932 | 39303330646365343865346130613061336534363432383633363131646134656362383661366534
933 | 31383838393735303438353132656565396536376434336262326335633933376531373431616431
934 | 34353263373462643438666630636437366231323466346631323034343032613333316132366463
935 | 30663032396231313337333361643264346262373438363437376130353236386636646430656562
936 | 30353831633830623237326563316132616230333933396330323430303965623064306266623634
937 | 37366561616563373964326432363737646637313331353736643466333133353236616163356435
938 | 62653564666532383066623334653864386462303936363161366133376532376437323936363466
939 | 34653031626433626439636434366332333437633030343636336462643135646530343339303933
940 | 37343734393465373937643566306439353631383433633964363366343265386535316433626165
941 | 30396361313236326663383464336565386662383030393930666335396462303632653133353232
942 | 35356664333737363135393739666263656239636638323635303831653763386535356135663765
943 | 38663233316336663261333865316562653062643462356538366135633265323136353262303261
944 | 31323432343636393661303832353635633739356465306461383436653965616637373532323639
945 | 66363032303231326366653730353961343039353738633930666464303565656131623862376463
946 | 62343465386636356261656639653035313661616662333237656139313637396234383662386364
947 | 30656361623362353661653937336235333366393637343430363166353362363933386333663166
948 | 33306562626233626461376431363561653036313439623463366639643961643061623864346261
949 | 63383430353063353530623761366439653564393139336166343162633661643963373434373565
950 | 32343338336163623034663536636362663564393039313264623934353034663531383961316466
951 | 34316134303035666635643734616537396463663364336439373763343031663434393630323637
952 | 36316235636531313930306162613538373434656533323331373864626335633839393861396662
953 | 35623130616361326563333761636532333565613866613163343939393235323166313035613536
954 | 34316362323935303137363938306431373162633733663561333837373764656535343131373634
955 | 61323533366535613033376132323033613138613738316364656432313332336135303738363735
956 | 62616237626163313135636363626638383134643836376461383664313331356233626534666139
957 | 33613538366533616331353865336666333365623566386437393332343730326239353238623361
958 | 39326366636263393835636639333063316534383134646533303339383166323430376137393064
959 | 64656662393635396339633333313332333562343763373736366361343562373337323561363361
960 | 32633734393766363430306436663032643432363663326338393637626536653834323635363738
961 | 35336361323564633561613065643330353930386636636239343331313532636435356535373430
962 | 39306134623431623239376661333736323036373936363438663134613362643031336338306237
963 | 66373735396536656564336631356162633738376266363337646436353163313537623738646633
964 | 30333130653339323134613330306431333863336533656231633262343165366435316262363335
965 | 37333938653861373731363731656162353138623461383531383164323062383363353830393135
966 | 37326265373535643336366138366435616463323166356463616165383364623437363663663465
967 | 30626165613436663739346537323330623365383163613063646362346465323334323133303135
968 | 66646561656563346230643930326231333034306565396664393338633465616630653765323137
969 | 32366634363864653637343136616662313239306135346465316462356138333337376234373132
970 | 35366139383132363436303032613566303937303333633334333831333633343931636434623337
971 | 30316265386262396562316530656238346339383230383764663734383535666439663635336662
972 | 66636630343666336132356265326266653337613234356435373634633833323031656330363065
973 | 34613637613731313962333730333264346661366666393135353263366435383465386133663538
974 | 64653565643466613832643436353965656530376161373739613133646631613663303964633866
975 | 34356661316339353238316461616136653333623336376236383639636139643531653939343134
976 | 64363233323163623038666366393634366365613036356362656462336435396235396436343561
977 | 63633764373633323065393066356265316165663065313439373634393437336530333331333462
978 | 61613830343638653431356262643663653261353035393135316439383832626638393231636534
979 | 35316461396337383731616366636439623662373633343131383437336564616362386363313862
980 | 64633739643430653765313533393635343633663431313435616231643539356561383738626438
981 | 63643066303132316363333435663337633464303962393365626535363435386235386231626463
982 | 64623264353139366666366536333833396135613035643937326562393161383566363231326535
983 | 32353265633734373834356531623634646366656439616662623630383831396363346263356634
984 | 34653130633836376533633866666334343131613136313063663163616361623538346338393762
985 | 34306433343561303634343234666334643131613637346531653866383164333765346563666363
986 | 30376133346432396566383865313366656132323166646435386366373437326263666363386631
987 | 61633265333765303134656534666462386338303161663164363837643164356466623938303063
988 | 65343463346563343835646363363437326263363063643665353133633333663561333434353532
989 | 33636135626438316439666636613735353938336130353462356334396337366434643532653963
990 | 32653465636536313965373539393735663162383335333461376139633735343163376232613239
991 | 62386236663739333465303463306634323633303630666233623632323732653338613163663230
992 | 64653964376634613138363134633863363335663934623936353935656232326663626634663633
993 | 65393532303537626263666634336365663964356130653538356462643939323338636336393530
994 | 61323164633264646437396332643632396162303966636634323232386137393364666535383266
995 | 38316433386363663939316466313535396365623236646238623064663165393739376130363735
996 | 63616435643631353330316134383535613864383938633038666463653337396165616635393266
997 | 32333139643339623338
998 |
--------------------------------------------------------------------------------
/roles/ca-gateway/vars/vault.yml.sample:
--------------------------------------------------------------------------------
1 | dockerhub:
2 | user: REDACTED
3 | pwd: REDACTED
4 | ca_gateway_uri: "REDACTED"
5 | db_pwd: "REDACTED"
6 | ssg_pwd: "REDACTED"
7 | license: ""
8 | ## License is a very long string, which is basically:
9 | ## export SSG_LICENSE="$(cat ~/path/to/CA_LICENSE.xml | gzip | base64)"
--------------------------------------------------------------------------------
/roles/common-baseline/tasks/add-layerfs-support.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Find the latest available version of linux-image-extra
4 | shell: "apt-cache search linux-image-extra | grep linux-image-extra-.*-generic | tail -1 | awk '{print $1;}'"
5 | register: linux_image_extra
6 | # the .stdout should be something like: linux-image-extra-3.19.0-23-generic
7 |
8 |
9 | - name: Install dependencies and updates to enable overlayfs support
10 | apt:
11 | name: "{{ item }}"
12 | state: present
13 | with_items:
14 | - "{{linux_image_extra.stdout}}"
15 |
16 | - name: Reboot servers to let overlayfs support kick-in.
17 | command: /sbin/shutdown -r now
18 | register: reboot_result
19 |
20 | - name: Wait for instance to come online (5 minute timeout)
21 | sudo: false
22 | local_action:
23 | module: wait_for
24 | host={{ inventory_hostname }}
25 | port=22
26 | delay=1
27 | timeout=300
28 |
--------------------------------------------------------------------------------
/roles/common-baseline/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: "Update APT cache"
4 | apt: update_cache=yes
5 |
6 | - name: Install common packages
7 | apt: name={{ item }} state=present
8 | with_items:
9 | - vim
10 | - wget
11 | - curl
12 | - zip
13 | - unzip
14 | - sudo
15 |
16 | # we only need this for older-ish kernels which require kernel upgrade
17 | - include: add-layerfs-support.yml
18 | when: ansible_kernel | match("3.\d.*-generic")
19 |
--------------------------------------------------------------------------------
/roles/consul-clients/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | #- debug:
4 | # msg: "{{ groups.tag_class_consul_servers | length }}"
5 |
6 | - name: Stop/Removes docker client agent containers if running (Idempotence)
7 | docker:
8 | image: "gliderlabs/consul-agent"
9 | state: absent
10 |
11 | - name: Launch Consul Client Agent Containers
12 | docker:
13 | name: consul-agent
14 | detach: true
15 | image: "gliderlabs/consul-agent"
16 | command: "-advertise {{inventory_hostname}} -recursor 8.8.8.8 -dc '{{ aws_region }}' -encrypt '{{consul_secretkey}}'"
17 | #volumes: "/var/run/docker.sock:/var/run/docker.sock"
18 | state: started
19 | env:
20 | # http://gliderlabs.com/registrator/latest/user/services/#detecting-services
21 | # https://github.com/gliderlabs/registrator/issues/132
22 | SERVICE_IGNORE: always
23 | # restart_policy: "on-failure"
24 | ports:
25 | - 8300:8300
26 | - 8301:8301
27 | - 8301:8301/udp
28 | - 8302:8302
29 | - 8302:8302/udp
30 | - 8400:8400
31 | - 8500:8500
32 | - 53:8600
33 | - 53:8600/udp
34 | #- 53:53/udp
35 |
36 | - name: Make Consul agents join the cluster
37 | shell: "docker exec consul-agent consul join {{item}}"
38 | with_items: groups.tag_class_consul_servers
--------------------------------------------------------------------------------
/roles/consul-servers/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # Docker container "restarting" is flaky. Remove and start is the bulletproof way
4 |
5 | - name: Stop/Removes docker server containers if running (Idempotence)
6 | docker:
7 | image: "gliderlabs/consul-server"
8 | state: absent
9 |
10 | - name: Launch Consul Server Containers
11 | docker:
12 | name: consul-server
13 | detach: true
14 | image: "gliderlabs/consul-server"
15 | command: "-server -advertise {{inventory_hostname}} -bootstrap-expect {{ groups.tag_class_consul_servers | length }} -recursor 8.8.8.8 -dc '{{ aws_region }}' -encrypt '{{consul_secretkey}}'"
16 | state: started
17 | env:
18 | # http://gliderlabs.com/registrator/latest/user/services/#detecting-services
19 | # https://github.com/gliderlabs/registrator/issues/132
20 | SERVICE_IGNORE: always
21 | # restart_policy: "on-failure"
22 | ports:
23 | - 8300:8300
24 | - 8301:8301
25 | - 8301:8301/udp
26 | - 8302:8302
27 | - 8302:8302/udp
28 | - 8400:8400
29 | - 8500:8500
30 | - 53:8600
31 | - 53:8600/udp
32 | #- 53:53/udp
33 |
34 | - name: Make Consul servers join the cluster
35 | shell: "docker exec consul-server consul join {{item}}"
36 | with_items: groups.tag_class_consul_servers
37 | #when: item != "{{ groups.tag_class_consul_servers | first }}"
--------------------------------------------------------------------------------
/roles/docker-hosts/tasks/install-docker.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # @see: https://docs.docker.com/installation/ubuntulinux/
4 |
5 | - name: Add Docker Repository Key
6 | apt_key:
7 | keyserver: "hkp://pgp.mit.edu:80"
8 | id: "58118E89F3A912897C070ADBF76221572C52609D"
9 |
10 | - name: Add Docker repository and update apt cache
11 | apt_repository:
12 | repo: "deb https://apt.dockerproject.org/repo ubuntu-trusty main"
13 | update_cache: yes
14 | state: present
15 |
16 | - name: Install (or update) docker package
17 | apt:
18 | name: "docker-engine"
19 | state: latest
20 | update_cache: yes
21 | cache_valid_time: 600
22 |
23 | - name: Install pip
24 | apt: name="python-pip" state="present"
25 |
26 | - name: Set docker daemon options to switch from devicemapper to overlayfs. Devicemapper is flaaaakyyyy (understatement)
27 | copy:
28 | content: "DOCKER_OPTS=\"-g /data/docker -s overlay\""
29 | dest: /etc/default/docker
30 | owner: root
31 | group: root
32 | mode: 0644
33 |
34 | - name: Upgrade version of pip
35 | shell: easy_install -U pip
36 |
37 | - name: Install docker python library
38 | shell: pip install docker-py
39 |
40 | - name: "Add docker group"
41 | group: name=docker state=present
42 |
43 | - name: Add default \"ubuntu\" user (for AWS) docker group
44 | shell: "usermod -aG docker ubuntu"
45 |
46 | - name: Check if /etc/updatedb.conf exists
47 | stat:
48 | path: /etc/updatedb.conf
49 | register: updatedb_conf_exists
50 |
51 | - name: Ensure updatedb does not index /var/lib/docker
52 | lineinfile:
53 | dest: /etc/updatedb.conf
54 | state: present
55 | backrefs: yes
56 | regexp: '^PRUNEPATHS="(/var/lib/docker )?(.*)"$'
57 | line: 'PRUNEPATHS="/var/lib/docker \2"'
58 | when: updatedb_conf_exists.stat.exists
59 |
60 | - name: Check if /etc/default/ufw exists
61 | stat:
62 | path: /etc/default/ufw
63 | register: ufw_default_exists
64 |
65 | - name: Change ufw default forward policy from drop to accept
66 | lineinfile:
67 | dest: /etc/default/ufw
68 | regexp: "^DEFAULT_FORWARD_POLICY="
69 | line: "DEFAULT_FORWARD_POLICY=\"ACCEPT\""
70 | when: ufw_default_exists.stat.exists
71 |
72 | - name: Install Docker Compose
73 | shell: "pip install -U docker-compose"
74 |
75 | - name: (Re)Start docker-engine services
76 | service:
77 | name: docker
78 | state: restarted
--------------------------------------------------------------------------------
/roles/docker-hosts/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Install Docker Hosts
4 | include: "install-docker.yml"
5 | tags:
6 | - docker
7 |
8 | #- name: Reboot servers to let docker re-init.
9 | # command: /sbin/shutdown -r now
10 | # register: reboot_result
11 |
12 | #- name: Wait for instance to come online (5 minute timeout)
13 | # sudo: false
14 | # local_action:
15 | # module: wait_for
16 | # host={{ inventory_hostname }}
17 | # port=22
18 | # delay=1
19 | # timeout=300
--------------------------------------------------------------------------------
/roles/install-microservices/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Stop/Removes microservice containers if running (Idempotence)
4 | docker:
5 | image: "{{item.image}}"
6 | state: absent
7 | with_items: dockerhub_microservices
8 |
9 | - name: Remove microservice images, if present, to avoid stale versions (Idempotence)
10 | docker_image: name="{{item.image}}" state=absent
11 | with_items: dockerhub_microservices
12 |
13 | - name: Launch Microservices
14 | docker:
15 | name: "{{item.name}}"
16 | image: "{{item.image}}"
17 | state: started
18 | ports: "{{item.ports}}"
19 | #restart_policy: "on-failure"
20 | env:
21 | # http://gliderlabs.com/registrator/latest/user/services/#detecting-services
22 | # https://github.com/gliderlabs/registrator/issues/132
23 | SERVICE_NAME: "{{item.name}}"
24 | SERVICE_TAGS: "microservices"
25 | SERVICE_CHECK_HTTP: "/healthcheck"
26 | SERVICE_CHECK_INTERVAL: "30s"
27 | SERVICE_CHECK_TIMEOUT: "5s"
28 | with_items: dockerhub_microservices
29 |
--------------------------------------------------------------------------------
/roles/launch-registrators/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - pause: seconds=5
4 |
5 | - name: Stop/Removes docker client agent containers if running (Idempotence)
6 | docker:
7 | image: "gliderlabs/registrator"
8 | state: absent
9 |
10 | - name: Launch Registrator Containers
11 | docker:
12 | name: docker-registrator
13 | detach: true
14 | net: host
15 | image: "gliderlabs/registrator"
16 | command: "-resync 10 -ip {{inventory_hostname}} consul://localhost:8500"
17 | #command: "consul://localhost:8500 -ip {{inventory_hostname}} -resync 15"
18 | #command: "-ip {{inventory_hostname}} -resync 15"
19 | state: started
20 | #restart_policy: "on-failure"
21 | volumes: "/var/run/docker.sock:/tmp/docker.sock"
--------------------------------------------------------------------------------