From dfb906028367cc3d282994bb9a25fc3940bccbd1 Mon Sep 17 00:00:00 2001 From: Allar Viik Date: Fri, 24 Jul 2026 18:11:41 +0300 Subject: [PATCH 1/7] Added `coredns` role --- nova/core/galaxy.yml | 2 +- nova/core/roles/caddy/README.md | 2 +- nova/core/roles/coredns/README.md | 102 +++++++++++++++ nova/core/roles/coredns/defaults/main.yml | 26 ++++ nova/core/roles/coredns/tasks/main.yml | 120 ++++++++++++++++++ nova/core/roles/coredns/templates/corefile | 28 ++++ .../coredns/templates/docker-compose.yml | 38 ++++++ .../coredns/templates/forwarder_template.conf | 6 + .../roles/coredns/templates/zonefile_template | 35 +++++ 9 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 nova/core/roles/coredns/README.md create mode 100644 nova/core/roles/coredns/defaults/main.yml create mode 100644 nova/core/roles/coredns/tasks/main.yml create mode 100644 nova/core/roles/coredns/templates/corefile create mode 100644 nova/core/roles/coredns/templates/docker-compose.yml create mode 100644 nova/core/roles/coredns/templates/forwarder_template.conf create mode 100644 nova/core/roles/coredns/templates/zonefile_template diff --git a/nova/core/galaxy.yml b/nova/core/galaxy.yml index ec0b43c2b..9d18978e9 100644 --- a/nova/core/galaxy.yml +++ b/nova/core/galaxy.yml @@ -1,6 +1,6 @@ namespace: nova name: core -version: 11.3.0 +version: 11.5.0 readme: README.md authors: - https://github.com/novateams diff --git a/nova/core/roles/caddy/README.md b/nova/core/roles/caddy/README.md index b03b7a5e8..191f68873 100644 --- a/nova/core/roles/caddy/README.md +++ b/nova/core/roles/caddy/README.md @@ -12,7 +12,7 @@ Refer to the [defaults/main.yml](https://github.com/novateams/nova.core/blob/mai ## Dependencies -- Depends on Docker and Docker Compose being installed on the host. Docker can be installed using the [nova.core.docker](https://github.com/novateams/nova.core/tree/main/nova/core/roles/caddy) role. +- Depends on Docker and Docker Compose being installed on the host. Docker can be installed using the [nova.core.docker](https://github.com/novateams/nova.core/tree/main/nova/core/roles/docker) role. ## Example diff --git a/nova/core/roles/coredns/README.md b/nova/core/roles/coredns/README.md new file mode 100644 index 000000000..dd095a3bb --- /dev/null +++ b/nova/core/roles/coredns/README.md @@ -0,0 +1,102 @@ +# coredns + +This role installs and configures CoreDNS on Debian-based systems and allows generating zonefiles and configuring forwarders for CoreDNS + +## Requirements + +none + +## Role Variables + +Refer to the [defaults/main.yml](https://github.com/novateams/nova.core/blob/main/nova/core/roles/coredns/defaults/main.yml) file for a list and description of the variables used in this role. + +## Dependencies + +- Depends on Docker and Docker Compose being installed on the host. Docker can be installed using the [nova.core.docker](https://github.com/novateams/nova.core/tree/main/nova/core/roles/docker) role. + +## Example + +```yml +# Installing CoreDNS without any custom zonefiles or forwarders +# By default it'll use DoT (DNS over TLS) to forward all requests to Cloudflare or Google if Cloudflare is not reachable. +- name: Including CoreDNS role... + ansible.builtin.include_role: + name: nova.core.coredns +``` + +```yml +# Installing CoreDNS and generating custom zonefiles based on the provided coredns_records variable. +# 2 zonefiles will be generated, one for example.com and one for example.org with the provided records. +- name: Including CoreDNS role... + ansible.builtin.include_role: + name: nova.core.coredns + vars: + coredns_records: + - domain: example.com + records: + - type: A + name: www + value: 10.0.0.1 + - type: AAAA + name: www + value: 2001:db8::1 + - type: CNAME + name: mail + value: mail.example.com + - domain: example.org + records: + - type: A + name: www + value: 10.0.0.2 +``` + +```yml +# Installing CoreDNS and generating custom zonefiles based on the provided coredns_records variable. +# 2 zonefiles will be generated, one for example.com and one for example.org with the provided records. +# Additionally setting custom nameservers for example.com zone when nameservers is not defined, CoreDNS will itself act as the nameserver for the zone. +- name: Including CoreDNS role... + ansible.builtin.include_role: + name: nova.core.coredns + vars: + coredns_records: + - domain: example.com + # OPTIONAL + nameservers: + - name: ns1 + address: 10.10.10.1 + - name: ns1 + address: 2001:db8::1 + - name: ns2 + address: 10.10.10.2 + records: + - type: A + name: www + value: 10.0.0.1 + - type: AAAA + name: www + value: 2001:db8::1 + - type: CNAME + name: mail + value: mail.example.com + - domain: example.org + records: + - type: A + name: www + value: 10.0.0.2 +``` + +```yml +# Installing CoreDNS without any zonefiles but with custom forwarders defined in the coredns_forwarders variable. +# CoreDNS will now forward requests for example.com and example.org to the specified DNS servers. +- name: Including CoreDNS role... + ansible.builtin.include_role: + name: nova.core.coredns + vars: + coredns_forwarders: + - domains: + - example.com + - example.org + addresses: + - 10.10.10.1 + - 2001:db8::1 +``` diff --git a/nova/core/roles/coredns/defaults/main.yml b/nova/core/roles/coredns/defaults/main.yml new file mode 100644 index 000000000..38d4941b4 --- /dev/null +++ b/nova/core/roles/coredns/defaults/main.yml @@ -0,0 +1,26 @@ +--- +coredns_version: coredns/coredns:1.14.6 +coredns_config_folder: /srv/coredns +coredns_docker_network: local-network # This is the default for nova.core.docker role + +# List of DNS server that will go to resolve.conf file, in order of preference. +# By default, the first entry is the IP address of CoreDNS itself and the rest are public DNS servers. +# This is to avoid race conditions when setting up CoreDNS for the first time and disabling systemd-resolved service causing DNS resolution to fail. +coredns_upstream_dns_servers: + - "{{ connection_address }}" + - 1.1.1.1 + - 8.8.8.8 + +# The default CoreDNS configuration file template to use. Default comes with this role. +# This can be overridden by providing a different path for the template file. +coredns_corefile: corefile + +# The list of DNS records to be created in CoreDNS. +# Refer to this roles README for the format of the records. +# By leaving this empty, no records will be created, +# this is useful if you want to generate the zonefiles outside of this role and just want to use this role to setup CoreDNS. +coredns_records: [] + +# The list of DNS forwarders to be used in CoreDNS. These will forward matching domains to the specified DNS servers. +# Refer to this roles README for the format of the forwarders. +coredns_forwarders: [] diff --git a/nova/core/roles/coredns/tasks/main.yml b/nova/core/roles/coredns/tasks/main.yml new file mode 100644 index 000000000..fa428b178 --- /dev/null +++ b/nova/core/roles/coredns/tasks/main.yml @@ -0,0 +1,120 @@ +--- +# Needed for verifying zonefiles with named-checkzone command +- name: Installing bind9 utilities + ansible.builtin.package: + name: bind9-utils + state: present + +- name: Check if systemd-resolved service exists + ansible.builtin.systemd: + name: systemd-resolved + register: resolved_service + +- name: Stopping & disabling systemd-resolved if it exists... + when: resolved_service.status.LoadState != "not-found" + block: + - name: Stopping & disabling systemd-resolved... + ansible.builtin.systemd: + name: systemd-resolved + state: stopped + enabled: false + masked: true + + - name: Configuring custom resolv.conf... + ansible.builtin.copy: + content: | + {% for ns in coredns_upstream_dns_servers %} + nameserver {{ ns }} + {% endfor %} + dest: /etc/resolv.conf + mode: "0644" + +- name: Creating CoreDNS configuration folder... + ansible.builtin.file: + path: "{{ item }}" + state: directory + recurse: true + loop: + - "{{ coredns_config_folder }}" + - "{{ coredns_config_folder }}/zones" + - "{{ coredns_config_folder }}/conf.d" + +- name: Templating CoreDNS configuration files... + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + mode: "0644" + register: coredns_template_results + loop: + - src: corefile + dest: "{{ coredns_config_folder }}/Corefile" + + - src: docker-compose.yml + dest: "{{ coredns_config_folder }}/docker-compose.yml" + +- name: Getting the time... + ansible.builtin.setup: + filter: "*_time" + +- name: Templating zonefile(s) for following domain(s)... + ansible.builtin.template: + src: zonefile_template + dest: "{{ coredns_config_folder }}/zones/db.{{ zone.domain }}" + mode: "0644" + lstrip_blocks: true + when: coredns_records != [] + loop: "{{ coredns_records }}" + loop_control: + loop_var: zone + label: "{{ zone.domain }}" + +- name: Verifying zonefile(s) for following domain(s)... + ansible.builtin.command: named-checkzone {{ zone.domain }} {{ coredns_config_folder }}/zones/db.{{ zone.domain }} + register: checkzone_results + changed_when: false + loop: "{{ coredns_records }}" + loop_control: + loop_var: zone + label: "{{ zone.domain }}" + +- name: Templating following forwarders... + ansible.builtin.template: + src: forwarder_template.conf + dest: "{{ coredns_config_folder }}/conf.d/{{ forwarder.domains | join('_') }}.conf" + mode: "0644" + lstrip_blocks: true + when: coredns_forwarders != [] + loop: "{{ coredns_forwarders }}" + loop_control: + loop_var: forwarder + label: "{{ forwarder.domains | join(', ') }}" + +- name: Adding conf file placeholders... + when: coredns_forwarders == [] # Placeholder is not needed if there's at least one forwarder + block: + - name: Checking if any conf.d files exist... + ansible.builtin.find: + paths: "{{ coredns_config_folder }}/conf.d" + patterns: "*.conf" + register: existing_conf_files + + - name: Deploying an empty placeholder conf.d file... + ansible.builtin.copy: + dest: "{{ coredns_config_folder }}/conf.d/00-placeholder.conf" + content: | + # Empty placeholder file to avoid warnings in CoreDNS when no other conf.d files exist. + mode: "0644" + when: existing_conf_files.matched == 0 + + - name: Remove empty placeholder conf.d file... + ansible.builtin.file: + path: "{{ coredns_config_folder }}/conf.d/00-placeholder.conf" + state: absent + when: existing_conf_files.matched > 1 + +- name: Composing CoreDNS container... + community.docker.docker_compose_v2: + project_src: "{{ coredns_config_folder }}" + state: present + recreate: "{{ 'always' if coredns_template_results.changed else 'auto' }}" + wait: true diff --git a/nova/core/roles/coredns/templates/corefile b/nova/core/roles/coredns/templates/corefile new file mode 100644 index 000000000..b381cbeee --- /dev/null +++ b/nova/core/roles/coredns/templates/corefile @@ -0,0 +1,28 @@ +. { + reload 30s + + auto { + directory /zones + } + + forward . tls://1.1.1.1 tls://1.0.0.1 dns://127.0.0.1:5301 { + tls_servername cloudflare-dns.com + policy sequential + health_check 30s + } + log + errors +} + +# Fallback to Google DNS if Cloudflare is down +.:5301 { + bind 127.0.0.1 + forward . tls://8.8.8.8 tls://8.8.4.4 { + tls_servername dns.google + health_check 30s + failfast_all_unhealthy_upstreams + } + errors +} + +import /etc/coredns/conf.d/*.conf diff --git a/nova/core/roles/coredns/templates/docker-compose.yml b/nova/core/roles/coredns/templates/docker-compose.yml new file mode 100644 index 000000000..f8a5028ed --- /dev/null +++ b/nova/core/roles/coredns/templates/docker-compose.yml @@ -0,0 +1,38 @@ +--- +services: + coredns: + image: "{{ coredns_version }}" + container_name: coredns + restart: unless-stopped + volumes: + - "{{ coredns_config_folder }}/Corefile:/Corefile" + - coredns_zones:/zones + - coredns_extra_config:/etc/coredns/conf.d + ports: + - "53:53" + - "53:53/udp" + logging: + driver: json-file + options: + max-size: 250m + max-file: "1" + +volumes: + coredns_zones: + driver: local + driver_opts: + type: none + o: bind + device: "{{ coredns_config_folder }}/zones" + + coredns_extra_config: + driver: local + driver_opts: + type: none + o: bind + device: "{{ coredns_config_folder }}/conf.d" + +networks: + default: + name: "{{ coredns_docker_network }}" + external: true diff --git a/nova/core/roles/coredns/templates/forwarder_template.conf b/nova/core/roles/coredns/templates/forwarder_template.conf new file mode 100644 index 000000000..10a8f9635 --- /dev/null +++ b/nova/core/roles/coredns/templates/forwarder_template.conf @@ -0,0 +1,6 @@ +{{ forwarder.domains | join(" ") }} { + forward . {{ forwarder.addresses | join(" ") }} + cache 60 + log + errors +} diff --git a/nova/core/roles/coredns/templates/zonefile_template b/nova/core/roles/coredns/templates/zonefile_template new file mode 100644 index 000000000..e9c07e500 --- /dev/null +++ b/nova/core/roles/coredns/templates/zonefile_template @@ -0,0 +1,35 @@ +$TTL 60 + +@ IN SOA ns.{{ zone.domain }}. hostmaster.{{ zone.domain }}. ( + {{ ansible_facts.date_time.epoch_int }} ; serial + 7200 ; refresh + 3600 ; retry + 1209600 ; expire + 0 ) ; negative-cache TTL + + {% if zone.nameservers is defined %} + {% for ns in zone.nameservers | map(attribute='name') | unique %} + IN NS {{ ns }}.{{ zone.domain }}. + {% endfor %} + {% else %} + IN NS ns.{{ zone.domain }}. + {% endif %} + +; Nameservers +{% if zone.nameservers is defined %} +{% for ns in zone.nameservers %} +{{ "%-24s IN %-6s %s" | format(ns.name, 'A' if ns.address is ansible.utils.ipv4 else 'AAAA', ns.address) }} +{% endfor %} +{% else %} +{{ "%-24s IN %-6s %s" | format('ns', 'A' if connection_address is ansible.utils.ipv4 else 'AAAA', connection_address) }} +{% endif %} + +; From coredns_records variable +{% for record in zone.records %} +{% set record_name = '@' if record.name == zone.domain else record.name %} +{% set record_value = '"' + (record.value | regex_replace("[\"']", '')) + '"' if record.type == 'TXT' else record.value %} +{% if record.type == 'CNAME' %} +{% set record_value = record.value if record.value.endswith('.') else record.value + '.' %} +{% endif %} +{{ "%-24s IN %-6s %s" | format(record_name, record.type, record_value) }} +{% endfor %} \ No newline at end of file From 240124ef908e9557b60c795d490e6ff5e04b049f Mon Sep 17 00:00:00 2001 From: Allar Viik Date: Tue, 28 Jul 2026 14:09:35 +0300 Subject: [PATCH 2/7] In `updates` role for Kali OS removing unused kernels from /boot to keep it from filling up --- .../roles/updates/tasks/debian_family.yml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/nova/core/roles/updates/tasks/debian_family.yml b/nova/core/roles/updates/tasks/debian_family.yml index d98133313..452d6f851 100644 --- a/nova/core/roles/updates/tasks/debian_family.yml +++ b/nova/core/roles/updates/tasks/debian_family.yml @@ -1,4 +1,27 @@ --- +# Kali kernels update very often and boot partition can get filled up with old kernels +# Removing currently unused kernels from /boot before updating packages to avoid running out of space +- name: Removing currently unused kernels from /boot... + ansible.builtin.shell: | + set -eou pipefail + + # Get the current boot files + VMLINUZ=/boot/vmlinuz-$(uname -r) + INITRD=/boot/initrd.img-$(uname -r) + CONFIG=/boot/config-$(uname -r) + SYSMAP=/boot/System.map-$(uname -r) + + # Deleting all other vmlinuz and initrd files except the current one + find /boot -maxdepth 1 -type f \( -name 'vmlinuz-*' -o -name 'initrd.img-*' -o -name 'config-*' -o -name 'System.map-*' \) \ + ! -path "$VMLINUZ" ! -path "$INITRD" ! -path "$CONFIG" ! -path "$SYSMAP" -print -exec rm -f {} \; + + apt --fix-broken install -y + vars: + exec: /bin/bash + register: remove_unused_kernels + changed_when: remove_unused_kernels.stdout is search("vmlinuz") + when: ansible_facts.distribution | default("") == "Kali" + - name: Updating packages... ansible.builtin.apt: update_cache: true From 79af5ac816c52137a37d17557404ff56d0ee3f16 Mon Sep 17 00:00:00 2001 From: Allar Viik Date: Fri, 31 Jul 2026 09:11:14 +0300 Subject: [PATCH 3/7] Set `gitlab` version to 19.2.1 --- nova/core/roles/gitlab/defaults/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/core/roles/gitlab/defaults/main.yml b/nova/core/roles/gitlab/defaults/main.yml index 9e43f5be6..b76ab930e 100644 --- a/nova/core/roles/gitlab/defaults/main.yml +++ b/nova/core/roles/gitlab/defaults/main.yml @@ -1,6 +1,6 @@ --- ### gitlab general -gitlab_version: 19.2.0-ee.0 +gitlab_version: 19.2.1-ee.0 gitlab_ssh_port: 10022 # Increase gitlab ssh MaxAuthTries to avoid connection issues for users with more than 6 keys in their SSH agent set to {} to disable gitlab_ssh_max_auth_tries: 20 From 99cfddb3a34edea5096d1a94f986e874bb94476e Mon Sep 17 00:00:00 2001 From: Allar Viik Date: Sat, 1 Aug 2026 16:11:08 +0300 Subject: [PATCH 4/7] Retrying failed Proxmox VM cloning up to 3 times --- .../machine_operations/tasks/proxmox/create.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/nova/core/roles/machine_operations/tasks/proxmox/create.yml b/nova/core/roles/machine_operations/tasks/proxmox/create.yml index f016ec367..cf654eab4 100644 --- a/nova/core/roles/machine_operations/tasks/proxmox/create.yml +++ b/nova/core/roles/machine_operations/tasks/proxmox/create.yml @@ -17,7 +17,9 @@ when: fresh_deploy block: - name: Checking for IP conflict for non-automatic IP assignment... - when: connection_mode not in ['ipv4_dhcp', 'ipv6_dhcp', 'ipv6_slaac'] # The list of connection modes are the ones that are available in Providentia + when: + - pmx_cloning_rescue_loop_count is not defined # Not needed when trying to re-clone after a failed attempt + - connection_mode not in ['ipv4_dhcp', 'ipv6_dhcp', 'ipv6_slaac'] # The list of connection modes are the ones that are available in Providentia block: - name: Checking that nothing is already responding on {{ connection_address }}... # noqa: command-instead-of-shell ansible.builtin.shell: "{{ lookup('ansible.builtin.template', 'Test-IPAddress.sh') }}" @@ -120,11 +122,20 @@ - name: Including remove tasks to clean up disks from failed cloning attempt... ansible.builtin.include_tasks: remove.yml + - name: Setting cloning rescue loop count to {{ pmx_cloning_rescue_loop_count | default(0) | int + 1 }}/3... + ansible.builtin.set_fact: + pmx_cloning_rescue_loop_count: "{{ pmx_cloning_rescue_loop_count | default(0) | int + 1 }}" + + - name: Re-including cloning tasks... + ansible.builtin.include_tasks: main.yml + when: pmx_cloning_rescue_loop_count | default(0) | int <= 3 + - name: FAILED CLONING ATTEMPT ansible.builtin.fail: msg: | - Cloning {{ custom_vm_name | default(vm_name) }} failed. + Cloning {{ custom_vm_name | default(vm_name) }} failed {{ pmx_cloning_rescue_loop_count | default(0) }} times. Check the errors above for this host and try again. + when: pmx_cloning_rescue_loop_count | default(0) | int >= 3 # Writing the file here in case the non-existing machines are in the same batch as existing ones # Otherwise the non-existing will just timeout waiting for the previous host to finish cloning From 3b24f0e77a36783cfc95b4f79fb650b125d55539 Mon Sep 17 00:00:00 2001 From: Allar Viik Date: Mon, 3 Aug 2026 09:10:56 +0300 Subject: [PATCH 5/7] Added a feature to define on-link for netplan routes when using custom settings --- .../roles/configure_networking/templates/netplan.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nova/core/roles/configure_networking/templates/netplan.yml b/nova/core/roles/configure_networking/templates/netplan.yml index 34ad05ca1..75730ecb8 100644 --- a/nova/core/roles/configure_networking/templates/netplan.yml +++ b/nova/core/roles/configure_networking/templates/netplan.yml @@ -40,18 +40,27 @@ network: {% if (ip_address.mode == "ipv4_static") and (ip_address.gateway is defined) and (ip_address.gateway != none) %} - to: default via: "{{ ip_address.gateway }}" + {% if ip_address.on_link is defined and ip_address.on_link %} + on-link: true + {% endif %} {% endif %} {% endfor %} {% for ip_address in interface.addresses %} {% if (ip_address.mode == "ipv6_static") and (ip_address.gateway is defined) and (ip_address.gateway != none) %} - to: default via: "{{ ip_address.gateway }}" + {% if ip_address.on_link is defined and ip_address.on_link %} + on-link: true + {% endif %} {% endif %} {% endfor %} {% if extra_routes[interface_names[interface_loop]] is defined %} {% for route in extra_routes[interface_names[interface_loop]] %} - to: {{ route.to }} via: "{{ route.via }}" + {% if route.on_link is defined and route.on_link %} + on-link: true + {% endif %} {% endfor %} {% endif %} {% if extra_ipv4 is defined or extra_ipv6 is defined %} From 4ada2fafb3b183cb04ee4954b408c9e0181fb4e2 Mon Sep 17 00:00:00 2001 From: Allar Viik Date: Mon, 3 Aug 2026 16:44:58 +0300 Subject: [PATCH 6/7] Fixed interfaces network customziation method for Debian on Proxmox --- .../tasks/proxmox/interfaces.yml | 90 +++++++++++++++---- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/nova/core/roles/configure_networking/tasks/proxmox/interfaces.yml b/nova/core/roles/configure_networking/tasks/proxmox/interfaces.yml index ce01f88f7..8e7ac2b5f 100644 --- a/nova/core/roles/configure_networking/tasks/proxmox/interfaces.yml +++ b/nova/core/roles/configure_networking/tasks/proxmox/interfaces.yml @@ -3,24 +3,53 @@ become: false delegate_to: localhost block: - - name: Templating following network configuration files... + - name: Getting OS type... + ansible.builtin.uri: + url: "{{ proxmox_api_url }}/nodes/{{ cfg_net_proxmox_node }}/qemu/{{ cfg_net_proxmox_vmid }}/agent/file-read?file=/etc/os-release" + headers: + Authorization: PVEAPIToken={{ proxmox_defaults.api_user }}!{{ proxmox_defaults.api_token_id }}={{ proxmox_defaults.api_token_secret }} + method: GET + validate_certs: "{{ proxmox_defaults.validate_certs }}" + register: network_config_command + + - name: Setting OS type variable + ansible.builtin.set_fact: + configure_networking_os: >- + {% if 'ID=debian' in network_config_command.json.data['content'] | trim %} + Debian + {% elif 'ID=alpine' in network_config_command.json.data['content'] | trim %} + Alpine + {% else %} + Unknown + {% endif %} + + - name: Templating interfaces... ansible.builtin.template: - src: "{{ item.src }}" - dest: "{{ item.dest }}" + src: interfaces.j2 + dest: /tmp/{{ project_fullname | default('') }}_{{ inventory_hostname }}_interfaces lstrip_blocks: true mode: "0644" - loop_control: - label: "{{ item.dest }}" - loop: - - src: interfaces.j2 - dest: /tmp/{{ project_fullname | default('') }}_{{ inventory_hostname }}_interfaces - - src: resolv.conf - dest: /tmp/{{ project_fullname | default('') }}_{{ inventory_hostname }}_resolv.conf + - name: Templating resolv.conf... + ansible.builtin.template: + src: resolv.conf + dest: /tmp/{{ project_fullname | default('') }}_{{ inventory_hostname }}_resolv.conf + lstrip_blocks: true + mode: "0644" - # For locking MAC addresses to interface names - - src: mactab - dest: /tmp/{{ project_fullname | default('') }}_{{ inventory_hostname }}_mactab + - name: Templating udev persistent net rules... + ansible.builtin.template: + src: 70-persistent-net.rules + dest: /tmp/{{ project_fullname | default('') }}_{{ inventory_hostname }}_70-persistent-net.rules + mode: "0644" + lstrip_blocks: true + + - name: Templating mactab persistent net rules... + ansible.builtin.template: + src: mactab + dest: /tmp/{{ project_fullname | default('') }}_{{ inventory_hostname }}_mactab + mode: "0644" + lstrip_blocks: true - name: Getting network configuration file contents... ansible.builtin.slurp: @@ -29,13 +58,15 @@ loop: - /tmp/{{ project_fullname | default('') }}_{{ inventory_hostname }}_interfaces - /tmp/{{ project_fullname | default('') }}_{{ inventory_hostname }}_resolv.conf + - /tmp/{{ project_fullname | default('') }}_{{ inventory_hostname }}_70-persistent-net.rules - /tmp/{{ project_fullname | default('') }}_{{ inventory_hostname }}_mactab # Since the Proxmox API can be quite unstable especially under load, we implement a rescue loop here # to retry the network configuration up to 3 times before failing the task completely. - name: Including network configuration tasks... block: - - name: Writing following configuration to {{ custom_vm_name | default(vm_name) }}... + - name: Writing following Debian configuration to {{ custom_vm_name | default(vm_name) }}... + when: configure_networking_os is search('Debian') ansible.builtin.uri: url: "{{ proxmox_api_url }}/nodes/{{ cfg_net_proxmox_node }}/qemu/{{ cfg_net_proxmox_vmid }}/agent/file-write" headers: @@ -53,12 +84,39 @@ file: /etc/network/interfaces - content: "{{ network_files.results[1].content | b64decode }}" - file: /etc/resolv.conf + file: /tmp/resolv.conf - content: "{{ network_files.results[2].content | b64decode }}" + file: /etc/udev/rules.d/70-persistent-net.rules + + - content: update-initramfs -u && cp /tmp/resolv.conf /etc/resolv.conf && reboot + file: /tmp/rebuild_initramfs.sh + + - name: Writing following Alpine configuration to {{ custom_vm_name | default(vm_name) }}... + when: configure_networking_os is search('Alpine') + ansible.builtin.uri: + url: "{{ proxmox_api_url }}/nodes/{{ cfg_net_proxmox_node }}/qemu/{{ cfg_net_proxmox_vmid }}/agent/file-write" + headers: + Authorization: PVEAPIToken={{ proxmox_defaults.api_user }}!{{ proxmox_defaults.api_token_id }}={{ proxmox_defaults.api_token_secret }} + method: POST + body: + content: "{{ item.content }}" + file: "{{ item.file }}" + body_format: json + validate_certs: "{{ proxmox_defaults.validate_certs }}" + loop_control: + label: "{{ item.file }}" + loop: + - content: "{{ network_files.results[0].content | b64decode }}" + file: /etc/network/interfaces + + - content: "{{ network_files.results[1].content | b64decode }}" + file: /tmp/resolv.conf + + - content: "{{ network_files.results[3].content | b64decode }}" file: /etc/mactab - - content: mkinitfs && reboot + - content: mkinitfs && cp /tmp/resolv.conf /etc/resolv.conf && reboot file: /tmp/rebuild_initramfs.sh - name: Rebuilding initramfs on {{ custom_vm_name | default(vm_name) }}... From 8a813a8815f34f83d7a0002da0bde7c2777ca163 Mon Sep 17 00:00:00 2001 From: Allar Viik Date: Mon, 3 Aug 2026 16:45:57 +0300 Subject: [PATCH 7/7] Stopping play on no_undeploy only if the VM exists to avoid incorrect fail with redeploy --- .../core/roles/machine_operations/tasks/aws/remove.yml | 10 +++++----- .../roles/machine_operations/tasks/azure/remove.yml | 10 +++++----- .../roles/machine_operations/tasks/google/remove.yml | 5 +++++ .../machine_operations/tasks/openstack/remove.yml | 10 +++++----- .../roles/machine_operations/tasks/proxmox/remove.yml | 10 +++++----- .../tasks/vmware_workstation/remove.yml | 10 +++++----- 6 files changed, 30 insertions(+), 25 deletions(-) diff --git a/nova/core/roles/machine_operations/tasks/aws/remove.yml b/nova/core/roles/machine_operations/tasks/aws/remove.yml index 82f200fb8..5e28a7a13 100644 --- a/nova/core/roles/machine_operations/tasks/aws/remove.yml +++ b/nova/core/roles/machine_operations/tasks/aws/remove.yml @@ -1,13 +1,13 @@ --- -- name: NO UNDEPLOY - ansible.builtin.fail: - msg: "{{ inventory_hostname }} Has no_undeploy set and won't be removed" - when: no_undeploy or ['no_undeploy', 'custom_no_undeploy'] | intersect(group_names) | length > 0 - - name: Deleting {{ custom_vm_name | default(vm_name) }} EC2 instance and security group... delegate_to: localhost become: false block: + - name: NO UNDEPLOY + ansible.builtin.fail: + msg: "{{ inventory_hostname }} Has no_undeploy set and won't be removed" + when: no_undeploy or ['no_undeploy', 'custom_no_undeploy'] | intersect(group_names) | length > 0 + - name: Deleting {{ custom_vm_name | default(vm_name) }} EC2 instance... amazon.aws.ec2_instance: name: "{{ custom_vm_name | default(vm_name) }}" diff --git a/nova/core/roles/machine_operations/tasks/azure/remove.yml b/nova/core/roles/machine_operations/tasks/azure/remove.yml index bffdad81c..c5cbc7ca4 100644 --- a/nova/core/roles/machine_operations/tasks/azure/remove.yml +++ b/nova/core/roles/machine_operations/tasks/azure/remove.yml @@ -1,14 +1,14 @@ --- -- name: NO UNDEPLOY - ansible.builtin.fail: - msg: "{{ inventory_hostname }} Has no_undeploy set and won't be removed" - when: no_undeploy or ['no_undeploy', 'custom_no_undeploy'] | intersect(group_names) | length > 0 - - name: Removing VM and it's resources... become: false delegate_to: localhost when: resource_group_info.resourcegroups != [] # If there is no resource group, there is nothing to remove block: + - name: NO UNDEPLOY + ansible.builtin.fail: + msg: "{{ inventory_hostname }} Has no_undeploy set and won't be removed" + when: no_undeploy or ['no_undeploy', 'custom_no_undeploy'] | intersect(group_names) | length > 0 + - name: Removing {{ custom_vm_name | default(vm_name) }} VM from Azure... azure.azcollection.azure_rm_virtualmachine: resource_group: "{{ azure_resource_group }}" diff --git a/nova/core/roles/machine_operations/tasks/google/remove.yml b/nova/core/roles/machine_operations/tasks/google/remove.yml index c663d44b3..f1878514f 100644 --- a/nova/core/roles/machine_operations/tasks/google/remove.yml +++ b/nova/core/roles/machine_operations/tasks/google/remove.yml @@ -1,4 +1,9 @@ --- +- name: NO UNDEPLOY + ansible.builtin.fail: + msg: "{{ inventory_hostname }} Has no_undeploy set and won't be removed" + when: no_undeploy or ['no_undeploy', 'custom_no_undeploy'] | intersect(group_names) | length > 0 + - name: Deleting {{ custom_vm_name | default(vm_name) | replace('_', '-') }}... google.cloud.gcp_compute_instance: name: "{{ custom_vm_name | default(vm_name) | replace('_', '-') }}" diff --git a/nova/core/roles/machine_operations/tasks/openstack/remove.yml b/nova/core/roles/machine_operations/tasks/openstack/remove.yml index 9ea428b1c..87a80b3b8 100644 --- a/nova/core/roles/machine_operations/tasks/openstack/remove.yml +++ b/nova/core/roles/machine_operations/tasks/openstack/remove.yml @@ -1,13 +1,13 @@ --- -- name: NO UNDEPLOY - ansible.builtin.fail: - msg: "{{ inventory_hostname }} Has no_undeploy set and won't be removed" - when: no_undeploy or ['no_undeploy', 'custom_no_undeploy'] | intersect(group_names) | length > 0 - - name: Removing {{ custom_vm_name | default(vm_name) }} and related configuration... delegate_to: localhost become: false block: + - name: NO UNDEPLOY + ansible.builtin.fail: + msg: "{{ inventory_hostname }} Has no_undeploy set and won't be removed" + when: no_undeploy or ['no_undeploy', 'custom_no_undeploy'] | intersect(group_names) | length > 0 + - name: Removing {{ custom_vm_name | default(vm_name) }} VM... openstack.cloud.server: name: "{{ custom_vm_name | default(vm_name) }}" diff --git a/nova/core/roles/machine_operations/tasks/proxmox/remove.yml b/nova/core/roles/machine_operations/tasks/proxmox/remove.yml index 41011388f..74f23c2e0 100644 --- a/nova/core/roles/machine_operations/tasks/proxmox/remove.yml +++ b/nova/core/roles/machine_operations/tasks/proxmox/remove.yml @@ -1,14 +1,14 @@ --- -- name: NO UNDEPLOY - ansible.builtin.fail: - msg: "{{ inventory_hostname }} Has no_undeploy set and won't be removed" - when: no_undeploy or ['no_undeploy', 'custom_no_undeploy'] | intersect(group_names) | length > 0 - - name: Removing machine... when: proxmox_vm_exists.proxmox_vms != [] become: false delegate_to: localhost block: + - name: NO UNDEPLOY + ansible.builtin.fail: + msg: "{{ inventory_hostname }} Has no_undeploy set and won't be removed" + when: no_undeploy or ['no_undeploy', 'custom_no_undeploy'] | intersect(group_names) | length > 0 + - name: Removing cloning limiter file... ansible.builtin.file: path: /tmp/proxmox_vm_clone_{{ project_fullname | default('') }}_{{ inventory_hostname }}.done diff --git a/nova/core/roles/machine_operations/tasks/vmware_workstation/remove.yml b/nova/core/roles/machine_operations/tasks/vmware_workstation/remove.yml index 93712e239..017944775 100644 --- a/nova/core/roles/machine_operations/tasks/vmware_workstation/remove.yml +++ b/nova/core/roles/machine_operations/tasks/vmware_workstation/remove.yml @@ -1,9 +1,4 @@ --- -- name: NO UNDEPLOY - ansible.builtin.fail: - msg: "{{ inventory_hostname }} Has no_undeploy set and won't be removed" - when: no_undeploy or ['no_undeploy', 'custom_no_undeploy'] | intersect(group_names) | length > 0 - - name: Checking if {{ inventory_hostname }} exists... ansible.builtin.stat: path: "{{ vmware_workstation.local_vmx_path }}" @@ -12,6 +7,11 @@ - name: Removing {{ inventory_hostname }} if it exists... when: vm_exists.stat.exists block: + - name: NO UNDEPLOY + ansible.builtin.fail: + msg: "{{ inventory_hostname }} Has no_undeploy set and won't be removed" + when: no_undeploy or ['no_undeploy', 'custom_no_undeploy'] | intersect(group_names) | length > 0 + - name: Listing running VMs... ansible.builtin.command: vmrun -T ws list changed_when: true