Category Archives: Automation

Installing Ansible on Oracle Linux 7 for test and development use

There are a few alternative ways of installing Ansible on Linux, and the install guide for Ansible 2.7 (the current version at the time of writing) does a great job in explaining them all in detail.  There is a potentially easier way to get to a current Ansible version if you are using Oracle Linux 7, but it comes with a very important limitation. Let’s get that out of the way first.

You need to be aware that the RPM-based installation of Ansible as described in this blog post requires you to enable Oracle’s EPEL repository. As per https://yum.oracle.com/oracle-linux-7.html, the EPEL repository is listed under “Packages for Test and Development” (bottom of the page) and these come with the following warning:  Note: The contents in the following repositories are for development purposes only. Oracle suggests these not be used in production. 

This is really important!

If you are ok with the limitation I just quoted from Oracle’s YUM server, please read on. If not, head back to the official Ansible documentation and use a different method instead. I only use Ansible in my own lab and therefore don’t mind.

Updating the repository configuration file

Back to the topic of Ansible … Before I get around to install Ansible on my machines I update my yum repository configuration file. Things are changing quickly, and I found /etc/yum.repos.d/public-yum-ol7.repo to be outdated at times. I always refresh it from yum.oracle.com just to be sure I’m not missing out on the new stuff.

# cd /etc/yum.repos.d
# mv -iv public-yum-ol7.repo public-yum-ol7.repo.$(date +%y%m%d)
# wget http://yum.oracle.com/public-yum-ol7.repo

With the new file in place, use your preferred method to enable the ol7_developer_EPEL repository. I simply edit public-yum-ol7.repo, there are other ways like yum-config-manager getting you there. But be advised: I just noticed the from December 17 at least the UEK Release 5 repository might be enabled by default leading to a potentially unwanted kernel upgrade.

Install Ansible

With the developer EPEL repository enabled, you have access to a great many Ansible versions. At the time of writing, these were available:

# yum --showduplicates list ansible
Loaded plugins: ulninfo
Available Packages
ansible.noarch                      2.3.1.0-1.el7                       ol7_developer_EPEL
ansible.noarch                      2.4.2.0-1.el7                       ol7_developer_EPEL
ansible.noarch                      2.5.0-2.el7                         ol7_developer_EPEL
ansible.noarch                      2.5.1-1.el7                         ol7_developer_EPEL
ansible.noarch                      2.5.2-1.el7                         ol7_developer_EPEL
ansible.noarch                      2.6.1-1.el7                         ol7_developer_EPEL
ansible.noarch                      2.6.2-1.el7                         ol7_developer_EPEL
ansible.noarch                      2.6.4-1.el7                         ol7_developer_EPEL
ansible.noarch                      2.6.5-1.el7                         ol7_developer_EPEL
ansible.noarch                      2.7.0-1.el7                         ol7_developer_EPEL

# yum info ansible
Loaded plugins: ulninfo
Available Packages
Name        : ansible
Arch        : noarch
Version     : 2.7.0
Release     : 1.el7
Size        : 11 M
Repo        : ol7_developer_EPEL/x86_64
Summary     : SSH-based configuration management, deployment, and task execution system
URL         : http://ansible.com
Licence     : GPLv3+
Description : Ansible is a radically simple model-driven configuration management,
            : multi-node deployment, and remote task execution system. Ansible works
            : over SSH and does not require any software or daemons to be installed
            : on remote nodes. Extension modules can be written in any language and
            : are transferred to managed machines automatically.

Happy testing!

Advertisements

Terraforming the Oracle Cloud: choosing and using an image family

For a few times now I have presented about “cloud deployments done the cloud way”, sharing lessons learned in the changing world I find myself in. It’s a lot of fun and so far I have been far too busy to blog about things I learned by trial and error. Working with Terraform turned out to be a very good source for blog posts, I’ll put a few of these up in the hope of saving you a few minutes.

