├── README.md └── playbooks ├── base ├── ansible.cfg ├── dockerguy.yml ├── getdiskname.yml ├── http.j2 ├── inventory ├── inventoryans.py ├── mariadb55.retry ├── mariadb55.yml ├── motd.retry ├── motd.yml ├── nomoresecret.yml ├── pascal.py ├── secret.yml ├── un-ansible-loop.yml ├── user-with-items.retry ├── user-with-items.yml ├── variables-jinja.yml ├── variables.yml ├── when-test.retry └── when-test.yml ├── delegation ├── addhosts.retry ├── addhosts.yml ├── ansible.cfg ├── bigfile.yaml ├── delegate.yml ├── download.retry ├── download.yml ├── inventory ├── reboot.yml ├── templates │ ├── ansible2.example.com-httpd.conf.j2 │ ├── index.html.j2 │ └── proxy.example.com-httpd.conf.j2 ├── waitforme.retry ├── waitforme.yaml ├── web.retry └── web.yml ├── docker ├── ansible.cfg └── inventory ├── ec2 ├── ansible.cfg ├── ec2.ini ├── ec2.py ├── inventory │ ├── ec2.ini │ ├── ec2.py │ └── inventory ├── list-instances.yaml ├── ubuntu-ami.yaml └── ubuntu.yaml ├── error_handling ├── ansible.cfg ├── assert.retry ├── assert.yml ├── block.yml ├── inventory ├── rescue.yml └── uricheck.yml ├── exam-lab ├── ansible.cfg ├── inventory ├── main.yml └── roles │ ├── mountweb │ ├── README.md │ ├── defaults │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ ├── meta │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ ├── templates │ │ └── http.j2 │ ├── tests │ │ ├── inventory │ │ └── test.yml │ └── vars │ │ └── main.yml │ └── web-role │ ├── README.md │ ├── defaults │ └── main.yml │ ├── handlers │ └── main.yml │ ├── meta │ └── main.yml │ ├── tasks │ ├── main.yml │ ├── webclients.yml │ └── webservers.yml │ ├── templates │ └── httpd.j2 │ ├── tests │ ├── inventory │ └── test.yml │ └── vars │ └── main.yml ├── ftp ├── ansible.cfg ├── inventory └── vsftpd.yml ├── includes ├── ansible.cfg ├── inventory.yml ├── site.yml └── tasks │ ├── file.yml │ └── lamp.yml ├── lab ├── ansible.cfg ├── custom.fact ├── lab-copy-facts.yml ├── lab-custom.facts ├── lab-facts ├── lab-inventory ├── lab-playbook.yml ├── lab-tasks │ ├── file.yml │ └── lamp.yml ├── lab-vars │ └── allvars.yml ├── playbook.retry └── playbook.yml ├── lab10 ├── ansible.cfg ├── disks.yml ├── inventory ├── lab10-3.yml ├── main.yml ├── roles │ ├── mountweb │ │ ├── README.md │ │ ├── defaults │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ ├── templates │ │ │ └── http.j2 │ │ ├── tests │ │ │ ├── inventory │ │ │ └── test.yml │ │ └── vars │ │ │ └── main.yml │ └── web-role │ │ ├── README.md │ │ ├── defaults │ │ └── main.yml │ │ ├── handlers │ │ └── main.yml │ │ ├── meta │ │ └── main.yml │ │ ├── tasks │ │ ├── main.yml │ │ ├── webclients.yml │ │ └── webservers.yml │ │ ├── templates │ │ └── httpd.j2 │ │ ├── tests │ │ ├── inventory │ │ └── test.yml │ │ └── vars │ │ └── main.yml ├── templates │ └── httpd.j2 └── vars │ └── webservers.yml ├── lab4 ├── ansible.cfg ├── inventory ├── tasks │ ├── redhat.yml │ └── ubuntu.yml ├── variables.yml └── vars │ ├── redhat.yml │ └── ubuntu.yml ├── loops ├── ansible.cfg ├── conditionalrestart.yml ├── copytxt.retry ├── copytxt.yml ├── errorlog ├── handlers-lab.yml ├── handlers.retry ├── handlers.yml ├── ifsize.yml ├── inventory ├── register.retry ├── register.yml ├── register2.retry ├── register2.yml ├── register3.yml ├── with_nested.yml └── with_nested_cleanup.yml ├── newvault ├── ansible.cfg ├── createusers.retry ├── createusers.yml ├── inventory ├── vars │ └── secret.yml └── vaultpw ├── roles-demo ├── ansible.cfg ├── inventory ├── motd-role.yml ├── nginx-role.yml └── roles │ └── motd │ ├── README.md │ ├── defaults │ └── main.yml │ ├── handlers │ └── main.yml │ ├── meta │ └── main.yml │ ├── tasks │ └── main.yml │ ├── templates │ └── motd.j2 │ ├── tests │ ├── inventory │ └── test.yml │ └── vars │ └── main.yml ├── tags ├── ansible.cfg ├── install.yml ├── inventory └── tag_include.yml ├── vault ├── ansible.cfg ├── inventory └── vault-pw └── windows ├── ansible.cfg ├── inventory └── playbook.yml /README.md: -------------------------------------------------------------------------------- 1 | # AutomatingWithAnsible 2 | -------------------------------------------------------------------------------- /playbooks/base/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | remote_user = ansible 3 | host_key_checking = false 4 | inventory = inventory 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/base/dockerguy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: use role to install docker 3 | hosts: ansible2.example.com 4 | 5 | roles: 6 | - geerlingguy.docker 7 | -------------------------------------------------------------------------------- /playbooks/base/getdiskname.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: get disk names 3 | hosts: ansible2.example.com 4 | tasks: 5 | 6 | - name: get device name 7 | set_fact: 8 | device_name: "{{ item.key }}" 9 | no_log: True 10 | with_dict: "{{ ansible_devices }}" 11 | when: "item.value.host.startswith('SATA')" 12 | 13 | - name: show all values for selected device name 14 | debug: var=ansible_devices[device_name] 15 | 16 | - name: show only device name 17 | debug: var=device_name 18 | -------------------------------------------------------------------------------- /playbooks/base/http.j2: -------------------------------------------------------------------------------- 1 | Listen *:80 2 | NameVirtualHost {{ ansible_fqdn }} 3 | 4 | 5 | ServerName {{ ansible_fqdn }} 6 | ServerAdmin {{ system_owner }} 7 | DocumentRoot /var/www/html 8 | 9 | -------------------------------------------------------------------------------- /playbooks/base/inventory: -------------------------------------------------------------------------------- 1 | ansible1.example.com 2 | ansible2.example.com 3 | ubuntu.example.com 4 | -------------------------------------------------------------------------------- /playbooks/base/inventoryans.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from subprocess import Popen,PIPE 4 | import sys 5 | import json 6 | 7 | result = {} 8 | result['all'] = {} 9 | 10 | pipe = Popen(['getent', 'hosts'], stdout=PIPE, universal_newlines=True) 11 | 12 | result['all']['hosts'] = [] 13 | for line in pipe.stdout.readlines(): 14 | s = line.split() 15 | 16 | result['all']['vars'] = {} 17 | 18 | if len(sys.argv) == 2 and sys.argv[1] == '--list': 19 | print(json.dumps(result)) 20 | elif len(sys.argv) == 3 and sys.argv[1] == '--host': 21 | print(json.dumps({})) 22 | else: 23 | print("Requires an argument, please use --list or --host ") 24 | -------------------------------------------------------------------------------- /playbooks/base/mariadb55.retry: -------------------------------------------------------------------------------- 1 | ansible2.example.com 2 | -------------------------------------------------------------------------------- /playbooks/base/mariadb55.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: use role to install mariadb 3 | hosts: ansible2.example.com 4 | 5 | roles: 6 | - f500.mariadb55 7 | -------------------------------------------------------------------------------- /playbooks/base/motd.retry: -------------------------------------------------------------------------------- 1 | ansible2.example.com 2 | -------------------------------------------------------------------------------- /playbooks/base/motd.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: role usage example 3 | hosts: ansible2.example.com 4 | 5 | roles: 6 | - motd 7 | -------------------------------------------------------------------------------- /playbooks/base/nomoresecret.yml: -------------------------------------------------------------------------------- 1 | hello world 2 | byebye world 3 | -------------------------------------------------------------------------------- /playbooks/base/pascal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from subprocess import Popen,PIPE 4 | import sys 5 | 6 | try: 7 | import json 8 | except ImportError: 9 | import simplejson as json 10 | 11 | 12 | 13 | result = {} 14 | 15 | result['all'] = {} 16 | 17 | 18 | 19 | pipe = Popen(['getent', 'hosts'], stdout=PIPE, universal_newlines=True) 20 | 21 | 22 | result['all']['hosts'] = [] 23 | 24 | for line in pipe.stdout.readlines(): 25 | s = line.split() 26 | result['all']['hosts']=result['all']['hosts']+s 27 | 28 | 29 | result['all']['vars'] = {} 30 | 31 | 32 | if len(sys.argv) == 2 and sys.argv[1] == '--list': 33 | print(json.dumps(result)) 34 | 35 | elif len(sys.argv) == 3 and sys.argv[1] == '--host': 36 | print(json.dumps({})) 37 | 38 | else: 39 | print("Requires an argument, please use --list or --host ") 40 | -------------------------------------------------------------------------------- /playbooks/base/secret.yml: -------------------------------------------------------------------------------- 1 | $ANSIBLE_VAULT;1.1;AES256 2 | 33616466613334353330636362343339313831393561303362383737636631396363613735383830 3 | 6138323337633166336337333762343132633265613764610a336238653637343135323464313736 4 | 63396230666366633666613363613266626530346563633238666530316637313261623031356438 5 | 3438656234333363340a393237643439666466666534356434323933643566303433356630383130 6 | 39313933356234383862366537323139366463626432316539333939663235666339 7 | -------------------------------------------------------------------------------- /playbooks/base/un-ansible-loop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: clean up all 3 | hosts: all 4 | tasks: 5 | - name: remove services 6 | yum: 7 | name: "{{ item }}" 8 | state: absent 9 | with_items: 10 | - httpd 11 | - vsftpf 12 | - name: remove files 13 | file: 14 | path: "{{ item }}" 15 | state: absent 16 | with_items: 17 | - /etc/ansible/facts.d 18 | - /var/www/html/index.html 19 | - /var/ftp/pub/README 20 | - /etc/motd 21 | -------------------------------------------------------------------------------- /playbooks/base/user-with-items.retry: -------------------------------------------------------------------------------- 1 | ansible2.example.com 2 | -------------------------------------------------------------------------------- /playbooks/base/user-with-items.yml: -------------------------------------------------------------------------------- 1 | - name: create users 2 | hosts: ansible2.example.com 3 | tasks: 4 | - name: manage users and group membership 5 | user: 6 | name: "{{ item.name }}" 7 | state: present 8 | groups: "{{ item.groups }}" 9 | with_items: 10 | - { name: 'linda', groups: 'students' } 11 | - { name: 'anna', groups: 'profs' } 12 | -------------------------------------------------------------------------------- /playbooks/base/variables-jinja.yml: -------------------------------------------------------------------------------- 1 | - name: deploy and start Apache 2 | hosts: ansible2.example.com 3 | vars: 4 | apache_package: httpd 5 | firewall_package: firewalld 6 | web_service: httpd 7 | firewall_service: firewalld 8 | rule: http 9 | system_owner: anna@example.com 10 | 11 | tasks: 12 | - name: install and update latest packages 13 | yum: 14 | name: 15 | - "{{ apache_package }}" 16 | - "{{ firewall_package }}" 17 | state: latest 18 | 19 | - name: start and enable {{ firewall_service }} 20 | service: 21 | name: "{{ firewall_service }}" 22 | enabled: true 23 | state: started 24 | 25 | - template: 26 | src: http.j2 27 | dest: /etc/httpd/conf.d 28 | owner: root 29 | group: root 30 | mode: 0644 31 | 32 | - name: create web content 33 | copy: 34 | content: "Welcome at the Ansible managed web server" 35 | dest: /var/www/html/index.html 36 | 37 | - name: start and enable {{ web_service }} 38 | service: 39 | name: "{{ web_service }}" 40 | enabled: true 41 | state: started 42 | 43 | - name: open firewall for {{ rule }} 44 | firewalld: 45 | service: "{{ rule }}" 46 | permanent: true 47 | immediate: true 48 | state: enabled 49 | 50 | - name: verify the web server 51 | hosts: localhost 52 | become: false 53 | tasks: 54 | - name: test that webserver is available 55 | uri: 56 | url: http://ansible2.example.com 57 | status_code: 200 58 | 59 | -------------------------------------------------------------------------------- /playbooks/base/variables.yml: -------------------------------------------------------------------------------- 1 | - name: deploy and start Apache 2 | hosts: ansible2.example.com 3 | vars: 4 | apache_package: httpd 5 | firewall_package: firewalld 6 | web_service: httpd 7 | firewall_service: firewalld 8 | rule: http 9 | 10 | tasks: 11 | - name: install and update latest packages 12 | yum: 13 | name: 14 | - "{{ apache_package }}" 15 | - "{{ firewall_package }}" 16 | state: latest 17 | 18 | - name: start and enable {{ firewall_service }} 19 | service: 20 | name: "{{ firewall_service }}" 21 | enabled: true 22 | state: started 23 | 24 | - name: create web content 25 | copy: 26 | content: "Welcome at the Ansible managed web server" 27 | dest: /var/www/html/index.html 28 | 29 | - name: start and enable {{ web_service }} 30 | service: 31 | name: "{{ web_service }}" 32 | enabled: true 33 | state: started 34 | 35 | - name: open firewall for {{ rule }} 36 | firewalld: 37 | service: "{{ rule }}" 38 | permanent: true 39 | immediate: true 40 | state: enabled 41 | 42 | - name: verify the web server 43 | hosts: localhost 44 | become: false 45 | tasks: 46 | - name: test that webserver is available 47 | uri: 48 | url: http://ansible2.example.com 49 | status_code: 200 50 | 51 | -------------------------------------------------------------------------------- /playbooks/base/when-test.retry: -------------------------------------------------------------------------------- 1 | ubuntu.example.com 2 | -------------------------------------------------------------------------------- /playbooks/base/when-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | name: conditional test 4 | tasks: 5 | - name: conditional create user 6 | user: 7 | name: laura 8 | when: ansible_default_ipv4.address == "192.168.4.81" 9 | -------------------------------------------------------------------------------- /playbooks/delegation/addhosts.retry: -------------------------------------------------------------------------------- 1 | localhost 2 | -------------------------------------------------------------------------------- /playbooks/delegation/addhosts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: test add host 3 | hosts: localhost 4 | tasks: 5 | - name: add another host 6 | add_host: 7 | name: proxy 8 | ansible_host: 192.168.4.232 9 | ansible_user: ansible 10 | 11 | - name: show where the command has been running 12 | command: hostname 13 | delegate_to: proxy 14 | register: command1 15 | 16 | - name: show how facts are handled 17 | command: echo "this is on {{ inventory_hostname }}" 18 | delegate_to: proxy 19 | register: output 20 | 21 | - debug: 22 | msg: "{{ command1.stdout }}" 23 | - debug: 24 | msg: "{{ output.stdout }}" 25 | -------------------------------------------------------------------------------- /playbooks/delegation/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = inventory 3 | remote_user = ansible 4 | host_key_checking = false 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/delegation/bigfile.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: download a big file 3 | hosts: all 4 | serial: 2 5 | tasks: 6 | - name: start download 7 | get_url: 8 | url: http://192.168.122.1/largefile 9 | async: 1800 10 | poll: 30 11 | 12 | -------------------------------------------------------------------------------- /playbooks/delegation/delegate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: understand delegate_to 3 | hosts: ansible2.example.com 4 | tasks: 5 | - name: get process information 6 | command: ps 7 | register: remote_process 8 | changed_when: false 9 | 10 | - name: get localhost processes 11 | command: ps 12 | delegate_to: localhost 13 | register: local_process 14 | changed_when: false 15 | 16 | - name: display information about localhost processes 17 | debug: 18 | msg: "{{ local_process.stdout }}" 19 | 20 | - name: show information about remotehost processes 21 | debug: 22 | msg: "{{ remote_process.stdout }}" 23 | -------------------------------------------------------------------------------- /playbooks/delegation/download.retry: -------------------------------------------------------------------------------- 1 | ansible2.example.com 2 | -------------------------------------------------------------------------------- /playbooks/delegation/download.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: download large file 3 | hosts: ansible2.example.com 4 | tasks: 5 | - name: download large file 6 | get_url: 7 | url: https://www.rhatcert.com/pub/labipa-3.2.zip 8 | dest: /tmp/ 9 | async: 7200 10 | poll: 0 11 | register: background_download 12 | - name: wait for download completion 13 | async_status: 14 | jid: "{{ background_download.ansible_job_id }}" 15 | register: job_result 16 | until: job_result.finished 17 | retries: 30 18 | delay: 120 19 | 20 | -------------------------------------------------------------------------------- /playbooks/delegation/inventory: -------------------------------------------------------------------------------- 1 | ansible1.example.com 2 | 3 | [webservers] 4 | ansible2.example.com 5 | 6 | [proxyservers] 7 | proxy.example.com 8 | -------------------------------------------------------------------------------- /playbooks/delegation/reboot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart a server 3 | hosts: ansible2.example.com 4 | tasks: 5 | - name: restart server 6 | shell: sleep 2 && shutdown -r now "rebooting, please wait" 7 | async: 1 8 | poll: 0 9 | ignore_errors: true 10 | 11 | - name: waiting for server to come back 12 | wait_for: 13 | host: "{{ inventory_hostname }}" 14 | state: started 15 | delay: 30 16 | timeout: 300 17 | port: 22 18 | delegate_to: localhost 19 | -------------------------------------------------------------------------------- /playbooks/delegation/templates/ansible2.example.com-httpd.conf.j2: -------------------------------------------------------------------------------- 1 | #{{ ansible_managed }} 2 | NameVirtualHost *:80 3 | 4 | 5 | ServerAdmin root@{{ ansible_fqdn }} 6 | DocumentRoot /var/www/html 7 | ServerName {{ ansible_fqdn }} 8 | ErrorLog logs/{{ ansible_fqdn }}-error.log 9 | CustomLog logs/{{ ansible_fqdn }}-access.log common 10 | 11 | -------------------------------------------------------------------------------- /playbooks/delegation/templates/index.html.j2: -------------------------------------------------------------------------------- 1 | You are connected to {{ ansible_fqdn }} 2 | -------------------------------------------------------------------------------- /playbooks/delegation/templates/proxy.example.com-httpd.conf.j2: -------------------------------------------------------------------------------- 1 | #{{ ansible_managed }} 2 | 3 | ProxyPass "/external" "http://{{ ansible_hostname}}" 4 | ProxyPassReverse "/external" "http://{{ ansible_hostname }}" 5 | -------------------------------------------------------------------------------- /playbooks/delegation/waitforme.retry: -------------------------------------------------------------------------------- 1 | ansible1.example.com 2 | ansible2.example.com 3 | -------------------------------------------------------------------------------- /playbooks/delegation/waitforme.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | tasks: 4 | - name: set waiting limit 5 | command: /bin/sleep 5 6 | async: 10 7 | poll: 5 8 | -------------------------------------------------------------------------------- /playbooks/delegation/web.retry: -------------------------------------------------------------------------------- 1 | ansible2.example.com 2 | proxy.example.com 3 | -------------------------------------------------------------------------------- /playbooks/delegation/web.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install and configure apache 3 | hosts: ansible2.example.com,proxy.example.com 4 | tasks: 5 | - name: install apache 6 | package: 7 | name: httpd 8 | state: installed 9 | - name: start and enable apache 10 | service: 11 | name: httpd 12 | state: started 13 | enabled: yes 14 | - name: install firewalld 15 | package: 16 | name: firewalld 17 | state: installed 18 | - name: start and enable firewalld 19 | service: 20 | name: firewalld 21 | state: started 22 | enabled: yes 23 | - name: configure firewalld 24 | firewalld: 25 | zone: public 26 | service: http 27 | permanent: true 28 | immediate: true 29 | state: enabled 30 | - name: copy webserver template 31 | template: 32 | src: "templates/{{ inventory_hostname }}-httpd.conf.j2" 33 | dest: /etc/httpd/conf.d/ansible.conf 34 | owner: root 35 | group: root 36 | mode: 0644 37 | notify: 38 | - restart httpd 39 | 40 | handlers: 41 | - name: restart httpd 42 | service: 43 | name: httpd 44 | state: restarted 45 | 46 | - name: deploy apache and disable proxy server 47 | hosts: webservers 48 | tasks: 49 | - name: stop apache proxy 50 | service: 51 | name: httpd 52 | state: stopped 53 | delegate_to: "{{ item }}" 54 | with_items: "{{ groups['proxyservers'] }}" 55 | - name: deploy webpages 56 | template: 57 | src: templates/index.html.j2 58 | dest: /var/www/html/index.html 59 | owner: apache 60 | group: apache 61 | mode: 0644 62 | - name: start apache proxy server 63 | service: 64 | name: httpd 65 | state: started 66 | delegate_to: "{{ item }}" 67 | with_items: "{{ groups['proxyservers'] }}" 68 | -------------------------------------------------------------------------------- /playbooks/docker/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | remote_user = ansible 3 | host_key_checking = false 4 | inventory = inventory 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/docker/inventory: -------------------------------------------------------------------------------- 1 | ansible1.example.com 2 | ansible2.example.com 3 | ubuntu.example.com 4 | fedora.example.com 5 | -------------------------------------------------------------------------------- /playbooks/ec2/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = inventory 3 | remote_user = ubuntu 4 | host_key_checking = false 5 | 6 | 7 | [privilege_escalation] 8 | become = True 9 | become_method = sudo 10 | become_user = root 11 | become_ask_pass = False 12 | 13 | -------------------------------------------------------------------------------- /playbooks/ec2/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' and do not 14 | # provide the 'regions_exclude' option. If this is set to 'auto', AWS_REGION or 15 | # AWS_DEFAULT_REGION environment variable will be read to determine the region. 16 | regions = all 17 | regions_exclude = us-gov-west-1, cn-north-1 18 | 19 | # When generating inventory, Ansible needs to know how to address a server. 20 | # Each EC2 instance has a lot of variables associated with it. Here is the list: 21 | # http://docs.pythonboto.org/en/latest/ref/ec2.html#module-boto.ec2.instance 22 | # Below are 2 variables that are used as the address of a server: 23 | # - destination_variable 24 | # - vpc_destination_variable 25 | 26 | # This is the normal destination variable to use. If you are running Ansible 27 | # from outside EC2, then 'public_dns_name' makes the most sense. If you are 28 | # running Ansible from within EC2, then perhaps you want to use the internal 29 | # address, and should set this to 'private_dns_name'. The key of an EC2 tag 30 | # may optionally be used; however the boto instance variables hold precedence 31 | # in the event of a collision. 32 | destination_variable = public_dns_name 33 | 34 | # This allows you to override the inventory_name with an ec2 variable, instead 35 | # of using the destination_variable above. Addressing (aka ansible_ssh_host) 36 | # will still use destination_variable. Tags should be written as 'tag_TAGNAME'. 37 | #hostname_variable = tag_Name 38 | 39 | # For server inside a VPC, using DNS names may not make sense. When an instance 40 | # has 'subnet_id' set, this variable is used. If the subnet is public, setting 41 | # this to 'ip_address' will return the public IP address. For instances in a 42 | # private subnet, this should be set to 'private_ip_address', and Ansible must 43 | # be run from within EC2. The key of an EC2 tag may optionally be used; however 44 | # the boto instance variables hold precedence in the event of a collision. 45 | # WARNING: - instances that are in the private vpc, _without_ public ip address 46 | # will not be listed in the inventory until You set: 47 | # vpc_destination_variable = private_ip_address 48 | vpc_destination_variable = ip_address 49 | 50 | # The following two settings allow flexible ansible host naming based on a 51 | # python format string and a comma-separated list of ec2 tags. Note that: 52 | # 53 | # 1) If the tags referenced are not present for some instances, empty strings 54 | # will be substituted in the format string. 55 | # 2) This overrides both destination_variable and vpc_destination_variable. 56 | # 57 | #destination_format = {0}.{1}.example.com 58 | #destination_format_tags = Name,environment 59 | 60 | # To tag instances on EC2 with the resource records that point to them from 61 | # Route53, set 'route53' to True. 62 | route53 = False 63 | 64 | # To use Route53 records as the inventory hostnames, uncomment and set 65 | # to equal the domain name you wish to use. You must also have 'route53' (above) 66 | # set to True. 67 | # route53_hostnames = .example.com 68 | 69 | # To exclude RDS instances from the inventory, uncomment and set to False. 70 | #rds = False 71 | 72 | # To exclude ElastiCache instances from the inventory, uncomment and set to False. 73 | #elasticache = False 74 | 75 | # Additionally, you can specify the list of zones to exclude looking up in 76 | # 'route53_excluded_zones' as a comma-separated list. 77 | # route53_excluded_zones = samplezone1.com, samplezone2.com 78 | 79 | # By default, only EC2 instances in the 'running' state are returned. Set 80 | # 'all_instances' to True to return all instances regardless of state. 81 | all_instances = False 82 | 83 | # By default, only EC2 instances in the 'running' state are returned. Specify 84 | # EC2 instance states to return as a comma-separated list. This 85 | # option is overridden when 'all_instances' is True. 86 | # instance_states = pending, running, shutting-down, terminated, stopping, stopped 87 | 88 | # By default, only RDS instances in the 'available' state are returned. Set 89 | # 'all_rds_instances' to True return all RDS instances regardless of state. 90 | all_rds_instances = False 91 | 92 | # Include RDS cluster information (Aurora etc.) 93 | include_rds_clusters = False 94 | 95 | # By default, only ElastiCache clusters and nodes in the 'available' state 96 | # are returned. Set 'all_elasticache_clusters' and/or 'all_elastic_nodes' 97 | # to True return all ElastiCache clusters and nodes, regardless of state. 98 | # 99 | # Note that all_elasticache_nodes only applies to listed clusters. That means 100 | # if you set all_elastic_clusters to false, no node will be return from 101 | # unavailable clusters, regardless of the state and to what you set for 102 | # all_elasticache_nodes. 103 | all_elasticache_replication_groups = False 104 | all_elasticache_clusters = False 105 | all_elasticache_nodes = False 106 | 107 | # API calls to EC2 are slow. For this reason, we cache the results of an API 108 | # call. Set this to the path you want cache files to be written to. Two files 109 | # will be written to this directory: 110 | # - ansible-ec2.cache 111 | # - ansible-ec2.index 112 | cache_path = ~/.ansible/tmp 113 | 114 | # The number of seconds a cache file is considered valid. After this many 115 | # seconds, a new API call will be made, and the cache file will be updated. 116 | # To disable the cache, set this value to 0 117 | cache_max_age = 300 118 | 119 | # Organize groups into a nested/hierarchy instead of a flat namespace. 120 | nested_groups = False 121 | 122 | # Replace - tags when creating groups to avoid issues with ansible 123 | replace_dash_in_groups = True 124 | 125 | # If set to true, any tag of the form "a,b,c" is expanded into a list 126 | # and the results are used to create additional tag_* inventory groups. 127 | expand_csv_tags = False 128 | 129 | # The EC2 inventory output can become very large. To manage its size, 130 | # configure which groups should be created. 131 | group_by_instance_id = True 132 | group_by_region = True 133 | group_by_availability_zone = True 134 | group_by_aws_account = False 135 | group_by_ami_id = True 136 | group_by_instance_type = True 137 | group_by_instance_state = False 138 | group_by_platform = True 139 | group_by_key_pair = True 140 | group_by_vpc_id = True 141 | group_by_security_group = True 142 | group_by_tag_keys = True 143 | group_by_tag_none = True 144 | group_by_route53_names = True 145 | group_by_rds_engine = True 146 | group_by_rds_parameter_group = True 147 | group_by_elasticache_engine = True 148 | group_by_elasticache_cluster = True 149 | group_by_elasticache_parameter_group = True 150 | group_by_elasticache_replication_group = True 151 | 152 | # If you only want to include hosts that match a certain regular expression 153 | # pattern_include = staging-* 154 | 155 | # If you want to exclude any hosts that match a certain regular expression 156 | # pattern_exclude = staging-* 157 | 158 | # Instance filters can be used to control which instances are retrieved for 159 | # inventory. For the full list of possible filters, please read the EC2 API 160 | # docs: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html#query-DescribeInstances-filters 161 | # Filters are key/value pairs separated by '=', to list multiple filters use 162 | # a list separated by commas. To "AND" criteria together, use "&". Note that 163 | # the "AND" is not useful along with stack_filters and so such usage is not allowed. 164 | # See examples below. 165 | 166 | # If you want to apply multiple filters simultaneously, set stack_filters to 167 | # True. Default behaviour is to combine the results of all filters. Stacking 168 | # allows the use of multiple conditions to filter down, for example by 169 | # environment and type of host. 170 | stack_filters = False 171 | 172 | # Retrieve only instances with (key=value) env=staging tag 173 | # instance_filters = tag:env=staging 174 | 175 | # Retrieve only instances with role=webservers OR role=dbservers tag 176 | # instance_filters = tag:role=webservers,tag:role=dbservers 177 | 178 | # Retrieve only t1.micro instances OR instances with tag env=staging 179 | # instance_filters = instance-type=t1.micro,tag:env=staging 180 | 181 | # You can use wildcards in filter values also. Below will list instances which 182 | # tag Name value matches webservers1* 183 | # (ex. webservers15, webservers1a, webservers123 etc) 184 | # instance_filters = tag:Name=webservers1* 185 | 186 | # Retrieve only instances of type t1.micro that also have tag env=stage 187 | # instance_filters = instance-type=t1.micro&tag:env=stage 188 | 189 | # Retrieve instances of type t1.micro AND tag env=stage, as well as any instance 190 | # that are of type m3.large, regardless of env tag 191 | # instance_filters = instance-type=t1.micro&tag:env=stage,instance-type=m3.large 192 | 193 | # An IAM role can be assumed, so all requests are run as that role. 194 | # This can be useful for connecting across different accounts, or to limit user 195 | # access 196 | # iam_role = role-arn 197 | 198 | # A boto configuration profile may be used to separate out credentials 199 | # see https://boto.readthedocs.io/en/latest/boto_config_tut.html 200 | # boto_profile = some-boto-profile-name 201 | 202 | 203 | [credentials] 204 | 205 | # The AWS credentials can optionally be specified here. Credentials specified 206 | # here are ignored if the environment variable AWS_ACCESS_KEY_ID or 207 | # AWS_PROFILE is set, or if the boto_profile property above is set. 208 | # 209 | # Supplying AWS credentials here is not recommended, as it introduces 210 | # non-trivial security concerns. When going down this route, please make sure 211 | # to set access permissions for this file correctly, e.g. handle it the same 212 | # way as you would a private SSH key. 213 | # 214 | # Unlike the boto and AWS configure files, this section does not support 215 | # profiles. 216 | # 217 | # aws_access_key_id = AXXXXXXXXXXXXXX 218 | # aws_secret_access_key = XXXXXXXXXXXXXXXXXXX 219 | # aws_security_token = XXXXXXXXXXXXXXXXXXXXXXXXXXXX 220 | -------------------------------------------------------------------------------- /playbooks/ec2/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 | Optional region environment variable if region is 'auto' 16 | 17 | This script also assumes that there is an ec2.ini file alongside it. To specify a 18 | different path to ec2.ini, define the EC2_INI_PATH environment variable: 19 | 20 | export EC2_INI_PATH=/path/to/my_ec2.ini 21 | 22 | If you're using eucalyptus you need to set the above variables and 23 | you need to define: 24 | 25 | export EC2_URL=http://hostname_of_your_cc:port/services/Eucalyptus 26 | 27 | If you're using boto profiles (requires boto>=2.24.0) you can choose a profile 28 | using the --boto-profile command line argument (e.g. ec2.py --boto-profile prod) or using 29 | the AWS_PROFILE variable: 30 | 31 | AWS_PROFILE=prod ansible-playbook -i ec2.py myplaybook.yml 32 | 33 | For more details, see: http://docs.pythonboto.org/en/latest/boto_config_tut.html 34 | 35 | You can filter for specific EC2 instances by creating an environment variable 36 | named EC2_INSTANCE_FILTERS, which has the same format as the instance_filters 37 | entry documented in ec2.ini. For example, to find all hosts whose name begins 38 | with 'webserver', one might use: 39 | 40 | export EC2_INSTANCE_FILTERS='tag:Name=webserver*' 41 | 42 | When run against a specific host, this script returns the following variables: 43 | - ec2_ami_launch_index 44 | - ec2_architecture 45 | - ec2_association 46 | - ec2_attachTime 47 | - ec2_attachment 48 | - ec2_attachmentId 49 | - ec2_block_devices 50 | - ec2_client_token 51 | - ec2_deleteOnTermination 52 | - ec2_description 53 | - ec2_deviceIndex 54 | - ec2_dns_name 55 | - ec2_eventsSet 56 | - ec2_group_name 57 | - ec2_hypervisor 58 | - ec2_id 59 | - ec2_image_id 60 | - ec2_instanceState 61 | - ec2_instance_type 62 | - ec2_ipOwnerId 63 | - ec2_ip_address 64 | - ec2_item 65 | - ec2_kernel 66 | - ec2_key_name 67 | - ec2_launch_time 68 | - ec2_monitored 69 | - ec2_monitoring 70 | - ec2_networkInterfaceId 71 | - ec2_ownerId 72 | - ec2_persistent 73 | - ec2_placement 74 | - ec2_platform 75 | - ec2_previous_state 76 | - ec2_private_dns_name 77 | - ec2_private_ip_address 78 | - ec2_publicIp 79 | - ec2_public_dns_name 80 | - ec2_ramdisk 81 | - ec2_reason 82 | - ec2_region 83 | - ec2_requester_id 84 | - ec2_root_device_name 85 | - ec2_root_device_type 86 | - ec2_security_group_ids 87 | - ec2_security_group_names 88 | - ec2_shutdown_state 89 | - ec2_sourceDestCheck 90 | - ec2_spot_instance_request_id 91 | - ec2_state 92 | - ec2_state_code 93 | - ec2_state_reason 94 | - ec2_status 95 | - ec2_subnet_id 96 | - ec2_tenancy 97 | - ec2_virtualization_type 98 | - ec2_vpc_id 99 | 100 | These variables are pulled out of a boto.ec2.instance object. There is a lack of 101 | consistency with variable spellings (camelCase and underscores) since this 102 | just loops through all variables the object exposes. It is preferred to use the 103 | ones with underscores when multiple exist. 104 | 105 | In addition, if an instance has AWS tags associated with it, each tag is a new 106 | variable named: 107 | - ec2_tag_[Key] = [Value] 108 | 109 | Security groups are comma-separated in 'ec2_security_group_ids' and 110 | 'ec2_security_group_names'. 111 | 112 | When destination_format and destination_format_tags are specified 113 | the destination_format can be built from the instance tags and attributes. 114 | The behavior will first check the user defined tags, then proceed to 115 | check instance attributes, and finally if neither are found 'nil' will 116 | be used instead. 117 | 118 | 'my_instance': { 119 | 'region': 'us-east-1', # attribute 120 | 'availability_zone': 'us-east-1a', # attribute 121 | 'private_dns_name': '172.31.0.1', # attribute 122 | 'ec2_tag_deployment': 'blue', # tag 123 | 'ec2_tag_clusterid': 'ansible', # tag 124 | 'ec2_tag_Name': 'webserver', # tag 125 | ... 126 | } 127 | 128 | Inside of the ec2.ini file the following settings are specified: 129 | ... 130 | destination_format: {0}-{1}-{2}-{3} 131 | destination_format_tags: Name,clusterid,deployment,private_dns_name 132 | ... 133 | 134 | These settings would produce a destination_format as the following: 135 | 'webserver-ansible-blue-172.31.0.1' 136 | ''' 137 | 138 | # (c) 2012, Peter Sankauskas 139 | # 140 | # This file is part of Ansible, 141 | # 142 | # Ansible is free software: you can redistribute it and/or modify 143 | # it under the terms of the GNU General Public License as published by 144 | # the Free Software Foundation, either version 3 of the License, or 145 | # (at your option) any later version. 146 | # 147 | # Ansible is distributed in the hope that it will be useful, 148 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 149 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 150 | # GNU General Public License for more details. 151 | # 152 | # You should have received a copy of the GNU General Public License 153 | # along with Ansible. If not, see . 154 | 155 | ###################################################################### 156 | 157 | import sys 158 | import os 159 | import argparse 160 | import re 161 | from time import time 162 | import boto 163 | from boto import ec2 164 | from boto import rds 165 | from boto import elasticache 166 | from boto import route53 167 | from boto import sts 168 | import six 169 | 170 | from ansible.module_utils import ec2 as ec2_utils 171 | 172 | HAS_BOTO3 = False 173 | try: 174 | import boto3 # noqa 175 | HAS_BOTO3 = True 176 | except ImportError: 177 | pass 178 | 179 | from six.moves import configparser 180 | from collections import defaultdict 181 | 182 | import json 183 | 184 | DEFAULTS = { 185 | 'all_elasticache_clusters': 'False', 186 | 'all_elasticache_nodes': 'False', 187 | 'all_elasticache_replication_groups': 'False', 188 | 'all_instances': 'False', 189 | 'all_rds_instances': 'False', 190 | 'aws_access_key_id': None, 191 | 'aws_secret_access_key': None, 192 | 'aws_security_token': None, 193 | 'boto_profile': None, 194 | 'cache_max_age': '300', 195 | 'cache_path': '~/.ansible/tmp', 196 | 'destination_variable': 'public_dns_name', 197 | 'elasticache': 'True', 198 | 'eucalyptus': 'False', 199 | 'eucalyptus_host': None, 200 | 'expand_csv_tags': 'False', 201 | 'group_by_ami_id': 'True', 202 | 'group_by_availability_zone': 'True', 203 | 'group_by_aws_account': 'False', 204 | 'group_by_elasticache_cluster': 'True', 205 | 'group_by_elasticache_engine': 'True', 206 | 'group_by_elasticache_parameter_group': 'True', 207 | 'group_by_elasticache_replication_group': 'True', 208 | 'group_by_instance_id': 'True', 209 | 'group_by_instance_state': 'False', 210 | 'group_by_instance_type': 'True', 211 | 'group_by_key_pair': 'True', 212 | 'group_by_platform': 'True', 213 | 'group_by_rds_engine': 'True', 214 | 'group_by_rds_parameter_group': 'True', 215 | 'group_by_region': 'True', 216 | 'group_by_route53_names': 'True', 217 | 'group_by_security_group': 'True', 218 | 'group_by_tag_keys': 'True', 219 | 'group_by_tag_none': 'True', 220 | 'group_by_vpc_id': 'True', 221 | 'hostname_variable': None, 222 | 'iam_role': None, 223 | 'include_rds_clusters': 'False', 224 | 'nested_groups': 'False', 225 | 'pattern_exclude': None, 226 | 'pattern_include': None, 227 | 'rds': 'False', 228 | 'regions': 'all', 229 | 'regions_exclude': 'us-gov-west-1, cn-north-1', 230 | 'replace_dash_in_groups': 'True', 231 | 'route53': 'False', 232 | 'route53_excluded_zones': '', 233 | 'route53_hostnames': None, 234 | 'stack_filters': 'False', 235 | 'vpc_destination_variable': 'ip_address' 236 | } 237 | 238 | 239 | class Ec2Inventory(object): 240 | 241 | def _empty_inventory(self): 242 | return {"_meta": {"hostvars": {}}} 243 | 244 | def __init__(self): 245 | ''' Main execution path ''' 246 | 247 | # Inventory grouped by instance IDs, tags, security groups, regions, 248 | # and availability zones 249 | self.inventory = self._empty_inventory() 250 | 251 | self.aws_account_id = None 252 | 253 | # Index of hostname (address) to instance ID 254 | self.index = {} 255 | 256 | # Boto profile to use (if any) 257 | self.boto_profile = None 258 | 259 | # AWS credentials. 260 | self.credentials = {} 261 | 262 | # Read settings and parse CLI arguments 263 | self.parse_cli_args() 264 | self.read_settings() 265 | 266 | # Make sure that profile_name is not passed at all if not set 267 | # as pre 2.24 boto will fall over otherwise 268 | if self.boto_profile: 269 | if not hasattr(boto.ec2.EC2Connection, 'profile_name'): 270 | self.fail_with_error("boto version must be >= 2.24 to use profile") 271 | 272 | # Cache 273 | if self.args.refresh_cache: 274 | self.do_api_calls_update_cache() 275 | elif not self.is_cache_valid(): 276 | self.do_api_calls_update_cache() 277 | 278 | # Data to print 279 | if self.args.host: 280 | data_to_print = self.get_host_info() 281 | 282 | elif self.args.list: 283 | # Display list of instances for inventory 284 | if self.inventory == self._empty_inventory(): 285 | data_to_print = self.get_inventory_from_cache() 286 | else: 287 | data_to_print = self.json_format_dict(self.inventory, True) 288 | 289 | print(data_to_print) 290 | 291 | def is_cache_valid(self): 292 | ''' Determines if the cache files have expired, or if it is still valid ''' 293 | 294 | if os.path.isfile(self.cache_path_cache): 295 | mod_time = os.path.getmtime(self.cache_path_cache) 296 | current_time = time() 297 | if (mod_time + self.cache_max_age) > current_time: 298 | if os.path.isfile(self.cache_path_index): 299 | return True 300 | 301 | return False 302 | 303 | def read_settings(self): 304 | ''' Reads the settings from the ec2.ini file ''' 305 | 306 | scriptbasename = __file__ 307 | scriptbasename = os.path.basename(scriptbasename) 308 | scriptbasename = scriptbasename.replace('.py', '') 309 | 310 | defaults = { 311 | 'ec2': { 312 | 'ini_fallback': os.path.join(os.path.dirname(__file__), 'ec2.ini'), 313 | 'ini_path': os.path.join(os.path.dirname(__file__), '%s.ini' % scriptbasename) 314 | } 315 | } 316 | 317 | if six.PY3: 318 | config = configparser.ConfigParser(DEFAULTS) 319 | else: 320 | config = configparser.SafeConfigParser(DEFAULTS) 321 | ec2_ini_path = os.environ.get('EC2_INI_PATH', defaults['ec2']['ini_path']) 322 | ec2_ini_path = os.path.expanduser(os.path.expandvars(ec2_ini_path)) 323 | 324 | if not os.path.isfile(ec2_ini_path): 325 | ec2_ini_path = os.path.expanduser(defaults['ec2']['ini_fallback']) 326 | 327 | if os.path.isfile(ec2_ini_path): 328 | config.read(ec2_ini_path) 329 | 330 | # Add empty sections if they don't exist 331 | try: 332 | config.add_section('ec2') 333 | except configparser.DuplicateSectionError: 334 | pass 335 | 336 | try: 337 | config.add_section('credentials') 338 | except configparser.DuplicateSectionError: 339 | pass 340 | 341 | # is eucalyptus? 342 | self.eucalyptus = config.getboolean('ec2', 'eucalyptus') 343 | self.eucalyptus_host = config.get('ec2', 'eucalyptus_host') 344 | 345 | # Regions 346 | self.regions = [] 347 | config_regions = config.get('ec2', 'regions') 348 | if (config_regions == 'all'): 349 | if self.eucalyptus_host: 350 | self.regions.append(boto.connect_euca(host=self.eucalyptus_host).region.name, **self.credentials) 351 | else: 352 | config_regions_exclude = config.get('ec2', 'regions_exclude') 353 | 354 | for region_info in ec2.regions(): 355 | if region_info.name not in config_regions_exclude: 356 | self.regions.append(region_info.name) 357 | else: 358 | self.regions = config_regions.split(",") 359 | if 'auto' in self.regions: 360 | env_region = os.environ.get('AWS_REGION') 361 | if env_region is None: 362 | env_region = os.environ.get('AWS_DEFAULT_REGION') 363 | self.regions = [env_region] 364 | 365 | # Destination addresses 366 | self.destination_variable = config.get('ec2', 'destination_variable') 367 | self.vpc_destination_variable = config.get('ec2', 'vpc_destination_variable') 368 | self.hostname_variable = config.get('ec2', 'hostname_variable') 369 | 370 | if config.has_option('ec2', 'destination_format') and \ 371 | config.has_option('ec2', 'destination_format_tags'): 372 | self.destination_format = config.get('ec2', 'destination_format') 373 | self.destination_format_tags = config.get('ec2', 'destination_format_tags').split(',') 374 | else: 375 | self.destination_format = None 376 | self.destination_format_tags = None 377 | 378 | # Route53 379 | self.route53_enabled = config.getboolean('ec2', 'route53') 380 | self.route53_hostnames = config.get('ec2', 'route53_hostnames') 381 | 382 | self.route53_excluded_zones = [] 383 | self.route53_excluded_zones = [a for a in config.get('ec2', 'route53_excluded_zones').split(',') if a] 384 | 385 | # Include RDS instances? 386 | self.rds_enabled = config.getboolean('ec2', 'rds') 387 | 388 | # Include RDS cluster instances? 389 | self.include_rds_clusters = config.getboolean('ec2', 'include_rds_clusters') 390 | 391 | # Include ElastiCache instances? 392 | self.elasticache_enabled = config.getboolean('ec2', 'elasticache') 393 | 394 | # Return all EC2 instances? 395 | self.all_instances = config.getboolean('ec2', 'all_instances') 396 | 397 | # Instance states to be gathered in inventory. Default is 'running'. 398 | # Setting 'all_instances' to 'yes' overrides this option. 399 | ec2_valid_instance_states = [ 400 | 'pending', 401 | 'running', 402 | 'shutting-down', 403 | 'terminated', 404 | 'stopping', 405 | 'stopped' 406 | ] 407 | self.ec2_instance_states = [] 408 | if self.all_instances: 409 | self.ec2_instance_states = ec2_valid_instance_states 410 | elif config.has_option('ec2', 'instance_states'): 411 | for instance_state in config.get('ec2', 'instance_states').split(','): 412 | instance_state = instance_state.strip() 413 | if instance_state not in ec2_valid_instance_states: 414 | continue 415 | self.ec2_instance_states.append(instance_state) 416 | else: 417 | self.ec2_instance_states = ['running'] 418 | 419 | # Return all RDS instances? (if RDS is enabled) 420 | self.all_rds_instances = config.getboolean('ec2', 'all_rds_instances') 421 | 422 | # Return all ElastiCache replication groups? (if ElastiCache is enabled) 423 | self.all_elasticache_replication_groups = config.getboolean('ec2', 'all_elasticache_replication_groups') 424 | 425 | # Return all ElastiCache clusters? (if ElastiCache is enabled) 426 | self.all_elasticache_clusters = config.getboolean('ec2', 'all_elasticache_clusters') 427 | 428 | # Return all ElastiCache nodes? (if ElastiCache is enabled) 429 | self.all_elasticache_nodes = config.getboolean('ec2', 'all_elasticache_nodes') 430 | 431 | # boto configuration profile (prefer CLI argument then environment variables then config file) 432 | self.boto_profile = self.args.boto_profile or \ 433 | os.environ.get('AWS_PROFILE') or \ 434 | config.get('ec2', 'boto_profile') 435 | 436 | # AWS credentials (prefer environment variables) 437 | if not (self.boto_profile or os.environ.get('AWS_ACCESS_KEY_ID') or 438 | os.environ.get('AWS_PROFILE')): 439 | 440 | aws_access_key_id = config.get('credentials', 'aws_access_key_id') 441 | aws_secret_access_key = config.get('credentials', 'aws_secret_access_key') 442 | aws_security_token = config.get('credentials', 'aws_security_token') 443 | 444 | if aws_access_key_id: 445 | self.credentials = { 446 | 'aws_access_key_id': aws_access_key_id, 447 | 'aws_secret_access_key': aws_secret_access_key 448 | } 449 | if aws_security_token: 450 | self.credentials['security_token'] = aws_security_token 451 | 452 | # Cache related 453 | cache_dir = os.path.expanduser(config.get('ec2', 'cache_path')) 454 | if self.boto_profile: 455 | cache_dir = os.path.join(cache_dir, 'profile_' + self.boto_profile) 456 | if not os.path.exists(cache_dir): 457 | os.makedirs(cache_dir) 458 | 459 | cache_name = 'ansible-ec2' 460 | cache_id = self.boto_profile or os.environ.get('AWS_ACCESS_KEY_ID', self.credentials.get('aws_access_key_id')) 461 | if cache_id: 462 | cache_name = '%s-%s' % (cache_name, cache_id) 463 | cache_name += '-' + str(abs(hash(__file__)))[1:7] 464 | self.cache_path_cache = os.path.join(cache_dir, "%s.cache" % cache_name) 465 | self.cache_path_index = os.path.join(cache_dir, "%s.index" % cache_name) 466 | self.cache_max_age = config.getint('ec2', 'cache_max_age') 467 | 468 | self.expand_csv_tags = config.getboolean('ec2', 'expand_csv_tags') 469 | 470 | # Configure nested groups instead of flat namespace. 471 | self.nested_groups = config.getboolean('ec2', 'nested_groups') 472 | 473 | # Replace dash or not in group names 474 | self.replace_dash_in_groups = config.getboolean('ec2', 'replace_dash_in_groups') 475 | 476 | # IAM role to assume for connection 477 | self.iam_role = config.get('ec2', 'iam_role') 478 | 479 | # Configure which groups should be created. 480 | 481 | group_by_options = [a for a in DEFAULTS if a.startswith('group_by')] 482 | for option in group_by_options: 483 | setattr(self, option, config.getboolean('ec2', option)) 484 | 485 | # Do we need to just include hosts that match a pattern? 486 | self.pattern_include = config.get('ec2', 'pattern_include') 487 | if self.pattern_include: 488 | self.pattern_include = re.compile(self.pattern_include) 489 | 490 | # Do we need to exclude hosts that match a pattern? 491 | self.pattern_exclude = config.get('ec2', 'pattern_exclude') 492 | if self.pattern_exclude: 493 | self.pattern_exclude = re.compile(self.pattern_exclude) 494 | 495 | # Do we want to stack multiple filters? 496 | self.stack_filters = config.getboolean('ec2', 'stack_filters') 497 | 498 | # Instance filters (see boto and EC2 API docs). Ignore invalid filters. 499 | self.ec2_instance_filters = [] 500 | 501 | if config.has_option('ec2', 'instance_filters') or 'EC2_INSTANCE_FILTERS' in os.environ: 502 | filters = os.getenv('EC2_INSTANCE_FILTERS', config.get('ec2', 'instance_filters') if config.has_option('ec2', 'instance_filters') else '') 503 | 504 | if self.stack_filters and '&' in filters: 505 | self.fail_with_error("AND filters along with stack_filter enabled is not supported.\n") 506 | 507 | filter_sets = [f for f in filters.split(',') if f] 508 | 509 | for filter_set in filter_sets: 510 | filters = {} 511 | filter_set = filter_set.strip() 512 | for instance_filter in filter_set.split("&"): 513 | instance_filter = instance_filter.strip() 514 | if not instance_filter or '=' not in instance_filter: 515 | continue 516 | filter_key, filter_value = [x.strip() for x in instance_filter.split('=', 1)] 517 | if not filter_key: 518 | continue 519 | filters[filter_key] = filter_value 520 | self.ec2_instance_filters.append(filters.copy()) 521 | 522 | def parse_cli_args(self): 523 | ''' Command line argument processing ''' 524 | 525 | parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on EC2') 526 | parser.add_argument('--list', action='store_true', default=True, 527 | help='List instances (default: True)') 528 | parser.add_argument('--host', action='store', 529 | help='Get all the variables about a specific instance') 530 | parser.add_argument('--refresh-cache', action='store_true', default=False, 531 | help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)') 532 | parser.add_argument('--profile', '--boto-profile', action='store', dest='boto_profile', 533 | help='Use boto profile for connections to EC2') 534 | self.args = parser.parse_args() 535 | 536 | def do_api_calls_update_cache(self): 537 | ''' Do API calls to each region, and save data in cache files ''' 538 | 539 | if self.route53_enabled: 540 | self.get_route53_records() 541 | 542 | for region in self.regions: 543 | self.get_instances_by_region(region) 544 | if self.rds_enabled: 545 | self.get_rds_instances_by_region(region) 546 | if self.elasticache_enabled: 547 | self.get_elasticache_clusters_by_region(region) 548 | self.get_elasticache_replication_groups_by_region(region) 549 | if self.include_rds_clusters: 550 | self.include_rds_clusters_by_region(region) 551 | 552 | self.write_to_cache(self.inventory, self.cache_path_cache) 553 | self.write_to_cache(self.index, self.cache_path_index) 554 | 555 | def connect(self, region): 556 | ''' create connection to api server''' 557 | if self.eucalyptus: 558 | conn = boto.connect_euca(host=self.eucalyptus_host, **self.credentials) 559 | conn.APIVersion = '2010-08-31' 560 | else: 561 | conn = self.connect_to_aws(ec2, region) 562 | return conn 563 | 564 | def boto_fix_security_token_in_profile(self, connect_args): 565 | ''' monkey patch for boto issue boto/boto#2100 ''' 566 | profile = 'profile ' + self.boto_profile 567 | if boto.config.has_option(profile, 'aws_security_token'): 568 | connect_args['security_token'] = boto.config.get(profile, 'aws_security_token') 569 | return connect_args 570 | 571 | def connect_to_aws(self, module, region): 572 | connect_args = self.credentials 573 | 574 | # only pass the profile name if it's set (as it is not supported by older boto versions) 575 | if self.boto_profile: 576 | connect_args['profile_name'] = self.boto_profile 577 | self.boto_fix_security_token_in_profile(connect_args) 578 | 579 | if self.iam_role: 580 | sts_conn = sts.connect_to_region(region, **connect_args) 581 | role = sts_conn.assume_role(self.iam_role, 'ansible_dynamic_inventory') 582 | connect_args['aws_access_key_id'] = role.credentials.access_key 583 | connect_args['aws_secret_access_key'] = role.credentials.secret_key 584 | connect_args['security_token'] = role.credentials.session_token 585 | 586 | conn = module.connect_to_region(region, **connect_args) 587 | # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported 588 | if conn is None: 589 | self.fail_with_error("region name: %s likely not supported, or AWS is down. connection to region failed." % region) 590 | return conn 591 | 592 | def get_instances_by_region(self, region): 593 | ''' Makes an AWS EC2 API call to the list of instances in a particular 594 | region ''' 595 | 596 | try: 597 | conn = self.connect(region) 598 | reservations = [] 599 | if self.ec2_instance_filters: 600 | if self.stack_filters: 601 | filters_dict = {} 602 | for filters in self.ec2_instance_filters: 603 | filters_dict.update(filters) 604 | reservations.extend(conn.get_all_instances(filters=filters_dict)) 605 | else: 606 | for filters in self.ec2_instance_filters: 607 | reservations.extend(conn.get_all_instances(filters=filters)) 608 | else: 609 | reservations = conn.get_all_instances() 610 | 611 | # Pull the tags back in a second step 612 | # AWS are on record as saying that the tags fetched in the first `get_all_instances` request are not 613 | # reliable and may be missing, and the only way to guarantee they are there is by calling `get_all_tags` 614 | instance_ids = [] 615 | for reservation in reservations: 616 | instance_ids.extend([instance.id for instance in reservation.instances]) 617 | 618 | max_filter_value = 199 619 | tags = [] 620 | for i in range(0, len(instance_ids), max_filter_value): 621 | tags.extend(conn.get_all_tags(filters={'resource-type': 'instance', 'resource-id': instance_ids[i:i + max_filter_value]})) 622 | 623 | tags_by_instance_id = defaultdict(dict) 624 | for tag in tags: 625 | tags_by_instance_id[tag.res_id][tag.name] = tag.value 626 | 627 | if (not self.aws_account_id) and reservations: 628 | self.aws_account_id = reservations[0].owner_id 629 | 630 | for reservation in reservations: 631 | for instance in reservation.instances: 632 | instance.tags = tags_by_instance_id[instance.id] 633 | self.add_instance(instance, region) 634 | 635 | except boto.exception.BotoServerError as e: 636 | if e.error_code == 'AuthFailure': 637 | error = self.get_auth_error_message() 638 | else: 639 | backend = 'Eucalyptus' if self.eucalyptus else 'AWS' 640 | error = "Error connecting to %s backend.\n%s" % (backend, e.message) 641 | self.fail_with_error(error, 'getting EC2 instances') 642 | 643 | def tags_match_filters(self, tags): 644 | ''' return True if given tags match configured filters ''' 645 | if not self.ec2_instance_filters: 646 | return True 647 | 648 | for filters in self.ec2_instance_filters: 649 | for filter_name, filter_value in filters.items(): 650 | if filter_name[:4] != 'tag:': 651 | continue 652 | filter_name = filter_name[4:] 653 | if filter_name not in tags: 654 | if self.stack_filters: 655 | return False 656 | continue 657 | if isinstance(filter_value, list): 658 | if self.stack_filters and tags[filter_name] not in filter_value: 659 | return False 660 | if not self.stack_filters and tags[filter_name] in filter_value: 661 | return True 662 | if isinstance(filter_value, six.string_types): 663 | if self.stack_filters and tags[filter_name] != filter_value: 664 | return False 665 | if not self.stack_filters and tags[filter_name] == filter_value: 666 | return True 667 | 668 | return self.stack_filters 669 | 670 | def get_rds_instances_by_region(self, region): 671 | ''' Makes an AWS API call to the list of RDS instances in a particular 672 | region ''' 673 | 674 | if not HAS_BOTO3: 675 | self.fail_with_error("Working with RDS instances requires boto3 - please install boto3 and try again", 676 | "getting RDS instances") 677 | 678 | client = ec2_utils.boto3_inventory_conn('client', 'rds', region, **self.credentials) 679 | db_instances = client.describe_db_instances() 680 | 681 | try: 682 | conn = self.connect_to_aws(rds, region) 683 | if conn: 684 | marker = None 685 | while True: 686 | instances = conn.get_all_dbinstances(marker=marker) 687 | marker = instances.marker 688 | for index, instance in enumerate(instances): 689 | # Add tags to instances. 690 | instance.arn = db_instances['DBInstances'][index]['DBInstanceArn'] 691 | tags = client.list_tags_for_resource(ResourceName=instance.arn)['TagList'] 692 | instance.tags = {} 693 | for tag in tags: 694 | instance.tags[tag['Key']] = tag['Value'] 695 | if self.tags_match_filters(instance.tags): 696 | self.add_rds_instance(instance, region) 697 | if not marker: 698 | break 699 | except boto.exception.BotoServerError as e: 700 | error = e.reason 701 | 702 | if e.error_code == 'AuthFailure': 703 | error = self.get_auth_error_message() 704 | elif e.error_code == "OptInRequired": 705 | error = "RDS hasn't been enabled for this account yet. " \ 706 | "You must either log in to the RDS service through the AWS console to enable it, " \ 707 | "or set 'rds = False' in ec2.ini" 708 | elif not e.reason == "Forbidden": 709 | error = "Looks like AWS RDS is down:\n%s" % e.message 710 | self.fail_with_error(error, 'getting RDS instances') 711 | 712 | def include_rds_clusters_by_region(self, region): 713 | if not HAS_BOTO3: 714 | self.fail_with_error("Working with RDS clusters requires boto3 - please install boto3 and try again", 715 | "getting RDS clusters") 716 | 717 | client = ec2_utils.boto3_inventory_conn('client', 'rds', region, **self.credentials) 718 | 719 | marker, clusters = '', [] 720 | while marker is not None: 721 | resp = client.describe_db_clusters(Marker=marker) 722 | clusters.extend(resp["DBClusters"]) 723 | marker = resp.get('Marker', None) 724 | 725 | account_id = boto.connect_iam().get_user().arn.split(':')[4] 726 | c_dict = {} 727 | for c in clusters: 728 | # remove these datetime objects as there is no serialisation to json 729 | # currently in place and we don't need the data yet 730 | if 'EarliestRestorableTime' in c: 731 | del c['EarliestRestorableTime'] 732 | if 'LatestRestorableTime' in c: 733 | del c['LatestRestorableTime'] 734 | 735 | if not self.ec2_instance_filters: 736 | matches_filter = True 737 | else: 738 | matches_filter = False 739 | 740 | try: 741 | # arn:aws:rds:::: 742 | tags = client.list_tags_for_resource( 743 | ResourceName='arn:aws:rds:' + region + ':' + account_id + ':cluster:' + c['DBClusterIdentifier']) 744 | c['Tags'] = tags['TagList'] 745 | 746 | if self.ec2_instance_filters: 747 | for filters in self.ec2_instance_filters: 748 | for filter_key, filter_values in filters.items(): 749 | # get AWS tag key e.g. tag:env will be 'env' 750 | tag_name = filter_key.split(":", 1)[1] 751 | # Filter values is a list (if you put multiple values for the same tag name) 752 | matches_filter = any(d['Key'] == tag_name and d['Value'] in filter_values for d in c['Tags']) 753 | 754 | if matches_filter: 755 | # it matches a filter, so stop looking for further matches 756 | break 757 | 758 | if matches_filter: 759 | break 760 | 761 | except Exception as e: 762 | if e.message.find('DBInstanceNotFound') >= 0: 763 | # AWS RDS bug (2016-01-06) means deletion does not fully complete and leave an 'empty' cluster. 764 | # Ignore errors when trying to find tags for these 765 | pass 766 | 767 | # ignore empty clusters caused by AWS bug 768 | if len(c['DBClusterMembers']) == 0: 769 | continue 770 | elif matches_filter: 771 | c_dict[c['DBClusterIdentifier']] = c 772 | 773 | self.inventory['db_clusters'] = c_dict 774 | 775 | def get_elasticache_clusters_by_region(self, region): 776 | ''' Makes an AWS API call to the list of ElastiCache clusters (with 777 | nodes' info) in a particular region.''' 778 | 779 | # ElastiCache boto module doesn't provide a get_all_instances method, 780 | # that's why we need to call describe directly (it would be called by 781 | # the shorthand method anyway...) 782 | clusters = [] 783 | try: 784 | conn = self.connect_to_aws(elasticache, region) 785 | if conn: 786 | # show_cache_node_info = True 787 | # because we also want nodes' information 788 | _marker = 1 789 | while _marker: 790 | if _marker == 1: 791 | _marker = None 792 | response = conn.describe_cache_clusters(None, None, _marker, True) 793 | _marker = response['DescribeCacheClustersResponse']['DescribeCacheClustersResult']['Marker'] 794 | try: 795 | # Boto also doesn't provide wrapper classes to CacheClusters or 796 | # CacheNodes. Because of that we can't make use of the get_list 797 | # method in the AWSQueryConnection. Let's do the work manually 798 | clusters = clusters + response['DescribeCacheClustersResponse']['DescribeCacheClustersResult']['CacheClusters'] 799 | except KeyError as e: 800 | error = "ElastiCache query to AWS failed (unexpected format)." 801 | self.fail_with_error(error, 'getting ElastiCache clusters') 802 | except boto.exception.BotoServerError as e: 803 | error = e.reason 804 | 805 | if e.error_code == 'AuthFailure': 806 | error = self.get_auth_error_message() 807 | elif e.error_code == "OptInRequired": 808 | error = "ElastiCache hasn't been enabled for this account yet. " \ 809 | "You must either log in to the ElastiCache service through the AWS console to enable it, " \ 810 | "or set 'elasticache = False' in ec2.ini" 811 | elif not e.reason == "Forbidden": 812 | error = "Looks like AWS ElastiCache is down:\n%s" % e.message 813 | self.fail_with_error(error, 'getting ElastiCache clusters') 814 | 815 | for cluster in clusters: 816 | self.add_elasticache_cluster(cluster, region) 817 | 818 | def get_elasticache_replication_groups_by_region(self, region): 819 | ''' Makes an AWS API call to the list of ElastiCache replication groups 820 | in a particular region.''' 821 | 822 | # ElastiCache boto module doesn't provide a get_all_instances method, 823 | # that's why we need to call describe directly (it would be called by 824 | # the shorthand method anyway...) 825 | try: 826 | conn = self.connect_to_aws(elasticache, region) 827 | if conn: 828 | response = conn.describe_replication_groups() 829 | 830 | except boto.exception.BotoServerError as e: 831 | error = e.reason 832 | 833 | if e.error_code == 'AuthFailure': 834 | error = self.get_auth_error_message() 835 | if not e.reason == "Forbidden": 836 | error = "Looks like AWS ElastiCache [Replication Groups] is down:\n%s" % e.message 837 | self.fail_with_error(error, 'getting ElastiCache clusters') 838 | 839 | try: 840 | # Boto also doesn't provide wrapper classes to ReplicationGroups 841 | # Because of that we can't make use of the get_list method in the 842 | # AWSQueryConnection. Let's do the work manually 843 | replication_groups = response['DescribeReplicationGroupsResponse']['DescribeReplicationGroupsResult']['ReplicationGroups'] 844 | 845 | except KeyError as e: 846 | error = "ElastiCache [Replication Groups] query to AWS failed (unexpected format)." 847 | self.fail_with_error(error, 'getting ElastiCache clusters') 848 | 849 | for replication_group in replication_groups: 850 | self.add_elasticache_replication_group(replication_group, region) 851 | 852 | def get_auth_error_message(self): 853 | ''' create an informative error message if there is an issue authenticating''' 854 | errors = ["Authentication error retrieving ec2 inventory."] 855 | if None in [os.environ.get('AWS_ACCESS_KEY_ID'), os.environ.get('AWS_SECRET_ACCESS_KEY')]: 856 | errors.append(' - No AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY environment vars found') 857 | else: 858 | errors.append(' - AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment vars found but may not be correct') 859 | 860 | boto_paths = ['/etc/boto.cfg', '~/.boto', '~/.aws/credentials'] 861 | boto_config_found = [p for p in boto_paths if os.path.isfile(os.path.expanduser(p))] 862 | if len(boto_config_found) > 0: 863 | errors.append(" - Boto configs found at '%s', but the credentials contained may not be correct" % ', '.join(boto_config_found)) 864 | else: 865 | errors.append(" - No Boto config found at any expected location '%s'" % ', '.join(boto_paths)) 866 | 867 | return '\n'.join(errors) 868 | 869 | def fail_with_error(self, err_msg, err_operation=None): 870 | '''log an error to std err for ansible-playbook to consume and exit''' 871 | if err_operation: 872 | err_msg = 'ERROR: "{err_msg}", while: {err_operation}'.format( 873 | err_msg=err_msg, err_operation=err_operation) 874 | sys.stderr.write(err_msg) 875 | sys.exit(1) 876 | 877 | def get_instance(self, region, instance_id): 878 | conn = self.connect(region) 879 | 880 | reservations = conn.get_all_instances([instance_id]) 881 | for reservation in reservations: 882 | for instance in reservation.instances: 883 | return instance 884 | 885 | def add_instance(self, instance, region): 886 | ''' Adds an instance to the inventory and index, as long as it is 887 | addressable ''' 888 | 889 | # Only return instances with desired instance states 890 | if instance.state not in self.ec2_instance_states: 891 | return 892 | 893 | # Select the best destination address 894 | # When destination_format and destination_format_tags are specified 895 | # the following code will attempt to find the instance tags first, 896 | # then the instance attributes next, and finally if neither are found 897 | # assign nil for the desired destination format attribute. 898 | if self.destination_format and self.destination_format_tags: 899 | dest_vars = [] 900 | inst_tags = getattr(instance, 'tags') 901 | for tag in self.destination_format_tags: 902 | if tag in inst_tags: 903 | dest_vars.append(inst_tags[tag]) 904 | elif hasattr(instance, tag): 905 | dest_vars.append(getattr(instance, tag)) 906 | else: 907 | dest_vars.append('nil') 908 | 909 | dest = self.destination_format.format(*dest_vars) 910 | elif instance.subnet_id: 911 | dest = getattr(instance, self.vpc_destination_variable, None) 912 | if dest is None: 913 | dest = getattr(instance, 'tags').get(self.vpc_destination_variable, None) 914 | else: 915 | dest = getattr(instance, self.destination_variable, None) 916 | if dest is None: 917 | dest = getattr(instance, 'tags').get(self.destination_variable, None) 918 | 919 | if not dest: 920 | # Skip instances we cannot address (e.g. private VPC subnet) 921 | return 922 | 923 | # Set the inventory name 924 | hostname = None 925 | if self.hostname_variable: 926 | if self.hostname_variable.startswith('tag_'): 927 | hostname = instance.tags.get(self.hostname_variable[4:], None) 928 | else: 929 | hostname = getattr(instance, self.hostname_variable) 930 | 931 | # set the hostname from route53 932 | if self.route53_enabled and self.route53_hostnames: 933 | route53_names = self.get_instance_route53_names(instance) 934 | for name in route53_names: 935 | if name.endswith(self.route53_hostnames): 936 | hostname = name 937 | 938 | # If we can't get a nice hostname, use the destination address 939 | if not hostname: 940 | hostname = dest 941 | # to_safe strips hostname characters like dots, so don't strip route53 hostnames 942 | elif self.route53_enabled and self.route53_hostnames and hostname.endswith(self.route53_hostnames): 943 | hostname = hostname.lower() 944 | else: 945 | hostname = self.to_safe(hostname).lower() 946 | 947 | # if we only want to include hosts that match a pattern, skip those that don't 948 | if self.pattern_include and not self.pattern_include.match(hostname): 949 | return 950 | 951 | # if we need to exclude hosts that match a pattern, skip those 952 | if self.pattern_exclude and self.pattern_exclude.match(hostname): 953 | return 954 | 955 | # Add to index 956 | self.index[hostname] = [region, instance.id] 957 | 958 | # Inventory: Group by instance ID (always a group of 1) 959 | if self.group_by_instance_id: 960 | self.inventory[instance.id] = [hostname] 961 | if self.nested_groups: 962 | self.push_group(self.inventory, 'instances', instance.id) 963 | 964 | # Inventory: Group by region 965 | if self.group_by_region: 966 | self.push(self.inventory, region, hostname) 967 | if self.nested_groups: 968 | self.push_group(self.inventory, 'regions', region) 969 | 970 | # Inventory: Group by availability zone 971 | if self.group_by_availability_zone: 972 | self.push(self.inventory, instance.placement, hostname) 973 | if self.nested_groups: 974 | if self.group_by_region: 975 | self.push_group(self.inventory, region, instance.placement) 976 | self.push_group(self.inventory, 'zones', instance.placement) 977 | 978 | # Inventory: Group by Amazon Machine Image (AMI) ID 979 | if self.group_by_ami_id: 980 | ami_id = self.to_safe(instance.image_id) 981 | self.push(self.inventory, ami_id, hostname) 982 | if self.nested_groups: 983 | self.push_group(self.inventory, 'images', ami_id) 984 | 985 | # Inventory: Group by instance type 986 | if self.group_by_instance_type: 987 | type_name = self.to_safe('type_' + instance.instance_type) 988 | self.push(self.inventory, type_name, hostname) 989 | if self.nested_groups: 990 | self.push_group(self.inventory, 'types', type_name) 991 | 992 | # Inventory: Group by instance state 993 | if self.group_by_instance_state: 994 | state_name = self.to_safe('instance_state_' + instance.state) 995 | self.push(self.inventory, state_name, hostname) 996 | if self.nested_groups: 997 | self.push_group(self.inventory, 'instance_states', state_name) 998 | 999 | # Inventory: Group by platform 1000 | if self.group_by_platform: 1001 | if instance.platform: 1002 | platform = self.to_safe('platform_' + instance.platform) 1003 | else: 1004 | platform = self.to_safe('platform_undefined') 1005 | self.push(self.inventory, platform, hostname) 1006 | if self.nested_groups: 1007 | self.push_group(self.inventory, 'platforms', platform) 1008 | 1009 | # Inventory: Group by key pair 1010 | if self.group_by_key_pair and instance.key_name: 1011 | key_name = self.to_safe('key_' + instance.key_name) 1012 | self.push(self.inventory, key_name, hostname) 1013 | if self.nested_groups: 1014 | self.push_group(self.inventory, 'keys', key_name) 1015 | 1016 | # Inventory: Group by VPC 1017 | if self.group_by_vpc_id and instance.vpc_id: 1018 | vpc_id_name = self.to_safe('vpc_id_' + instance.vpc_id) 1019 | self.push(self.inventory, vpc_id_name, hostname) 1020 | if self.nested_groups: 1021 | self.push_group(self.inventory, 'vpcs', vpc_id_name) 1022 | 1023 | # Inventory: Group by security group 1024 | if self.group_by_security_group: 1025 | try: 1026 | for group in instance.groups: 1027 | key = self.to_safe("security_group_" + group.name) 1028 | self.push(self.inventory, key, hostname) 1029 | if self.nested_groups: 1030 | self.push_group(self.inventory, 'security_groups', key) 1031 | except AttributeError: 1032 | self.fail_with_error('\n'.join(['Package boto seems a bit older.', 1033 | 'Please upgrade boto >= 2.3.0.'])) 1034 | 1035 | # Inventory: Group by AWS account ID 1036 | if self.group_by_aws_account: 1037 | self.push(self.inventory, self.aws_account_id, hostname) 1038 | if self.nested_groups: 1039 | self.push_group(self.inventory, 'accounts', self.aws_account_id) 1040 | 1041 | # Inventory: Group by tag keys 1042 | if self.group_by_tag_keys: 1043 | for k, v in instance.tags.items(): 1044 | if self.expand_csv_tags and v and ',' in v: 1045 | values = map(lambda x: x.strip(), v.split(',')) 1046 | else: 1047 | values = [v] 1048 | 1049 | for v in values: 1050 | if v: 1051 | key = self.to_safe("tag_" + k + "=" + v) 1052 | else: 1053 | key = self.to_safe("tag_" + k) 1054 | self.push(self.inventory, key, hostname) 1055 | if self.nested_groups: 1056 | self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k)) 1057 | if v: 1058 | self.push_group(self.inventory, self.to_safe("tag_" + k), key) 1059 | 1060 | # Inventory: Group by Route53 domain names if enabled 1061 | if self.route53_enabled and self.group_by_route53_names: 1062 | route53_names = self.get_instance_route53_names(instance) 1063 | for name in route53_names: 1064 | self.push(self.inventory, name, hostname) 1065 | if self.nested_groups: 1066 | self.push_group(self.inventory, 'route53', name) 1067 | 1068 | # Global Tag: instances without tags 1069 | if self.group_by_tag_none and len(instance.tags) == 0: 1070 | self.push(self.inventory, 'tag_none', hostname) 1071 | if self.nested_groups: 1072 | self.push_group(self.inventory, 'tags', 'tag_none') 1073 | 1074 | # Global Tag: tag all EC2 instances 1075 | self.push(self.inventory, 'ec2', hostname) 1076 | 1077 | self.inventory["_meta"]["hostvars"][hostname] = self.get_host_info_dict_from_instance(instance) 1078 | self.inventory["_meta"]["hostvars"][hostname]['ansible_host'] = dest 1079 | 1080 | def add_rds_instance(self, instance, region): 1081 | ''' Adds an RDS instance to the inventory and index, as long as it is 1082 | addressable ''' 1083 | 1084 | # Only want available instances unless all_rds_instances is True 1085 | if not self.all_rds_instances and instance.status != 'available': 1086 | return 1087 | 1088 | # Select the best destination address 1089 | dest = instance.endpoint[0] 1090 | 1091 | if not dest: 1092 | # Skip instances we cannot address (e.g. private VPC subnet) 1093 | return 1094 | 1095 | # Set the inventory name 1096 | hostname = None 1097 | if self.hostname_variable: 1098 | if self.hostname_variable.startswith('tag_'): 1099 | hostname = instance.tags.get(self.hostname_variable[4:], None) 1100 | else: 1101 | hostname = getattr(instance, self.hostname_variable) 1102 | 1103 | # If we can't get a nice hostname, use the destination address 1104 | if not hostname: 1105 | hostname = dest 1106 | 1107 | hostname = self.to_safe(hostname).lower() 1108 | 1109 | # Add to index 1110 | self.index[hostname] = [region, instance.id] 1111 | 1112 | # Inventory: Group by instance ID (always a group of 1) 1113 | if self.group_by_instance_id: 1114 | self.inventory[instance.id] = [hostname] 1115 | if self.nested_groups: 1116 | self.push_group(self.inventory, 'instances', instance.id) 1117 | 1118 | # Inventory: Group by region 1119 | if self.group_by_region: 1120 | self.push(self.inventory, region, hostname) 1121 | if self.nested_groups: 1122 | self.push_group(self.inventory, 'regions', region) 1123 | 1124 | # Inventory: Group by availability zone 1125 | if self.group_by_availability_zone: 1126 | self.push(self.inventory, instance.availability_zone, hostname) 1127 | if self.nested_groups: 1128 | if self.group_by_region: 1129 | self.push_group(self.inventory, region, instance.availability_zone) 1130 | self.push_group(self.inventory, 'zones', instance.availability_zone) 1131 | 1132 | # Inventory: Group by instance type 1133 | if self.group_by_instance_type: 1134 | type_name = self.to_safe('type_' + instance.instance_class) 1135 | self.push(self.inventory, type_name, hostname) 1136 | if self.nested_groups: 1137 | self.push_group(self.inventory, 'types', type_name) 1138 | 1139 | # Inventory: Group by VPC 1140 | if self.group_by_vpc_id and instance.subnet_group and instance.subnet_group.vpc_id: 1141 | vpc_id_name = self.to_safe('vpc_id_' + instance.subnet_group.vpc_id) 1142 | self.push(self.inventory, vpc_id_name, hostname) 1143 | if self.nested_groups: 1144 | self.push_group(self.inventory, 'vpcs', vpc_id_name) 1145 | 1146 | # Inventory: Group by security group 1147 | if self.group_by_security_group: 1148 | try: 1149 | if instance.security_group: 1150 | key = self.to_safe("security_group_" + instance.security_group.name) 1151 | self.push(self.inventory, key, hostname) 1152 | if self.nested_groups: 1153 | self.push_group(self.inventory, 'security_groups', key) 1154 | 1155 | except AttributeError: 1156 | self.fail_with_error('\n'.join(['Package boto seems a bit older.', 1157 | 'Please upgrade boto >= 2.3.0.'])) 1158 | # Inventory: Group by tag keys 1159 | if self.group_by_tag_keys: 1160 | for k, v in instance.tags.items(): 1161 | if self.expand_csv_tags and v and ',' in v: 1162 | values = map(lambda x: x.strip(), v.split(',')) 1163 | else: 1164 | values = [v] 1165 | 1166 | for v in values: 1167 | if v: 1168 | key = self.to_safe("tag_" + k + "=" + v) 1169 | else: 1170 | key = self.to_safe("tag_" + k) 1171 | self.push(self.inventory, key, hostname) 1172 | if self.nested_groups: 1173 | self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k)) 1174 | if v: 1175 | self.push_group(self.inventory, self.to_safe("tag_" + k), key) 1176 | 1177 | # Inventory: Group by engine 1178 | if self.group_by_rds_engine: 1179 | self.push(self.inventory, self.to_safe("rds_" + instance.engine), hostname) 1180 | if self.nested_groups: 1181 | self.push_group(self.inventory, 'rds_engines', self.to_safe("rds_" + instance.engine)) 1182 | 1183 | # Inventory: Group by parameter group 1184 | if self.group_by_rds_parameter_group: 1185 | self.push(self.inventory, self.to_safe("rds_parameter_group_" + instance.parameter_group.name), hostname) 1186 | if self.nested_groups: 1187 | self.push_group(self.inventory, 'rds_parameter_groups', self.to_safe("rds_parameter_group_" + instance.parameter_group.name)) 1188 | 1189 | # Global Tag: instances without tags 1190 | if self.group_by_tag_none and len(instance.tags) == 0: 1191 | self.push(self.inventory, 'tag_none', hostname) 1192 | if self.nested_groups: 1193 | self.push_group(self.inventory, 'tags', 'tag_none') 1194 | 1195 | # Global Tag: all RDS instances 1196 | self.push(self.inventory, 'rds', hostname) 1197 | 1198 | self.inventory["_meta"]["hostvars"][hostname] = self.get_host_info_dict_from_instance(instance) 1199 | self.inventory["_meta"]["hostvars"][hostname]['ansible_host'] = dest 1200 | 1201 | def add_elasticache_cluster(self, cluster, region): 1202 | ''' Adds an ElastiCache cluster to the inventory and index, as long as 1203 | it's nodes are addressable ''' 1204 | 1205 | # Only want available clusters unless all_elasticache_clusters is True 1206 | if not self.all_elasticache_clusters and cluster['CacheClusterStatus'] != 'available': 1207 | return 1208 | 1209 | # Select the best destination address 1210 | if 'ConfigurationEndpoint' in cluster and cluster['ConfigurationEndpoint']: 1211 | # Memcached cluster 1212 | dest = cluster['ConfigurationEndpoint']['Address'] 1213 | is_redis = False 1214 | else: 1215 | # Redis sigle node cluster 1216 | # Because all Redis clusters are single nodes, we'll merge the 1217 | # info from the cluster with info about the node 1218 | dest = cluster['CacheNodes'][0]['Endpoint']['Address'] 1219 | is_redis = True 1220 | 1221 | if not dest: 1222 | # Skip clusters we cannot address (e.g. private VPC subnet) 1223 | return 1224 | 1225 | # Add to index 1226 | self.index[dest] = [region, cluster['CacheClusterId']] 1227 | 1228 | # Inventory: Group by instance ID (always a group of 1) 1229 | if self.group_by_instance_id: 1230 | self.inventory[cluster['CacheClusterId']] = [dest] 1231 | if self.nested_groups: 1232 | self.push_group(self.inventory, 'instances', cluster['CacheClusterId']) 1233 | 1234 | # Inventory: Group by region 1235 | if self.group_by_region and not is_redis: 1236 | self.push(self.inventory, region, dest) 1237 | if self.nested_groups: 1238 | self.push_group(self.inventory, 'regions', region) 1239 | 1240 | # Inventory: Group by availability zone 1241 | if self.group_by_availability_zone and not is_redis: 1242 | self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest) 1243 | if self.nested_groups: 1244 | if self.group_by_region: 1245 | self.push_group(self.inventory, region, cluster['PreferredAvailabilityZone']) 1246 | self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone']) 1247 | 1248 | # Inventory: Group by node type 1249 | if self.group_by_instance_type and not is_redis: 1250 | type_name = self.to_safe('type_' + cluster['CacheNodeType']) 1251 | self.push(self.inventory, type_name, dest) 1252 | if self.nested_groups: 1253 | self.push_group(self.inventory, 'types', type_name) 1254 | 1255 | # Inventory: Group by VPC (information not available in the current 1256 | # AWS API version for ElastiCache) 1257 | 1258 | # Inventory: Group by security group 1259 | if self.group_by_security_group and not is_redis: 1260 | 1261 | # Check for the existence of the 'SecurityGroups' key and also if 1262 | # this key has some value. When the cluster is not placed in a SG 1263 | # the query can return None here and cause an error. 1264 | if 'SecurityGroups' in cluster and cluster['SecurityGroups'] is not None: 1265 | for security_group in cluster['SecurityGroups']: 1266 | key = self.to_safe("security_group_" + security_group['SecurityGroupId']) 1267 | self.push(self.inventory, key, dest) 1268 | if self.nested_groups: 1269 | self.push_group(self.inventory, 'security_groups', key) 1270 | 1271 | # Inventory: Group by engine 1272 | if self.group_by_elasticache_engine and not is_redis: 1273 | self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest) 1274 | if self.nested_groups: 1275 | self.push_group(self.inventory, 'elasticache_engines', self.to_safe(cluster['Engine'])) 1276 | 1277 | # Inventory: Group by parameter group 1278 | if self.group_by_elasticache_parameter_group: 1279 | self.push(self.inventory, self.to_safe("elasticache_parameter_group_" + cluster['CacheParameterGroup']['CacheParameterGroupName']), dest) 1280 | if self.nested_groups: 1281 | self.push_group(self.inventory, 'elasticache_parameter_groups', self.to_safe(cluster['CacheParameterGroup']['CacheParameterGroupName'])) 1282 | 1283 | # Inventory: Group by replication group 1284 | if self.group_by_elasticache_replication_group and 'ReplicationGroupId' in cluster and cluster['ReplicationGroupId']: 1285 | self.push(self.inventory, self.to_safe("elasticache_replication_group_" + cluster['ReplicationGroupId']), dest) 1286 | if self.nested_groups: 1287 | self.push_group(self.inventory, 'elasticache_replication_groups', self.to_safe(cluster['ReplicationGroupId'])) 1288 | 1289 | # Global Tag: all ElastiCache clusters 1290 | self.push(self.inventory, 'elasticache_clusters', cluster['CacheClusterId']) 1291 | 1292 | host_info = self.get_host_info_dict_from_describe_dict(cluster) 1293 | 1294 | self.inventory["_meta"]["hostvars"][dest] = host_info 1295 | 1296 | # Add the nodes 1297 | for node in cluster['CacheNodes']: 1298 | self.add_elasticache_node(node, cluster, region) 1299 | 1300 | def add_elasticache_node(self, node, cluster, region): 1301 | ''' Adds an ElastiCache node to the inventory and index, as long as 1302 | it is addressable ''' 1303 | 1304 | # Only want available nodes unless all_elasticache_nodes is True 1305 | if not self.all_elasticache_nodes and node['CacheNodeStatus'] != 'available': 1306 | return 1307 | 1308 | # Select the best destination address 1309 | dest = node['Endpoint']['Address'] 1310 | 1311 | if not dest: 1312 | # Skip nodes we cannot address (e.g. private VPC subnet) 1313 | return 1314 | 1315 | node_id = self.to_safe(cluster['CacheClusterId'] + '_' + node['CacheNodeId']) 1316 | 1317 | # Add to index 1318 | self.index[dest] = [region, node_id] 1319 | 1320 | # Inventory: Group by node ID (always a group of 1) 1321 | if self.group_by_instance_id: 1322 | self.inventory[node_id] = [dest] 1323 | if self.nested_groups: 1324 | self.push_group(self.inventory, 'instances', node_id) 1325 | 1326 | # Inventory: Group by region 1327 | if self.group_by_region: 1328 | self.push(self.inventory, region, dest) 1329 | if self.nested_groups: 1330 | self.push_group(self.inventory, 'regions', region) 1331 | 1332 | # Inventory: Group by availability zone 1333 | if self.group_by_availability_zone: 1334 | self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest) 1335 | if self.nested_groups: 1336 | if self.group_by_region: 1337 | self.push_group(self.inventory, region, cluster['PreferredAvailabilityZone']) 1338 | self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone']) 1339 | 1340 | # Inventory: Group by node type 1341 | if self.group_by_instance_type: 1342 | type_name = self.to_safe('type_' + cluster['CacheNodeType']) 1343 | self.push(self.inventory, type_name, dest) 1344 | if self.nested_groups: 1345 | self.push_group(self.inventory, 'types', type_name) 1346 | 1347 | # Inventory: Group by VPC (information not available in the current 1348 | # AWS API version for ElastiCache) 1349 | 1350 | # Inventory: Group by security group 1351 | if self.group_by_security_group: 1352 | 1353 | # Check for the existence of the 'SecurityGroups' key and also if 1354 | # this key has some value. When the cluster is not placed in a SG 1355 | # the query can return None here and cause an error. 1356 | if 'SecurityGroups' in cluster and cluster['SecurityGroups'] is not None: 1357 | for security_group in cluster['SecurityGroups']: 1358 | key = self.to_safe("security_group_" + security_group['SecurityGroupId']) 1359 | self.push(self.inventory, key, dest) 1360 | if self.nested_groups: 1361 | self.push_group(self.inventory, 'security_groups', key) 1362 | 1363 | # Inventory: Group by engine 1364 | if self.group_by_elasticache_engine: 1365 | self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest) 1366 | if self.nested_groups: 1367 | self.push_group(self.inventory, 'elasticache_engines', self.to_safe("elasticache_" + cluster['Engine'])) 1368 | 1369 | # Inventory: Group by parameter group (done at cluster level) 1370 | 1371 | # Inventory: Group by replication group (done at cluster level) 1372 | 1373 | # Inventory: Group by ElastiCache Cluster 1374 | if self.group_by_elasticache_cluster: 1375 | self.push(self.inventory, self.to_safe("elasticache_cluster_" + cluster['CacheClusterId']), dest) 1376 | 1377 | # Global Tag: all ElastiCache nodes 1378 | self.push(self.inventory, 'elasticache_nodes', dest) 1379 | 1380 | host_info = self.get_host_info_dict_from_describe_dict(node) 1381 | 1382 | if dest in self.inventory["_meta"]["hostvars"]: 1383 | self.inventory["_meta"]["hostvars"][dest].update(host_info) 1384 | else: 1385 | self.inventory["_meta"]["hostvars"][dest] = host_info 1386 | 1387 | def add_elasticache_replication_group(self, replication_group, region): 1388 | ''' Adds an ElastiCache replication group to the inventory and index ''' 1389 | 1390 | # Only want available clusters unless all_elasticache_replication_groups is True 1391 | if not self.all_elasticache_replication_groups and replication_group['Status'] != 'available': 1392 | return 1393 | 1394 | # Skip clusters we cannot address (e.g. private VPC subnet or clustered redis) 1395 | if replication_group['NodeGroups'][0]['PrimaryEndpoint'] is None or \ 1396 | replication_group['NodeGroups'][0]['PrimaryEndpoint']['Address'] is None: 1397 | return 1398 | 1399 | # Select the best destination address (PrimaryEndpoint) 1400 | dest = replication_group['NodeGroups'][0]['PrimaryEndpoint']['Address'] 1401 | 1402 | # Add to index 1403 | self.index[dest] = [region, replication_group['ReplicationGroupId']] 1404 | 1405 | # Inventory: Group by ID (always a group of 1) 1406 | if self.group_by_instance_id: 1407 | self.inventory[replication_group['ReplicationGroupId']] = [dest] 1408 | if self.nested_groups: 1409 | self.push_group(self.inventory, 'instances', replication_group['ReplicationGroupId']) 1410 | 1411 | # Inventory: Group by region 1412 | if self.group_by_region: 1413 | self.push(self.inventory, region, dest) 1414 | if self.nested_groups: 1415 | self.push_group(self.inventory, 'regions', region) 1416 | 1417 | # Inventory: Group by availability zone (doesn't apply to replication groups) 1418 | 1419 | # Inventory: Group by node type (doesn't apply to replication groups) 1420 | 1421 | # Inventory: Group by VPC (information not available in the current 1422 | # AWS API version for replication groups 1423 | 1424 | # Inventory: Group by security group (doesn't apply to replication groups) 1425 | # Check this value in cluster level 1426 | 1427 | # Inventory: Group by engine (replication groups are always Redis) 1428 | if self.group_by_elasticache_engine: 1429 | self.push(self.inventory, 'elasticache_redis', dest) 1430 | if self.nested_groups: 1431 | self.push_group(self.inventory, 'elasticache_engines', 'redis') 1432 | 1433 | # Global Tag: all ElastiCache clusters 1434 | self.push(self.inventory, 'elasticache_replication_groups', replication_group['ReplicationGroupId']) 1435 | 1436 | host_info = self.get_host_info_dict_from_describe_dict(replication_group) 1437 | 1438 | self.inventory["_meta"]["hostvars"][dest] = host_info 1439 | 1440 | def get_route53_records(self): 1441 | ''' Get and store the map of resource records to domain names that 1442 | point to them. ''' 1443 | 1444 | if self.boto_profile: 1445 | r53_conn = route53.Route53Connection(profile_name=self.boto_profile) 1446 | else: 1447 | r53_conn = route53.Route53Connection() 1448 | all_zones = r53_conn.get_zones() 1449 | 1450 | route53_zones = [zone for zone in all_zones if zone.name[:-1] not in self.route53_excluded_zones] 1451 | 1452 | self.route53_records = {} 1453 | 1454 | for zone in route53_zones: 1455 | rrsets = r53_conn.get_all_rrsets(zone.id) 1456 | 1457 | for record_set in rrsets: 1458 | record_name = record_set.name 1459 | 1460 | if record_name.endswith('.'): 1461 | record_name = record_name[:-1] 1462 | 1463 | for resource in record_set.resource_records: 1464 | self.route53_records.setdefault(resource, set()) 1465 | self.route53_records[resource].add(record_name) 1466 | 1467 | def get_instance_route53_names(self, instance): 1468 | ''' Check if an instance is referenced in the records we have from 1469 | Route53. If it is, return the list of domain names pointing to said 1470 | instance. If nothing points to it, return an empty list. ''' 1471 | 1472 | instance_attributes = ['public_dns_name', 'private_dns_name', 1473 | 'ip_address', 'private_ip_address'] 1474 | 1475 | name_list = set() 1476 | 1477 | for attrib in instance_attributes: 1478 | try: 1479 | value = getattr(instance, attrib) 1480 | except AttributeError: 1481 | continue 1482 | 1483 | if value in self.route53_records: 1484 | name_list.update(self.route53_records[value]) 1485 | 1486 | return list(name_list) 1487 | 1488 | def get_host_info_dict_from_instance(self, instance): 1489 | instance_vars = {} 1490 | for key in vars(instance): 1491 | value = getattr(instance, key) 1492 | key = self.to_safe('ec2_' + key) 1493 | 1494 | # Handle complex types 1495 | # state/previous_state changed to properties in boto in https://github.com/boto/boto/commit/a23c379837f698212252720d2af8dec0325c9518 1496 | if key == 'ec2__state': 1497 | instance_vars['ec2_state'] = instance.state or '' 1498 | instance_vars['ec2_state_code'] = instance.state_code 1499 | elif key == 'ec2__previous_state': 1500 | instance_vars['ec2_previous_state'] = instance.previous_state or '' 1501 | instance_vars['ec2_previous_state_code'] = instance.previous_state_code 1502 | elif isinstance(value, (int, bool)): 1503 | instance_vars[key] = value 1504 | elif isinstance(value, six.string_types): 1505 | instance_vars[key] = value.strip() 1506 | elif value is None: 1507 | instance_vars[key] = '' 1508 | elif key == 'ec2_region': 1509 | instance_vars[key] = value.name 1510 | elif key == 'ec2__placement': 1511 | instance_vars['ec2_placement'] = value.zone 1512 | elif key == 'ec2_tags': 1513 | for k, v in value.items(): 1514 | if self.expand_csv_tags and ',' in v: 1515 | v = list(map(lambda x: x.strip(), v.split(','))) 1516 | key = self.to_safe('ec2_tag_' + k) 1517 | instance_vars[key] = v 1518 | elif key == 'ec2_groups': 1519 | group_ids = [] 1520 | group_names = [] 1521 | for group in value: 1522 | group_ids.append(group.id) 1523 | group_names.append(group.name) 1524 | instance_vars["ec2_security_group_ids"] = ','.join([str(i) for i in group_ids]) 1525 | instance_vars["ec2_security_group_names"] = ','.join([str(i) for i in group_names]) 1526 | elif key == 'ec2_block_device_mapping': 1527 | instance_vars["ec2_block_devices"] = {} 1528 | for k, v in value.items(): 1529 | instance_vars["ec2_block_devices"][os.path.basename(k)] = v.volume_id 1530 | else: 1531 | pass 1532 | # TODO Product codes if someone finds them useful 1533 | # print key 1534 | # print type(value) 1535 | # print value 1536 | 1537 | instance_vars[self.to_safe('ec2_account_id')] = self.aws_account_id 1538 | 1539 | return instance_vars 1540 | 1541 | def get_host_info_dict_from_describe_dict(self, describe_dict): 1542 | ''' Parses the dictionary returned by the API call into a flat list 1543 | of parameters. This method should be used only when 'describe' is 1544 | used directly because Boto doesn't provide specific classes. ''' 1545 | 1546 | # I really don't agree with prefixing everything with 'ec2' 1547 | # because EC2, RDS and ElastiCache are different services. 1548 | # I'm just following the pattern used until now to not break any 1549 | # compatibility. 1550 | 1551 | host_info = {} 1552 | for key in describe_dict: 1553 | value = describe_dict[key] 1554 | key = self.to_safe('ec2_' + self.uncammelize(key)) 1555 | 1556 | # Handle complex types 1557 | 1558 | # Target: Memcached Cache Clusters 1559 | if key == 'ec2_configuration_endpoint' and value: 1560 | host_info['ec2_configuration_endpoint_address'] = value['Address'] 1561 | host_info['ec2_configuration_endpoint_port'] = value['Port'] 1562 | 1563 | # Target: Cache Nodes and Redis Cache Clusters (single node) 1564 | if key == 'ec2_endpoint' and value: 1565 | host_info['ec2_endpoint_address'] = value['Address'] 1566 | host_info['ec2_endpoint_port'] = value['Port'] 1567 | 1568 | # Target: Redis Replication Groups 1569 | if key == 'ec2_node_groups' and value: 1570 | host_info['ec2_endpoint_address'] = value[0]['PrimaryEndpoint']['Address'] 1571 | host_info['ec2_endpoint_port'] = value[0]['PrimaryEndpoint']['Port'] 1572 | replica_count = 0 1573 | for node in value[0]['NodeGroupMembers']: 1574 | if node['CurrentRole'] == 'primary': 1575 | host_info['ec2_primary_cluster_address'] = node['ReadEndpoint']['Address'] 1576 | host_info['ec2_primary_cluster_port'] = node['ReadEndpoint']['Port'] 1577 | host_info['ec2_primary_cluster_id'] = node['CacheClusterId'] 1578 | elif node['CurrentRole'] == 'replica': 1579 | host_info['ec2_replica_cluster_address_' + str(replica_count)] = node['ReadEndpoint']['Address'] 1580 | host_info['ec2_replica_cluster_port_' + str(replica_count)] = node['ReadEndpoint']['Port'] 1581 | host_info['ec2_replica_cluster_id_' + str(replica_count)] = node['CacheClusterId'] 1582 | replica_count += 1 1583 | 1584 | # Target: Redis Replication Groups 1585 | if key == 'ec2_member_clusters' and value: 1586 | host_info['ec2_member_clusters'] = ','.join([str(i) for i in value]) 1587 | 1588 | # Target: All Cache Clusters 1589 | elif key == 'ec2_cache_parameter_group': 1590 | host_info["ec2_cache_node_ids_to_reboot"] = ','.join([str(i) for i in value['CacheNodeIdsToReboot']]) 1591 | host_info['ec2_cache_parameter_group_name'] = value['CacheParameterGroupName'] 1592 | host_info['ec2_cache_parameter_apply_status'] = value['ParameterApplyStatus'] 1593 | 1594 | # Target: Almost everything 1595 | elif key == 'ec2_security_groups': 1596 | 1597 | # Skip if SecurityGroups is None 1598 | # (it is possible to have the key defined but no value in it). 1599 | if value is not None: 1600 | sg_ids = [] 1601 | for sg in value: 1602 | sg_ids.append(sg['SecurityGroupId']) 1603 | host_info["ec2_security_group_ids"] = ','.join([str(i) for i in sg_ids]) 1604 | 1605 | # Target: Everything 1606 | # Preserve booleans and integers 1607 | elif isinstance(value, (int, bool)): 1608 | host_info[key] = value 1609 | 1610 | # Target: Everything 1611 | # Sanitize string values 1612 | elif isinstance(value, six.string_types): 1613 | host_info[key] = value.strip() 1614 | 1615 | # Target: Everything 1616 | # Replace None by an empty string 1617 | elif value is None: 1618 | host_info[key] = '' 1619 | 1620 | else: 1621 | # Remove non-processed complex types 1622 | pass 1623 | 1624 | return host_info 1625 | 1626 | def get_host_info(self): 1627 | ''' Get variables about a specific host ''' 1628 | 1629 | if len(self.index) == 0: 1630 | # Need to load index from cache 1631 | self.load_index_from_cache() 1632 | 1633 | if self.args.host not in self.index: 1634 | # try updating the cache 1635 | self.do_api_calls_update_cache() 1636 | if self.args.host not in self.index: 1637 | # host might not exist anymore 1638 | return self.json_format_dict({}, True) 1639 | 1640 | (region, instance_id) = self.index[self.args.host] 1641 | 1642 | instance = self.get_instance(region, instance_id) 1643 | return self.json_format_dict(self.get_host_info_dict_from_instance(instance), True) 1644 | 1645 | def push(self, my_dict, key, element): 1646 | ''' Push an element onto an array that may not have been defined in 1647 | the dict ''' 1648 | group_info = my_dict.setdefault(key, []) 1649 | if isinstance(group_info, dict): 1650 | host_list = group_info.setdefault('hosts', []) 1651 | host_list.append(element) 1652 | else: 1653 | group_info.append(element) 1654 | 1655 | def push_group(self, my_dict, key, element): 1656 | ''' Push a group as a child of another group. ''' 1657 | parent_group = my_dict.setdefault(key, {}) 1658 | if not isinstance(parent_group, dict): 1659 | parent_group = my_dict[key] = {'hosts': parent_group} 1660 | child_groups = parent_group.setdefault('children', []) 1661 | if element not in child_groups: 1662 | child_groups.append(element) 1663 | 1664 | def get_inventory_from_cache(self): 1665 | ''' Reads the inventory from the cache file and returns it as a JSON 1666 | object ''' 1667 | 1668 | with open(self.cache_path_cache, 'r') as f: 1669 | json_inventory = f.read() 1670 | return json_inventory 1671 | 1672 | def load_index_from_cache(self): 1673 | ''' Reads the index from the cache file sets self.index ''' 1674 | 1675 | with open(self.cache_path_index, 'rb') as f: 1676 | self.index = json.load(f) 1677 | 1678 | def write_to_cache(self, data, filename): 1679 | ''' Writes data in JSON format to a file ''' 1680 | 1681 | json_data = self.json_format_dict(data, True) 1682 | with open(filename, 'w') as f: 1683 | f.write(json_data) 1684 | 1685 | def uncammelize(self, key): 1686 | temp = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', key) 1687 | return re.sub('([a-z0-9])([A-Z])', r'\1_\2', temp).lower() 1688 | 1689 | def to_safe(self, word): 1690 | ''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups ''' 1691 | regex = r"[^A-Za-z0-9\_" 1692 | if not self.replace_dash_in_groups: 1693 | regex += r"\-" 1694 | return re.sub(regex + "]", "_", word) 1695 | 1696 | def json_format_dict(self, data, pretty=False): 1697 | ''' Converts a dict to a JSON object and dumps it as a formatted 1698 | string ''' 1699 | 1700 | if pretty: 1701 | return json.dumps(data, sort_keys=True, indent=2) 1702 | else: 1703 | return json.dumps(data) 1704 | 1705 | 1706 | if __name__ == '__main__': 1707 | # Run the script 1708 | Ec2Inventory() 1709 | -------------------------------------------------------------------------------- /playbooks/ec2/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' and do not 14 | # provide the 'regions_exclude' option. If this is set to 'auto', AWS_REGION or 15 | # AWS_DEFAULT_REGION environment variable will be read to determine the region. 16 | regions = all 17 | regions_exclude = us-gov-west-1, cn-north-1 18 | 19 | # When generating inventory, Ansible needs to know how to address a server. 20 | # Each EC2 instance has a lot of variables associated with it. Here is the list: 21 | # http://docs.pythonboto.org/en/latest/ref/ec2.html#module-boto.ec2.instance 22 | # Below are 2 variables that are used as the address of a server: 23 | # - destination_variable 24 | # - vpc_destination_variable 25 | 26 | # This is the normal destination variable to use. If you are running Ansible 27 | # from outside EC2, then 'public_dns_name' makes the most sense. If you are 28 | # running Ansible from within EC2, then perhaps you want to use the internal 29 | # address, and should set this to 'private_dns_name'. The key of an EC2 tag 30 | # may optionally be used; however the boto instance variables hold precedence 31 | # in the event of a collision. 32 | destination_variable = public_dns_name 33 | 34 | # This allows you to override the inventory_name with an ec2 variable, instead 35 | # of using the destination_variable above. Addressing (aka ansible_ssh_host) 36 | # will still use destination_variable. Tags should be written as 'tag_TAGNAME'. 37 | #hostname_variable = tag_Name 38 | 39 | # For server inside a VPC, using DNS names may not make sense. When an instance 40 | # has 'subnet_id' set, this variable is used. If the subnet is public, setting 41 | # this to 'ip_address' will return the public IP address. For instances in a 42 | # private subnet, this should be set to 'private_ip_address', and Ansible must 43 | # be run from within EC2. The key of an EC2 tag may optionally be used; however 44 | # the boto instance variables hold precedence in the event of a collision. 45 | # WARNING: - instances that are in the private vpc, _without_ public ip address 46 | # will not be listed in the inventory until You set: 47 | # vpc_destination_variable = private_ip_address 48 | vpc_destination_variable = ip_address 49 | 50 | # The following two settings allow flexible ansible host naming based on a 51 | # python format string and a comma-separated list of ec2 tags. Note that: 52 | # 53 | # 1) If the tags referenced are not present for some instances, empty strings 54 | # will be substituted in the format string. 55 | # 2) This overrides both destination_variable and vpc_destination_variable. 56 | # 57 | #destination_format = {0}.{1}.example.com 58 | #destination_format_tags = Name,environment 59 | 60 | # To tag instances on EC2 with the resource records that point to them from 61 | # Route53, set 'route53' to True. 62 | route53 = False 63 | 64 | # To use Route53 records as the inventory hostnames, uncomment and set 65 | # to equal the domain name you wish to use. You must also have 'route53' (above) 66 | # set to True. 67 | # route53_hostnames = .example.com 68 | 69 | # To exclude RDS instances from the inventory, uncomment and set to False. 70 | #rds = False 71 | 72 | # To exclude ElastiCache instances from the inventory, uncomment and set to False. 73 | #elasticache = False 74 | 75 | # Additionally, you can specify the list of zones to exclude looking up in 76 | # 'route53_excluded_zones' as a comma-separated list. 77 | # route53_excluded_zones = samplezone1.com, samplezone2.com 78 | 79 | # By default, only EC2 instances in the 'running' state are returned. Set 80 | # 'all_instances' to True to return all instances regardless of state. 81 | all_instances = False 82 | 83 | # By default, only EC2 instances in the 'running' state are returned. Specify 84 | # EC2 instance states to return as a comma-separated list. This 85 | # option is overridden when 'all_instances' is True. 86 | # instance_states = pending, running, shutting-down, terminated, stopping, stopped 87 | 88 | # By default, only RDS instances in the 'available' state are returned. Set 89 | # 'all_rds_instances' to True return all RDS instances regardless of state. 90 | all_rds_instances = False 91 | 92 | # Include RDS cluster information (Aurora etc.) 93 | include_rds_clusters = False 94 | 95 | # By default, only ElastiCache clusters and nodes in the 'available' state 96 | # are returned. Set 'all_elasticache_clusters' and/or 'all_elastic_nodes' 97 | # to True return all ElastiCache clusters and nodes, regardless of state. 98 | # 99 | # Note that all_elasticache_nodes only applies to listed clusters. That means 100 | # if you set all_elastic_clusters to false, no node will be return from 101 | # unavailable clusters, regardless of the state and to what you set for 102 | # all_elasticache_nodes. 103 | all_elasticache_replication_groups = False 104 | all_elasticache_clusters = False 105 | all_elasticache_nodes = False 106 | 107 | # API calls to EC2 are slow. For this reason, we cache the results of an API 108 | # call. Set this to the path you want cache files to be written to. Two files 109 | # will be written to this directory: 110 | # - ansible-ec2.cache 111 | # - ansible-ec2.index 112 | cache_path = ~/.ansible/tmp 113 | 114 | # The number of seconds a cache file is considered valid. After this many 115 | # seconds, a new API call will be made, and the cache file will be updated. 116 | # To disable the cache, set this value to 0 117 | cache_max_age = 300 118 | 119 | # Organize groups into a nested/hierarchy instead of a flat namespace. 120 | nested_groups = False 121 | 122 | # Replace - tags when creating groups to avoid issues with ansible 123 | replace_dash_in_groups = True 124 | 125 | # If set to true, any tag of the form "a,b,c" is expanded into a list 126 | # and the results are used to create additional tag_* inventory groups. 127 | expand_csv_tags = False 128 | 129 | # The EC2 inventory output can become very large. To manage its size, 130 | # configure which groups should be created. 131 | group_by_instance_id = True 132 | group_by_region = True 133 | group_by_availability_zone = True 134 | group_by_aws_account = False 135 | group_by_ami_id = True 136 | group_by_instance_type = True 137 | group_by_instance_state = False 138 | group_by_platform = True 139 | group_by_key_pair = True 140 | group_by_vpc_id = True 141 | group_by_security_group = True 142 | group_by_tag_keys = True 143 | group_by_tag_none = True 144 | group_by_route53_names = True 145 | group_by_rds_engine = True 146 | group_by_rds_parameter_group = True 147 | group_by_elasticache_engine = True 148 | group_by_elasticache_cluster = True 149 | group_by_elasticache_parameter_group = True 150 | group_by_elasticache_replication_group = True 151 | 152 | # If you only want to include hosts that match a certain regular expression 153 | # pattern_include = staging-* 154 | 155 | # If you want to exclude any hosts that match a certain regular expression 156 | # pattern_exclude = staging-* 157 | 158 | # Instance filters can be used to control which instances are retrieved for 159 | # inventory. For the full list of possible filters, please read the EC2 API 160 | # docs: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html#query-DescribeInstances-filters 161 | # Filters are key/value pairs separated by '=', to list multiple filters use 162 | # a list separated by commas. To "AND" criteria together, use "&". Note that 163 | # the "AND" is not useful along with stack_filters and so such usage is not allowed. 164 | # See examples below. 165 | 166 | # If you want to apply multiple filters simultaneously, set stack_filters to 167 | # True. Default behaviour is to combine the results of all filters. Stacking 168 | # allows the use of multiple conditions to filter down, for example by 169 | # environment and type of host. 170 | stack_filters = False 171 | 172 | # Retrieve only instances with (key=value) env=staging tag 173 | # instance_filters = tag:env=staging 174 | 175 | # Retrieve only instances with role=webservers OR role=dbservers tag 176 | # instance_filters = tag:role=webservers,tag:role=dbservers 177 | 178 | # Retrieve only t1.micro instances OR instances with tag env=staging 179 | # instance_filters = instance-type=t1.micro,tag:env=staging 180 | 181 | # You can use wildcards in filter values also. Below will list instances which 182 | # tag Name value matches webservers1* 183 | # (ex. webservers15, webservers1a, webservers123 etc) 184 | # instance_filters = tag:Name=webservers1* 185 | 186 | # Retrieve only instances of type t1.micro that also have tag env=stage 187 | # instance_filters = instance-type=t1.micro&tag:env=stage 188 | 189 | # Retrieve instances of type t1.micro AND tag env=stage, as well as any instance 190 | # that are of type m3.large, regardless of env tag 191 | # instance_filters = instance-type=t1.micro&tag:env=stage,instance-type=m3.large 192 | 193 | # An IAM role can be assumed, so all requests are run as that role. 194 | # This can be useful for connecting across different accounts, or to limit user 195 | # access 196 | # iam_role = role-arn 197 | 198 | # A boto configuration profile may be used to separate out credentials 199 | # see https://boto.readthedocs.io/en/latest/boto_config_tut.html 200 | # boto_profile = some-boto-profile-name 201 | 202 | 203 | [credentials] 204 | 205 | # The AWS credentials can optionally be specified here. Credentials specified 206 | # here are ignored if the environment variable AWS_ACCESS_KEY_ID or 207 | # AWS_PROFILE is set, or if the boto_profile property above is set. 208 | # 209 | # Supplying AWS credentials here is not recommended, as it introduces 210 | # non-trivial security concerns. When going down this route, please make sure 211 | # to set access permissions for this file correctly, e.g. handle it the same 212 | # way as you would a private SSH key. 213 | # 214 | # Unlike the boto and AWS configure files, this section does not support 215 | # profiles. 216 | # 217 | # aws_access_key_id = AXXXXXXXXXXXXXX 218 | # aws_secret_access_key = XXXXXXXXXXXXXXXXXXX 219 | # aws_security_token = XXXXXXXXXXXXXXXXXXXXXXXXXXXX 220 | -------------------------------------------------------------------------------- /playbooks/ec2/inventory/inventory: -------------------------------------------------------------------------------- 1 | ansible1.example.com 2 | 3 | [webservers] 4 | ansible2.example.com 5 | 6 | [proxyservers] 7 | proxy.example.com 8 | 9 | [dockerhost] 10 | docker.example.com 11 | -------------------------------------------------------------------------------- /playbooks/ec2/list-instances.yaml: -------------------------------------------------------------------------------- 1 | - name: list instances 2 | hosts: localhost 3 | tasks: 4 | - name: find instances 5 | ec2_instance_facts: 6 | filters: 7 | availability-zone: usa-west-2 8 | -------------------------------------------------------------------------------- /playbooks/ec2/ubuntu-ami.yaml: -------------------------------------------------------------------------------- 1 | - name: create an ubuntu instance on EC2 2 | hosts: localhost 3 | tasks: 4 | - name: get the ubuntu bionic latest AMI 5 | ec2_ami_find: 6 | name: "ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*" 7 | aws_access_key: 8 | aws_secret_key: 9 | region: us-west-2 10 | sort: name 11 | sort_order: descending 12 | sort_end: 1 13 | register: ubuntu_image 14 | 15 | - name: start the instance 16 | ec2: 17 | image: "{{ ubuntu_image.results[0].ami_id }}" 18 | region: us-west-2 19 | aws_access_key: 20 | aws_secret_key: 21 | instance_type: t2.nano 22 | key_name: june2018 23 | group: all-open 24 | -------------------------------------------------------------------------------- /playbooks/ec2/ubuntu.yaml: -------------------------------------------------------------------------------- 1 | - name: create an ubuntu instance on EC2 2 | hosts: localhost 3 | tasks: 4 | - name: start the instance 5 | ec2: 6 | image: ami-09eb876a926ae86db 7 | region: us-west-2 8 | instance_type: t2.nano 9 | key_name: june2018 10 | group: all-open 11 | -------------------------------------------------------------------------------- /playbooks/error_handling/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = inventory 3 | remote_user = ansible 4 | host_key_checking = false 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/error_handling/assert.retry: -------------------------------------------------------------------------------- 1 | ansible2.example.com 2 | -------------------------------------------------------------------------------- /playbooks/error_handling/assert.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: test for file existence 3 | hosts: ansible2.example.com 4 | tasks: 5 | - stat: 6 | path: /etc/osts 7 | register: hosts 8 | 9 | - assert: 10 | that: 11 | - hosts.stat.exists 12 | -------------------------------------------------------------------------------- /playbooks/error_handling/block.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: block example 3 | hosts: all 4 | tasks: 5 | - name: install apache 6 | block: 7 | - package: 8 | name: "{{ item }}" 9 | state: installed 10 | with_items: 11 | - httpd 12 | - elinks 13 | - mod_ssl 14 | - service: 15 | name: httpd 16 | state: started 17 | enabled: True 18 | when: ansible_distribution == 'CentOS' 19 | 20 | 21 | -------------------------------------------------------------------------------- /playbooks/error_handling/inventory: -------------------------------------------------------------------------------- 1 | [all] 2 | ansible1.example.com 3 | ansible2.example.com 4 | 5 | [lamp] 6 | ansible1.example.com 7 | 8 | [file] 9 | ansible2.example.com 10 | -------------------------------------------------------------------------------- /playbooks/error_handling/rescue.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: error handling 3 | hosts: all 4 | tasks: 5 | - block: 6 | - name: upgrade the database 7 | shell: 8 | cmd: /usr/local/lib/upgrade-database 9 | rescue: 10 | - name: revert after failure 11 | shell: 12 | cmd: /usr/local/lib/revert-database 13 | always: 14 | - name: always restart the database 15 | service: 16 | name: mariadb 17 | state: restarted 18 | -------------------------------------------------------------------------------- /playbooks/error_handling/uricheck.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: check URL 3 | hosts: ansible2.example.com 4 | tasks: 5 | - uri: 6 | url: http://sandervanvugt.com 7 | return_content: yes 8 | register: webpage 9 | 10 | - name: Fail if sander is not in the page content 11 | fail: 12 | msg: 'not the right content' 13 | when: "'sander' not in webpage.content" 14 | -------------------------------------------------------------------------------- /playbooks/exam-lab/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | remote_user = ansible 3 | host_key_checking = false 4 | inventory = inventory 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/exam-lab/inventory: -------------------------------------------------------------------------------- 1 | [webservers] 2 | ansible1.example.com 3 | 4 | [webclients] 5 | ansible2.example.com 6 | ansible3.example.com 7 | -------------------------------------------------------------------------------- /playbooks/exam-lab/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: setup web environment 3 | hosts: all 4 | 5 | roles: 6 | - web-role 7 | - mountweb 8 | -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/mountweb/README.md: -------------------------------------------------------------------------------- 1 | Role Name 2 | ========= 3 | 4 | A brief description of the role goes here. 5 | 6 | Requirements 7 | ------------ 8 | 9 | Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. 15 | 16 | Dependencies 17 | ------------ 18 | 19 | A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: 25 | 26 | - hosts: servers 27 | roles: 28 | - { role: username.rolename, x: 42 } 29 | 30 | License 31 | ------- 32 | 33 | BSD 34 | 35 | Author Information 36 | ------------------ 37 | 38 | An optional section for the role authors to include contact information, or a website (HTML is not allowed). 39 | -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/mountweb/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for mountweb -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/mountweb/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for mountweb -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/mountweb/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: your name 3 | description: your description 4 | company: your company (optional) 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Some suggested licenses: 11 | # - BSD (default) 12 | # - MIT 13 | # - GPLv2 14 | # - GPLv3 15 | # - Apache 16 | # - CC-BY 17 | license: license (GPLv2, CC-BY, etc) 18 | 19 | min_ansible_version: 1.2 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # Optionally specify the branch Galaxy will use when accessing the GitHub 25 | # repo for this role. During role install, if no tags are available, 26 | # Galaxy will use this branch. During import Galaxy will access files on 27 | # this branch. If Travis integration is configured, only notifications for this 28 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 29 | # (usually master) will be used. 30 | #github_branch: 31 | 32 | # 33 | # platforms is a list of platforms, and each platform has a name and a list of versions. 34 | # 35 | # platforms: 36 | # - name: Fedora 37 | # versions: 38 | # - all 39 | # - 25 40 | # - name: SomePlatform 41 | # versions: 42 | # - all 43 | # - 1.0 44 | # - 7 45 | # - 99.99 46 | 47 | galaxy_tags: [] 48 | # List tags for your role here, one per line. A tag is a keyword that describes 49 | # and categorizes the role. Users find roles by searching for tags. Be sure to 50 | # remove the '[]' above, if you add tags to this list. 51 | # 52 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 53 | # Maximum 20 tags per role. 54 | 55 | dependencies: [] 56 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 57 | # if you add dependencies to this list. -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/mountweb/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for mountweb 3 | - name: setup webserver 4 | hosts: webservers 5 | tasks: 6 | - name: create partition 7 | parted: 8 | device: /dev/sdb 9 | number: 1 10 | state: present 11 | part_end: 2G 12 | - name: create filesystem 13 | filesystem: 14 | fstype: xfs 15 | dev: /dev/sdb1 16 | force: no 17 | - name: mount 18 | mount: 19 | path: /web 20 | src: /dev/sdb1 21 | fstype: xfs 22 | state: present 23 | - name: set_selinux 24 | sefcontext: 25 | setype: httpd_sys_content_t 26 | target: '/web(/.*)?' 27 | state: present 28 | - name: run_restorecon 29 | command: 'restorecon -r /web' 30 | 31 | -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/mountweb/templates/http.j2: -------------------------------------------------------------------------------- 1 | Listen *:80 2 | NameVirtualHost {{ ansible_fqdn }} 3 | 4 | 5 | ServerName {{ ansible_fqdn }} 6 | DocumentRoot /web 7 | 8 | -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/mountweb/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/mountweb/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - mountweb -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/mountweb/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for mountweb -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/web-role/README.md: -------------------------------------------------------------------------------- 1 | Role Name 2 | ========= 3 | 4 | A brief description of the role goes here. 5 | 6 | Requirements 7 | ------------ 8 | 9 | Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. 15 | 16 | Dependencies 17 | ------------ 18 | 19 | A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: 25 | 26 | - hosts: servers 27 | roles: 28 | - { role: username.rolename, x: 42 } 29 | 30 | License 31 | ------- 32 | 33 | BSD 34 | 35 | Author Information 36 | ------------------ 37 | 38 | An optional section for the role authors to include contact information, or a website (HTML is not allowed). 39 | -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/web-role/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for web-role -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/web-role/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for web-role 3 | 4 | handlers: 5 | - name: restart_web 6 | service: 7 | name: httpd 8 | state: restarted 9 | -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/web-role/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: your name 3 | description: your description 4 | company: your company (optional) 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Some suggested licenses: 11 | # - BSD (default) 12 | # - MIT 13 | # - GPLv2 14 | # - GPLv3 15 | # - Apache 16 | # - CC-BY 17 | license: license (GPLv2, CC-BY, etc) 18 | 19 | min_ansible_version: 1.2 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # Optionally specify the branch Galaxy will use when accessing the GitHub 25 | # repo for this role. During role install, if no tags are available, 26 | # Galaxy will use this branch. During import Galaxy will access files on 27 | # this branch. If Travis integration is configured, only notifications for this 28 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 29 | # (usually master) will be used. 30 | #github_branch: 31 | 32 | # 33 | # platforms is a list of platforms, and each platform has a name and a list of versions. 34 | # 35 | # platforms: 36 | # - name: Fedora 37 | # versions: 38 | # - all 39 | # - 25 40 | # - name: SomePlatform 41 | # versions: 42 | # - all 43 | # - 1.0 44 | # - 7 45 | # - 99.99 46 | 47 | galaxy_tags: [] 48 | # List tags for your role here, one per line. A tag is a keyword that describes 49 | # and categorizes the role. Users find roles by searching for tags. Be sure to 50 | # remove the '[]' above, if you add tags to this list. 51 | # 52 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 53 | # Maximum 20 tags per role. 54 | 55 | dependencies: [] 56 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 57 | # if you add dependencies to this list. -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/web-role/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: webservers.yml 3 | when: 'webservers' in group_names 4 | 5 | - include_tasks: webclients.yml 6 | when: 'webclients' in group_names 7 | 8 | -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/web-role/tasks/webclients.yml: -------------------------------------------------------------------------------- 1 | - hosts: webclients 2 | tasks: 3 | - name: install packages 4 | package: 5 | name: "{{ item }}" 6 | state: latest 7 | with_items: 8 | - elinks 9 | - wget 10 | -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/web-role/tasks/webservers.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: include the variable file 3 | include_vars: vars/webservers.yml 4 | 5 | - name: install packages 6 | package: 7 | name: "{{ web_packages }}" 8 | state: latest 9 | 10 | - name: copy template 11 | template: 12 | src: templates/httpd.j2 13 | dest: "{{ web_config_file }}" 14 | owner: root 15 | group: root 16 | mode: 0644 17 | notify: 18 | - restart_web 19 | 20 | - name: start service 21 | service: 22 | name: httpd 23 | state: started 24 | enabled: true 25 | 26 | - name: open port in firewall 27 | firewalld: 28 | service: "{{ firewall_service }}" 29 | state: enabled 30 | immediate: true 31 | permanent: true 32 | -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/web-role/templates/httpd.j2: -------------------------------------------------------------------------------- 1 | # 2 | # This is the main Apache HTTP server configuration file. It contains the 3 | # configuration directives that give the server its instructions. 4 | # See for detailed information. 5 | # In particular, see 6 | # 7 | # for a discussion of each configuration directive. 8 | # 9 | # Do NOT simply read the instructions in here without understanding 10 | # what they do. They're here only as hints or reminders. If you are unsure 11 | # consult the online docs. You have been warned. 12 | # 13 | # Configuration and logfile names: If the filenames you specify for many 14 | # of the server's control files begin with "/" (or "drive:/" for Win32), the 15 | # server will use that explicit path. If the filenames do *not* begin 16 | # with "/", the value of ServerRoot is prepended -- so 'log/access_log' 17 | # with ServerRoot set to '/www' will be interpreted by the 18 | # server as '/www/log/access_log', where as '/log/access_log' will be 19 | # interpreted as '/log/access_log'. 20 | 21 | # 22 | # ServerRoot: The top of the directory tree under which the server's 23 | # configuration, error, and log files are kept. 24 | # 25 | # Do not add a slash at the end of the directory path. If you point 26 | # ServerRoot at a non-local disk, be sure to specify a local disk on the 27 | # Mutex directive, if file-based mutexes are used. If you wish to share the 28 | # same ServerRoot for multiple httpd daemons, you will need to change at 29 | # least PidFile. 30 | # 31 | ServerRoot "/etc/httpd" 32 | 33 | # 34 | # Listen: Allows you to bind Apache to specific IP addresses and/or 35 | # ports, instead of the default. See also the 36 | # directive. 37 | # 38 | # Change this to Listen on specific IP addresses as shown below to 39 | # prevent Apache from glomming onto all bound IP addresses. 40 | # 41 | #Listen 12.34.56.78:80 42 | Listen 80 43 | 44 | # 45 | # Dynamic Shared Object (DSO) Support 46 | # 47 | # To be able to use the functionality of a module which was built as a DSO you 48 | # have to place corresponding `LoadModule' lines at this location so the 49 | # directives contained in it are actually available _before_ they are used. 50 | # Statically compiled modules (those listed by `httpd -l') do not need 51 | # to be loaded here. 52 | # 53 | # Example: 54 | # LoadModule foo_module modules/mod_foo.so 55 | # 56 | Include conf.modules.d/*.conf 57 | 58 | # 59 | # If you wish httpd to run as a different user or group, you must run 60 | # httpd as root initially and it will switch. 61 | # 62 | # User/Group: The name (or #number) of the user/group to run httpd as. 63 | # It is usually good practice to create a dedicated user and group for 64 | # running httpd, as with most system services. 65 | # 66 | User apache 67 | Group apache 68 | 69 | # 'Main' server configuration 70 | # 71 | # The directives in this section set up the values used by the 'main' 72 | # server, which responds to any requests that aren't handled by a 73 | # definition. These values also provide defaults for 74 | # any containers you may define later in the file. 75 | # 76 | # All of these directives may appear inside containers, 77 | # in which case these default settings will be overridden for the 78 | # virtual host being defined. 79 | # 80 | 81 | # 82 | # ServerAdmin: Your address, where problems with the server should be 83 | # e-mailed. This address appears on some server-generated pages, such 84 | # as error documents. e.g. admin@your-domain.com 85 | # 86 | ServerAdmin root@localhost 87 | 88 | # 89 | # ServerName gives the name and port that the server uses to identify itself. 90 | # This can often be determined automatically, but we recommend you specify 91 | # it explicitly to prevent problems during startup. 92 | # 93 | # If your host doesn't have a registered DNS name, enter its IP address here. 94 | # 95 | #ServerName www.example.com:80 96 | 97 | # 98 | # Deny access to the entirety of your server's filesystem. You must 99 | # explicitly permit access to web content directories in other 100 | # blocks below. 101 | # 102 | 103 | AllowOverride none 104 | Require all denied 105 | 106 | 107 | # 108 | # Note that from this point forward you must specifically allow 109 | # particular features to be enabled - so if something's not working as 110 | # you might expect, make sure that you have specifically enabled it 111 | # below. 112 | # 113 | 114 | # 115 | # DocumentRoot: The directory out of which you will serve your 116 | # documents. By default, all requests are taken from this directory, but 117 | # symbolic links and aliases may be used to point to other locations. 118 | # 119 | DocumentRoot "/var/www/html" 120 | 121 | # 122 | # Relax access to content within /var/www. 123 | # 124 | 125 | AllowOverride None 126 | # Allow open access: 127 | Require all granted 128 | 129 | 130 | # Further relax access to the default document root: 131 | 132 | # 133 | # Possible values for the Options directive are "None", "All", 134 | # or any combination of: 135 | # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews 136 | # 137 | # Note that "MultiViews" must be named *explicitly* --- "Options All" 138 | # doesn't give it to you. 139 | # 140 | # The Options directive is both complicated and important. Please see 141 | # http://httpd.apache.org/docs/2.4/mod/core.html#options 142 | # for more information. 143 | # 144 | Options Indexes FollowSymLinks 145 | 146 | # 147 | # AllowOverride controls what directives may be placed in .htaccess files. 148 | # It can be "All", "None", or any combination of the keywords: 149 | # Options FileInfo AuthConfig Limit 150 | # 151 | AllowOverride None 152 | 153 | # 154 | # Controls who can get stuff from this server. 155 | # 156 | Require all granted 157 | 158 | 159 | # 160 | # DirectoryIndex: sets the file that Apache will serve if a directory 161 | # is requested. 162 | # 163 | 164 | DirectoryIndex index.html 165 | 166 | 167 | # 168 | # The following lines prevent .htaccess and .htpasswd files from being 169 | # viewed by Web clients. 170 | # 171 | 172 | Require all denied 173 | 174 | 175 | # 176 | # ErrorLog: The location of the error log file. 177 | # If you do not specify an ErrorLog directive within a 178 | # container, error messages relating to that virtual host will be 179 | # logged here. If you *do* define an error logfile for a 180 | # container, that host's errors will be logged there and not here. 181 | # 182 | ErrorLog "logs/error_log" 183 | 184 | # 185 | # LogLevel: Control the number of messages logged to the error_log. 186 | # Possible values include: debug, info, notice, warn, error, crit, 187 | # alert, emerg. 188 | # 189 | LogLevel warn 190 | 191 | 192 | # 193 | # The following directives define some format nicknames for use with 194 | # a CustomLog directive (see below). 195 | # 196 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 197 | LogFormat "%h %l %u %t \"%r\" %>s %b" common 198 | 199 | 200 | # You need to enable mod_logio.c to use %I and %O 201 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio 202 | 203 | 204 | # 205 | # The location and format of the access logfile (Common Logfile Format). 206 | # If you do not define any access logfiles within a 207 | # container, they will be logged here. Contrariwise, if you *do* 208 | # define per- access logfiles, transactions will be 209 | # logged therein and *not* in this file. 210 | # 211 | #CustomLog "logs/access_log" common 212 | 213 | # 214 | # If you prefer a logfile with access, agent, and referer information 215 | # (Combined Logfile Format) you can use the following directive. 216 | # 217 | CustomLog "logs/access_log" combined 218 | 219 | 220 | 221 | # 222 | # Redirect: Allows you to tell clients about documents that used to 223 | # exist in your server's namespace, but do not anymore. The client 224 | # will make a new request for the document at its new location. 225 | # Example: 226 | # Redirect permanent /foo http://www.example.com/bar 227 | 228 | # 229 | # Alias: Maps web paths into filesystem paths and is used to 230 | # access content that does not live under the DocumentRoot. 231 | # Example: 232 | # Alias /webpath /full/filesystem/path 233 | # 234 | # If you include a trailing / on /webpath then the server will 235 | # require it to be present in the URL. You will also likely 236 | # need to provide a section to allow access to 237 | # the filesystem path. 238 | 239 | # 240 | # ScriptAlias: This controls which directories contain server scripts. 241 | # ScriptAliases are essentially the same as Aliases, except that 242 | # documents in the target directory are treated as applications and 243 | # run by the server when requested rather than as documents sent to the 244 | # client. The same rules about trailing "/" apply to ScriptAlias 245 | # directives as to Alias. 246 | # 247 | ScriptAlias /cgi-bin/ "/var/www/cgi-bin/" 248 | 249 | 250 | 251 | # 252 | # "/var/www/cgi-bin" should be changed to whatever your ScriptAliased 253 | # CGI directory exists, if you have that configured. 254 | # 255 | 256 | AllowOverride None 257 | Options None 258 | Require all granted 259 | 260 | 261 | 262 | # 263 | # TypesConfig points to the file containing the list of mappings from 264 | # filename extension to MIME-type. 265 | # 266 | TypesConfig /etc/mime.types 267 | 268 | # 269 | # AddType allows you to add to or override the MIME configuration 270 | # file specified in TypesConfig for specific file types. 271 | # 272 | #AddType application/x-gzip .tgz 273 | # 274 | # AddEncoding allows you to have certain browsers uncompress 275 | # information on the fly. Note: Not all browsers support this. 276 | # 277 | #AddEncoding x-compress .Z 278 | #AddEncoding x-gzip .gz .tgz 279 | # 280 | # If the AddEncoding directives above are commented-out, then you 281 | # probably should define those extensions to indicate media types: 282 | # 283 | AddType application/x-compress .Z 284 | AddType application/x-gzip .gz .tgz 285 | 286 | # 287 | # AddHandler allows you to map certain file extensions to "handlers": 288 | # actions unrelated to filetype. These can be either built into the server 289 | # or added with the Action directive (see below) 290 | # 291 | # To use CGI scripts outside of ScriptAliased directories: 292 | # (You will also need to add "ExecCGI" to the "Options" directive.) 293 | # 294 | #AddHandler cgi-script .cgi 295 | 296 | # For type maps (negotiated resources): 297 | #AddHandler type-map var 298 | 299 | # 300 | # Filters allow you to process content before it is sent to the client. 301 | # 302 | # To parse .shtml files for server-side includes (SSI): 303 | # (You will also need to add "Includes" to the "Options" directive.) 304 | # 305 | AddType text/html .shtml 306 | AddOutputFilter INCLUDES .shtml 307 | 308 | 309 | # 310 | # Specify a default charset for all content served; this enables 311 | # interpretation of all content as UTF-8 by default. To use the 312 | # default browser choice (ISO-8859-1), or to allow the META tags 313 | # in HTML content to override this choice, comment out this 314 | # directive: 315 | # 316 | AddDefaultCharset UTF-8 317 | 318 | 319 | # 320 | # The mod_mime_magic module allows the server to use various hints from the 321 | # contents of the file itself to determine its type. The MIMEMagicFile 322 | # directive tells the module where the hint definitions are located. 323 | # 324 | MIMEMagicFile conf/magic 325 | 326 | 327 | # 328 | # Customizable error responses come in three flavors: 329 | # 1) plain text 2) local redirects 3) external redirects 330 | # 331 | # Some examples: 332 | #ErrorDocument 500 "The server made a boo boo." 333 | #ErrorDocument 404 /missing.html 334 | #ErrorDocument 404 "/cgi-bin/missing_handler.pl" 335 | #ErrorDocument 402 http://www.example.com/subscription_info.html 336 | # 337 | 338 | # 339 | # EnableMMAP and EnableSendfile: On systems that support it, 340 | # memory-mapping or the sendfile syscall may be used to deliver 341 | # files. This usually improves server performance, but must 342 | # be turned off when serving from networked-mounted 343 | # filesystems or if support for these functions is otherwise 344 | # broken on your system. 345 | # Defaults if commented: EnableMMAP On, EnableSendfile Off 346 | # 347 | #EnableMMAP off 348 | EnableSendfile on 349 | 350 | # Supplemental configuration 351 | # 352 | # Load config files in the "/etc/httpd/conf.d" directory, if any. 353 | IncludeOptional conf.d/*.conf 354 | -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/web-role/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/web-role/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - web-role -------------------------------------------------------------------------------- /playbooks/exam-lab/roles/web-role/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for web-role 3 | web_packages: httpd 4 | web_service: httpd 5 | web_config_file: /etc/httpd/conf/httpd.conf 6 | firewall_service: http 7 | -------------------------------------------------------------------------------- /playbooks/ftp/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | remote_user = ansible 3 | host_key_checking = false 4 | inventory = inventory 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/ftp/inventory: -------------------------------------------------------------------------------- 1 | [all] 2 | ansible1.example.com 3 | ansible2.example.com 4 | -------------------------------------------------------------------------------- /playbooks/ftp/vsftpd.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: deploy vsftpd 3 | hosts: all 4 | tasks: 5 | - name: install vsftpd 6 | package: 7 | name: vsftpd 8 | state: latest 9 | - name: enable vsftpd 10 | service: name=vsftpd enabled=true 11 | - name: create readme file 12 | copy: 13 | content: "welcome to my friendly server\n" 14 | dest: /var/ftp/pub/README 15 | force: no 16 | mode: 0444 17 | ... 18 | -------------------------------------------------------------------------------- /playbooks/includes/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = lab-inventory 3 | remote_user = ansible 4 | host_key_checking = false 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/includes/inventory.yml: -------------------------------------------------------------------------------- 1 | [all] 2 | ansible1.example.com 3 | ansible2.example.com 4 | 5 | [lamp] 6 | ansible1.example.com 7 | 8 | [file] 9 | ansible2.example.com 10 | -------------------------------------------------------------------------------- /playbooks/includes/site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: lamp 3 | tasks: 4 | - name: include lamp tasks 5 | include: tasks/lamp.yml 6 | 7 | - hosts: file 8 | tasks: 9 | - name: include file tasks 10 | include: tasks/file.yml 11 | 12 | 13 | -------------------------------------------------------------------------------- /playbooks/includes/tasks/file.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install and start file services 3 | yum: 4 | name: 5 | - samba 6 | - vsftpd 7 | state: latest 8 | 9 | - name: start samba server 10 | service: 11 | name: samba 12 | state: started 13 | enabled: true 14 | 15 | - name: start the ftp service 16 | service: 17 | name: vsftpd 18 | state: started 19 | enabled: true 20 | -------------------------------------------------------------------------------- /playbooks/includes/tasks/lamp.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install and start the servers 3 | yum: 4 | name: 5 | - mariadb 6 | - httpd 7 | state: latest 8 | 9 | - name: start db server 10 | service: 11 | name: mariadb 12 | state: started 13 | enabled: true 14 | 15 | - name: start the web service 16 | service: 17 | name: httpd 18 | state: started 19 | enabled: true 20 | 21 | -------------------------------------------------------------------------------- /playbooks/lab/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = lab-inventory 3 | remote_user = ansible 4 | host_key_checking = false 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/lab/custom.fact: -------------------------------------------------------------------------------- 1 | [packages] 2 | smb_package = samba 3 | ftp_package = vsftpd 4 | db_package = mariadb-server 5 | web_package = httpd 6 | 7 | [services] 8 | smb_service = smb 9 | ftp_service = vsftpd 10 | db_service = mariadb 11 | web_service = httpd 12 | -------------------------------------------------------------------------------- /playbooks/lab/lab-copy-facts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install remote facts 3 | hosts: all 4 | vars: 5 | remote_dir: /etc/ansible/facts.d 6 | facts_file: custom.fact 7 | tasks: 8 | - name: create remote directory 9 | file: 10 | state: directory 11 | recurse: yes 12 | path: "{{ remote_dir }}" 13 | - name: install new facts 14 | copy: 15 | src: "{{ facts_file }}" 16 | dest: "{{ remote_dir }}" 17 | -------------------------------------------------------------------------------- /playbooks/lab/lab-custom.facts: -------------------------------------------------------------------------------- 1 | [packages] 2 | smb_package = smb 3 | ftp_package = vsftpd 4 | db_package = mariadb-server 5 | web_package = httpd 6 | 7 | [services] 8 | smb_service = smb 9 | ftp_service = vsftpd 10 | db_service = mariadb 11 | web_service = httpd 12 | -------------------------------------------------------------------------------- /playbooks/lab/lab-facts: -------------------------------------------------------------------------------- 1 | [packages] 2 | smb_package = smb 3 | ftp_package = vsftpd 4 | db_package = mariadb-server 5 | web_package = httpd 6 | 7 | [services] 8 | smb_service = smb 9 | ftp_service = vsftpd 10 | db_service = mariadb 11 | web_service = httpd 12 | -------------------------------------------------------------------------------- /playbooks/lab/lab-inventory: -------------------------------------------------------------------------------- 1 | [all] 2 | ansible1.example.com 3 | ansible2.example.com 4 | 5 | [lamp] 6 | ansible1.example.com 7 | 8 | [file] 9 | ansible2.example.com 10 | -------------------------------------------------------------------------------- /playbooks/lab/lab-playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars: 4 | firewall: firewalld 5 | 6 | tasks: 7 | - name: install the firewall 8 | yum: 9 | name: "{{ firewall }}" 10 | state: latest 11 | 12 | - name: start the firewall 13 | service: 14 | name: "{{ firewall }}" 15 | state: started 16 | enabled: true 17 | 18 | 19 | - hosts: lamp 20 | tasks: 21 | - name: include the variable file 22 | include_vars: lab-vars/allvars.yml 23 | 24 | - name: include the tasks 25 | include: lab-tasks/lamp.yml 26 | 27 | - name: open the port for the web server 28 | firewalld: 29 | service: http 30 | state: enabled 31 | immediate: true 32 | permanent: true 33 | 34 | - name: create index.html 35 | copy: 36 | content: "{{ ansible_fqdn }}({{ ansible_default_ipv4.address }}) managed by Ansible\n" 37 | dest: "{{ web_root }}/index.html" 38 | 39 | - hosts: file 40 | tasks: 41 | - name: include the variabe file 42 | include_vars: lab-vars/allvars.yml 43 | 44 | - name: include the tasks 45 | include: lab-tasks/file.yml 46 | 47 | - name: open the port for the ftp service 48 | firewalld: 49 | service: samba 50 | state: enabled 51 | immediate: true 52 | permanent: true 53 | 54 | - name: open the port for the smb service 55 | firewalld: 56 | service: ftp 57 | state: enabled 58 | immediate: true 59 | permanent: true 60 | 61 | 62 | -------------------------------------------------------------------------------- /playbooks/lab/lab-tasks/file.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install and start file services 3 | yum: 4 | name: 5 | - "{{ ansible_local.custom.packages.smb_package }}" 6 | - "{{ ansible_local.custom.packages.ftp_package }}" 7 | state: latest 8 | 9 | - name: start samba server 10 | service: 11 | name: "{{ ansible_local.custom.services.smb_service }}" 12 | state: started 13 | enabled: true 14 | 15 | - name: start the ftp service 16 | service: 17 | name: "{{ ansible_local.custom.services.ftp_service }}" 18 | state: started 19 | enabled: true 20 | -------------------------------------------------------------------------------- /playbooks/lab/lab-tasks/lamp.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install and start the servers 3 | yum: 4 | name: 5 | - "{{ ansible_local.custom.packages.ftp_package }}" 6 | - "{{ ansible_local.custom.packages.web_package }}" 7 | state: latest 8 | 9 | - name: start database server 10 | service: 11 | name: "{{ ansible_local.custom.services.ftp_service }}" 12 | state: started 13 | enabled: true 14 | 15 | - name: start the web service 16 | service: 17 | name: "{{ ansible_local.custom.services.web_service }}" 18 | state: started 19 | enabled: true 20 | 21 | -------------------------------------------------------------------------------- /playbooks/lab/lab-vars/allvars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | web_root: /var/www/html 3 | ftp_root: /var/ftp 4 | -------------------------------------------------------------------------------- /playbooks/lab/playbook.retry: -------------------------------------------------------------------------------- 1 | ansible2.example.com 2 | -------------------------------------------------------------------------------- /playbooks/lab/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars: 4 | firewall: firewalld 5 | 6 | tasks: 7 | - name: install the firewall 8 | yum: 9 | name: "{{ firewall }}" 10 | state: latest 11 | 12 | - name: start the firewall 13 | service: 14 | name: "{{ firewall }}" 15 | state: started 16 | enabled: true 17 | 18 | 19 | - hosts: lamp 20 | tasks: 21 | - name: include the variable file 22 | include_vars: lab-vars/allvars.yml 23 | 24 | - name: include the tasks 25 | include: lab-tasks/lamp.yml 26 | 27 | - name: open the port for the web server 28 | firewalld: 29 | service: http 30 | state: enabled 31 | immediate: true 32 | permanent: true 33 | 34 | - name: create index.html 35 | copy: 36 | content: "{{ ansible_fqdn }}({{ ansible_default_ipv4.address }}) managed by Ansible\n" 37 | dest: "{{ web_root }}/index.html" 38 | 39 | - hosts: file 40 | tasks: 41 | - name: include the variabe file 42 | include_vars: lab-vars/allvars.yml 43 | 44 | - name: include the tasks 45 | include: lab-tasks/file.yml 46 | 47 | - name: open the port for the ftp service 48 | firewalld: 49 | service: ftp 50 | state: enabled 51 | immediate: true 52 | permanent: true 53 | 54 | - name: open the port for the smb service 55 | firewalld: 56 | service: samba 57 | state: enabled 58 | immediate: true 59 | permanent: true 60 | 61 | 62 | -------------------------------------------------------------------------------- /playbooks/lab10/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | remote_user = ansible 3 | host_key_checking = false 4 | inventory = inventory 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/lab10/disks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: configure LVM 3 | hosts: ansible2.example.com 4 | tasks: 5 | - name: create partition 6 | parted: 7 | device: /dev/sdb 8 | number: 1 9 | state: present 10 | part_end: 2G 11 | - name: create filesystem 12 | filesystem: 13 | fstype: xfs 14 | dev: /dev/sdb1 15 | force: no 16 | - name: mount 17 | mount: 18 | path: /web 19 | src: /dev/sdb1 20 | fstype: xfs 21 | state: present 22 | -------------------------------------------------------------------------------- /playbooks/lab10/inventory: -------------------------------------------------------------------------------- 1 | [webservers] 2 | ansible1.example.com 3 | 4 | [webclients] 5 | ansible2.example.com 6 | ansible3.example.com 7 | -------------------------------------------------------------------------------- /playbooks/lab10/lab10-3.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: setup web environment 3 | hosts: all 4 | 5 | roles: 6 | - web-role 7 | - mountweb 8 | -------------------------------------------------------------------------------- /playbooks/lab10/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars: 4 | 5 | tasks: 6 | 7 | - hosts: webservers 8 | tasks: 9 | - name: include the variable file 10 | include_vars: vars/webservers.yml 11 | 12 | - name: install packages 13 | package: 14 | name: "{{ web_packages }}" 15 | state: latest 16 | 17 | - name: copy template 18 | template: 19 | src: templates:/httpd.j2 20 | dest: "{{ web_config_file }}" 21 | owner: root 22 | group: root 23 | mode: 0644 24 | notify: 25 | - restart_web 26 | 27 | - name: start service 28 | service: 29 | name: httpd 30 | state: started 31 | enabled: true 32 | 33 | - name: open port in firewall 34 | firewalld: 35 | service: "{{ firewall_service }}" 36 | state: enabled 37 | immediate: true 38 | permanent: true 39 | 40 | handlers: 41 | - name: restart_web 42 | service: 43 | name: httpd 44 | state: restarted 45 | 46 | - hosts: webclients 47 | tasks: 48 | - name: install packages 49 | package: 50 | name: "{{ item }}" 51 | state: latest 52 | with_items: 53 | - elinks 54 | - wget 55 | -------------------------------------------------------------------------------- /playbooks/lab10/roles/mountweb/README.md: -------------------------------------------------------------------------------- 1 | Role Name 2 | ========= 3 | 4 | A brief description of the role goes here. 5 | 6 | Requirements 7 | ------------ 8 | 9 | Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. 15 | 16 | Dependencies 17 | ------------ 18 | 19 | A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: 25 | 26 | - hosts: servers 27 | roles: 28 | - { role: username.rolename, x: 42 } 29 | 30 | License 31 | ------- 32 | 33 | BSD 34 | 35 | Author Information 36 | ------------------ 37 | 38 | An optional section for the role authors to include contact information, or a website (HTML is not allowed). 39 | -------------------------------------------------------------------------------- /playbooks/lab10/roles/mountweb/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for mountweb -------------------------------------------------------------------------------- /playbooks/lab10/roles/mountweb/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for mountweb -------------------------------------------------------------------------------- /playbooks/lab10/roles/mountweb/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: your name 3 | description: your description 4 | company: your company (optional) 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Some suggested licenses: 11 | # - BSD (default) 12 | # - MIT 13 | # - GPLv2 14 | # - GPLv3 15 | # - Apache 16 | # - CC-BY 17 | license: license (GPLv2, CC-BY, etc) 18 | 19 | min_ansible_version: 1.2 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # Optionally specify the branch Galaxy will use when accessing the GitHub 25 | # repo for this role. During role install, if no tags are available, 26 | # Galaxy will use this branch. During import Galaxy will access files on 27 | # this branch. If Travis integration is configured, only notifications for this 28 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 29 | # (usually master) will be used. 30 | #github_branch: 31 | 32 | # 33 | # platforms is a list of platforms, and each platform has a name and a list of versions. 34 | # 35 | # platforms: 36 | # - name: Fedora 37 | # versions: 38 | # - all 39 | # - 25 40 | # - name: SomePlatform 41 | # versions: 42 | # - all 43 | # - 1.0 44 | # - 7 45 | # - 99.99 46 | 47 | galaxy_tags: [] 48 | # List tags for your role here, one per line. A tag is a keyword that describes 49 | # and categorizes the role. Users find roles by searching for tags. Be sure to 50 | # remove the '[]' above, if you add tags to this list. 51 | # 52 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 53 | # Maximum 20 tags per role. 54 | 55 | dependencies: [] 56 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 57 | # if you add dependencies to this list. -------------------------------------------------------------------------------- /playbooks/lab10/roles/mountweb/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for mountweb 3 | - name: setup webserver 4 | hosts: webservers 5 | tasks: 6 | - name: create partition 7 | parted: 8 | device: /dev/sdb 9 | number: 1 10 | state: present 11 | part_end: 2G 12 | - name: create filesystem 13 | filesystem: 14 | fstype: xfs 15 | dev: /dev/sdb1 16 | force: no 17 | - name: mount 18 | mount: 19 | path: /web 20 | src: /dev/sdb1 21 | fstype: xfs 22 | state: present 23 | - name: set_selinux 24 | sefcontext: 25 | setype: httpd_sys_content_t 26 | target: '/web(/.*)?' 27 | state: present 28 | - name: run_restorecon 29 | command: 'restorecon -r /web' 30 | 31 | -------------------------------------------------------------------------------- /playbooks/lab10/roles/mountweb/templates/http.j2: -------------------------------------------------------------------------------- 1 | Listen *:80 2 | NameVirtualHost {{ ansible_fqdn }} 3 | 4 | 5 | ServerName {{ ansible_fqdn }} 6 | DocumentRoot /web 7 | 8 | -------------------------------------------------------------------------------- /playbooks/lab10/roles/mountweb/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /playbooks/lab10/roles/mountweb/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - mountweb -------------------------------------------------------------------------------- /playbooks/lab10/roles/mountweb/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for mountweb -------------------------------------------------------------------------------- /playbooks/lab10/roles/web-role/README.md: -------------------------------------------------------------------------------- 1 | Role Name 2 | ========= 3 | 4 | A brief description of the role goes here. 5 | 6 | Requirements 7 | ------------ 8 | 9 | Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. 15 | 16 | Dependencies 17 | ------------ 18 | 19 | A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: 25 | 26 | - hosts: servers 27 | roles: 28 | - { role: username.rolename, x: 42 } 29 | 30 | License 31 | ------- 32 | 33 | BSD 34 | 35 | Author Information 36 | ------------------ 37 | 38 | An optional section for the role authors to include contact information, or a website (HTML is not allowed). 39 | -------------------------------------------------------------------------------- /playbooks/lab10/roles/web-role/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for web-role -------------------------------------------------------------------------------- /playbooks/lab10/roles/web-role/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for web-role 3 | 4 | handlers: 5 | - name: restart_web 6 | service: 7 | name: httpd 8 | state: restarted 9 | -------------------------------------------------------------------------------- /playbooks/lab10/roles/web-role/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: your name 3 | description: your description 4 | company: your company (optional) 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Some suggested licenses: 11 | # - BSD (default) 12 | # - MIT 13 | # - GPLv2 14 | # - GPLv3 15 | # - Apache 16 | # - CC-BY 17 | license: license (GPLv2, CC-BY, etc) 18 | 19 | min_ansible_version: 1.2 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # Optionally specify the branch Galaxy will use when accessing the GitHub 25 | # repo for this role. During role install, if no tags are available, 26 | # Galaxy will use this branch. During import Galaxy will access files on 27 | # this branch. If Travis integration is configured, only notifications for this 28 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 29 | # (usually master) will be used. 30 | #github_branch: 31 | 32 | # 33 | # platforms is a list of platforms, and each platform has a name and a list of versions. 34 | # 35 | # platforms: 36 | # - name: Fedora 37 | # versions: 38 | # - all 39 | # - 25 40 | # - name: SomePlatform 41 | # versions: 42 | # - all 43 | # - 1.0 44 | # - 7 45 | # - 99.99 46 | 47 | galaxy_tags: [] 48 | # List tags for your role here, one per line. A tag is a keyword that describes 49 | # and categorizes the role. Users find roles by searching for tags. Be sure to 50 | # remove the '[]' above, if you add tags to this list. 51 | # 52 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 53 | # Maximum 20 tags per role. 54 | 55 | dependencies: [] 56 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 57 | # if you add dependencies to this list. -------------------------------------------------------------------------------- /playbooks/lab10/roles/web-role/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: webservers.yml 3 | when: 'webservers' in group_names 4 | 5 | - include_tasks: webclients.yml 6 | when: 'webclients' in group_names 7 | 8 | -------------------------------------------------------------------------------- /playbooks/lab10/roles/web-role/tasks/webclients.yml: -------------------------------------------------------------------------------- 1 | - hosts: webclients 2 | tasks: 3 | - name: install packages 4 | package: 5 | name: "{{ item }}" 6 | state: latest 7 | with_items: 8 | - elinks 9 | - wget 10 | -------------------------------------------------------------------------------- /playbooks/lab10/roles/web-role/tasks/webservers.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: include the variable file 3 | include_vars: vars/webservers.yml 4 | 5 | - name: install packages 6 | package: 7 | name: "{{ web_packages }}" 8 | state: latest 9 | 10 | - name: copy template 11 | template: 12 | src: templates/httpd.j2 13 | dest: "{{ web_config_file }}" 14 | owner: root 15 | group: root 16 | mode: 0644 17 | notify: 18 | - restart_web 19 | 20 | - name: start service 21 | service: 22 | name: httpd 23 | state: started 24 | enabled: true 25 | 26 | - name: open port in firewall 27 | firewalld: 28 | service: "{{ firewall_service }}" 29 | state: enabled 30 | immediate: true 31 | permanent: true 32 | -------------------------------------------------------------------------------- /playbooks/lab10/roles/web-role/templates/httpd.j2: -------------------------------------------------------------------------------- 1 | # 2 | # This is the main Apache HTTP server configuration file. It contains the 3 | # configuration directives that give the server its instructions. 4 | # See for detailed information. 5 | # In particular, see 6 | # 7 | # for a discussion of each configuration directive. 8 | # 9 | # Do NOT simply read the instructions in here without understanding 10 | # what they do. They're here only as hints or reminders. If you are unsure 11 | # consult the online docs. You have been warned. 12 | # 13 | # Configuration and logfile names: If the filenames you specify for many 14 | # of the server's control files begin with "/" (or "drive:/" for Win32), the 15 | # server will use that explicit path. If the filenames do *not* begin 16 | # with "/", the value of ServerRoot is prepended -- so 'log/access_log' 17 | # with ServerRoot set to '/www' will be interpreted by the 18 | # server as '/www/log/access_log', where as '/log/access_log' will be 19 | # interpreted as '/log/access_log'. 20 | 21 | # 22 | # ServerRoot: The top of the directory tree under which the server's 23 | # configuration, error, and log files are kept. 24 | # 25 | # Do not add a slash at the end of the directory path. If you point 26 | # ServerRoot at a non-local disk, be sure to specify a local disk on the 27 | # Mutex directive, if file-based mutexes are used. If you wish to share the 28 | # same ServerRoot for multiple httpd daemons, you will need to change at 29 | # least PidFile. 30 | # 31 | ServerRoot "/etc/httpd" 32 | 33 | # 34 | # Listen: Allows you to bind Apache to specific IP addresses and/or 35 | # ports, instead of the default. See also the 36 | # directive. 37 | # 38 | # Change this to Listen on specific IP addresses as shown below to 39 | # prevent Apache from glomming onto all bound IP addresses. 40 | # 41 | #Listen 12.34.56.78:80 42 | Listen 80 43 | 44 | # 45 | # Dynamic Shared Object (DSO) Support 46 | # 47 | # To be able to use the functionality of a module which was built as a DSO you 48 | # have to place corresponding `LoadModule' lines at this location so the 49 | # directives contained in it are actually available _before_ they are used. 50 | # Statically compiled modules (those listed by `httpd -l') do not need 51 | # to be loaded here. 52 | # 53 | # Example: 54 | # LoadModule foo_module modules/mod_foo.so 55 | # 56 | Include conf.modules.d/*.conf 57 | 58 | # 59 | # If you wish httpd to run as a different user or group, you must run 60 | # httpd as root initially and it will switch. 61 | # 62 | # User/Group: The name (or #number) of the user/group to run httpd as. 63 | # It is usually good practice to create a dedicated user and group for 64 | # running httpd, as with most system services. 65 | # 66 | User apache 67 | Group apache 68 | 69 | # 'Main' server configuration 70 | # 71 | # The directives in this section set up the values used by the 'main' 72 | # server, which responds to any requests that aren't handled by a 73 | # definition. These values also provide defaults for 74 | # any containers you may define later in the file. 75 | # 76 | # All of these directives may appear inside containers, 77 | # in which case these default settings will be overridden for the 78 | # virtual host being defined. 79 | # 80 | 81 | # 82 | # ServerAdmin: Your address, where problems with the server should be 83 | # e-mailed. This address appears on some server-generated pages, such 84 | # as error documents. e.g. admin@your-domain.com 85 | # 86 | ServerAdmin root@localhost 87 | 88 | # 89 | # ServerName gives the name and port that the server uses to identify itself. 90 | # This can often be determined automatically, but we recommend you specify 91 | # it explicitly to prevent problems during startup. 92 | # 93 | # If your host doesn't have a registered DNS name, enter its IP address here. 94 | # 95 | #ServerName www.example.com:80 96 | 97 | # 98 | # Deny access to the entirety of your server's filesystem. You must 99 | # explicitly permit access to web content directories in other 100 | # blocks below. 101 | # 102 | 103 | AllowOverride none 104 | Require all denied 105 | 106 | 107 | # 108 | # Note that from this point forward you must specifically allow 109 | # particular features to be enabled - so if something's not working as 110 | # you might expect, make sure that you have specifically enabled it 111 | # below. 112 | # 113 | 114 | # 115 | # DocumentRoot: The directory out of which you will serve your 116 | # documents. By default, all requests are taken from this directory, but 117 | # symbolic links and aliases may be used to point to other locations. 118 | # 119 | DocumentRoot "/var/www/html" 120 | 121 | # 122 | # Relax access to content within /var/www. 123 | # 124 | 125 | AllowOverride None 126 | # Allow open access: 127 | Require all granted 128 | 129 | 130 | # Further relax access to the default document root: 131 | 132 | # 133 | # Possible values for the Options directive are "None", "All", 134 | # or any combination of: 135 | # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews 136 | # 137 | # Note that "MultiViews" must be named *explicitly* --- "Options All" 138 | # doesn't give it to you. 139 | # 140 | # The Options directive is both complicated and important. Please see 141 | # http://httpd.apache.org/docs/2.4/mod/core.html#options 142 | # for more information. 143 | # 144 | Options Indexes FollowSymLinks 145 | 146 | # 147 | # AllowOverride controls what directives may be placed in .htaccess files. 148 | # It can be "All", "None", or any combination of the keywords: 149 | # Options FileInfo AuthConfig Limit 150 | # 151 | AllowOverride None 152 | 153 | # 154 | # Controls who can get stuff from this server. 155 | # 156 | Require all granted 157 | 158 | 159 | # 160 | # DirectoryIndex: sets the file that Apache will serve if a directory 161 | # is requested. 162 | # 163 | 164 | DirectoryIndex index.html 165 | 166 | 167 | # 168 | # The following lines prevent .htaccess and .htpasswd files from being 169 | # viewed by Web clients. 170 | # 171 | 172 | Require all denied 173 | 174 | 175 | # 176 | # ErrorLog: The location of the error log file. 177 | # If you do not specify an ErrorLog directive within a 178 | # container, error messages relating to that virtual host will be 179 | # logged here. If you *do* define an error logfile for a 180 | # container, that host's errors will be logged there and not here. 181 | # 182 | ErrorLog "logs/error_log" 183 | 184 | # 185 | # LogLevel: Control the number of messages logged to the error_log. 186 | # Possible values include: debug, info, notice, warn, error, crit, 187 | # alert, emerg. 188 | # 189 | LogLevel warn 190 | 191 | 192 | # 193 | # The following directives define some format nicknames for use with 194 | # a CustomLog directive (see below). 195 | # 196 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 197 | LogFormat "%h %l %u %t \"%r\" %>s %b" common 198 | 199 | 200 | # You need to enable mod_logio.c to use %I and %O 201 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio 202 | 203 | 204 | # 205 | # The location and format of the access logfile (Common Logfile Format). 206 | # If you do not define any access logfiles within a 207 | # container, they will be logged here. Contrariwise, if you *do* 208 | # define per- access logfiles, transactions will be 209 | # logged therein and *not* in this file. 210 | # 211 | #CustomLog "logs/access_log" common 212 | 213 | # 214 | # If you prefer a logfile with access, agent, and referer information 215 | # (Combined Logfile Format) you can use the following directive. 216 | # 217 | CustomLog "logs/access_log" combined 218 | 219 | 220 | 221 | # 222 | # Redirect: Allows you to tell clients about documents that used to 223 | # exist in your server's namespace, but do not anymore. The client 224 | # will make a new request for the document at its new location. 225 | # Example: 226 | # Redirect permanent /foo http://www.example.com/bar 227 | 228 | # 229 | # Alias: Maps web paths into filesystem paths and is used to 230 | # access content that does not live under the DocumentRoot. 231 | # Example: 232 | # Alias /webpath /full/filesystem/path 233 | # 234 | # If you include a trailing / on /webpath then the server will 235 | # require it to be present in the URL. You will also likely 236 | # need to provide a section to allow access to 237 | # the filesystem path. 238 | 239 | # 240 | # ScriptAlias: This controls which directories contain server scripts. 241 | # ScriptAliases are essentially the same as Aliases, except that 242 | # documents in the target directory are treated as applications and 243 | # run by the server when requested rather than as documents sent to the 244 | # client. The same rules about trailing "/" apply to ScriptAlias 245 | # directives as to Alias. 246 | # 247 | ScriptAlias /cgi-bin/ "/var/www/cgi-bin/" 248 | 249 | 250 | 251 | # 252 | # "/var/www/cgi-bin" should be changed to whatever your ScriptAliased 253 | # CGI directory exists, if you have that configured. 254 | # 255 | 256 | AllowOverride None 257 | Options None 258 | Require all granted 259 | 260 | 261 | 262 | # 263 | # TypesConfig points to the file containing the list of mappings from 264 | # filename extension to MIME-type. 265 | # 266 | TypesConfig /etc/mime.types 267 | 268 | # 269 | # AddType allows you to add to or override the MIME configuration 270 | # file specified in TypesConfig for specific file types. 271 | # 272 | #AddType application/x-gzip .tgz 273 | # 274 | # AddEncoding allows you to have certain browsers uncompress 275 | # information on the fly. Note: Not all browsers support this. 276 | # 277 | #AddEncoding x-compress .Z 278 | #AddEncoding x-gzip .gz .tgz 279 | # 280 | # If the AddEncoding directives above are commented-out, then you 281 | # probably should define those extensions to indicate media types: 282 | # 283 | AddType application/x-compress .Z 284 | AddType application/x-gzip .gz .tgz 285 | 286 | # 287 | # AddHandler allows you to map certain file extensions to "handlers": 288 | # actions unrelated to filetype. These can be either built into the server 289 | # or added with the Action directive (see below) 290 | # 291 | # To use CGI scripts outside of ScriptAliased directories: 292 | # (You will also need to add "ExecCGI" to the "Options" directive.) 293 | # 294 | #AddHandler cgi-script .cgi 295 | 296 | # For type maps (negotiated resources): 297 | #AddHandler type-map var 298 | 299 | # 300 | # Filters allow you to process content before it is sent to the client. 301 | # 302 | # To parse .shtml files for server-side includes (SSI): 303 | # (You will also need to add "Includes" to the "Options" directive.) 304 | # 305 | AddType text/html .shtml 306 | AddOutputFilter INCLUDES .shtml 307 | 308 | 309 | # 310 | # Specify a default charset for all content served; this enables 311 | # interpretation of all content as UTF-8 by default. To use the 312 | # default browser choice (ISO-8859-1), or to allow the META tags 313 | # in HTML content to override this choice, comment out this 314 | # directive: 315 | # 316 | AddDefaultCharset UTF-8 317 | 318 | 319 | # 320 | # The mod_mime_magic module allows the server to use various hints from the 321 | # contents of the file itself to determine its type. The MIMEMagicFile 322 | # directive tells the module where the hint definitions are located. 323 | # 324 | MIMEMagicFile conf/magic 325 | 326 | 327 | # 328 | # Customizable error responses come in three flavors: 329 | # 1) plain text 2) local redirects 3) external redirects 330 | # 331 | # Some examples: 332 | #ErrorDocument 500 "The server made a boo boo." 333 | #ErrorDocument 404 /missing.html 334 | #ErrorDocument 404 "/cgi-bin/missing_handler.pl" 335 | #ErrorDocument 402 http://www.example.com/subscription_info.html 336 | # 337 | 338 | # 339 | # EnableMMAP and EnableSendfile: On systems that support it, 340 | # memory-mapping or the sendfile syscall may be used to deliver 341 | # files. This usually improves server performance, but must 342 | # be turned off when serving from networked-mounted 343 | # filesystems or if support for these functions is otherwise 344 | # broken on your system. 345 | # Defaults if commented: EnableMMAP On, EnableSendfile Off 346 | # 347 | #EnableMMAP off 348 | EnableSendfile on 349 | 350 | # Supplemental configuration 351 | # 352 | # Load config files in the "/etc/httpd/conf.d" directory, if any. 353 | IncludeOptional conf.d/*.conf 354 | -------------------------------------------------------------------------------- /playbooks/lab10/roles/web-role/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /playbooks/lab10/roles/web-role/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - web-role -------------------------------------------------------------------------------- /playbooks/lab10/roles/web-role/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for web-role 3 | web_packages: httpd 4 | web_service: httpd 5 | web_config_file: /etc/httpd/conf/httpd.conf 6 | firewall_service: http 7 | -------------------------------------------------------------------------------- /playbooks/lab10/templates/httpd.j2: -------------------------------------------------------------------------------- 1 | # 2 | # This is the main Apache HTTP server configuration file. It contains the 3 | # configuration directives that give the server its instructions. 4 | # See for detailed information. 5 | # In particular, see 6 | # 7 | # for a discussion of each configuration directive. 8 | # 9 | # Do NOT simply read the instructions in here without understanding 10 | # what they do. They're here only as hints or reminders. If you are unsure 11 | # consult the online docs. You have been warned. 12 | # 13 | # Configuration and logfile names: If the filenames you specify for many 14 | # of the server's control files begin with "/" (or "drive:/" for Win32), the 15 | # server will use that explicit path. If the filenames do *not* begin 16 | # with "/", the value of ServerRoot is prepended -- so 'log/access_log' 17 | # with ServerRoot set to '/www' will be interpreted by the 18 | # server as '/www/log/access_log', where as '/log/access_log' will be 19 | # interpreted as '/log/access_log'. 20 | 21 | # 22 | # ServerRoot: The top of the directory tree under which the server's 23 | # configuration, error, and log files are kept. 24 | # 25 | # Do not add a slash at the end of the directory path. If you point 26 | # ServerRoot at a non-local disk, be sure to specify a local disk on the 27 | # Mutex directive, if file-based mutexes are used. If you wish to share the 28 | # same ServerRoot for multiple httpd daemons, you will need to change at 29 | # least PidFile. 30 | # 31 | ServerRoot "/etc/httpd" 32 | 33 | # 34 | # Listen: Allows you to bind Apache to specific IP addresses and/or 35 | # ports, instead of the default. See also the 36 | # directive. 37 | # 38 | # Change this to Listen on specific IP addresses as shown below to 39 | # prevent Apache from glomming onto all bound IP addresses. 40 | # 41 | #Listen 12.34.56.78:80 42 | Listen 80 43 | 44 | # 45 | # Dynamic Shared Object (DSO) Support 46 | # 47 | # To be able to use the functionality of a module which was built as a DSO you 48 | # have to place corresponding `LoadModule' lines at this location so the 49 | # directives contained in it are actually available _before_ they are used. 50 | # Statically compiled modules (those listed by `httpd -l') do not need 51 | # to be loaded here. 52 | # 53 | # Example: 54 | # LoadModule foo_module modules/mod_foo.so 55 | # 56 | Include conf.modules.d/*.conf 57 | 58 | # 59 | # If you wish httpd to run as a different user or group, you must run 60 | # httpd as root initially and it will switch. 61 | # 62 | # User/Group: The name (or #number) of the user/group to run httpd as. 63 | # It is usually good practice to create a dedicated user and group for 64 | # running httpd, as with most system services. 65 | # 66 | User apache 67 | Group apache 68 | 69 | # 'Main' server configuration 70 | # 71 | # The directives in this section set up the values used by the 'main' 72 | # server, which responds to any requests that aren't handled by a 73 | # definition. These values also provide defaults for 74 | # any containers you may define later in the file. 75 | # 76 | # All of these directives may appear inside containers, 77 | # in which case these default settings will be overridden for the 78 | # virtual host being defined. 79 | # 80 | 81 | # 82 | # ServerAdmin: Your address, where problems with the server should be 83 | # e-mailed. This address appears on some server-generated pages, such 84 | # as error documents. e.g. admin@your-domain.com 85 | # 86 | ServerAdmin root@localhost 87 | 88 | # 89 | # ServerName gives the name and port that the server uses to identify itself. 90 | # This can often be determined automatically, but we recommend you specify 91 | # it explicitly to prevent problems during startup. 92 | # 93 | # If your host doesn't have a registered DNS name, enter its IP address here. 94 | # 95 | #ServerName www.example.com:80 96 | 97 | # 98 | # Deny access to the entirety of your server's filesystem. You must 99 | # explicitly permit access to web content directories in other 100 | # blocks below. 101 | # 102 | 103 | AllowOverride none 104 | Require all denied 105 | 106 | 107 | # 108 | # Note that from this point forward you must specifically allow 109 | # particular features to be enabled - so if something's not working as 110 | # you might expect, make sure that you have specifically enabled it 111 | # below. 112 | # 113 | 114 | # 115 | # DocumentRoot: The directory out of which you will serve your 116 | # documents. By default, all requests are taken from this directory, but 117 | # symbolic links and aliases may be used to point to other locations. 118 | # 119 | DocumentRoot "/var/www/html" 120 | 121 | # 122 | # Relax access to content within /var/www. 123 | # 124 | 125 | AllowOverride None 126 | # Allow open access: 127 | Require all granted 128 | 129 | 130 | # Further relax access to the default document root: 131 | 132 | # 133 | # Possible values for the Options directive are "None", "All", 134 | # or any combination of: 135 | # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews 136 | # 137 | # Note that "MultiViews" must be named *explicitly* --- "Options All" 138 | # doesn't give it to you. 139 | # 140 | # The Options directive is both complicated and important. Please see 141 | # http://httpd.apache.org/docs/2.4/mod/core.html#options 142 | # for more information. 143 | # 144 | Options Indexes FollowSymLinks 145 | 146 | # 147 | # AllowOverride controls what directives may be placed in .htaccess files. 148 | # It can be "All", "None", or any combination of the keywords: 149 | # Options FileInfo AuthConfig Limit 150 | # 151 | AllowOverride None 152 | 153 | # 154 | # Controls who can get stuff from this server. 155 | # 156 | Require all granted 157 | 158 | 159 | # 160 | # DirectoryIndex: sets the file that Apache will serve if a directory 161 | # is requested. 162 | # 163 | 164 | DirectoryIndex index.html 165 | 166 | 167 | # 168 | # The following lines prevent .htaccess and .htpasswd files from being 169 | # viewed by Web clients. 170 | # 171 | 172 | Require all denied 173 | 174 | 175 | # 176 | # ErrorLog: The location of the error log file. 177 | # If you do not specify an ErrorLog directive within a 178 | # container, error messages relating to that virtual host will be 179 | # logged here. If you *do* define an error logfile for a 180 | # container, that host's errors will be logged there and not here. 181 | # 182 | ErrorLog "logs/error_log" 183 | 184 | # 185 | # LogLevel: Control the number of messages logged to the error_log. 186 | # Possible values include: debug, info, notice, warn, error, crit, 187 | # alert, emerg. 188 | # 189 | LogLevel warn 190 | 191 | 192 | # 193 | # The following directives define some format nicknames for use with 194 | # a CustomLog directive (see below). 195 | # 196 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 197 | LogFormat "%h %l %u %t \"%r\" %>s %b" common 198 | 199 | 200 | # You need to enable mod_logio.c to use %I and %O 201 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio 202 | 203 | 204 | # 205 | # The location and format of the access logfile (Common Logfile Format). 206 | # If you do not define any access logfiles within a 207 | # container, they will be logged here. Contrariwise, if you *do* 208 | # define per- access logfiles, transactions will be 209 | # logged therein and *not* in this file. 210 | # 211 | #CustomLog "logs/access_log" common 212 | 213 | # 214 | # If you prefer a logfile with access, agent, and referer information 215 | # (Combined Logfile Format) you can use the following directive. 216 | # 217 | CustomLog "logs/access_log" combined 218 | 219 | 220 | 221 | # 222 | # Redirect: Allows you to tell clients about documents that used to 223 | # exist in your server's namespace, but do not anymore. The client 224 | # will make a new request for the document at its new location. 225 | # Example: 226 | # Redirect permanent /foo http://www.example.com/bar 227 | 228 | # 229 | # Alias: Maps web paths into filesystem paths and is used to 230 | # access content that does not live under the DocumentRoot. 231 | # Example: 232 | # Alias /webpath /full/filesystem/path 233 | # 234 | # If you include a trailing / on /webpath then the server will 235 | # require it to be present in the URL. You will also likely 236 | # need to provide a section to allow access to 237 | # the filesystem path. 238 | 239 | # 240 | # ScriptAlias: This controls which directories contain server scripts. 241 | # ScriptAliases are essentially the same as Aliases, except that 242 | # documents in the target directory are treated as applications and 243 | # run by the server when requested rather than as documents sent to the 244 | # client. The same rules about trailing "/" apply to ScriptAlias 245 | # directives as to Alias. 246 | # 247 | ScriptAlias /cgi-bin/ "/var/www/cgi-bin/" 248 | 249 | 250 | 251 | # 252 | # "/var/www/cgi-bin" should be changed to whatever your ScriptAliased 253 | # CGI directory exists, if you have that configured. 254 | # 255 | 256 | AllowOverride None 257 | Options None 258 | Require all granted 259 | 260 | 261 | 262 | # 263 | # TypesConfig points to the file containing the list of mappings from 264 | # filename extension to MIME-type. 265 | # 266 | TypesConfig /etc/mime.types 267 | 268 | # 269 | # AddType allows you to add to or override the MIME configuration 270 | # file specified in TypesConfig for specific file types. 271 | # 272 | #AddType application/x-gzip .tgz 273 | # 274 | # AddEncoding allows you to have certain browsers uncompress 275 | # information on the fly. Note: Not all browsers support this. 276 | # 277 | #AddEncoding x-compress .Z 278 | #AddEncoding x-gzip .gz .tgz 279 | # 280 | # If the AddEncoding directives above are commented-out, then you 281 | # probably should define those extensions to indicate media types: 282 | # 283 | AddType application/x-compress .Z 284 | AddType application/x-gzip .gz .tgz 285 | 286 | # 287 | # AddHandler allows you to map certain file extensions to "handlers": 288 | # actions unrelated to filetype. These can be either built into the server 289 | # or added with the Action directive (see below) 290 | # 291 | # To use CGI scripts outside of ScriptAliased directories: 292 | # (You will also need to add "ExecCGI" to the "Options" directive.) 293 | # 294 | #AddHandler cgi-script .cgi 295 | 296 | # For type maps (negotiated resources): 297 | #AddHandler type-map var 298 | 299 | # 300 | # Filters allow you to process content before it is sent to the client. 301 | # 302 | # To parse .shtml files for server-side includes (SSI): 303 | # (You will also need to add "Includes" to the "Options" directive.) 304 | # 305 | AddType text/html .shtml 306 | AddOutputFilter INCLUDES .shtml 307 | 308 | 309 | # 310 | # Specify a default charset for all content served; this enables 311 | # interpretation of all content as UTF-8 by default. To use the 312 | # default browser choice (ISO-8859-1), or to allow the META tags 313 | # in HTML content to override this choice, comment out this 314 | # directive: 315 | # 316 | AddDefaultCharset UTF-8 317 | 318 | 319 | # 320 | # The mod_mime_magic module allows the server to use various hints from the 321 | # contents of the file itself to determine its type. The MIMEMagicFile 322 | # directive tells the module where the hint definitions are located. 323 | # 324 | MIMEMagicFile conf/magic 325 | 326 | 327 | # 328 | # Customizable error responses come in three flavors: 329 | # 1) plain text 2) local redirects 3) external redirects 330 | # 331 | # Some examples: 332 | #ErrorDocument 500 "The server made a boo boo." 333 | #ErrorDocument 404 /missing.html 334 | #ErrorDocument 404 "/cgi-bin/missing_handler.pl" 335 | #ErrorDocument 402 http://www.example.com/subscription_info.html 336 | # 337 | 338 | # 339 | # EnableMMAP and EnableSendfile: On systems that support it, 340 | # memory-mapping or the sendfile syscall may be used to deliver 341 | # files. This usually improves server performance, but must 342 | # be turned off when serving from networked-mounted 343 | # filesystems or if support for these functions is otherwise 344 | # broken on your system. 345 | # Defaults if commented: EnableMMAP On, EnableSendfile Off 346 | # 347 | #EnableMMAP off 348 | EnableSendfile on 349 | 350 | # Supplemental configuration 351 | # 352 | # Load config files in the "/etc/httpd/conf.d" directory, if any. 353 | IncludeOptional conf.d/*.conf 354 | -------------------------------------------------------------------------------- /playbooks/lab10/vars/webservers.yml: -------------------------------------------------------------------------------- 1 | web_packages: httpd 2 | web_service: httpd 3 | web_config_file: /etc/httpd/conf/httpd.conf 4 | firewall_service: http 5 | -------------------------------------------------------------------------------- /playbooks/lab4/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = lab-inventory 3 | remote_user = ansible 4 | host_key_checking = false 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/lab4/inventory: -------------------------------------------------------------------------------- 1 | ansible1.example.com 2 | ansible2.example.com 3 | ubuntu.example.com 4 | -------------------------------------------------------------------------------- /playbooks/lab4/tasks/redhat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: manage firewalld firewall 3 | firewalld: 4 | service: "{{ item }}" 5 | permanent: true 6 | immediate: true 7 | state: enabled 8 | 9 | with_items: 10 | - httpd 11 | - vsftpd 12 | - mariadb 13 | 14 | 15 | -------------------------------------------------------------------------------- /playbooks/lab4/tasks/ubuntu.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: manage ufw firewall 3 | ufw: 4 | port: "{{ item }}" 5 | rule: allow 6 | 7 | with_items: 8 | - ssh 9 | - ftp 10 | - http 11 | 12 | 13 | -------------------------------------------------------------------------------- /playbooks/lab4/variables.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # note: this solution is incomplete but gives a good impression 3 | # of where to go 4 | - name: deploy and start Apache 5 | hosts: all 6 | include_vars: vars/{{ ansible_os_family }}.yml 7 | 8 | tasks: 9 | - name: install and update latest packages 10 | package: 11 | name: 12 | - "{{ web_package }}" 13 | - "{{ firewall_package }}" 14 | - "{{ ftp_package }}" 15 | - "{{ db_package }}" 16 | state: latest 17 | notify: 18 | - success 19 | 20 | 21 | - name: start and enable {{ firewall_service }} 22 | service: 23 | name: "{{ firewall_service }}" 24 | enabled: true 25 | state: started 26 | 27 | - name: start and enable {{ web_service }} 28 | service: 29 | name: "{{ web_service }}" 30 | enabled: true 31 | state: started 32 | 33 | - include_tasks: ubuntu.yml 34 | when: ansible_os_family == 'Ubuntu' 35 | 36 | - include_tasks: redhat.yml 37 | when: ansible_os_family == 'RedHat' 38 | 39 | handlers: 40 | - name: success 41 | debug: 42 | msg: package installation on {{ inventory_hostname }} successful 43 | -------------------------------------------------------------------------------- /playbooks/lab4/vars/redhat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | [packages] 3 | web_package: httpd 4 | ftp_package: vsftpd 5 | db_package: mariadb-server 6 | firewall_package: firewalld 7 | 8 | [services] 9 | web_service: httpd 10 | ftp_service: vsftpd 11 | db_service: mariadb 12 | firewall_service: firewalld 13 | -------------------------------------------------------------------------------- /playbooks/lab4/vars/ubuntu.yml: -------------------------------------------------------------------------------- 1 | --- 2 | [packages] 3 | web_package: apache2 4 | ftp_package: vsftpd 5 | db_package: mysql-server 6 | firewall_package: ufw 7 | 8 | [services] 9 | web_service: apache2 10 | ftp_package: vsftpd 11 | db_package: mysql 12 | firewall_service: ufw 13 | -------------------------------------------------------------------------------- /playbooks/loops/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | remote_user = ansible 3 | host_key_checking = false 4 | inventory = inventory 5 | log_path = errorlog 6 | 7 | [privilege_escalation] 8 | become = True 9 | become_method = sudo 10 | become_user = root 11 | become_ask_pass = False 12 | 13 | -------------------------------------------------------------------------------- /playbooks/loops/conditionalrestart.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | tasks: 4 | - name: vsftpd status 5 | command: /usr/bin/systemctl is-active vsftpd 6 | ignore_errors: yes 7 | register: result 8 | 9 | - name: create file in FTP doc root 10 | copy: 11 | src: /etc/hosts 12 | dest: /var/ftp/pub 13 | when: result.rc == 0 14 | -------------------------------------------------------------------------------- /playbooks/loops/copytxt.retry: -------------------------------------------------------------------------------- 1 | ansible1.example.com 2 | ansible2.example.com 3 | ubuntu.example.com 4 | -------------------------------------------------------------------------------- /playbooks/loops/copytxt.yml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | tasks: 3 | - name: use with_output in registered variables 4 | shell: 'find . "*.yml"' 5 | args: 6 | chdir: /home/ansible/ 7 | register: with_output 8 | 9 | - shell: "cp {{ item }} /tmp/{{ item }}_bak" 10 | with_items: 11 | - "{{ with_output.stdout_lines }}" 12 | -------------------------------------------------------------------------------- /playbooks/loops/handlers-lab.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: copy new index.html 3 | hosts: centos 4 | tasks: 5 | - name: install httpd 6 | package: 7 | name: httpd 8 | state: present 9 | - name: start httpd 10 | service: 11 | name: httpd 12 | state: started 13 | - name: copy index.html 14 | copy: 15 | src: /tmp/index.html 16 | dest: /var/www/html/index.html 17 | notify: 18 | - restart_web 19 | handlers: 20 | - name: restart_web 21 | service: 22 | name: httpd 23 | state: restarted 24 | 25 | -------------------------------------------------------------------------------- /playbooks/loops/handlers.retry: -------------------------------------------------------------------------------- 1 | ansible1.example.com 2 | ansible2.example.com 3 | ansible3.example.com 4 | -------------------------------------------------------------------------------- /playbooks/loops/handlers.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: copy new index.html 3 | hosts: centos 4 | force_handlers: yes 5 | tasks: 6 | - name: install httpd 7 | yum: name=httpd 8 | - name: copy index.html 9 | copy: 10 | src: /tmp/index.html 11 | dest: /var/www/html/index.html 12 | notify: 13 | - restart_web 14 | - name: copy nothing 15 | copy: 16 | src: /tmp/nothing 17 | dest: /nowhere 18 | handlers: 19 | - name: restart_web 20 | service: 21 | name: httpd 22 | state: restarted 23 | 24 | -------------------------------------------------------------------------------- /playbooks/loops/ifsize.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: conditionals test 3 | hosts: all 4 | tasks: 5 | - name: install vsftpd if sufficient space on /var/ftp 6 | package: 7 | name: vsftpd 8 | state: latest 9 | with_items: "{{ ansible_mounts }}" 10 | when: item.mount == "/var/ftp" and item.size_available > 100000000 11 | -------------------------------------------------------------------------------- /playbooks/loops/inventory: -------------------------------------------------------------------------------- 1 | [centos] 2 | ansible1.example.com 3 | ansible2.example.com 4 | ansible3.example.com 5 | 6 | [ubuntu] 7 | ubuntu.example.com 8 | 9 | [webservers] 10 | ansible3.example.com 11 | 192.168.4.50 12 | -------------------------------------------------------------------------------- /playbooks/loops/register.retry: -------------------------------------------------------------------------------- 1 | 192.168.4.50 2 | ansible3.example.com 3 | ubuntu.example.com 4 | -------------------------------------------------------------------------------- /playbooks/loops/register.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: registered variables demo 1 3 | hosts: all 4 | tasks: 5 | - name: capture output of the who command 6 | command: who 7 | register: loggedin 8 | - shell: echo "user ansible is logged in" 9 | when: loggedin.stdout.find('ansible') 10 | -------------------------------------------------------------------------------- /playbooks/loops/register2.retry: -------------------------------------------------------------------------------- 1 | ansible1.example.com 2 | ansible2.example.com 3 | ubuntu.example.com 4 | -------------------------------------------------------------------------------- /playbooks/loops/register2.yml: -------------------------------------------------------------------------------- 1 | - name: registered variable usage as a loop list 2 | hosts: all 3 | tasks: 4 | 5 | - name: create the backup spooler directory 6 | file: 7 | path: /var/bkspool 8 | state: directory 9 | 10 | - name: retrieve the list of home directories 11 | command: ls /home 12 | register: home_dirs 13 | 14 | - name: add home dirs to the backup spooler 15 | file: 16 | path: /var/bkspool/{{ item }} 17 | src: /home/{{ item }} 18 | state: link 19 | loop: "{{ home_dirs.stdout_lines }}" 20 | -------------------------------------------------------------------------------- /playbooks/loops/register3.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: show register 3 | hosts: ansible1.example.com 4 | tasks: 5 | - name: get output of id command 6 | command: id -un 7 | register: loggedin 8 | - debug: var=loggedin 9 | -------------------------------------------------------------------------------- /playbooks/loops/with_nested.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars: 4 | myusers: 5 | - linda 6 | - anna 7 | mygroups: 8 | - students 9 | - profs 10 | tasks: 11 | - name: create groups 12 | group: 13 | name: "{{ item }}" 14 | state: present 15 | with_items: "{{ mygroups }}" 16 | - name: create users with group membership 17 | user: 18 | name: "{{ item[0] }}" 19 | state: present 20 | groups: "{{ item[1] }}" 21 | with_nested: 22 | - "{{ myusers }}" 23 | - "{{ mygroups }}" 24 | -------------------------------------------------------------------------------- /playbooks/loops/with_nested_cleanup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars: 4 | myusers: 5 | - linda 6 | - anna 7 | mygroups: 8 | - students 9 | - profs 10 | tasks: 11 | - name: create groups 12 | group: 13 | name: "{{ item }}" 14 | state: absent 15 | with_items: "{{ mygroups }}" 16 | - name: create users with group membership 17 | user: 18 | name: "{{ item[0] }}" 19 | state: absent 20 | groups: "{{ item[1] }}" 21 | with_nested: 22 | - "{{ myusers }}" 23 | - "{{ mygroups }}" 24 | -------------------------------------------------------------------------------- /playbooks/newvault/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | remote_user = ansible 3 | host_key_checking = false 4 | inventory = inventory 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/newvault/createusers.retry: -------------------------------------------------------------------------------- 1 | ubuntu.example.com 2 | -------------------------------------------------------------------------------- /playbooks/newvault/createusers.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: create user accounts on all servers 3 | hosts: all 4 | vars_files: 5 | - vars/secret.yml 6 | tasks: 7 | - name: create users from secret.yml 8 | user: 9 | name: "{{ item.name }}" 10 | password: "{{ item.pw | password_hash('sha512')}}" 11 | with_items: "{{ newusers }}" 12 | -------------------------------------------------------------------------------- /playbooks/newvault/inventory: -------------------------------------------------------------------------------- 1 | ansible1.example.com 2 | ansible2.example.com 3 | ubuntu.example.com 4 | -------------------------------------------------------------------------------- /playbooks/newvault/vars/secret.yml: -------------------------------------------------------------------------------- 1 | $ANSIBLE_VAULT;1.1;AES256 2 | 61346664353831373130633135323063343839643634373638363461653161613332306663376464 3 | 3266653835353635396631336333353565616539326362370a326434333230326533653836613035 4 | 32333534653163346266663035333665353235626561313863303734386564393861373764636666 5 | 6532636564626165300a653834663638336366356332353638353730373234643266653765313637 6 | 32333965323536353165393565633363376264656635316564626265353539353766366539646333 7 | 66313732656533346631313463373831376135656634663565633832653462343239653233323162 8 | 63666338373765393235646536616664333430636462666434306231396539653562643637383465 9 | 35323864656139643233 10 | -------------------------------------------------------------------------------- /playbooks/newvault/vaultpw: -------------------------------------------------------------------------------- 1 | password 2 | -------------------------------------------------------------------------------- /playbooks/roles-demo/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | remote_user = ansible 3 | host_key_checking = false 4 | inventory = inventory 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/roles-demo/inventory: -------------------------------------------------------------------------------- 1 | ansible1.example.com 2 | ansible2.example.com 3 | ubuntu.example.com 4 | -------------------------------------------------------------------------------- /playbooks/roles-demo/motd-role.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: use motd role playbook 3 | hosts: ansible2.example.com 4 | user: ansible 5 | become: true 6 | 7 | roles: 8 | - role: motd 9 | system_manager: morgane@example.com 10 | -------------------------------------------------------------------------------- /playbooks/roles-demo/nginx-role.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: use galaxy nginx role 3 | hosts: ansible2.example.com 4 | user: ansible 5 | become: true 6 | 7 | roles: 8 | - role: geerlingguy.nginx 9 | -------------------------------------------------------------------------------- /playbooks/roles-demo/roles/motd/README.md: -------------------------------------------------------------------------------- 1 | Role Name 2 | ========= 3 | 4 | A brief description of the role goes here. 5 | 6 | Requirements 7 | ------------ 8 | 9 | Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. 15 | 16 | Dependencies 17 | ------------ 18 | 19 | A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: 25 | 26 | - hosts: servers 27 | roles: 28 | - { role: username.rolename, x: 42 } 29 | 30 | License 31 | ------- 32 | 33 | BSD 34 | 35 | Author Information 36 | ------------------ 37 | 38 | An optional section for the role authors to include contact information, or a website (HTML is not allowed). 39 | -------------------------------------------------------------------------------- /playbooks/roles-demo/roles/motd/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for motd 3 | system_manager: anna@example.com 4 | -------------------------------------------------------------------------------- /playbooks/roles-demo/roles/motd/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for motd -------------------------------------------------------------------------------- /playbooks/roles-demo/roles/motd/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: your name 3 | description: your description 4 | company: your company (optional) 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Some suggested licenses: 11 | # - BSD (default) 12 | # - MIT 13 | # - GPLv2 14 | # - GPLv3 15 | # - Apache 16 | # - CC-BY 17 | license: license (GPLv2, CC-BY, etc) 18 | 19 | min_ansible_version: 1.2 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # Optionally specify the branch Galaxy will use when accessing the GitHub 25 | # repo for this role. During role install, if no tags are available, 26 | # Galaxy will use this branch. During import Galaxy will access files on 27 | # this branch. If Travis integration is configured, only notifications for this 28 | # branch will be accepted. Otherwise, in all cases, the repo's default branch 29 | # (usually master) will be used. 30 | #github_branch: 31 | 32 | # 33 | # platforms is a list of platforms, and each platform has a name and a list of versions. 34 | # 35 | # platforms: 36 | # - name: Fedora 37 | # versions: 38 | # - all 39 | # - 25 40 | # - name: SomePlatform 41 | # versions: 42 | # - all 43 | # - 1.0 44 | # - 7 45 | # - 99.99 46 | 47 | galaxy_tags: [] 48 | # List tags for your role here, one per line. A tag is a keyword that describes 49 | # and categorizes the role. Users find roles by searching for tags. Be sure to 50 | # remove the '[]' above, if you add tags to this list. 51 | # 52 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 53 | # Maximum 20 tags per role. 54 | 55 | dependencies: [] 56 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 57 | # if you add dependencies to this list. -------------------------------------------------------------------------------- /playbooks/roles-demo/roles/motd/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for motd 3 | - name: copy motd file 4 | template: 5 | src: templates/motd.j2 6 | dest: /etc/motd 7 | owner: root 8 | group: root 9 | mode: 0444 10 | -------------------------------------------------------------------------------- /playbooks/roles-demo/roles/motd/templates/motd.j2: -------------------------------------------------------------------------------- 1 | Welcome to {{ ansible_hostname }} 2 | 3 | This file was created on {{ ansible_date_time.date }} 4 | Go away if you have no business being here 5 | 6 | Contact {{ system_manager }} if anything is wrong 7 | -------------------------------------------------------------------------------- /playbooks/roles-demo/roles/motd/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /playbooks/roles-demo/roles/motd/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - motd -------------------------------------------------------------------------------- /playbooks/roles-demo/roles/motd/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for motd -------------------------------------------------------------------------------- /playbooks/tags/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | inventory = inventory 3 | remote_user = ansible 4 | host_key_checking = false 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/tags/install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: tag example 3 | hosts: centos 4 | tasks: 5 | - name: install net analysis packages 6 | package: 7 | name: "{{ item }}" 8 | state: installed 9 | with_items: 10 | - nmap 11 | - wireshark 12 | tags: 13 | - net_analysis 14 | - name: install lamp packages 15 | package: 16 | name: "{{ item }}" 17 | state: installed 18 | with_items: 19 | - mariadb-server 20 | - httpd 21 | tags: 22 | - lamp 23 | 24 | -------------------------------------------------------------------------------- /playbooks/tags/inventory: -------------------------------------------------------------------------------- 1 | [centos] 2 | ansible1.example.com 3 | ansible2.example.com 4 | 5 | [lamp] 6 | ansible1.example.com 7 | 8 | [file] 9 | ansible2.example.com 10 | -------------------------------------------------------------------------------- /playbooks/tags/tag_include.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: tags with include demo 3 | hosts: centos 4 | - include: install.yml 5 | tags: 6 | - install_all 7 | -------------------------------------------------------------------------------- /playbooks/vault/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | remote_user = ansible 3 | host_key_checking = false 4 | inventory = inventory 5 | 6 | [privilege_escalation] 7 | become = True 8 | become_method = sudo 9 | become_user = root 10 | become_ask_pass = False 11 | 12 | -------------------------------------------------------------------------------- /playbooks/vault/inventory: -------------------------------------------------------------------------------- 1 | ansible1.example.com 2 | ansible2.example.com 3 | ubuntu.example.com 4 | -------------------------------------------------------------------------------- /playbooks/vault/vault-pw: -------------------------------------------------------------------------------- 1 | $ANSIBLE_VAULT;1.1;AES256 2 | 35323634653366356133376162636439326239636532613861633334663538336233623032613263 3 | 6161373361636137343265363530613663333464336434330a336661366364386134636263353034 4 | 62326162626233386231393962356265376439323839396532393063326330316533323435353734 5 | 3930376466653932360a646134616664636432323066633335356435373638303961386235383964 6 | 6635 7 | -------------------------------------------------------------------------------- /playbooks/windows/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | remote_user = ansible 3 | host_key_checking = false 4 | inventory = inventory 5 | 6 | [privilege_escalation] 7 | 8 | -------------------------------------------------------------------------------- /playbooks/windows/inventory: -------------------------------------------------------------------------------- 1 | [lamp] 2 | ansible1.example.com 3 | 4 | [file] 5 | ansible2.example.com 6 | 7 | [win] 8 | windows.example.com 9 | 10 | [win:vars] 11 | ansible_user=ansible 12 | ansible_password=@nsible123 13 | ansible_connection=winrm 14 | ansible_winrm_server_cert_validation=ignore 15 | -------------------------------------------------------------------------------- /playbooks/windows/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: create a windows user 3 | hosts: win 4 | tasks: 5 | - name: create windows users 6 | win_user: 7 | name: linda 8 | password: "@nsible123" 9 | state: present 10 | groups: 11 | - Users 12 | --------------------------------------------------------------------------------