Skip to content

Install software

OS agnostic:

- name: Install ntpdate
  become: true
  ansible.builtin.package:
    name: ntpdate
    state: present
    use: dnf # optional

Using dnf:

- name: Install EPEL repo
  become: true
  ansible.builtin.dnf:
    name: epel-release
    state: present
    update_cache: true

Install latest github release

- name: Add User and Group
  hosts: localhost
  vars:
    helm_wanted_version: '3.12.3'
    helm_architecture: 'amd64'
    helm_mirror: 'https://get.helm.sh'
    helm_install_dir: '/usr/local/bin/'
    helm_download_dir: "{{ x_ansible_download_dir | default(ansible_env.HOME + '/.ansible/tmp/downloads') }}"
    helm_os: 'linux'
  tasks:
    - name: Check current installed version
      ansible.builtin.command: >-
        {{ helm_install_dir }}/helm version --client --template
        {{ "'{{ if .Version }}{{ .Version }}{{ else }}{{ .Client.SemVer }}{{ end }}'" }}
      register: helm_current_version
      failed_when: false
      changed_when: false

    - name: Register current version
      set_fact:
        helm_current_version: "{{ helm_current_version.stdout }}" 

    # - name: debug helm_current_version
    #   debug:
    #     msg: "{{ helm_current_version }}"

    - name: Set the version to be installed to the defined version
      set_fact:
        helm_install_version: "{{ helm_wanted_version }}"
      when:
        - helm_wanted_version is defined
        - helm_wanted_version != helm_current_version

    - name: Compare latest version with current installed version if no wanted version exists
      when: helm_wanted_version is not defined
      block:
        - name: Check latest release
          uri:
            url: https://api.github.com/repos/helm/helm/tags
            method: GET
            return_content: yes
            status_code: 200
            body_format: json
          register: result

        - name: Register latest release
          set_fact:
            helm_latest_version: "{{ result.json[0].name | regex_replace('^v', '') }}" 

        # - name: debug helm_latest_version
        #   debug:
        #     msg: "{{ helm_latest_version }}"

        - name: 
          set_fact:
            helm_install_version: "{{ helm_latest_version }}"
          when:
            - helm_latest_version != helm_current_version

    # - name: Debug decision
    #   debug:
    #     msg: "Will install Helm {{ helm_install_version }}"
    #   when: helm_install_version is defined

    - name: Install helm
      when: helm_install_version is defined
      block:
        - name: create download directory
          ansible.builtin.file:
            state: directory
            mode: 'u=rwx,go=rx'
            dest: '{{ helm_download_dir }}'

        - name: Set filename variable
          set_fact:
            helm_redis_filename: 'helm-v{{ helm_install_version }}-{{ helm_os }}-{{ helm_architecture }}.tar.gz'

        - name: download sha256sum
          ansible.builtin.get_url:
            url: '{{ helm_mirror }}/{{ helm_redis_filename }}.sha256'
            dest: '{{ helm_download_dir }}/{{ helm_redis_filename }}.sha256'
            force: false
            use_proxy: true
            validate_certs: true
            mode: 'u=rw,go=r'

        - name: read sha256sum
          ansible.builtin.slurp:
            src: '{{ helm_download_dir }}/{{ helm_redis_filename }}.sha256'
          register: helm_sha256sum

        - name: download Helm
          ansible.builtin.get_url:
            url: '{{ helm_mirror }}/{{ helm_redis_filename }}'
            dest: '{{ helm_download_dir }}/{{ helm_redis_filename }}'
            checksum: 'sha256:{{ helm_sha256sum.content | b64decode | trim }}'
            force: false
            use_proxy: true
            validate_certs: true
            mode: 'u=rw,go=r'

        - name: remove existing installation
          become: true
          ansible.builtin.file:
            path: '{{ helm_install_dir }}/helm'
            state: absent
          when:
            - helm_current_version is defined 
            - helm_current_version | length > 0

        - name: install unarchive module dependencies (apt, yum, dnf, zypper)
          become: true
          ansible.builtin.package:
            name:
              - tar
              - unzip
              - gzip
            state: present
          when: ansible_pkg_mgr in ('apt', 'yum', 'dnf', 'zypper')

        - name: Extract Helm
          become: true
          ansible.builtin.unarchive:
            src: '{{ helm_download_dir }}/{{ helm_redis_filename }}'
            dest: '{{ helm_download_dir }}'
            remote_src: true
            extra_opts:
              - '--strip-components=1'
            creates: '{{ helm_download_dir }}/helm'

        - name: Install Helm
          become: true
          ansible.builtin.copy:
            src: '{{ helm_download_dir }}/helm'
            dest: '{{ helm_install_dir }}'
            remote_src: true
            owner: root
            group: root
            mode: 'u=rwx,go=rx'

        - name: Clean after installation
          become: true
          ansible.builtin.file:
            path: "{{ item }}"
            state: absent
          loop:
            - '{{ helm_download_dir }}/{{ helm_redis_filename }}'
            - '{{ helm_download_dir }}/helm'
            - '{{ helm_download_dir }}/LICENSE'
            - '{{ helm_download_dir }}/README.md'

