Skip to content

Commit 51d3b98

Browse files
author
Adam Kraitman
committed
maas dns role for configuring maas dns domains and records
Signed-off-by: Adam Kraitman <akraitma@li-8b09b2cc-35b7-11b2-a85c-cd1dbade58f9.ibm.com>
1 parent fcd39aa commit 51d3b98

File tree

6 files changed

+281
-0
lines changed

6 files changed

+281
-0
lines changed

maas_nameserver.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
- name: Configure MAAS DNS
3+
hosts: maas
4+
gather_facts: false
5+
roles:
6+
- maas_nameserver

roles/maas_nameserver/README.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# maas_nameserver Ansible Role
2+
3+
## Overview
4+
5+
The `maas_nameserver` role configures DNS domains and records in MAAS (Metal as a Service) based on an Ansible inventory. It manages DNS entries for hosts (e.g., Main interfaces, IPMI interfaces, VLAN interfaces) in specified domains, ensuring only desired records and domains exist while cleaning up unwanted ones. This role depends on a secrets file to load MAAS API credentials.
6+
7+
## Requirements
8+
9+
- Ansible: Version 2.9 or higher
10+
- MAAS CLI: Installed on the target MAAS server
11+
- Inventory: A valid Ansible inventory with `group_vars/all.yml` defining `dns_domains`
12+
13+
## Role Structure
14+
15+
```
16+
roles/
17+
maas_nameserver/
18+
defaults/
19+
main.yml
20+
handlers/
21+
main.yml
22+
tasks/
23+
main.yml
24+
vars/
25+
main.yml
26+
README.md
27+
```
28+
29+
## Dependencies
30+
31+
- **Secrets File**: A `maas.yml` file at `{{ secrets_path }}/maas.yml` provides MAAS API credentials (e.g., `maas_api_key`, `maas_api_url`). No separate secrets role is required; credentials are loaded via `include_vars`.
32+
33+
## Usage
34+
35+
1. **Place the Role**
36+
37+
Ensure the `maas_nameserver` role is in your `roles/` directory.
38+
39+
2. **Prepare Inventory**
40+
41+
Define your `maas` and target host groups:
42+
43+
```ini
44+
[maas]
45+
maas.internal.ceph.ibm.com ip=10.11.120.237
46+
47+
[snipe]
48+
snipe.internal.ceph.ibm.com ip=10.60.100.11
49+
50+
[machine]
51+
machine001.internal.ceph.ibm.com ip=10.18.131.100 ipmi=10.18.139.100 vlan104=10.18.144.3
52+
```
53+
54+
3. **Set Up Inventory Variables**
55+
56+
Define `dns_domains` in `group_vars/all.yml`:
57+
58+
```yaml
59+
---
60+
dns_domains:
61+
ceph: "internal.ceph.ibm.com"
62+
ipmi: "ipmi.ceph.ibm.com"
63+
vlan104: "vlan104.internal.ceph.ibm.com"
64+
```
65+
66+
4. **Set Up Secrets**
67+
68+
Create a secrets file at `{{ secrets_path }}/maas.yml`:
69+
70+
```yaml
71+
---
72+
maas_api_key: "XXXXXXXXXXXXXXXX"
73+
maas_api_url: "http://localhost:5240/MAAS/api/2.0/"
74+
maas_profile: "admin"
75+
```
76+
77+
5. **Run the Playbook**
78+
79+
```bash
80+
ansible-playbook maas_nameserver.yml
81+
```
82+
83+
## Variables
84+
85+
### defaults/main.yml
86+
87+
These are overridable defaults:
88+
89+
- `maas_api_url`: Default MAAS API endpoint (`http://localhost:5240/MAAS/api/2.0/`). Override in `secrets/maas.yml`.
90+
- `maas_profile`: Default MAAS profile name (`admin`). Override in `secrets/maas.yml`.
91+
- `default_domains`: Domains to preserve (default: `["maas"]`). The `maas` domain is used by MAAS for internal DNS records and is excluded from cleanup.
92+
- `target_hosts`: List of hosts for DNS records. Defaults to all inventory hosts except the `maas` group (e.g., `['machine001.internal.ceph.ibm.com', 'snipe.internal.ceph.ibm.com']`). Override in `secrets/maas.yml` or via extra vars:
93+
94+
```yaml
95+
target_hosts:
96+
- machine001.internal.ceph.ibm.com
97+
- machine002.internal.ceph.ibm.com
98+
```
99+
100+
Or via command line:
101+
102+
```bash
103+
ansible-playbook maas_nameserver.yml -e "target_hosts=['argo001.internal.ceph.ibm.com']"
104+
```
105+
106+
### vars/main.yml
107+
108+
No mandatory, non-overridable variables are defined. Environment-specific variables like `dns_domains` must be set in `inventory/group_vars/all.yml`.
109+
110+
### secrets/maas.yml
111+
112+
Provides MAAS API credentials and optional overrides:
113+
114+
- `maas_api_key`: MAAS API key for authentication.
115+
- `maas_api_url`: MAAS API endpoint (e.g., `http://127.0.0.1:5240/MAAS/api/2.0/`).
116+
- `maas_profile`: MAAS CLI profile name (e.g., `admin`).
117+
- `target_hosts` (optional): Override the default `target_hosts` list.
118+
119+
**Example**:
120+
121+
```yaml
122+
maas_api_key: "XXXXXXXXXXXXXXXX"
123+
maas_api_url: "http://127.0.0.1:5240/MAAS/api/2.0/"
124+
maas_profile: "admin"
125+
# Optional override
126+
target_hosts:
127+
- machine001.internal.ceph.ibm.com
128+
```
129+
130+
**Notes**:
131+
132+
- **Security**: Ensure file permissions are restricted (e.g., `chmod 600 maas.yml`).
133+
- **Vault**: If encrypted, provide the vault password (e.g., via `--vault-password-file ~/.vault_pass.txt`).
134+
135+
## Behavior
136+
137+
- **DNS Records**: Creates A records for hosts based on `dns_domains` and inventory variables (e.g., `ip`, `ipmi`, `vlan104`). Example:
138+
- `machine001.internal.ceph.ibm.com` → `10.18.131.100`
139+
- `machine001.ipmi.ceph.ibm.com` → `10.18.139.100`
140+
- `machine001.vlan104.internal.ceph.ibm.com` → `10.18.144.3`
141+
- **Cleanup**: Deletes DNS records and domains not in `dns_domains` or `default_domains`.
142+
- **Error Handling**: Logs failed DNS record deletions via a handler (`log_deletion_failure`).
143+
- **Idempotency**: Skips actions if the desired state is already met.
144+
145+
## Troubleshooting
146+
147+
- **Missing** `dns_domains`: Ensure `dns_domains` is defined in `inventory/group_vars/all.yml`. The playbook will fail if undefined.
148+
- **Secrets Not Loading**: Verify `secrets_path` points to the correct directory and `maas.yml` contains valid credentials.
149+
- **MAAS CLI Errors**: Confirm the MAAS CLI is installed on the target server and the API key is valid.
150+
- **Failed Deletions**: Check handler output for `log_deletion_failure` messages indicating why DNS record deletions failed (e.g., permissions, network issues).
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
maas_api_url: "http://localhost:5240/MAAS/api/2.0/" # Default, overridden by secrets
3+
maas_profile: "admin" # Default, overridden by secrets
4+
default_domains:
5+
- "maas"
6+
7+
# List of target hosts for DNS records, excluding the 'maas' group by default.
8+
# Can be overridden in secrets/maas.yml (e.g., target_hosts: ['host1.example.com', 'host2.example.com'])
9+
# or via extra vars (e.g., ansible-playbook -e "target_hosts=['host1.example.com']").
10+
# If not set, defaults to all hosts in inventory groups except 'maas'.
11+
target_hosts: []
12+
13+
# Note: dns_domains should be defined in the inventory's group_vars/all.yml

