#!/usr/bin/env ansible-playbook # ^ Trick: the above line can be used to make your play an executable # you also must add 'x' permissions to the file # # this file is based on phred's 'pedantically commented playbook' # https://github.com/phred/ansible-examples/blob/master/pedantically_commented_playbook.yml # --- # ^^^ YAML documents can begin with the document separator "---" # and end with the "...", neither is needed for Ansible # as it does not # support multiple YAML documents per file, but some linters incorrectly insist # you must have it .... # # The '#' is a comment character, so any line starting with it will be ignored by Ansible. # Blank lines are ignored, so can be used # to create spacing to your taste. # Note about YAML: like Python, cares about whitespace, it requires actual spaces, tabs won't work. # Indent consistently throughout. # Two-space or four space indents is what most users prefer, but do whatever you like. # # If you're new to YAML, keep in mind that YAML documents, like XML # documents, represent a tree-like structure of nodes and text. More # familiar with JSON? Think of YAML as a strict (with spaces) but also more flexible # JSON (fewer significant characters, e.g., :, "", {}, [] and liberal quoting). # Also, JSON is a subset of YAML, so most YAML parser can read JSON just the same. # # The curious may read more about YAML at: # http://www.yaml.org/spec/1.1/current.html # there is a 1.2, but Ansible uses pyyaml which is mostly 1.1 # the following line configures 'vim' to handle 2 space indents. # vim:ff=unix ts=2 sw=2 ai expandtab ### # Notice the minus (-) on the line below, this is the start of a 'list' in YAML # In Ansible this is the 'list of plays' which starts this playbook. # Plays map the inventory hosts to the tasks, the most minimal play you can have # just requires 'hosts' - hosts: all ########### # Play keyword: hosts # Required: yes # Description: # The selection of hosts (or host) that the tasks in this play play should apply to. # ## Example values: # hosts: all -- applies to all hosts # hosts: host1 -- apply ONLY to the host that inventory defines as 'host1' # hosts: group1 -- apply to all hosts in group1 # hosts: group1,group2 -- apply to hosts in group1 & group2 # hosts: group1,host1 -- hosts in group1 AND host # ## now using host patterns (TODO: url) # hosts: group1,!group3 -- hosts in group1 that are not in group3 # hosts: group1,&group3 -- hosts in group1 that are also in group3 # hosts: group1:&group3 -- same as above, but using : instead of , as separator # hosts: group1:!group2:&group3 -- hosts in group1 what are not in group2 but are also in group3 # ## Using a variable value for 'hosts' # # You can, in fact, set hosts to a variable, for example: # # hosts: '{{mygroups}}' -- apply to all hosts specified in the variable 'mygroups' # # This is handy for testing playbooks, running the same playbook against a # staging environment before running it against production, occasional # maintenance tasks, and other cases where you want to run the playbook # against just a few systems rather than a whole group. # Note that the variable cannot be set in inventory, since we need to know the hosts # before we can use invenotry variables. So normally 'extra vars' are used, as you can # see below. # # If you set hosts as shown above, then you can specify which hosts to # apply the playbook to on each run as so: # # ansible-playbook playbook.yml --extra-vars="mygroups=staging" # # Use --extra-vars to set the variable to any combination of groups, hostnames, # or host patterns just like the examples in the previous section. # name: my heavily commented play ########### # Play keyword: name # Default: play### # Required: no # Description: Just a description to document the play gather_facts: yes ########### # Play keyword: gather_facts # Default: None # Required: no # Description: # This controls if the play will trigger a 'fact gathering task' (aka 'gather_facts' or 'setup' action) to get information about the remote target. # These facts normally provide useful variables on which to base decisions and task inputs. For example `ansible_os_distribution` can tell us if # the target is a RHEL, Ubuntu or FreeBSD machine (among others), number of CPUs, RAM, etc. # TODO: url to fact gathering remote_user: login_user ########### # Play keyword: user # Default: depends on conneciton plugin, for ssh it is 'current user executing Ansible' # Required: no # Description: # Remote user to login on remote targets and 'normally' execute the tasks as become: True ########### # Play keyword: become # Default: False # Required: no # Description: # If True, always use privilege escalationj to run tasks from this play, just like passing the # --become flag to ansible or ansible-playbook. become_user: root ########### # Play keyword: become_user # Default: None # Required: no # Description: # When using privilege escalation this is the user you 'become' after login with the remote_user # for examplle you login to the remote as 'login_user' then you 'become' root to execute the tasks become_method: sudo ########### # Play keyword: become_method # Default: sudo # Required: no # Description: # When using privilege escalation this chooses the become plugin to use for privilege escalation. # use `ansible-doc -t become -l` to list all the options. connection: ssh ########### # Play keyword: connection # Default: ssh # Required: no # Description: # This sets which connection plugin Asnible will use to try to communicate with the target host. # notable options are paramiko (python implementation of ssh, mostly useful in corener cases in which the ssh cli # does not work well with the target. Also 'local' which forces a 'local fork' to execute the task, but normally # what you really want is `delegate_to: localhost` see examples below in 'Run things locally!' entry. # use `ansible-doc -t connection -l` to list all the options. vars: ########### # Play keyword: vars # Default: none # Required: no # Description: # Mapping of variables defined for this play, normally for use in templates or as variables for tasks. # to get the value just use {{color}} to reference that value color: brown # Mapping structures allow complex variables structures, to use you can reference # the variable name with {{web['memcache']}} when using nested key value or {{web}} # when using the whole structure.. web: memcache: 192.168.1.2 httpd: apache # lists use a slightly different notation {{ mylist[1] }} to get 'b', they are 0 indexed. mylist: - a - b - c # Variables can be dynamically set via Jinja templates, to be filled when consumed. # # In this playbook, this will always evaluate to False, because 'color' # is set to 'brown' above. # # When ansible interprets the following, it will first expand 'color' to # 'brown' and then evaluate 'brown' == 'blue' as a Jinja expression. is_color_blue: "{{ color == 'blue' }}" # TODO: (url variables) vars_files: ########## # Play keyword: vars_files # Required: no # Description: # Specifies a list of YAML files to load variables from. # # Always evaluated after the 'vars' section, no matter which section # occurs first in the playbook. Examples are below. # # Example YAML for a file to be included by vars_files: # --- # monitored_by: phobos.mars.nasa.gov # fish_sticks: "good with custard" # ... # (END OF DOCUMENT) # # Remove the indentation & comments of course, the '---' should be at # the left margin in the variables file. # # Include a file from this absolute path - /srv/ansible/vars/vars_file.yml # Include a file from a path relative to this playbook - vars/vars_file.yml # By the way, variables set in 'vars' or as extra vars are available here. - vars/{{something}}.yml # It's also possible to pass an array of files, in which case # Ansible will loop over the array and include the first file that # exists. If none exist, ansible-playbook will halt with an error. # # An excellent way to handle platform-specific differences. - [ 'vars/{{platform}}.yml', vars/default.yml ] # Files in vars_files process in order, so later files can # provide more specific configuration: - [ 'vars/{{host}}.yml' ] # Hey, but if you're doing host-specific variable files, you might # consider setting the variable for a group in your inventory and # adding your host to that group. Just a thought. vars_prompt: ########## # Play keyword: vars_prompt # Required: no # Description: # A list of variables that Ansible will prompt for manual input each time this playbook # runs. Used for sensitive data and also things like release numbers that # vary on each deployment. # # Ansible won't prompts for this value if already provided, like when # passed through --extra-vars, but not from inventory. # # Also it won't prompt if it detects that it is a non interactive session. # For example, when called from cron. # - name: passphrase prompt: "Please enter the passphrase for the SSL certificate" private: yes # The input won't be echoed back to the terminal when private (default yes) # Not sensitive, but something that should vary on each playbook run. - name: release_version prompt: "Please enter a release tag" private: no # you can even have a default - name: package_version prompt: "Please enter a package version" default: '1.0' # You can find more advanced features in https://docs.ansible.com/ansible/latest/user_guide/playbooks_prompts.html roles: ########## # Play keyword: roles # Required: no # Description: A list of roles to import and execute in this play. Executes AFTER pre_tasks and play fact gathering, but before 'tasks'. # TODO url roles + url to 'play stages' tasks: ########## # Play keyword: tasks # Required: no # Description: A list of tasks to perform in this play. Executes AFTER roles and before post_tasks # A simple task # Each task must have an action. 'name' is optional but very useful to document what the task does - name: Check that the target can execute Ansible tasks action: ping ########## # Ansible modules do the work!, 'action' is not needed, you can use the 'action itself' as part of the task - file: path=/tmp/secret mode=0600 owner=root group=root # # Format 'action' like above: # : # # Test your parameters using: # ansible -m -a "" # # Documentation for the stock modules: # http://ansible.github.com/modules.html # normally most will want to use 'k: v' notation instead of 'k=v' used above (but useful for adhoc execution). # while both formats are mostly interchangable, `k: v` is more explicit, 'type friendly' and simpler to escape. - name: Ensure secret is locked down file: path: /tmp/secret mode: '0600' owner: root group: root # note that 'action options' are indented inside the option, while 'task keywords' stay on the top level ########## # Use variables in the task! It expands on use. - name: Paint the server command: echo {{color}} # you can also define variables at the task level - name: Ensure secret is locked down file: path: '{{secret_file}}' mode: '0600' owner: root group: root vars: secret_file: /tmp/secret ########## # Trigger handlers when things change! # # Most Ansible actions can detect and report when something changed. # Like if file permissions were not the same as requested, # a file's content is different or a package was installed (or removed) # When a change is reported, the task assumes the 'changed' status. # Ansible can optionally notify one or more Handlers. # Handlers are like normal tasks, the main difference is that they only # run when notified. # A common use is to restart a service after updating it's configuration file. # https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html#handlers-running-operations-on-change # TODO: explain handler per stage execution # This will call the "Restart Apache" handler whenever 'copy' alters # the remote httpd.conf. - name: Update the Apache config copy: src: httpd.conf dest: /etc/httpd/httpd.conf notify: Restart Apache # Here's how to specify more than one handler - name: Update our app's configuration copy: src: myapp.conf dest: /etc/myapp/production.conf notify: - Restart Apache - Restart Redis ########## # Include tasks from another file! # # Ansible can insert a list of tasks from another file. The file # must represent a list of tasks, which is different than a play. # # Task list format: # --- # - name: create user # user: name={{myuser}} color={{color}} # # - name: add user to group # user: name={{myuser}} groups={{hisgroup}} append=true # ... # (END OF DOCUMENT) # # A 'tasks' YAML file represents a list of tasks. Don't use playbook # YAML for a 'tasks' file. # # Remove the indentation & comments of course, the '---' should be at # the left margin in the variables file. # TODO: point at import_playbook, includes and roles # In this example the user will be 'sklar' # and 'color' will be 'red' inside new_user.yml - import_tasks: tasks/new_user.yml vars: myuser: sklar color: red # In this example the user will be 'mosh' # and $color will be 'mauve' inside new_user.yml - import_tasks: tasks/new_user.yml vars: myuser: mosh color: mauve ########## # Run a task on each thing in a list! # # Ansible provides a simple loop facility. If 'loop' is provided for # a task, then the task will be run once for each item in the provided # list. Each iteration will create the 'item' variable with a different value. - name: Create a file named via variable in /tmp file: path=/tmp/{{item}} state=touched loop: - tangerine - lemon - name: Loop using a variable file: path=/tmp/{{item}} state=touched loop: '{{mylist}}' vars: # defined here, but could be anywhere before the task runs # also note that YAML lists can be flush with their key, # we normally indent for clarity, but this form is also correct. mylist: - tangerine - lemon ########## # Conditionally execute tasks! # # Sometimes you only want to run an action when a under certain conditions. # Ansible supports using conditional Jinja expression, executing the task only when 'True'. # # If you're trying to run an task only when a value changes, # consider rewriting the task as a handler and using 'notify' (see below). # - name: "shutdown all ubuntu" command: /sbin/shutdown -t now when: '{{is_ubuntu|bool}}' - name: "shutdown the if host is in the government" command: /sbin/shutdown -t now when: "{{inventory_hostname in groups['government']}}" # another way to write the same. - name: "shutdown the if host is in the government" command: /sbin/shutdown -t now when: "{{'government' in group_names}}" # Ansible has some built in variables, you can check them here (TODO url) # inventory_hostname is the name of the current host the task is executing for (derived from the hosts: keyword) # group_names has the list of groups the current host (inventory_hostname) is part of # groups is a mapping of the inventory groups with the list of hosts that belong to them ########## # Run things as other users! # # Each task has optional keywords that control which # user a task should run as and whether or not to use privilege escalation # (like sudo or su) to switch to that user. - name: login in as postgres and dump all postgres databases shell: pg_dumpall -w -f /tmp/backup.psql remote_user: postgres become: False - name: login normally, but sudo to postgres to dump all postgres databases shell: pg_dumpall -w -f /tmp/backup.psql become: true become_user: postgres become_method: sudo ########## # Run things locally! # # Each task can also be delegated to the control host - name: create tempfile local_action: shell dd if=/dev/urandom of=/tmp/random.txt count=100 # which is equivalent to the following - name: create tempfile shell: dd if=/dev/urandom of=/tmp/random.txt count=100 delegate_to: localhost # delegate_to can use any target, but for the case above, it is the same as using local_action # TODO url to delegation and implicit localhost handlers: ########## # Play keyword: handlers # Required: no # Description: # Handlers are tasks that run when another task has changed something. # See above for examples on how to trigger them. # The format to define a handler is exactly the same as for tasks. # Note that if multiple tasks notify the same handler in a playbook run # that handler will only run once for that host. # # Handlers are referred to by name or using the listen keyword. # They will be run in the order declared in the playbook. # For example: if a task were to notify the handlers in reverse order like so: # # - task: ensure file does not exist # file: # name: /tmp/lock.txt # state: absent # notify: # - Restart application # - Restart nginx # # The "Restart nginx" handler will still run before the "Restart application" # handler because it is declared first in this playbook. # this one can only be called by name - name: Restart nginx service: name: nginx state: restarted # this one can be called by name or via any entry in the listen keyword - name: redis restarter service: name: redis state: restarted listen: - Restart redis # Any module can be used for the handler action # even though this can be triggered multiple ways and times # it will only execute once per host - name: restart application that should really be a service command: /srv/myapp/restart.sh listen: - Restart application - restart myapp # It's also possible to include handlers from another file. Structure is # the same as a tasks file, see the tasks section above for an example. - import_tasks: handlers/site.yml # NOTE: this is not a complete list of all possible keywords in a play or task (TODO: url playbook object and keywords), just an example of very common options. # below is the "totally optional" YAML "End of document" marker. ...