This blog post is all about creating Ubuntu images in Oracle Cloud Infrastructure (OCI) using terraform. The technique is equally applicable for other Linux image types though. In case you find this post later using a search engine, here is some version information that might put everything into context:

$ ./terraform version
Terraform v0.11.10
+ provider.null v1.0.0
+ provider.oci v3.7.0

I used the “null” provider to run a few post-installation commands as shown in various terraform examples for OCI. Right now I’m trying to work out if I can’t do the same in a different way. If I am successful you can expect a blog post to follow…

Creating a Ubuntu 18.04 LTS image in OCI

To create the Ubuntu image (or any other image for that matter), I need information about the image family. Documentation about image families in OCI can be found at https://docs.cloud.oracle.com/iaas/images/.

Scrolling down/selecting the entry from the left hand side I found the link to the Ubuntu 18.04 LTS image family. Each supported image has its own documentation link, containing crucial data: an OCID per location. At the time of writing, the latest Ubuntu image was named Canonical-Ubuntu-18.04-2018.11.17-0 and had an image OCID of ocid1.image.oc1.eu-frankfurt-1.aaaa...i57q7bfsa. An OCID is short for Oracle Cloud Identifier and it’s used in many places in OCI. There are different OCIDs for the image depending on location; the (shortened) OCID I just showed you was for Frankfurt.

With the OCID at hand, I can open my favourite code editor and start putting the terraform script together. I create instances in OCI using the oci_core_instance type, documented at the terraform website.

Be careful, many of the references and code examples I found about oci_core_image are written for older versions of the terraform provider. I noticed some attributes used in the examples are deprecated. It might be useful to compare the source code examples against the current documentation

Part of the definition of an oci_core_instance requires the specification of the operating system in the source_details {} section. To create the Ubuntu VM in the Frankfurt region, I have to specify – amongst other things of course – this:

resource "oci_core_instance" "docker_tf_instance" {
...
    source_details {
        source_type = "image"
        source_id   = "ocid1.image.oc1.eu-frankfurt-1.aaaa..."
...
    }
...

The actual OCID is far longer, the example above is shortened for the sake of readability. I didn’t like it wrapping around the text box and thus destroying my layout. Make sure you use the correct OCID ;)

With the information at hand I can create the Ubuntu VM and connect to it using the specified SSH key. Have fun!

Ansible tips’n’tricks: executing related tasks together

I have recently written an ansible playbook to apply one-off patches to an Oracle Home. While doing this, I hit a little snag that needed ironing out. Before continuing this post, it’s worth pointing out that I’m on:

$ ansible --version
ansible 2.6.5

And it’s Ansible on Fedora.

Most likely the wrong way to do this …

So after a little bit of coding my initial attempt looked similar to this:

$ cat main.yml 
---
- hosts: 127.0.0.1
  connection: local
  vars:
    - patchIDs:
        - 123
        - 234
        - 456

  tasks:
  - name: say hello
    debug: msg="hello world"

  - name: unzip patch 
    debug: 
      msg: unzipping patch {{ item }}
    loop: "{{ patchIDs }}"
  
  - name: check patch for conflicts with Oracle Home
    debug: 
      msg: checking patch {{ item }} for conflict with $ORACLE_HOME
    loop: "{{ patchIDs }}"
  
  - name: apply patch
    debug: 
      msg: applying patch {{ item }} to $ORACLE_HOME
    loop: "{{ patchIDs }}"
    failed_when: "item == 456"

This is a stub of course … I have stripped any non-essential code from the playbook but it should give you a first idea.

Can you spot the bug

This looks ok-ish, but there’s a (not quite so hidden) bug in there. And no, I didn’t have a failed_when condition in my playbook that would always evaluate to true with this input :) Consider this output:

$ ansible-playbook main.yml 

PLAY [127.0.0.1] ***************************************************************************

TASK [Gathering Facts] *********************************************************************
ok: [127.0.0.1]

TASK [say hello] ***************************************************************************
ok: [127.0.0.1] => {}