roles/maas_nameserver/meta/main.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
dependencies:
3+
- role: secrets

roles/maas_nameserver/tasks/main.yml

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
---
2+
# Include secrets from maas.yml (e.g., maas_api_key, maas_api_url).
3+
# Note: dns_domains should be defined in the inventory's group_vars/all.yml
4+
- name: Include secrets
5+
ansible.builtin.include_vars: "{{ item }}"
6+
no_log: true
7+
with_first_found:
8+
- "{{ secrets_path | mandatory }}/maas.yml"
9+
tags:
10+
- always
11+
12+
- name: Ensure MAAS CLI is logged in
13+
ansible.builtin.command: "maas login {{ maas_profile }} {{ maas_api_url }} {{ maas_api_key }}"
14+
register: maas_login
15+
changed_when: maas_login.rc == 0
16+
failed_when: maas_login.rc != 0 and maas_login.stderr != "Profile already exists"
17+
18+
- name: Get existing DNS resources
19+
ansible.builtin.command: "maas {{ maas_profile }} dnsresources read"
20+
register: existing_resources
21+
changed_when: false
22+
23+
- name: Initialize DNS records list
24+
ansible.builtin.set_fact:
25+
dns_records: []
26+
27+
# Define target hosts for DNS records, excluding the 'maas' group.
28+
# After population, target_hosts will contain all hosts from inventory groups except 'maas', e.g.,
29+
# ['machine001.internal.example.com', 'snipe.internal.example.com', 'vm-14.example.com'].
30+
# This can be overridden in secrets/maas.yml (e.g., target_hosts: ['host1.example.com', 'host2.example.com'])
31+
# or via extra vars (e.g., -e "target_hosts=['host1.example.com']").
32+
- name: Define target hosts for DNS records
33+
ansible.builtin.set_fact:
34+
target_hosts: "{{ target_hosts | default(groups | dict2items | rejectattr('key', 'equalto', 'maas') | map(attribute='value') | flatten | unique | default([])) }}"
35+
when: groups.keys() | length > 1
36+
37+
- name: Build DNS records for all interfaces
38+
ansible.builtin.set_fact:
39+
dns_records: "{{ dns_records + [{'name': item[0].split('.')[0], 'ip': interface_ip, 'type': 'A', 'domain': item[1].value}] }}"
40+
loop: "{{ (target_hosts | default([])) | product(dns_domains | dict2items) | list }}"
41+
vars:
42+
interface_ip: "{{ hostvars[item[0]][item[1].key] if item[1].key != 'ceph' else hostvars[item[0]]['ip'] }}"
43+
when:
44+
- target_hosts is defined and target_hosts | length > 0
45+
- "item[1].key in hostvars[item[0]] or (item[1].key == 'ceph' and 'ip' in hostvars[item[0]])"
46+
47+
- name: Parse desired FQDNs
48+
ansible.builtin.set_fact:
49+
desired_fqdns: "{{ dns_records | map(attribute='name') | zip(dns_records | map(attribute='domain')) | map('join', '.') | list }}"
50+
when: dns_records | length > 0
51+
52+
- name: Remove unwanted DNS records
53+
ansible.builtin.command: "maas {{ maas_profile }} dnsresource delete {{ item.id }}"
54+
loop: "{{ existing_resources.stdout | from_json }}"
55+
when: >
56+
dns_records | length > 0 and
57+
item.fqdn not in desired_fqdns
58+
register: dns_deletion
59+
failed_when: dns_deletion.rc != 0 and "does not exist" not in dns_deletion.stderr
60+
notify: log_deletion_failure
61+
62+
- name: Get updated DNS resources after deletions
63+
ansible.builtin.command: "maas {{ maas_profile }} dnsresources read"
64+
register: updated_resources
65+
changed_when: false
66+
67+
- name: Get existing DNS domains
68+
ansible.builtin.command: "maas {{ maas_profile }} domains read"
69+
register: existing_domains
70+
changed_when: false
71+
72+
- name: Parse existing domains
73+
ansible.builtin.set_fact:
74+
current_domains: "{{ existing_domains.stdout | from_json | map(attribute='name') | list }}"
75+
76+
- name: Remove unwanted domains
77+
ansible.builtin.command: "maas {{ maas_profile }} domain delete {{ item.id }}"
78+
loop: "{{ existing_domains.stdout | from_json }}"
79+
when: >
80+
item.name not in default_domains and
81+
item.name not in dns_domains.values()
82+
register: domain_deletion
83+
failed_when: domain_deletion.rc != 0 and "does not exist" not in domain_deletion.stderr and "protected foreign keys" not in domain_deletion.stderr
84+
85+
- name: Ensure new DNS domains exist
86+
ansible.builtin.command: "maas {{ maas_profile }} domains create name={{ item.value }}"
87+
loop: "{{ dns_domains | dict2items }}"
88+
when: item.value not in current_domains
89+
register: domain_creation
90+
failed_when: domain_creation.rc != 0 and "already exists" not in domain_creation.stderr
91+
92+
- name: Ensure DNS records exist
93+
ansible.builtin.command: >
94+
maas {{ maas_profile }} dnsresources create
95+
fqdn={{ item.name }}.{{ item.domain }}
96+
ip_addresses={{ item.ip }}
97+
loop: "{{ dns_records }}"
98+
when: >
99+
dns_records | length > 0 and
100+
(item.name + '.' + item.domain) not in
101+
(updated_resources.stdout | from_json | map(attribute='fqdn') | list)
102+
register: dns_creation
103+
failed_when: dns_creation.rc != 0 and "already exists" not in dns_creation.stderr
104+
105+
- name: Logout from MAAS
106+
ansible.builtin.command: "maas logout {{ maas_profile }}"
107+
when: maas_login is success
108+
changed_when: true

roles/maas_nameserver/vars/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
---

0 commit comments

Comments
 (0)