{"id":7964,"date":"2026-03-31T21:31:42","date_gmt":"2026-04-01T03:31:42","guid":{"rendered":"https:\/\/gregsowell.com\/?p=7964"},"modified":"2026-03-31T21:31:42","modified_gmt":"2026-04-01T03:31:42","slug":"servicenow-to-terraform-via-event-driven-ansible","status":"publish","type":"post","link":"https:\/\/gregsowell.com\/?p=7964","title":{"rendered":"ServiceNow to Terraform via Event-Driven Ansible"},"content":{"rendered":"<p>This demo shows the dream.  A user goes to SNOW&#8217;s service catalog, clicks order on a VM, and the Ansible Automation Platform(AAP) calls Terraform to provision.  AAP will then configure the server, add it to monitoring, complete any tickets&#8230;essentially anything you want to happen.  Again, I can&#8217;t stress enough how insanely functional this is for your users&#8230;as far as they are concerned everything just works!<\/p>\n<h2>Simple Data Flow<\/h2>\n<p><a href=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_1-1.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_1-1-300x76.png\" alt=\"\" width=\"300\" height=\"76\" class=\"aligncenter size-medium wp-image-7965\" srcset=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_1-1-300x76.png 300w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_1-1-768x195.png 768w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_1-1-1024x259.png 1024w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_1-1.png 1555w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><br \/>\nIt starts with the SNOW service catalog item being ordered.<br \/>\nIt then securely passes all of the service catalog item data(brings it over as variables) to EDA.<br \/>\nEDA then, via a rulebook, passes those variables over to controller as extra-vars.<br \/>\nAAP then clones my terraform repo and provisions the infrastructure!<\/p>\n<h2>Demo<\/h2>\n<p><iframe loading=\"lazy\" width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/ABVP9iJRy8E?si=_Q5ztxJFO3odHxgi\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<h2>SNOW and EDA Configuration<\/h2>\n<p>This process looks like the following:<br \/>\n<a href=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_2-1.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_2-1-300x150.png\" alt=\"\" width=\"300\" height=\"150\" class=\"aligncenter size-medium wp-image-7966\" srcset=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_2-1-300x150.png 300w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_2-1-768x383.png 768w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_2-1-1024x511.png 1024w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_2-1.png 1478w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>I went into <a href=\"https:\/\/gregsowell.com\/?p=7905\">great detail here in another blog post<\/a>.  I detail configuring both ServiceNow and connecting it to EDA in that blog post, so reference that for full config info and a video!<\/p>\n<h2>Playbook<\/h2>\n<p>All of my playbooks can be found <a href=\"https:\/\/github.com\/gregsowell\/snow-eda\/tree\/main\/playbooks\">here in my public repo<\/a>.<br \/>\nI&#8217;ve got the Terraform playbook in there right now, which I&#8217;ve got posted below(be sure to check the source for any updates):<\/p>\n<pre class=\"gs-code\"><code class=\"language-plaintext\">---\r\n- name: Deploy VMware infrastructure using Terraform from EDA\r\n  hosts: ee-builder\r\n  gather_facts: no\r\n  vars:\r\n    snow_eda_directory: \/root\/snow-eda\r\n    terraform_directory: &quot;{{ snow_eda_directory }}\/vmware-terraform&quot;\r\n    terraform_variables_file: &quot;{{ terraform_directory }}\/terraform.tfvars&quot;\r\n\r\n    # test data\r\n    catalog_item: &quot;{{ ansible_eda.event.payload.catalog_item | default(&#039;GregSowell VMWare VM Terraform AAP&#039;)}}&quot;\r\n    cpus: &quot;{{ ansible_eda.event.payload.cpus | default(&#039;4&#039;) }}&quot;\r\n    dc_name: &quot;{{ ansible_eda.event.payload.dc_name | default(&#039;MNS&#039;) }}&quot;\r\n    default_gateway: &quot;{{ ansible_eda.event.payload.default_gateway | default(&#039;10.0.50.1&#039;) }}&quot;\r\n    event: &quot;{{ ansible_eda.event.payload.event | default(&#039;SERVICE_CATALOG&#039;) }}&quot;\r\n    hd_size: &quot;{{ ansible_eda.event.payload.hd_size | default(&#039;40&#039;) }}&quot;\r\n    ip_address: &quot;{{ ansible_eda.event.payload.ip_address | default(&#039;10.0.50.77&#039;) }}&quot;\r\n    os_type: &quot;{{ ansible_eda.event.payload.os_type | default(&#039;Rocky9&#039;) }}&quot;\r\n    ram_size: &quot;{{ ansible_eda.event.payload.ram_size | default(&#039;4096&#039;) }}&quot;\r\n    request_name: &quot;{{ ansible_eda.event.payload.request_name | default(&#039;1111111111&#039;) }}&quot;\r\n    requester: &quot;{{ ansible_eda.event.payload.requester | default(&#039;gsowell@redhat.com&#039;) }}&quot;\r\n    subnet_mask: &quot;{{ ansible_eda.event.payload.subnet_mask | default(&#039;255.255.255.0&#039;) }}&quot;\r\n    vm_folder: &quot;{{ ansible_eda.event.payload.vm_folder | default(&#039;\/Greg\/redhat\/terraform&#039;) }}&quot;\r\n    vm_name: &quot;{{ ansible_eda.event.payload.vm_name | default(&#039;NewVM1&#039;) }}&quot;\r\n\r\n    remove_vm: false\r\n\r\n    # Git settings\r\n    backup_repo: git@github.com:gregsowell\/snow-eda\r\n    git_name: Git Backup\r\n    git_email: gitbackup@gregsowell.com\r\n\r\n  tasks:\r\n    - name: Create the ssh key file based on the supplied cred\r\n      become: true\r\n      run_once: true\r\n      ansible.builtin.copy:\r\n        dest: &quot;\/tmp\/id_rsa&quot;\r\n        content: &quot;{{ cert_key }}&quot;\r\n        mode: &#039;0600&#039;\r\n\r\n    - name: Check if snow-eda directory exists\r\n      stat:\r\n        path: &quot;{{ snow_eda_directory }}&quot;\r\n      register: snow_eda_dir   \r\n\r\n    - name: Clone git repo if snow-eda directory does not exist\r\n      when: not snow_eda_dir.stat.exists\r\n      # ansible.builtin.shell: git clone https:\/\/github.com\/gregsowell\/snow-eda.git\r\n      ansible.builtin.shell: &quot;git config --global core.sshCommand &#039;ssh -o UserKnownHostsFile=\/dev\/null -o StrictHostKeyChecking=no -i \/tmp\/id_rsa&#039;; git clone {{ backup_repo }}&quot; \r\n      args:\r\n        chdir: \/root\r\n      environment:\r\n        GIT_COMMITTER_NAME: &quot;{{ git_name | default(omit) }}&quot;\r\n        GIT_COMMITTER_EMAIL: &quot;{{ git_email | default(omit) }}&quot;\r\n        GIT_AUTHOR_NAME: &quot;{{ git_name | default(omit) }}&quot;\r\n        GIT_AUTHOR_EMAIL: &quot;{{ git_email | default(omit) }}&quot;\r\n\r\n    - name: Git pull if snow-eda directory exists\r\n      when: snow_eda_dir.stat.exists\r\n      ansible.builtin.shell: &quot;git config --global core.sshCommand &#039;ssh -o UserKnownHostsFile=\/dev\/null -o StrictHostKeyChecking=no -i \/tmp\/id_rsa&#039;; git -C {{ snow_eda_directory }} pull&quot;\r\n      \r\n    - name: Remove existing VM entry block (reposition safeguard)\r\n      blockinfile:\r\n        path: &quot;{{ terraform_variables_file }}&quot;\r\n        marker: &quot;####{mark}-{{ vm_name }}&quot;\r\n        marker_begin: &quot;begin&quot;\r\n        marker_end: &quot;end&quot;\r\n        state: absent\r\n\r\n    - name: Insert or update VM entry in terraform.tfvars\r\n      when: remove_vm | bool == false\r\n      blockinfile:\r\n        path: &quot;{{ terraform_variables_file }}&quot;\r\n        insertafter: &#039;^\\s*####start-vms-here\\s*$&#039;\r\n        marker: &quot;####{mark}-{{ vm_name }}&quot;\r\n        marker_begin: &quot;begin&quot;\r\n        marker_end: &quot;end&quot;\r\n        block: |\r\n            {{ vm_name }} = {\r\n              template  = &quot;\/{{ dc_name }}\/vm\/Templates\/{{ os_type }}&quot;\r\n              network   = &quot;{{ network | default(&#039;Greg&#039;) }}&quot;\r\n              cpu       = {{ cpus | int }}\r\n              memory_mb = {{ ram_size | int }}\r\n              disk_gb   = {{ hd_size | int }}\r\n              notes     = &quot;{{ request_name }}&quot;\r\n            }\r\n\r\n    - name: Resolve vSphere credentials for Terraform\r\n      no_log: true\r\n      set_fact:\r\n        tf_vsphere_server: &quot;{{ vsphere_server | default(lookup(&#039;env&#039;, &#039;TF_VAR_vsphere_server&#039;), true) | default(lookup(&#039;env&#039;, &#039;VSPHERE_SERVER&#039;), true) }}&quot;\r\n        tf_vsphere_user: &quot;{{ vsphere_user | default(lookup(&#039;env&#039;, &#039;TF_VAR_vsphere_user&#039;), true) | default(lookup(&#039;env&#039;, &#039;VSPHERE_USER&#039;), true) }}&quot;\r\n        tf_vsphere_password: &quot;{{ vsphere_password | default(lookup(&#039;env&#039;, &#039;TF_VAR_vsphere_password&#039;), true) | default(lookup(&#039;env&#039;, &#039;VSPHERE_PASSWORD&#039;), true) }}&quot;\r\n\r\n    - name: Ensure required vSphere variables are provided\r\n      assert:\r\n        that:\r\n          - tf_vsphere_server is defined\r\n          - tf_vsphere_server | length &gt; 0\r\n          - tf_vsphere_user is defined\r\n          - tf_vsphere_user | length &gt; 0\r\n          - tf_vsphere_password is defined\r\n          - tf_vsphere_password | length &gt; 0\r\n        fail_msg: &quot;Missing required vSphere credentials. Provide extra vars (vsphere_server, vsphere_user, vsphere_password) or environment vars (TF_VAR_vsphere_*, or VSPHERE_*).&quot;\r\n\r\n    - name: Initialize Terraform\r\n      command:\r\n        argv:\r\n          - terraform\r\n          - init\r\n          - -no-color\r\n          - -input=false\r\n      args:\r\n        chdir: &quot;{{ terraform_directory }}&quot;\r\n      environment:\r\n        TF_VAR_vsphere_server: &quot;{{ tf_vsphere_server }}&quot;\r\n        TF_VAR_vsphere_user: &quot;{{ tf_vsphere_user }}&quot;\r\n        TF_VAR_vsphere_password: &quot;{{ tf_vsphere_password }}&quot;\r\n\r\n    - name: Plan Terraform deployment\r\n      command:\r\n        argv:\r\n          - terraform\r\n          - plan\r\n          - -no-color\r\n          - -input=false\r\n          - -out=tfplan\r\n      args:\r\n        chdir: &quot;{{ terraform_directory }}&quot;\r\n      environment:\r\n        TF_VAR_vsphere_server: &quot;{{ tf_vsphere_server }}&quot;\r\n        TF_VAR_vsphere_user: &quot;{{ tf_vsphere_user }}&quot;\r\n        TF_VAR_vsphere_password: &quot;{{ tf_vsphere_password }}&quot;\r\n      register: terraform_plan\r\n      async: 1800\r\n      poll: 5\r\n\r\n    - name: Display Terraform plan output\r\n      debug:\r\n        var: terraform_plan.stdout\r\n\r\n    - name: Apply Terraform deployment\r\n      command:\r\n        argv:\r\n          - terraform\r\n          - apply\r\n          - -no-color\r\n          - -input=false\r\n          - -auto-approve\r\n          - tfplan\r\n      args:\r\n        chdir: &quot;{{ terraform_directory }}&quot;\r\n      environment:\r\n        TF_VAR_vsphere_server: &quot;{{ tf_vsphere_server }}&quot;\r\n        TF_VAR_vsphere_user: &quot;{{ tf_vsphere_user }}&quot;\r\n        TF_VAR_vsphere_password: &quot;{{ tf_vsphere_password }}&quot;\r\n      async: 1800\r\n      poll: 5\r\n\r\n    - name: push the repo back with no tags\r\n      ansible.builtin.shell: &quot;git add *; git commit -m &#039;{{ vm_name }}-Add&#039;; git config --global core.sshCommand &#039;ssh -o UserKnownHostsFile=\/dev\/null -o StrictHostKeyChecking=no -i \/tmp\/id_rsa&#039;; git push&quot;\r\n      args:\r\n        chdir: &quot;{{ terraform_directory }}&quot;\r\n      environment:\r\n        GIT_COMMITTER_NAME: &quot;{{ git_name | default(omit) }}&quot;\r\n        GIT_COMMITTER_EMAIL: &quot;{{ git_email | default(omit) }}&quot;\r\n        GIT_AUTHOR_NAME: &quot;{{ git_name | default(omit) }}&quot;\r\n        GIT_AUTHOR_EMAIL: &quot;{{ git_email | default(omit) }}&quot;\r\n      # delegate_to: localhost\r\n      changed_when: git_return.stderr != &quot;Everything up-to-date&quot;\r\n      run_once: true\r\n      # connection: local\r\n      register: git_return\r\n      # become: True\r\n\r\n    - name: Remove temporary SSH key file &quot;\/tmp\/id_rsa&quot;\r\n      ansible.builtin.file:\r\n        path: \/tmp\/id_rsa\r\n        state: absent\r\n      run_once: true\r\n      # delegate_to: localhost\r\n      # become: true<\/code><\/pre>\n<p>For this quick and dirty demo I&#8217;m just connecting to a worker machine.  If I spent a bit more time I could get it all working purely from an EE, but for the sake of getting it done quick I have a VM that does the work.<\/p>\n<p>If you check the &#8220;vars&#8221; section you&#8217;ll notice I have all of the variables setup so they are ready to accept the variables passed over from EDA.<\/p>\n<p>Have a look at this overview:<br \/>\n<a href=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_3-1.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_3-1-300x126.png\" alt=\"\" width=\"300\" height=\"126\" class=\"aligncenter size-medium wp-image-7968\" srcset=\"https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_3-1-300x126.png 300w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_3-1-768x321.png 768w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_3-1-1024x428.png 1024w, https:\/\/gregsowell.com\/wp-content\/uploads\/2026\/03\/Screenshot_3-1.png 1355w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>1. You can see that I start by cloning the repository that has all of my terraform files(including state files).<br \/>\n2. I then use the &#8220;blockinfile&#8221; module to drop in the new VM configuration in my tfvars file.  I add a pre and post marker of &#8220;####ServerName-begin&#8221; and &#8220;####ServerName-end&#8221;.  This makes it easy to review the file as well as have automation go back and remove any infrastructure that should be deprovisioned.<br \/>\n3. I then supply terraform with the init, plan, and last apply commands.<br \/>\n4. Then at the end it will push the file updates(including the state file) back to my git repository for safe keeping.<\/p>\n<p>Keeping the files in the repo give me version control on everything too.  An big upgrade to the process could be to use Terraform enterprise to do the provisioning, and in that case I would do very similar things, but instead of having to run the terraform commands directly I would be throwing API commands at TE.<\/p>\n<h2>Conclusion<\/h2>\n<p>I hope you could see just how easy the process becomes once you use EDA to tie everything together.  Keep in mind that ANY tool you have in your environment can call AAP to perform the same process.<\/p>\n<p>If you have any questions or comments, I&#8217;d love to hear them.<\/p>\n<p>Good luck, and happy automating!  <\/p>\n","protected":false},"excerpt":{"rendered":"<p>This demo shows the dream. A user goes to SNOW&#8217;s service catalog, clicks order on a VM, and the Ansible Automation Platform(AAP) calls Terraform\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,13],"tags":[],"class_list":["post-7964","post","type-post","status-publish","format-standard","hentry","category-ansible","category-server"],"_links":{"self":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/7964","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=7964"}],"version-history":[{"count":3,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/7964\/revisions"}],"predecessor-version":[{"id":7970,"href":"https:\/\/gregsowell.com\/index.php?rest_route=\/wp\/v2\/posts\/7964\/revisions\/7970"}],"wp:attachment":[{"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7964"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7964"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/gregsowell.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7964"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}