Files and folders

Create a directory

- name: Create dnf-automatic.timer.d directory for the override
  become: true
  ansible.builtin.file:
    path: /etc/systemd/system/dnf-automatic.timer.d/
    state: directory
    owner: root
    group: root
    mode: 0755

Upload a file

ansible.builtin.copy

- name: Upload sysctl config
  become: true
  ansible.builtin.copy:
    src: 99-sysctl.conf
    dest: "/etc/sysctl.d/"
    owner: root
    group: root
    mode: 0600

Upload a folder

- name: Upload auditd files
  become: true
  ansible.builtin.copy:
    src: "./files/auditd/rules.d"
    dest: "/etc/audit/"
    owner: root
    group: root
    mode: 0700
  notify: Reload Auditd Rules

Create a file

- name: Set the time to trigger updates
  become: true
  ansible.builtin.copy:
    content: |
      [Timer]
      OnCalendar=*-*-* 01:30
    dest: '/etc/systemd/system/dnf-automatic.timer.d/override.conf'
    owner: root
    group: root
    mode: 0644
  notify:
    - Reload systemd ## Call a handler

Create folder/directory

- name: Create directory
  ansible.builtin.file:
    state: directory
    dest: '/path/to/directory'
    mode: 'u=rwx,go=rx'

Delete a folder

- name: Recursively remove directory
  ansible.builtin.file:
    path: /etc/foo
    state: absent

Delete a file

- name: Remove file (delete file)
  ansible.builtin.file:
    path: /etc/foo.txt
    state: absent

Include tasks

- name: Include do-something task
  ansible.builtin.include_tasks:
    file: do-something.yml
    apply:
      become: true
      become_user: "RunAsThisUserName"

Handlers

Flush Handlers

handlers run after all the tasks in a particular play have been completed. You can force them to run immediately with:

- meta: flush_handlers

Templates

Templates

- name: Update sshd configuration safely, avoid locking yourself out
  ansible.builtin.template:
    src: etc/ssh/sshd_config.j2
    dest: /etc/ssh/sshd_config
    owner: root
    group: root
    mode: 0600
    validate: /usr/sbin/sshd -t -f %s
    backup: yes

Conditionals

With a variable called rol and a possible value of server or client, you can do the following. Source

