├── .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" --------------------------------------------------------------------------------