MSG:

hello world


TASK [unzip patch] *************************************************************************
ok: [127.0.0.1] => (item=123) => {}

MSG:

unzipping patch 123

ok: [127.0.0.1] => (item=234) => {}

MSG:

unzipping patch 234

ok: [127.0.0.1] => (item=456) => {}

MSG:

unzipping patch 456


TASK [check patch for conflicts with Oracle Home] ******************************************
ok: [127.0.0.1] => (item=123) => {}

MSG:

checking patch 123 for conflict with $ORACLE_HOME

ok: [127.0.0.1] => (item=234) => {}

MSG:

checking patch 234 for conflict with $ORACLE_HOME

ok: [127.0.0.1] => (item=456) => {}

MSG:

checking patch 456 for conflict with $ORACLE_HOME


TASK [apply patch] *************************************************************************
ok: [127.0.0.1] => (item=123) => {}

MSG:

applying patch 123 to $ORACLE_HOME

ok: [127.0.0.1] => (item=234) => {}

MSG:

applying patch 234 to $ORACLE_HOME

failed: [127.0.0.1] (item=456) => {}

MSG:

applying patch 456 to $ORACLE_HOME

fatal: [127.0.0.1]: FAILED! => {}

MSG:

All items completed


PLAY RECAP *********************************************************************************
127.0.0.1                  : ok=4    changed=0    unreachable=0    failed=1

Whoops, that went wrong! As you would predict, the last task failed.

The simulated conflict check is performed for each patch, before the patch is applied in the next step. And this is where the bug in the code can hit you. Imagine you are on a recent PSU, let’s say 180717. The playbook:

  1. Checks patch 123 for incompatibilities with PSU 180717
  2. Followed by patch# 234 …
  3. and eventually 456.

No issues are detected. The next step is to apply patch 123 on top of our fictional PSU 180717, followed by 234 on top of 180717 plus 123, and so on. When it comes to patch 456, a conflict is detected: opatch tells you that you can’t apply 456 on top of 180717 + 234 … I have simulated this with the failed_when clause. The bug in this case is my playbook failing to detect a conflict before actually trying to apply a patch.

I need a procedure!

So what now? In a shell script, I would have defined a function, maybe called it apply_patch. It’s task include the unzipping, checking pre-requisites, and eventually the call to opatch apply. In the body of the script I would have looped over all patches to apply and called apply_patch() for each patch to be applied. In other words unzip/check prerequisites/apply are always performed for a given patch before the code advances to the next patch in sequence.

But how can this be done in Ansible? A little bit of research (and a conversation with @fritshoogland who confirmed what I thought was to be changed) later I noticed that you can include tasks and pass variables to them. So what if I rewrote my code to take advantage of that feature? Here’s the end result:

$ cat main.yml 
---
- hosts: 127.0.0.1
  connection: local
  vars:
    - patchIDs:
        - 123
        - 234
        - 456

  tasks:
  - name: say hello
    debug: msg="hello world"

  - name: include a task
    include_tasks: includedtask.yml
    loop: "{{ patchIDs }}"

Note that I’m using the loop syntax recommended from Ansible 2.5 and later (see section “Migrating from with_X to loop”).

The include file references the variable passed as {{ item }}.

$ cat includedtask.yml 
---
- name: unzip patch {{ item }}
  debug: 
    msg: unzipping patch {{ item }}

- name: check patch {{ item }} for conflicts with Oracle Home
  debug: 
    msg: checking patch {{ item }} for conflict with $ORACLE_HOME

- name: apply patch {{ item }}
  debug: 
    msg: applying patch {{ item }} to $ORACLE_HOME
  failed_when: "item == 456"

Now if I run this playbook, each task (unzip/check prerequisites/apply) is executed for each individual patch.

$ ansible-playbook main.yml

PLAY [127.0.0.1] ***************************************************************************

TASK [Gathering Facts] *********************************************************************
ok: [127.0.0.1]

