{"id":7121,"date":"2022-01-25T10:35:22","date_gmt":"2022-01-25T16:35:22","guid":{"rendered":"http:\/\/gregsowell.com\/?p=7121"},"modified":"2022-05-31T09:19:55","modified_gmt":"2022-05-31T15:19:55","slug":"automating-the-creation-and-installation-of-execution-environments-for-the-ansible-automation-platform","status":"publish","type":"post","link":"https:\/\/gregsowell.com\/?p=7121","title":{"rendered":"Automating The Creation And Installation Of Execution Environments For The Ansible Automation Platform"},"content":{"rendered":"<p><a href=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2022\/01\/demo-video.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/gregsowell.com\/wp-content\/uploads\/2022\/01\/demo-video-300x169.jpg\" alt=\"\" width=\"300\" height=\"169\" class=\"aligncenter size-medium wp-image-7129\" srcset=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2022\/01\/demo-video-300x169.jpg 300w, https:\/\/gregsowell.com\/wp-content\/uploads\/2022\/01\/demo-video-768x432.jpg 768w, https:\/\/gregsowell.com\/wp-content\/uploads\/2022\/01\/demo-video-1024x576.jpg 1024w, https:\/\/gregsowell.com\/wp-content\/uploads\/2022\/01\/demo-video.jpg 1280w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>First things first, here&#8217;s my <a href=\"http:\/\/gregsowell.com\/?p=7086\">blog post that gives you a step by step of creating, preparing, and using custom Execution Environments<\/a>.  This article picks up from there and tries to automate as much of the process as is possible using the Ansible Automation Platform(AAP).  It&#8217;s a little meta; I&#8217;m using AAP to build the Execution Environments(EEs) that are going to be used by AAP.<\/p>\n<h2>Video Demo<\/h2>\n<p>Alas, I&#8217;ve made adjustments to the playbook already, so there are slight differences.<br \/>\n<iframe loading=\"lazy\" width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/v0_pQgamFdU\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><\/p>\n<h2>Playbook<\/h2>\n<p>The link to the newest updated version of the playbook is <a href=\"https:\/\/github.com\/gregsowell\/ansible-builder\/blob\/main\/build-ee.yml\">right here in my github<\/a>.<br \/>\nThis is the complete playbook at the time of this writing(again, check github for the newest version).  Below will be the playbook broken into pieces and explained.<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">---\r\n- name: Build\/Push\/Install Custom EEs\r\n  hosts: aap.gregsowell.com\r\n  gather_facts: false\r\n  vars:\r\n    # aap username\/pass\r\n    controller_user: &quot;{{ gen2_user }}&quot;\r\n    controller_pass: &quot;{{ gen2_pword }}&quot;\r\n\r\n#    # what is the base EE that is used for building my custom EE\r\n#    base_ee: registry.redhat.io\/ansible-automation-platform-21\/ee-supported-rhel8\r\n\r\n    # what is the name of the new EE being built\r\n    new_ee: azure_ee_supported\r\n\r\n    # what is the path where the EE build files are stored\r\n    path_ee: \/root\/ansible-execution-environments\r\n    # base folder for cloning EE\r\n    base_ee_path: \/root\r\n    # private automation hub host\r\n    pah_host: pah.gregsowell.com\r\n    # ee repo\r\n    ee_repo: https:\/\/github.com\/gregsowell\/ansible-execution-environments\r\n\r\n    # private automation hub host\r\n    pah_host: pah.gregsowell.com\r\n\r\n    # credential name used in AAP\r\n    pah_cred: Automation Hub Container Registry\r\n\r\n#    # new version number for the pushed ee\r\n#    new_ee_ver: 1.0.2\r\n\r\n    # pah username\/pass\r\n    pah_user: &quot;{{ gen1_user }}&quot;\r\n    pah_pass: &quot;{{ gen1_pword }}&quot;<\/code><\/pre>\n<p>I setup a username and password for connecting via the AAP API; I&#8217;m passing in custom credentials at run-time(a benefit of using custom credentials is that if I specify a field as password, then no_log is set for that variable and will be obfuscated in output).<\/p>\n<p>You may notice that I have base_ee commented out.  This is the base container I&#8217;m using to build the EE with.  I&#8217;ve got it commented out because I&#8217;m pulling that directly from the EE configuration files, so it&#8217;s not necessary here(I left it if you want to change things up.<\/p>\n<p>There&#8217;s also setup for the build files repo; I&#8217;ve set it up so that all of my kit is stored in Source Control Management!<\/p>\n<p>You&#8217;ll also notice that I&#8217;m setting up info for using Red Hat&#8217;s Private Automation Hub(PAH or pah).  I&#8217;m using this as my container registry and will be pushing my custom EEs here.<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  tasks:\r\n  - name: Git clone the EE repo to ensure I have the newest files\r\n    ansible.builtin.shell: &quot;git clone {{ ee_repo }}&quot;\r\n    args:\r\n      chdir: &quot;{{ base_ee_path }}&quot;\r\n\r\n  - name: Grab the execution-environment.yml file to use as variables\r\n    ansible.builtin.fetch:\r\n      src: &quot;{{ path_ee }}\/{{ new_ee }}\/execution-environment.yml&quot;\r\n      dest: execution-environment.yml\r\n    register: ee_yml\r\n\r\n  - name: Include the EE file as variables\r\n    include_vars:\r\n      file: &quot;{{ ee_yml.dest }}&quot;\r\n      name: inc_vars\r\n\r\n  - name: Setup some base variables\r\n    ansible.builtin.set_fact:\r\n      base_ee: &quot;{{ inc_vars.build_arg_defaults.EE_BASE_IMAGE }}&quot;\r\n      new_ee_ver: &quot;{{ inc_vars.version }}&quot;<\/code><\/pre>\n<p>This beginning is all about reading the EE config files and grabbing some of the info.  First things first, I connect to my git repo and clone down the newst build files.  Next reach out to the remote server I&#8217;m using to build EEs at and pulling that file locally into my current EE for processing.  Notice I&#8217;m registering the results to a variable; this is because the path that containers use are pretty bonkers, and it&#8217;s easier to just reference it this way.  For this example, the path is &#8220;\/runner\/project\/execution-environment.yml\/aap.gregsowell.com\/root\/ee\/azure_ee_supported\/execution-environment.yml&#8221;.  <\/p>\n<p>I next include the execution-environment.yml file as variables(it is a simply YAML formatted file after all).<\/p>\n<p>Last here I grab the interesting variables(base image and version) and assign them.<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: Grab the list of EEs to ensure the correct EE exists\r\n    ansible.builtin.shell: podman image list\r\n    changed_when: false\r\n    register: ee_list\r\n\r\n  - name: Install the required base EE if not already present\r\n    when: ee_list.stdout_lines is not search(base_ee)\r\n    ansible.builtin.debug:\r\n      msg: &quot;Install the missing base EE here&quot;<\/code><\/pre>\n<p>Here I&#8217;m issuing a &#8220;podman image list&#8221; command to view what EEs exist on this server and registering it to a variable.  Next I do a quick check to see if that base EE I&#8217;m building from exists on the server; if not it will pull this EE(I&#8217;ll be adding that in version 2 of the script).<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: Begin the build process on the EE\r\n    when: ee_list.stdout_lines is search(base_ee)\r\n    block:\r\n      - name: Run the build on the selected new EE\r\n        ansible.builtin.shell: &quot;ansible-builder build --tag {{ new_ee }}&quot;\r\n        args:\r\n          chdir: &quot;{{ path_ee }}\/{{new_ee}}&quot;\r\n        register: ee_build_out\r\n        failed_when: ee_build_out.stdout_lines is not search(&#039;Complete!&#039;)<\/code><\/pre>\n<p>On my AAP server I have a directory named &#8220;ee&#8221;.  Inside of it I keep a directory for each custom EE I&#8217;m building.  So for example I have \/root\/ee\/azure_ee_supported\/.  This is where I&#8217;m building, you guessed it, my Azure execution environment.  Inside of this folder is all of my ansible-builder files(and ultimately where the context folder will be placed by builder).  I&#8217;m using the shell module here to fire off the build command, but I have to first add the args option to specify which folder to execute the command in.  I&#8217;m also adding in a failed_when condition so that if the command doesn&#8217;t return &#8220;Complete!&#8221;, it will fail the task.  This is the standard build.<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: search containing folder to determine if there are any crt files(used for self-signed pah server)\r\n    ansible.builtin.find:\r\n      paths: &quot;{{ path_ee }}\/{{ new_ee }}&quot;\r\n      patterns: &#039;*.crt&#039;\r\n    register: crt_files\r\n\r\n  - name: Begin the build process on the EE when certs do need to be installed\r\n    when: ee_list.stdout_lines is search(base_ee) and crt_files.matched &gt; 0\r\n#    when: ee_list.stdout_lines is search(inc_vars.build_arg_defaults.EE_BASE_IMAGE)\r\n    block:\r\n      - name: Run the create on the selected new EE - **install certs process**\r\n        ansible.builtin.shell: &quot;ansible-builder create&quot;\r\n        args:\r\n          chdir: &quot;{{ path_ee }}\/{{new_ee}}&quot;\r\n        register: ee_build_out\r\n        failed_when: ee_build_out.stdout_lines is not search(&#039;Complete!&#039;)\r\n\r\n      - name: Copy cert files to the build folder\r\n        ansible.builtin.shell: &quot;cp {{ path_ee }}\/{{new_ee}}\/*.crt {{ path_ee }}\/{{new_ee}}\/context\/_build&quot;\r\n        args:\r\n          chdir: &quot;{{ path_ee }}\/{{new_ee}}&quot;\r\n\r\n      - name: Add line to Containerfile to ensure the certificates are copied and applied\r\n        ansible.builtin.lineinfile:\r\n          path: &quot;{{ path_ee }}\/{{new_ee}}\/context\/Containerfile&quot;\r\n          insertbefore: &#039;^RUN ansible-galaxy role install.*&#039;\r\n          line: &#039;RUN cp *.crt \/etc\/pki\/ca-trust\/source\/anchors &amp;&amp; update-ca-trust&#039;\r\n\r\n      - name: Run the build on the selected new EE - with installed certs\r\n        ansible.builtin.shell: &quot;podman build -f context\/Containerfile -t {{ new_ee }} context&quot;\r\n        args:\r\n          chdir: &quot;{{ path_ee }}\/{{new_ee}}&quot;\r\n        register: ee_build_out<\/code><\/pre>\n<p>^^The above section is run <strong>when certificates need to be installed to connect to my private automation hub.<\/strong><br \/>\nThe first task checks the EE&#8217;s folder to see if there are any certificate files.<br \/>\nI then have a block with a conditional checking to see if there were any cert files, and when it found some the block will run.<br \/>\nI first run the ansible-builder create command which will build out the Containerfile which will be used to create the EE.<br \/>\nI then copy my certs to the _build folder, and then modify the Container file to install the certs.<br \/>\nLast I run the podman build command to create my new EE.<br \/>\nI have a breakdown of what&#8217;s happening in the blog post on all of the steps being performed here.<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: Push EE to container registry\r\n    ansible.builtin.shell: &quot;podman push --creds={{ pah_user }}:{{ pah_pass }} localhost\/{{ new_ee }}:latest docker:\/\/{{ pah_host }}\/{{ new_ee }}:{{ new_ee_ver }} --tls-verify=false&quot;\r\n    args:\r\n      chdir: &quot;{{ path_ee }}\/{{new_ee}}&quot;\r\n    register: pah_push_out\r\n    failed_when: pah_push_out.stderr_lines is not search(&#039;Storing signatures&#039;)<\/code><\/pre>\n<p>I&#8217;m next using the podman command to push the newly created EE to my PAH server.<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: Collect credentials\r\n    uri:\r\n      url: &quot;https:\/\/{{ inventory_hostname }}\/api\/v2\/credentials\/&quot;\r\n      user: &quot;{{ controller_user }}&quot;\r\n      password: &quot;{{ controller_pass }}&quot;\r\n      method: GET\r\n      validate_certs: false\r\n      force_basic_auth: true\r\n      status_code:\r\n        - 200\r\n        - 201\r\n        - 204\r\n    register: aap_cred_out\r\n\r\n  - name: Set ansible credential name to ID\r\n    when: item.name == pah_cred\r\n    ansible.builtin.set_fact:\r\n      pah_cred_id: &quot;{{ item.id }}&quot;\r\n    loop: &quot;{{ aap_cred_out.json.results }}&quot;<\/code><\/pre>\n<p>Alright, the first thing I need to do is map the credential name over to the credential ID.  I do this by first pulling all of the existing creds and registering them to a variable(aap_cred_out).<br \/>\nI next loop through aap_cred_out looking for the credential name specified in the vars section(pah_cred), then once it finds the cred name in question, it will assign it&#8217;s ID to a variable(pah_cred_id).<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: Collect EEs to check if it already exists\r\n    uri:\r\n      url: &quot;https:\/\/{{ inventory_hostname }}\/api\/v2\/execution_environments\/&quot;\r\n      user: &quot;{{ controller_user }}&quot;\r\n      password: &quot;{{ controller_pass }}&quot;\r\n      method: GET\r\n      validate_certs: false\r\n      force_basic_auth: true\r\n      status_code:\r\n        - 200\r\n        - 201\r\n        - 204\r\n    changed_when: false\r\n    register: aap_ee_out\r\n\r\n  - name: Set ansible ee id if name already exists\r\n    when: item.name == new_ee\r\n    ansible.builtin.set_fact:\r\n      new_ee_id: &quot;{{ item.id }}&quot;\r\n    loop: &quot;{{ aap_ee_out.json.results }}&quot;<\/code><\/pre>\n<p>Here I&#8217;m now going to collect a list of EEs.  I&#8217;m then looping through them to see if the EE I&#8217;m trying to add already exists or not(just in name, not in version).<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">  - name: Configure AAP for new EE\r\n    when: new_ee_id is undefined\r\n    vars:\r\n      hosts: controllers\r\n    uri:\r\n      url: &quot;https:\/\/{{ inventory_hostname }}\/api\/v2\/execution_environments\/&quot;\r\n      user: &quot;{{ controller_user }}&quot;\r\n      password: &quot;{{ controller_pass }}&quot;\r\n      method: POST\r\n      validate_certs: false\r\n      force_basic_auth: true\r\n      body_format: json\r\n      body: |\r\n          {\r\n              &quot;name&quot;: &quot;{{ new_ee }}&quot;,\r\n              &quot;image&quot;: &quot;{{ pah_host }}\/{{ new_ee }}:{{ new_ee_ver }}&quot;,\r\n              &quot;description&quot;: &quot;added by automation&quot;,\r\n              &quot;pull&quot;: &quot;missing&quot;,\r\n              &quot;credential&quot;: &quot;{{ pah_cred_id }}&quot;\r\n          }\r\n      status_code:\r\n        - 200\r\n        - 201\r\n        - 204\r\n    failed_when: aap_conf_out.status != 201 and aap_conf_out.json.name is not search(&#039;already exists&#039;)\r\n    register: aap_conf_out\r\n\r\n#  - name: Debug aap_conf_out\r\n#    ansible.builtin.debug:\r\n#      var: aap_conf_out\r\n\r\n  - name: If EE already exists on AAP, update version number\r\n#    when: aap_conf_out.status == 400 and aap_conf_out.json.name is search(&#039;already exists&#039;)\r\n    when: new_ee_id is defined\r\n    vars:\r\n      hosts: controllers\r\n    uri:\r\n      url: &quot;https:\/\/{{ inventory_hostname }}\/api\/v2\/execution_environments\/{{ new_ee_id }}\/&quot;\r\n      user: &quot;{{ controller_user }}&quot;\r\n      password: &quot;{{ controller_pass }}&quot;\r\n      method: PUT\r\n      validate_certs: false\r\n      force_basic_auth: true\r\n      body_format: json\r\n      body: |\r\n          {\r\n              &quot;name&quot;: &quot;{{ new_ee }}&quot;,\r\n              &quot;image&quot;: &quot;{{ pah_host }}\/{{ new_ee }}:{{ new_ee_ver }}&quot;,\r\n              &quot;description&quot;: &quot;added by automation&quot;,\r\n              &quot;pull&quot;: &quot;missing&quot;,\r\n              &quot;credential&quot;: &quot;{{ pah_cred_id }}&quot;\r\n          }\r\n      status_code:\r\n        - 200\r\n        - 201\r\n        - 204\r\n#    failed_when: aap_conf_out.status != 201 and aap_conf_out.json.name is not search(&#039;already exists&#039;)\r\n    register: aap_conf_patch_out<\/code><\/pre>\n<p>Now I a add the new EE into my controllers.<br \/>\nFirst I check if the EE exists, and if not, I simply add it.<br \/>\nIf the EE does exist, then I do an update to the existing settings.  This way if a new version of the EE has been created, the version number will be updated in the controller.<\/p>\n<p>Just as a note I like to set my EEs to pull if missing.  This way it will only pull the EE the one time it needs to(much more efficient).  Also note that if I do an update on the EE version number in my PAH and also update the EE version number in my controller, on the next piece of automation that runs using that EE, it will check and see that the stored EE has an older version, so it will connect to the PAH and download the new version.  So in short, updating the EE version number in controller will signal that it should pull the new EE from my container registry!<\/p>\n<h2>Conclusion<\/h2>\n<p>At this point the automation is done and I&#8217;m ready to start using the EE with my job templates.  This cuts the majority of manual labor out of the process, and makes my life just that much easier.  <\/p>\n<p>Again, this is version 1, so I plan to make it more efficient, so watch for updates.<\/p>\n<p>If you have any questions or comments, please fire them my way, and happy automating.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>First things first, here&#8217;s my blog post that gives you a step by step of creating, preparing, and using custom Execution Environments. This article\u2026<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49],"tags":[],"class_list":["post-7121","post","type-post","status-publish","format-standard","hentry","category-ansible"],"_links":{"self":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/7121","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=7121"}],"version-history":[{"count":9,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/7121\/revisions"}],"predecessor-version":[{"id":7270,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/7121\/revisions\/7270"}],"wp:attachment":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7121"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7121"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7121"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}