#!/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 inventory 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 connection 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 escalation 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 example 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 Ansible will use to try to communicate with the target host.
  #   notable options are paramiko (python implementation of ssh, mostly useful in corner 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:
    # <modulename>: <module parameters>
    #
    # Test your parameters using:
    #   ansible -m <module> -a "<module parameters>"
    #
    # 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.
...
