{"id":2403,"date":"2018-03-05T19:49:19","date_gmt":"2018-03-06T00:49:19","guid":{"rendered":"http:\/\/osric.com\/chris\/accidental-developer\/?p=2403"},"modified":"2018-03-05T19:51:09","modified_gmt":"2018-03-06T00:51:09","slug":"using-ansible-to-check-version-before-install-or-upgrade","status":"publish","type":"post","link":"https:\/\/osric.com\/chris\/accidental-developer\/2018\/03\/using-ansible-to-check-version-before-install-or-upgrade\/","title":{"rendered":"Using Ansible to check version before install or upgrade"},"content":{"rendered":"<p>One thing that I do frequently with an Ansible role is check to see if software is already installed and at the desired version. I do this for several related reasons:<\/p>\n<ol>\n<li>To avoid taking extra time and doing extra work<\/li>\n<li>To make the role idempotent (changes are only made if changes are needed)<\/li>\n<li>So that the play recap summary lists accurate results<\/li>\n<\/ol>\n<p>I&#8217;m thinking particularly of software that needs to be unpacked, configured, compiled, and installed (rather than .rpm or .deb packages). In this example, I&#8217;ll be installing the fictional <em>widgetizer<\/em> software.<\/p>\n<p>First I add a couple variables to the <code>defaults\/main.yml<\/code> file for the role:<\/p>\n<pre><code>---\r\npath_to_widgetizer: \/usr\/local\/bin\/widgetizer\r\nwidgetizer_target_version: 1.2\r\n...<\/code><\/pre>\n<p>Next I add a task to see if the installed binary already exists:<\/p>\n<pre><code>- name: check for existing widgetizer install\r\n  stat:\r\n    path: \"{{ path_to_widgetizer }}\"\r\n  register: result_a\r\n  tags: widgetizer<\/code><\/pre>\n<p>Then, if <em>widgetizer<\/em> is installed, I check which version is installed:<\/p>\n<pre><code>- name: check widgetizer version\r\n  command: \"{{ path_to_widgetizer }} --version\"\r\n  register: result_b\r\n  when: \"result_a.stat.exists\"\r\n  changed_when: False\r\n  failed_when: False\r\n  tags: widgetizer<\/code><\/pre>\n<p>2 things to note in the above:<\/p>\n<ul>\n<li>The command task normally reports <code>changed: true<\/code>, so specify <code>changed_when: False<\/code> to prevent this.<\/li>\n<li>Although this task should only run if <em>widgetizer<\/em> is present, we don&#8217;t want the task (and therefore the entire playbook) to fail if it is not present. Specify <code>failed_when: false<\/code> to prevent this. (I could also specify <code>ignore_errors: true<\/code>, which would report the error but would not prevent the rest of the playbook from running.)<\/li>\n<\/ul>\n<p>Now I can check the registered variables to determine if <em>widgetizer<\/em> needs to be installed or upgraded:<\/p>\n<pre><code>- name: install\/upgrade widgetizer, if needed\r\n  include: tasks\/install.yml\r\n  when: \"not result_a.stat.exists or widgetizer_target_version is not defined or widgetizer_target_version not in result_b.stdout\"\r\n  tags: widgetizer<\/code><\/pre>\n<p>However, when I ran my playbook I received an error:<\/p>\n<pre><code>$ ansible-playbook -i hosts site.yaml --limit localhost --tags widgetizer\r\n\r\n...\r\n\r\nfatal: [localhost]: FAILED! =&gt; {\"failed\": true, \"msg\": \"The conditional check 'not result_a.stat.exists or widgetizer_target_version is not defined or widgetizer_target_version not in result_b.stdout' failed. The error was: Unexpected templating type error occurred on ({% if not result_a.stat.exists or widgetizer_target_version is not defined or widgetizer_target_version not in result_b.stdout %} True {% else %} False {% endif %}): coercing to Unicode: need string or buffer, float found\\n\\nThe error appears to have been in '\/home\/chris\/projectz\/roles\/widgetizer\/tasks\/install.yml': line 3, column 3, but may\\nbe elsewhere in the file depending on the exact syntax problem.\\n\\nThe offending line appears to be:\\n\\n\\n- name: copy widgetizer source\\n  ^ here\\n\"}<\/code><\/pre>\n<p>The key piece of information to note in that error message is:<\/p>\n<pre><code>need string or buffer, float found<\/code><\/pre>\n<p>We&#8217;ve supplied <code>widgetizer_target_version<\/code> as <code>1.2<\/code> (a floating point number), but Python\/jinja2 wants a string to search for in <code>result_b.stdout<\/code>.<\/p>\n<p>There are at least 2 ways to fix this:<\/p>\n<ul>\n<li>Enclose the value in quotes to specify <code>widgetizer_target_version<\/code> as a string in the variable definition, e.g. <code>widgetizer_target_version: \"1.2\"<\/code><\/li>\n<li>Convert <code>widgetizer_target_version<\/code> to a string in the <code>when<\/code> statement, e.g. <code>widgetizer_target_version|string not in result_b.stdout<\/code><\/li>\n<\/ul>\n<p>After making either of those changes, the playbook runs successfully and correctly includes or ignores the <code>install.yml<\/code> file as appropriate.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Before copying source code, configuring, compiling, and installing a software package via Ansible, first check to see if the target package is already installed and at the target version. This post includes Ansible tasks to perform those checks and covers a couple common pitfalls along the way.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[439],"tags":[423,469],"class_list":["post-2403","post","type-post","status-publish","format-standard","hentry","category-ansible","tag-ansible","tag-jinja2"],"_links":{"self":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/2403","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/comments?post=2403"}],"version-history":[{"count":7,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/2403\/revisions"}],"predecessor-version":[{"id":2413,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/posts\/2403\/revisions\/2413"}],"wp:attachment":[{"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/media?parent=2403"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/categories?post=2403"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/osric.com\/chris\/accidental-developer\/wp-json\/wp\/v2\/tags?post=2403"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}