TASK [say hello] ***************************************************************************
ok: [127.0.0.1] => {}

MSG:

hello world


TASK [include a task] **********************************************************************
included: /home/martin/ansible/blogpost/including_task/better/includedtask.yml for 127.0.0.1
included: /home/martin/ansible/blogpost/including_task/better/includedtask.yml for 127.0.0.1
included: /home/martin/ansible/blogpost/including_task/better/includedtask.yml for 127.0.0.1

TASK [unzip patch 123] *********************************************************************
ok: [127.0.0.1] => {}

MSG:

unzipping patch 123


TASK [check patch 123 for conflicts with Oracle Home] **************************************
ok: [127.0.0.1] => {}

MSG:

checking patch 123 for conflict with $ORACLE_HOME


TASK [apply patch 123] *********************************************************************
ok: [127.0.0.1] => {}

MSG:

applying patch 123 to $ORACLE_HOME


TASK [unzip patch 234] *********************************************************************
ok: [127.0.0.1] => {}

MSG:

unzipping patch 234


TASK [check patch 234 for conflicts with Oracle Home] **************************************
ok: [127.0.0.1] => {}

MSG:

checking patch 234 for conflict with $ORACLE_HOME


TASK [apply patch 234] *********************************************************************
ok: [127.0.0.1] => {}

MSG:

applying patch 234 to $ORACLE_HOME


TASK [unzip patch 456] *********************************************************************
ok: [127.0.0.1] => {}

MSG:

unzipping patch 456


TASK [check patch 456 for conflicts with Oracle Home] **************************************
ok: [127.0.0.1] => {}

MSG:

checking patch 456 for conflict with $ORACLE_HOME


TASK [apply patch 456] *********************************************************************
fatal: [127.0.0.1]: FAILED! => {}

MSG:

applying patch 456 to $ORACLE_HOME


PLAY RECAP *********************************************************************************
127.0.0.1                  : ok=13   changed=0    unreachable=0    failed=1 

This way, I should be able to stop the playbook as soon as the pre-requisite conflict checker has completed.

Ansible tips’n’tricks: assessing your runtime environment

One thing that I frequently need to do is test for a certain condition, and fail if it is not met. After all, I want to write those playbooks in a safe way.

Here is an example: I need to ensure that my playbook only runs on Oracle Linux 7. How can I do this? Ansible offers a shell and a command module (make sure you read the notes in the command module documentation!), so I could simply write something testing for the output of, let’s say, /etc/os-release.

This is totally possible, although I believe it’s a bit messy and there is a more elegant way requiring far less coding. Ansible maintains a whole raft of variables it gathers when you run a playbook. Here is an example (I am again using the “debug” stdout_callback as described in my earlier blog posts):

[martin@controller ansible]$ ansible-playbook -i inventory.yml test.yml

PLAY [blogpost] ****************************************************************

TASK [Gathering Facts] *********************************************************
ok: [server1]

TASK [say hello] ***************************************************************
ok: [server1] => {}

MSG:

hello world

PLAY RECAP *********************************************************************
server1                    : ok=2    changed=0    unreachable=0    failed=0   

When invoking the play “blogpost” (I have defined my host “server1” to be part of a group named “blogposts” in the local inventory file), I can see that the first task is to gather facts. This is unusual, since I haven’t defined any such task:

[martin@controller ansible]$ cat test.yml 
---
- hosts: blogpost

  tasks:
    - name: say hello
      debug:
        msg: hello world

Nevertheless, ansible gathers these facts, and they are most useful. But which of these exist, and what are their values? It is not too hard to find out. The following command invokes the setup module (using a local inventory mapping “server1” to group “blogpost”) specifically against host “server1”:

[martin@controller ansible]$ ansible -i inventory.yml -m setup server1 | head -n 25
server1 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "192.168.abc.24"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::5054:ff:fe91:abcd"
        ],
        "ansible_apparmor": {
            "status": "disabled"
        },
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "04/01/2014",
        "ansible_bios_version": "1.10.2-2.fc27",
        "ansible_cmdline": {
            "BOOT_IMAGE": "/vmlinuz-4.1.12-94.3.9.el7uek.x86_64",
            "LANG": "en_US.UTF-8",
            "crashkernel": "auto",
            "quiet": true,
            "rd.lvm.lv": "rootvg/swaplv",
            "rhgb": true,
            "ro": true,
            "root": "/dev/mapper/rootvg-rootlv"
        },
        "ansible_date_time": {

There is a lot more output! You can query pretty much everything using these built-in variables. So what about our distribution? Let’s check the output of the setup module for a string containing the word “distribution”:

[martin@controller ansible]$ ansible -i inventory.yml -m setup server1 | grep -i distribution
        "ansible_distribution": "OracleLinux", 
        "ansible_distribution_file_parsed": true, 
        "ansible_distribution_file_path": "/etc/oracle-release", 
        "ansible_distribution_file_search_string": "Oracle Linux", 
        "ansible_distribution_file_variety": "OracleLinux", 
        "ansible_distribution_major_version": "7", 
        "ansible_distribution_release": "NA", 
        "ansible_distribution_version": "7.5", 

So it looks like there is something we could use here. I can re-write my playbook now to check for Oracle Linux 7 as follows:

[martin@controller ansible]$ cat test2.yml 
---
- hosts: blogpost

  tasks:
    - name: print debuginfo
      debug:
        var: "{{ item }}" 
      with_items:
        - ansible_distribution
        - ansible_distribution_major_version

    - name: ensure we run Oracle Linux 7
      fail:
        msg: This playbook only runs on Oracle Linux 7
      when: (ansible_distribution != "OracleLinux" and ansible_distribution_major_version|int != 7)

This does work all right when running against an Oracle Linux 7 system.

[martin@controller ansible]$ ansible-playbook -i inventory.yml test2.yml

PLAY [blogpost] ****************************************************************

TASK [Gathering Facts] *********************************************************
ok: [server1]

TASK [print debuginfo] *********************************************************
ok: [server1] => (item=ansible_distribution) => {
    "ansible_distribution": "OracleLinux", 
    "item": "ansible_distribution"
}
ok: [server1] => (item=ansible_distribution_major_version) => {
    "ansible_distribution_major_version": "7", 
    "item": "ansible_distribution_major_version"
}

TASK [ensure we run Oracle Linux 7] ********************************************
skipping: [server1]

PLAY RECAP *********************************************************************
server1                    : ok=2    changed=0    unreachable=0    failed=0   

The “skipping” message indicates this task [ensure we run Oracle Linux 7] has not fired and we can proceed with life. Pointing this playbook to another environment not running Oracle Linux 7, the play fails:

[martin@controller ansible]$ ansible-playbook -i inventory.yml test2.yml

PLAY [blogpost] ****************************************************************

TASK [Gathering Facts] *********************************************************
ok: [linuxdev]

TASK [print debuginfo] *********************************************************
ok: [linuxdev] => (item=ansible_distribution) => {
    "ansible_distribution": "Fedora", 
    "item": "ansible_distribution"
}
ok: [linuxdev] => (item=ansible_distribution_major_version) => {
    "ansible_distribution_major_version": "28", 
    "item": "ansible_distribution_major_version"
}

TASK [ensure we run Oracle Linux 7] ********************************************
fatal: [linuxdev]: FAILED! => {
    "changed": false
}

MSG:

This playbook only runs on Oracle Linux 7


PLAY RECAP *********************************************************************
linuxdev                   : ok=2    changed=0    unreachable=0    failed=1

So there we go! This example can easily be extended to check for other things, such as a combination of running UEK on Oracle Linux, etc.

Happy scripting!

Ansible tips’n’tricks: even more output options

In my last post I wrote about the “debug” option to format Ansible output differently. I came across this setting simply by searching the usual developer forums for an alternative Ansible output option.

Having found out about the “debug” option made me curious, especially since there wasn’t an awful lot of documentation available about additional alternatives. Or so It thought before writing this post, there is actually, as you will see later. So to recap what I had so far: I noticed “skippy” in my distribution’s /etc/ansible/ansible.cfg although it is commented out. And I found the “debug” option via my favourite search engine, and there is the “default” as well.

There surely had to be more …

This wasn’t quite good enough for me and I started to wonder if there were more of these callbacks. Here is my Ansible version in case some of these callbacks might be quite recent:

[martin@controller environment]$ ansible --version | head -n 1
ansible 2.6.4

My first idea was to perform a file system search using find – I assumed trying to look out for “debug” or “default” would result in too many irrelevant hits, but “skippy” sounded unique enough a search term.

$ find / -iname "*skippy*" 2> /dev/null
/usr/lib/python2.7/site-packages/ansible/plugins/callback/skippy.py
/usr/lib/python2.7/site-packages/ansible/plugins/callback/skippy.pyc
/usr/lib/python2.7/site-packages/ansible/plugins/callback/skippy.pyo
[martin@linuxdev ~]$ 

This looks like a hit! I opened the first file, skippy.py, and found a documentation section:

DOCUMENTATION = '''
    callback: skippy
    callback_type: stdout
    requirements:
      - set as main display callback
    short_description: Ansible screen output that ignores skipped status
    version_added: "2.0"
    extends_documentation_fragment:
      - default_callback
    description:
        - This callback does the same as the default except it does not output skipped host/task/item status

So there you go, more information about the purpose of this callback! Are there any others that might be useful candidates for stdout_callback? Possibly! Let’s see (still in the callback directory)…

$ grep -i 'type: stdout' *.py
actionable.py:    type: stdout
debug.py:    type: stdout
default.py:    type: stdout
dense.py:type: stdout
full_skip.py:    type: stdout
json.py:    type: stdout
minimal.py:    type: stdout
null.py:    callback_type: stdout
oneline.py:    type: stdout
selective.py:    callback_type: stdout
skippy.py:    callback_type: stdout
stderr.py:    callback_type: stdout
unixy.py:    type: stdout
yaml.py:    type: stdout

Having unearthed this list I eventually found the relevant part of the documentation set! So instead of showing you the DOCUMENTATION variable for each of these callbacks, I point you to the official Ansible 2.6 documentation:

https://docs.ansible.com/ansible/2.6/plugins/callback.html#plugin-list

Happy scripting!

Ansible tips’n’tricks: a different output option

When running ansible scripts, occasionally you wonder why a given task has failed. I found out more than once that it’s commonly a problem with the script, not the engine ;) Finding out exactly where in the script I made the mistake can be more of a challenge.

With the default ansible settings, output can be a bit hard to read. Consider this example: I do quite a bit of patching in my lab, and this almost always requires an upgrade of OPatch (d’oh!). So instead of connecting to each of my hosts and performing the same unzip command over and over again, I thought of using something else. Why not use ansible for this task? It won’t get tired copying/unzipping OPatch to all the destinations I indicate in my configuration. And it won’t introduce a mistake when dealing with the fifth ORACLE_HOME on the third server…

Before replacing $ORACLE_HOME/OPatch with the new version, I want to take a backup of the current OPatch just in case. I don’t want to keep more than 1 backup around in this particular lab environment, so I decided to check for an existing backup first, before creating a new one. If one exists, I remove it. Or at least, that’s the plan.

So I was happily coding away and in my usual trial-and-error approach was ready to test the script I wrote for the first time. Here’s the result (as shown in my 80×24 terminal):

[martin@controller environment]$ ansible-playbook -i inventory.yml broken.yml

PLAY [blogpost] ****************************************************************

TASK [Gathering Facts] *********************************************************
ok: [server1]

TASK [check if there is an old backup] *****************************************
ok: [server1]

TASK [remove old OPatch backup] ************************************************
fatal: [server1]: FAILED! => {"msg": "The conditional check 'backup_present.exis
ts' failed. The error was: error while evaluating conditional (backup_present.ex
ists): 'dict object' has no attribute 'exists'\n\nThe error appears to have been
 in '/home/martin/ansible/blogpost/environment/broken.yml': line 20, column 11, 
but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe 
offending line appears to be:\n\n\n        - name: remove old OPatch backup\n  
        ^ here\n"}

PLAY RECAP *********************************************************************
server1                    : ok=2    changed=0    unreachable=0    failed=1   

[martin@controller environment]$ 

It doesn’t really matter what I was trying to do here, what matters though is the somewhat illegible formatting of the output. The listing above really shows how the error displayed in my terminal. I haven’t quite understood yet why there are linebreaks (\n) in the output that don’t result in a carriage return on screen.

So I did a little bit of digging around and found a global setting named stdout_callback. This is usually defined in /etc/ansible/ansible.cfg which would be bad news for developers if we couldn’t override it. Thankfully you can – using $HOME/.ansible.cfg or even an ansible.cfg file in your project directory. Setting stdout_callback to “debug” reveals a much more readable version of the error:

TASK [remove old OPatch backup] ************************************************
fatal: [server1]: FAILED! => {}

MSG:

The conditional check 'backup_present.exists' failed. The error was: error while
 evaluating conditional (backup_present.exists): 'dict object' has no attribute 
'exists'

The error appears to have been in '/home/martin/ansible/blogpost/environment/bro
ken.yml': line 20, column 11, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:


        - name: remove old OPatch backup
          ^ here


PLAY RECAP *********************************************************************
server1                    : ok=2    changed=0    unreachable=0    failed=1

I find this much easier to read, and by setting stdout_callback to a non-default value in my project directory I don’t break anything inadvertently. It also immediately revealed I wasn’t checking backup_exists.stat.exists, I used backup_exists.exists. Pretty-printing the output helped me debug the mistake much quicker. Later on, when your script is ready to be deployed it’s probably a good idea not to use the debug callback ;)

The ansible version in this post is 2.6.4 by the way.

Happy scripting!

Ansible tips’n’tricks: a new series

In the past few months I have spent considerable amounts of time working on new technology (new as in “new to me”) and one of the things I have found a great interest-and more importantly-use cases with, is ansible. I don’t know how it is with you, but if I don’t have a practical use case for using a technology I find it hard to get familiar with it.

Ansible is a fantastic piece of technology with decent documentation and great community support. I didn’t find writing ansible code too hard. Most of the problems I have encountered while learning how to write ansible playbooks have-in some way or another-already been encountered and solved by other users. It does require a bit of Internet research, and you will need check if the solution offered somewhere on the Internet is still valid with the current version of the tool. As with a lot of other software, ansible evolves at a rather quick pace and keeping up can be a bit of a challenge.

I promise not to get into details about what ansible is, others have done so and I’m not a great fan of repeating what more clever people have said. Head over to the official documentation and see for yourself what ansible is. For the most part I consider it pretty straight forward and elegant. It sure beats my attempts at writing bash code ;)

Having said that it might require a minute or so to get used to the markup language; most so-called playbooks are written in YAML and you learn to appreciate the importance of white space in your scripts. Another key concept I personally found quite important to understand is idempotency. A script that is considered to be idempotent can be re-run multiple times safely. There are other important concepts to be aware of, and you should consult the documentation to learn more about these if you aren’t yet familiar with them.

My main reason for using ansible is to automate routine tasks in my personal lab environment, and this little series I’m planning on writing is primarily a “note to self” so I don’t forget how to do things. As with the Oracle posts I plan on writing I’ll add a “tested/written with version x” so later on when someone hits this article series via a search engine what it requires to do the same.

Things I’m planning on writing on are those that cost me a bit more time on <enter your favourite developer forum here> and I’m hoping these little snippets can help you with your automation project.