{# style 1 - long form #}
{% if filepath == '/var/opt/tomcat_1' %}
  {% set tomcat_value = tomcat_1_value %}
{% else %}
  {% set tomcat_value = tomcat_2_value %}
{% endif %}

{# style 2 - short form #}
{% set tomcat_value = tomcat_1_value if (filepath == '/var/opt/tomcat_1') else tomcat_2_value %}

{# style 3 - with ternary filter #}
{% set tomcat_value = (filepath == '/var/opt/tomcat_1')|ternary(tomcat_1_value, tomcat_2_value) %}

<Server port={{ tomcat_value }} shutdown="SHUTDOWN">

systemd

Configure services

Restart service

- name: Restart service crond
  become: true
  ansible.builtin.systemd_service:
    state: restarted
    daemon_reload: true
    name: crond

- name: Enable service crond
  become: true
  ansible.builtin.systemd_service:
    enabled: true
    daemon_reload: true
    name: crond

daemon reload

- name: Reload systemd
  become: true
  ansible.builtin.systemd_service:
    daemon_reload: true

- name: Reload systemd for your user
  ansible.builtin.systemd_service:
    daemon_reload: yes
    scope: user

- name: Reload systemd for another user
  become: true
  become_user: "username"
  ansible.builtin.systemd_service:
    daemon_reload: true
    scope: "user"

loops

loop

Loops are prefered over with_<lookup>.

- name: Add several users
  ansible.builtin.user:
    name: "{{ item }}"
    state: present
    groups: "wheel"
  loop:
     - testuser1
     - testuser2
  loop_control:
    pause: 3
- name: Add several users
  ansible.builtin.user:
    name: "{{ users }}"
    state: present
    groups: "wheel"
  loop:
     - testuser1
     - testuser2
  loop_control:
    loop_var: users
    pause: 3

with_

Multiple examples in the Ansible loops documentation

Can be used with the following lookups

Example to create users ^2

- name: Creating users with_items
  hosts: localhost
  tasks:
    - name: Create user
      user:
        name: "{{ item.name }}"
        uid: "{{ item.uid }}"
        state: present
      with_items:
        - { name: joe, uid: 1010 }
        - { name: george, uid: 1011 }
        - { name: ravi, uid: 1012 }

loop over dictionaries

Using the lookup filter ^1

- name: Validate Services
  hosts: myhosts
  vars:
    services:
      nginx:
        state: started
        enabled: yes
      httpd:
        state: stopped
        enabled: no
      mysqld:
        state: started
  tasks:
    - name: Ensure services are correct
      service:
        name: "{{ item.key }}"
        state: "{{ item.value.state }}"
        enabled: "{{ item.value.enabled | default('yes') }}"
      loop: "{{ lookup('dict', services) }}"

Using the dict2items filter ^1

- name: Create user accounts from a dictionary
  hosts: myhosts
  vars:
    users_dict:
      mjordan:
        uid: 1001
        groups: "dev"
      mmathers:
        uid: 1002
        groups: "prod"

  tasks:
    - name: Create user accounts
      user:
        name: "{{ item.key }}"
        uid: "{{ item.value.uid }}"
        groups: "{{ item.value.groups }}"
      loop: "{{ users_dict | dict2items }}"

Control

Error handling

Error handling in Ansible's Doc

Ignore errors

- name: Do not count this as a failure
  ansible.builtin.command: /bin/false
  ignore_errors: true

Blocks

- name: Install, configure, and start Apache
  when: ansible_facts['distribution'] == 'CentOS'
  block:
    - name: Install httpd and memcached
      ansible.builtin.yum:
        name:
        - httpd
        - memcached
        state: present

    - name: Apply the foo config template
      ansible.builtin.template:
        src: templates/src.j2
        dest: /etc/foo.conf

    - name: Start service bar and enable it
      ansible.builtin.service:
        name: bar
        state: started
        enabled: True
  become: true
  become_user: root
  ignore_errors: true

Containers

Podman

vars/main.yml

unpriv_user_name: noone
podman_data_path: "/path/to/containers/data"
podman_quadlet_path: "{{ unpriv_user_home }}/.config/containers/systemd"
podman_environment_path: "{{ unpriv_user_home }}/.config/containers/environment"

podman_cont_path: "{{ podman_data_path }}/cont"
podman_cont_path_config: "{{ podman_cont_path }}/config"
podman_cont_path_data: "{{ podman_cont_path }}/data"
podman_cont_path_quadlet: "{{ podman_quadlet_path }}/cont"

handlers/main.yml

- name: Reload Systemd User
  become: true
  become_user: "{{ unpriv_user_name }}"
  ansible.builtin.systemd:
    daemon_reload: true
    scope: user

- name: Restart Containers
  become: true
  become_user: "{{ unpriv_user_name }}"
  ansible.builtin.systemd_service:
    state: restarted
    daemon_reload: true
    scope: user
    name: "{{ item }}"
  loop:
    - "cont"

- name: Restart Cont
  become: true
  become_user: "{{ unpriv_user_name }}"
  ansible.builtin.systemd_service:
    state: restarted
    daemon_reload: true
    scope: user
    name: cont

Playbook

- name: Create Cont Paths
  ansible.builtin.file:
    path: "{{ container_paths }}"
    state: directory
    owner: "{{ unpriv_user_name }}"
    group: "{{ unpriv_user_name }}"
    mode: 0770
  loop:
    - "{{ podman_cont_path }}" ## The main path
    - "{{ podman_cont_path_config }}" ## Path to store the configuration
    - "{{ podman_cont_path_quadlet }}" ## Path to store the quadlets
  loop_control:
    loop_var: container_paths


- name: Update Cont Configuration Files
  ansible.builtin.copy:
    src: "{{ configuration_file_src }}"
    dest: "{{ podman_cont_path_config }}/"
    owner: "{{ unpriv_user_name }}"
    group: "{{ unpriv_user_name }}"
    mode: 0770
  loop:
    - "./files/podman/cont/file"
  loop_control:
    loop_var: configuration_file_src
  notify:
    - Restart Cont


- name: Upload Cont Quadlets
  ansible.builtin.copy:
    src: "{{ quadlet_file_src }}"
    dest: "{{ podman_quadlet_path }}/"
    owner: "{{ unpriv_user_name }}"
    group: "{{ unpriv_user_name }}"
    mode: 0400
  loop:
    - "./files/podman/cont/cont.network"
    - "./files/podman/cont/cont.volume"
    - "./files/podman/cont/cont-certs.volume"
    # - "./files/podman/cont/cont.container"
  loop_control:
    loop_var: quadlet_file_src
  notify:
    - Reload Systemd User
    - Restart Cont


- name: Upload Cont Quadlets Template
  ansible.builtin.template:
    src: "./templates/podman/cont/cont.container.j2"
    dest: "{{ podman_cont_path_quadlet }}/cont.container"
    owner: "{{ unpriv_user_name }}"
    group: "{{ unpriv_user_name }}"
    mode: 0400
    force: yes
  notify:
    - Reload Systemd User
    - Restart Cont

templates/cont.container.j2


files/cont/cont.volume

[Unit]
Description=Cont Container Volume

[Volume]
VolumeName=cont-data
Label=app=cont

files/cont/cont.network

[Unit]
Description=Cont Containers Network

[Network]
Label=app=cont
NetworkName=cont

[Install]
WantedBy=multi-user.target default.target


Sources