{"id":591,"date":"2025-09-27T21:33:39","date_gmt":"2025-09-27T21:33:39","guid":{"rendered":"https:\/\/camilo.matajira.com\/?p=591"},"modified":"2025-10-01T10:58:21","modified_gmt":"2025-10-01T10:58:21","slug":"ansible-meets-uv","status":"publish","type":"post","link":"https:\/\/camilo.matajira.com\/?p=591","title":{"rendered":"Ansible meets UV"},"content":{"rendered":"\n<h3 class=\"wp-block-heading\">Introduction<\/h3>\n\n\n\n<p class=\"has-black-color has-text-color has-link-color wp-elements-f18cca56dae939dae13023ac6d1f0bf0\">Ansible is still the most powerful Configuration Management tool in 2025. It&#8217;s used mainly to configure and manage bare-metal servers and virtual machines.<br>But can also be used to for network automation, cloud resource provisioning, CICD pipelines, etc.<\/p>\n\n\n\n<p class=\"has-background-alt-color has-text-color has-link-color wp-elements-398222667388e2dc49eacde0fb5051e3\">Ansible&#8217;s popularity (and configuration management tool&#8217;s in general) has decreased in recent years, probably due to a shift towards immutable infrastructure (despite that Ansible could be used for this too).<br>But there are still new applications in which Ansible could shine.<\/p>\n\n\n\n<div class=\"wp-block-group is-layout-constrained wp-block-group-is-layout-constrained\">\n<p class=\"has-background-alt-color has-text-color has-link-color wp-elements-abf41ee26d9e67b0b0a0a45c332c363a\"><strong>So why bother with Ansible in 2025? Because it gives you interesting features out of the box:1<\/strong><strong>) Idempotency, 2) Drift detection, 3) a really high level &#8216;language&#8217;. And because used with UV, <\/strong><strong>Ansible can be used as a standalone &#8216;scripting&#8217; &#8216;language&#8217;, with idempotency and drift detection, <\/strong><strong>again out of the box. For certain tasks, those are killer features.<\/strong> Which tasks? Think for example installation of software; installation of tools; Developer Experience (DevEx) setup; CD part of a CICD pipeline; setup of CI ephemeral runners etc.<\/p>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Why hasn&#8217;t it been done before?<\/h3>\n\n\n\n<p class=\"has-background-alt-color has-text-color has-link-color wp-elements-262caf9d77c62b9c2674d852d42c6372\">But historically Ansible, and more specifically ansible-playbooks, haven&#8217;t been thought to be used as a standalone script, why?<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-84e14f6602a4eeb51877849f977405f5\">Because installing Ansible in itself is (was) complicated.<br>1.1. Because being written in python, Ansible suffers python&#8217;s known packaging issues.<br>1.2. Because, Ansible versioning has been historically complicated.<br>1.3. Because probably, neither the author of the playbook nor the final user are python experts to be able to debug the packaging issues.<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-d993714f80c0aa48405525ffb890c0ff\">Because Ansible has been used already in a repo with complex inventory structures, complex overrides, with roles that need to be installed. And despite that this is normal, the usual complexity might shy away the usability or applicability of standalone script.<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-72f37aa36899a3e64b8ad6bb18c3fb33\">Because scripting could be done with simpler and more powerful tools like Bash and Make. Make can give us idempotency, but not drift detection out of the box. Bash can&#8217;t give neither idempotency nor drift detection, you&#8217;ll need to code them yourself.<br>The same for python or other scripting language, but they don&#8217;t provide a high-level language. At least not high-enough to Bash, Make or Ansible.<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-68b7c3e9198ea954abeecacc45ce5d9c\">Ansible is too verbose. Probably 3 lines of Bash can do what 50 lines of ansible-playbook does. Yet, if we want idempotency, drift detection and readability we would need to write some extra code.<\/li>\n<\/ol>\n\n\n\n<p class=\"has-background-alt-color has-text-color has-link-color wp-elements-141afd558469cb439740d4b52e497e8c\">(Just to be clear, I&#8217;m not advocating replacing Bash or Make with Ansible, that would be insane. What I&#8217;m trying to say, is that for certain specific set of tasks, Ansible could be a better fit. What you&#8217;ll see later is that I actually use together Make, Bash and Ansible)<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The UV solution and example<\/h3>\n\n\n\n<p class=\"has-background-alt-color has-text-color has-link-color wp-elements-f19f05b49414ff866a3dcafe36e9ce7d\">Back to the point, <strong>The UV project, has solved one of Ansible&#8217;s major issues: the difficulty to install, use and share.<\/strong> You can read about UV here: https:\/\/docs.astral.sh\/uv\/<\/p>\n\n\n\n<p class=\"has-background-alt-color has-text-color has-link-color wp-elements-7ab5be4aa9b921b0bd5fff20c6fea7e1\">Look at the ansible-playbook below. What is important is the shebang line: it is an executable script. It is a &#8216;UV run tool&#8217; script (to learn more see https:\/\/docs.astral.sh\/uv\/concepts\/tools\/#relationship-to-uv-run), though in reality it&#8217;s an Ansible playbook.<br>The only dependency of this script is UV, no need to have python, pip or Ansible installed.<br>UV installs python and Ansible for us, in an invisible manner. And now, with some caveats, I can run this script almost anywhere, the script could be distributed. And no one needs to know that it even is an ansible playbook!<\/p>\n\n\n\n<p class=\"has-background-alt-color has-text-color has-link-color wp-elements-03c730d9823adcba089acf510ae1418a\"><strong>Below is a script that I carved out from a larger project. Don&#8217;t expect to run it as is, its purpose is to exemplify what can be done with Ansible and UV.<\/strong><\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-highlight-color:rgba(253, 253, 237, 0.2);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#282A36\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"54\" height=\"14\" viewBox=\"0 0 54 14\"><g fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(1 1)\"><circle cx=\"6\" cy=\"6\" r=\"6\" fill=\"#FF5F56\" stroke=\"#E0443E\" stroke-width=\".5\"><\/circle><circle cx=\"26\" cy=\"6\" r=\"6\" fill=\"#FFBD2E\" stroke=\"#DEA123\" stroke-width=\".5\"><\/circle><circle cx=\"46\" cy=\"6\" r=\"6\" fill=\"#27C93F\" stroke=\"#1AAB29\" stroke-width=\".5\"><\/circle><\/g><\/svg><\/span><span role=\"button\" tabindex=\"0\" style=\"color:#F8F8F2;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>#!\/usr\/bin\/env -S uv tool run --with \"ansible==12.0.0\" --from ansible-core@2.19.2 ansible-playbook -D -K -v\n#\n# Usage:\n# - Assuming this file is called 'install', and you had already chmod +x install\n# .\/install\n# .\/install -CD # For dry-run\n---\n- hosts: localhost\n  tasks:\n    - name: Update apt cache\n      become: true\n      ansible.builtin.apt:\n        update_cache: true\n        cache_valid_time: 21600\n    - name: Check if running in a Docker container\n      ansible.builtin.stat:\n        path: \/.dockerenv\n      register: in_docker\n    - name: Install OS dependencies\n      become: true\n      ansible.builtin.apt:\n        name:\n          - curl\n          - \"{{ 'libglib2.0-0t64' if (ansible_distribution == 'Ubuntu' and ansible_distribution_version is version('24.04', '>=') or ansible_distribution == 'Debian' and ansible_distribution_version is version('13', '>=')) else 'libglib2.0-0' }}\"\n        update_cache: false\n        state: present\n    - block:\n        - name: Install python dependencies\n          become: false\n          shell: |\n            uv venv --python 3.12.0 --relocatable\n            uv sync --frozen --no-install-package\n          args:\n            creates: .venv\n      rescue:\n        - name: Cleanup .venv\n          ansible.builtin.file:\n            path: .venv\n            state: absent\n        - ansible.builtin.fail:\n            msg: Error while creating the virtual environment\n    - name: Ensure MariaDB server is enabled and running\n      become: true\n      ansible.builtin.service:\n        name: mariadb\n        state: started\n        enabled: true\n      when:\n        - not in_docker.stat.exists\n        - not lookup('ansible.builtin.env', 'GITHUB_ACTIONS') == \"true\"\n    - name: Ensure MariaDB server is enabled and running (docker)\n      become: true\n      shell: |\n        nohup mysqld_safe --user=mysql --datadir=\/var\/lib\/mysql > \/var\/log\/mysqld.log 2>&amp;1 &amp;\n      when: in_docker.stat.exists or lookup('ansible.builtin.env', 'GITHUB_ACTIONS') == \"true\"\n    - name: Create my_project root user with privileges\n      become: true\n      community.mysql.mysql_user:\n        name: my_project_root_user\n        password: my_project_root_password\n        host: localhost\n        priv: '*.*:ALL,GRANT'\n        state: present\n        login_unix_socket: \/var\/run\/mysqld\/mysqld.sock\n      environment:\n        PYTHONPATH: \/usr\/lib\/python3\/dist-packages\/\n    - name: Render \/etc\/my_conf\/project.conf config file\n      become: true\n      ansible.builtin.copy:\n        dest: \/etc\/my_conf\/project.conf\n        content: |\n          (...)\n          &#91;api&#93;\n          path = {{ playbook_dir }}\/.venv\/lib\/python3.12\/site-packages\/modules\/\n          (...)\n    - name: Check if database already initialized\n      become: true\n      shell: |\n        mysql -e \"SHOW DATABASES\" | grep target_database_to_check\n      register: target_database_to_check_is_present\n      ignore_errors: true\n      changed_when: false\n      failed_when: false\n    - name: Long and destructive database initialization script\n      shell: |\n        uv run python3 install_databases.py\n      args:\n        executable: \/bin\/bash\n      when: target_database_to_check_is_present.rc != 0\n<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dracula\" style=\"background-color: #282A36\" tabindex=\"0\"><code><span class=\"line cbp-line-highlight\"><span style=\"color: #6272A4\">#!\/usr\/bin\/env -S uv tool run --with &quot;ansible==12.0.0&quot; --from ansible-core@2.19.2 ansible-playbook -D -K -v<\/span><\/span>\n<span class=\"line\"><span style=\"color: #6272A4\">#<\/span><\/span>\n<span class=\"line\"><span style=\"color: #6272A4\"># Usage:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #6272A4\"># - Assuming this file is called &#39;install&#39;, and you had already chmod +x install<\/span><\/span>\n<span class=\"line\"><span style=\"color: #6272A4\"># .\/install<\/span><\/span>\n<span class=\"line\"><span style=\"color: #6272A4\"># .\/install -CD # For dry-run<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">---<\/span><\/span>\n<span class=\"line\"><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">hosts<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">localhost<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">  <\/span><span style=\"color: #8BE9FD\">tasks<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">Update apt cache<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">become<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">ansible.builtin.apt<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">update_cache<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">cache_valid_time<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">21600<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">Check if running in a Docker container<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">ansible.builtin.stat<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">path<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">\/.dockerenv<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">register<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">in_docker<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">Install OS dependencies<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">become<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">ansible.builtin.apt<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">          <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">curl<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">          <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #E9F284\">&quot;<\/span><span style=\"color: #F1FA8C\">{{ &#39;libglib2.0-0t64&#39; if (ansible_distribution == &#39;Ubuntu&#39; and ansible_distribution_version is version(&#39;24.04&#39;, &#39;&gt;=&#39;) or ansible_distribution == &#39;Debian&#39; and ansible_distribution_version is version(&#39;13&#39;, &#39;&gt;=&#39;)) else &#39;libglib2.0-0&#39; }}<\/span><span style=\"color: #E9F284\">&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">update_cache<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">false<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">state<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">present<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">block<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">Install python dependencies<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">          <\/span><span style=\"color: #8BE9FD\">become<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">false<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">          <\/span><span style=\"color: #8BE9FD\">shell<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #FF79C6\">|<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F1FA8C\">            uv venv --python 3.12.0 --relocatable<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F1FA8C\">            uv sync --frozen --no-install-package<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">          <\/span><span style=\"color: #8BE9FD\">args<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">            <\/span><span style=\"color: #8BE9FD\">creates<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">.venv<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">rescue<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">Cleanup .venv<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">          <\/span><span style=\"color: #8BE9FD\">ansible.builtin.file<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">            <\/span><span style=\"color: #8BE9FD\">path<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">.venv<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">            <\/span><span style=\"color: #8BE9FD\">state<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">absent<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">ansible.builtin.fail<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">            <\/span><span style=\"color: #8BE9FD\">msg<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">Error while creating the virtual environment<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">Ensure MariaDB server is enabled and running<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">become<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">ansible.builtin.service<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">mariadb<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">state<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">started<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">enabled<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">when<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">not in_docker.stat.exists<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">not lookup(&#39;ansible.builtin.env&#39;, &#39;GITHUB_ACTIONS&#39;) == &quot;true&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">Ensure MariaDB server is enabled and running (docker)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">become<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">shell<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #FF79C6\">|<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F1FA8C\">        nohup mysqld_safe --user=mysql --datadir=\/var\/lib\/mysql &gt; \/var\/log\/mysqld.log 2&gt;&amp;1 &amp;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">when<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">in_docker.stat.exists or lookup(&#39;ansible.builtin.env&#39;, &#39;GITHUB_ACTIONS&#39;) == &quot;true&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">Create my_project root user with privileges<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">become<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">community.mysql.mysql_user<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">my_project_root_user<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">password<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">my_project_root_password<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">host<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">localhost<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">priv<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #E9F284\">&#39;<\/span><span style=\"color: #F1FA8C\">*.*:ALL,GRANT<\/span><span style=\"color: #E9F284\">&#39;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">state<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">present<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">login_unix_socket<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">\/var\/run\/mysqld\/mysqld.sock<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">environment<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">PYTHONPATH<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">\/usr\/lib\/python3\/dist-packages\/<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">Render \/etc\/my_conf\/project.conf config file<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">become<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">ansible.builtin.copy<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">dest<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">\/etc\/my_conf\/project.conf<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">content<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #FF79C6\">|<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F1FA8C\">          (...)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F1FA8C\">          &#91;api&#93;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F1FA8C\">          path = {{ playbook_dir }}\/.venv\/lib\/python3.12\/site-packages\/modules\/<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F1FA8C\">          (...)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">Check if database already initialized<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">become<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">shell<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #FF79C6\">|<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F1FA8C\">        mysql -e &quot;SHOW DATABASES&quot; | grep target_database_to_check<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">register<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">target_database_to_check_is_present<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">ignore_errors<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">true<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">changed_when<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">false<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">failed_when<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #BD93F9\">false<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">    <\/span><span style=\"color: #FF79C6\">-<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #8BE9FD\">name<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">Long and destructive database initialization script<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">shell<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #FF79C6\">|<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F1FA8C\">        uv run python3 install_databases.py<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">args<\/span><span style=\"color: #FF79C6\">:<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">        <\/span><span style=\"color: #8BE9FD\">executable<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">\/bin\/bash<\/span><\/span>\n<span class=\"line\"><span style=\"color: #F8F8F2\">      <\/span><span style=\"color: #8BE9FD\">when<\/span><span style=\"color: #FF79C6\">:<\/span><span style=\"color: #F8F8F2\"> <\/span><span style=\"color: #F1FA8C\">target_database_to_check_is_present.rc != 0<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">What we get for free<\/h3>\n\n\n\n<p class=\"has-background-alt-color has-text-color has-link-color wp-elements-0855cd933e72f2406c15594cbf2fdc80\">We get:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-ed5f3660abc9f00670d70ea044939506\">Nice looking interface in the terminal, with colors and the possibility of extra verbosity (see image 1).<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-745bfd51f5e7ade89e3585a08fc47380\">Idempotency: we can run it n times and we will get the same result. No fear of side effects.<br>In fact, if not necessary the tasks won&#8217;t be executed at all.<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-f27e21e11d501bd5de052c4004f54551\">Drift detection: we can run the script in dry-run mode giving the -CD flag.<br>And even if you don&#8217;t run it in dry-run the script will output the drift (see image 2).<\/li>\n\n\n\n<li>Possibility to run in &#8220;dry-run&#8221; mode.<\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"762\" height=\"826\" src=\"https:\/\/camilo.matajira.com\/wp-content\/uploads\/2025\/09\/Pasted-image-20250930215155.png\" alt=\"Image 1. Nice looking summary output from Ansible\" class=\"wp-image-618\" srcset=\"https:\/\/camilo.matajira.com\/wp-content\/uploads\/2025\/09\/Pasted-image-20250930215155.png 762w, https:\/\/camilo.matajira.com\/wp-content\/uploads\/2025\/09\/Pasted-image-20250930215155-277x300.png 277w\" sizes=\"auto, (max-width: 762px) 100vw, 762px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"712\" height=\"1024\" src=\"https:\/\/camilo.matajira.com\/wp-content\/uploads\/2025\/09\/Pasted-image-20250930215412-712x1024.png\" alt=\"\" class=\"wp-image-620\" srcset=\"https:\/\/camilo.matajira.com\/wp-content\/uploads\/2025\/09\/Pasted-image-20250930215412-712x1024.png 712w, https:\/\/camilo.matajira.com\/wp-content\/uploads\/2025\/09\/Pasted-image-20250930215412-209x300.png 209w, https:\/\/camilo.matajira.com\/wp-content\/uploads\/2025\/09\/Pasted-image-20250930215412-768x1105.png 768w, https:\/\/camilo.matajira.com\/wp-content\/uploads\/2025\/09\/Pasted-image-20250930215412.png 771w\" sizes=\"auto, (max-width: 712px) 100vw, 712px\" \/><\/figure>\n\n\n\n<p class=\"has-background-alt-color has-text-color has-link-color wp-elements-bd3afba9f3bb0956918e16803fc75469\">Looking specifically to the playbook, we get the following for free:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-c8c34c80b0f5d6dff2b63a11ab6303ba\">Update cache only if necessary! Makes subsequent runs faster.<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-87cc775d6888c44d61d81da1f0575ed6\">Easy differentiation between which tasks needs to run as root and which doesn&#8217;t. With &#8220;become&#8221; keyword. And single sudo password prompt at the beginning.<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-d2e5b46befbdb1efd875afeaa4bebf05\">Easy differentiate between running in a container or a runner. Checking for \/.dockerenv or GITHUB_ACTIONS env var. (This is not ansible specific).<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-e53087291bff598415a96c6e3db0bf5b\">Easy differentiation between OS and distribution. Easy to distinguish between an Ubuntu machine of certain version, from Debian, or from other distros.<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-929eff44620962913c83588ae4f0bfa6\">Install python dependencies only if .venv does not exist already. (This is easier in Make, and easy to do in Bash).<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-5cb241cf9168e05adbb7b4b6fd75ec67\">Delete .venv in case the complete operation did not succeed. (Easier in Make, more code in Bash)<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-36bd6aa014f9500c148719107290f3fc\">Ensure DB (and other services) are running, even without systemd.<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-2adc113cf9bab366f766849f348c7b4d\">Easy provisioning of database users, with determined permissions. (This involves code in Make or Bash).<\/li>\n\n\n\n<li class=\"has-background-alt-color has-text-color has-link-color wp-elements-5dbd796f26176a1b2873041b032278c9\">Easy rendering of configuration files, and check drift. (Harder in Make and Bash)<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Conclusion<\/h3>\n\n\n\n<p class=\"has-background-alt-color has-text-color has-link-color wp-elements-85c2f339f0dae0ca2ea7d8a92ff46242\">As a conclusion, UV has given a new wind to Ansible. Ansible is easier to install than ever, and this opens the possibilities to use its powers in new and unusual ways.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Ansible is still the most powerful Configuration Management tool in 2025. It&#8217;s used mainly to configure and manage bare-metal servers and virtual machines.But can also be used to for network automation, cloud resource provisioning, CICD pipelines, etc. Ansible&#8217;s popularity (and configuration management tool&#8217;s in general) has decreased in recent years, probably due to a&#8230;<\/p>\n","protected":false},"author":1,"featured_media":558,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-591","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-projects"],"_links":{"self":[{"href":"https:\/\/camilo.matajira.com\/index.php?rest_route=\/wp\/v2\/posts\/591","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/camilo.matajira.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/camilo.matajira.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/camilo.matajira.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/camilo.matajira.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=591"}],"version-history":[{"count":12,"href":"https:\/\/camilo.matajira.com\/index.php?rest_route=\/wp\/v2\/posts\/591\/revisions"}],"predecessor-version":[{"id":628,"href":"https:\/\/camilo.matajira.com\/index.php?rest_route=\/wp\/v2\/posts\/591\/revisions\/628"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/camilo.matajira.com\/index.php?rest_route=\/wp\/v2\/media\/558"}],"wp:attachment":[{"href":"https:\/\/camilo.matajira.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=591"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/camilo.matajira.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=591"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/camilo.matajira.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=591"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}