Skip to content

Commit b346a19

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 b346a19

File tree

5 files changed

+307
-0
lines changed

5 files changed

+307
-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: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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+
tasks/
21+
main.yml
22+
meta/
23+
main.yml
24+
README.md
25+
```
26+
27+
## Dependencies
28+
29+
- **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`.
30+
31+
## Usage
32+
33+
1. **Place the Role**
34+
35+
Ensure the `maas_nameserver` role is in your `roles/` directory.
36+
37+
2. **Prepare Inventory**
38+
39+
Define your `maas` and target host groups:
40+
41+
```ini
42+
[maas]
43+
maas.internal.ceph.ibm.com ip=10.11.120.237
44+
45+
[snipeit]
46+
snipe-it.internal.ceph.ibm.com ip=10.60.100.11
47+
48+
[machine]
49+
machine001.internal.ceph.ibm.com ip=10.18.131.100 ipmi=10.18.139.100 vlan104=10.18.144.3
50+
```
51+
52+
3. **Set Up Inventory Variables**
53+
54+
Define `dns_domains` in `group_vars/all.yml`:
55+
56+
```yaml
57+
---
58+
dns_domains:
59+
ceph: "internal.ceph.ibm.com"
60+
ipmi: "ipmi.ceph.ibm.com"
61+
vlan104: "vlan104.internal.ceph.ibm.com"
62+
```
63+
64+
4. **Set Up Secrets**
65+
66+
Create a secrets file at `{{ secrets_path }}/maas.yml`:
67+
68+
```yaml
69+
---
70+
maas_api_key: "XXXXXXXXXXXXXXXX"
71+
maas_api_url: "http://localhost:5240/MAAS/api/2.0/"
72+
maas_profile: "admin"
73+
```
74+
75+
5. **Run the Playbook**
76+
77+
```bash
78+
ansible-playbook maas_nameserver.yml
79+
```
80+
81+
## Variables
82+
83+
### defaults/main.yml
84+
85+
These are overridable defaults:
86+
87+
- `maas_api_url`: Default MAAS API endpoint (`http://localhost:5240/MAAS/api/2.0/`). Override in `secrets/maas.yml`.
88+
89+
- `maas_profile`: Default MAAS profile name (`admin`). Override in `secrets/maas.yml`.
90+
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+
93+
- `target_hosts`: List of hosts for DNS records. Defaults to an empty list (`[]`), in which case the role dynamically selects all hosts from inventory groups except those in `exclude_groups`. Override in `secrets/maas.yml` or via extra vars:
94+
95+
```yaml
96+
target_hosts:
97+
- machine001.internal.ceph.ibm.com
98+
- machine002.internal.ceph.ibm.com
99+
```
100+
101+
Or via command line:
102+
103+
```bash
104+
ansible-playbook maas_nameserver.yml -e "target_hosts=['machine001.internal.ceph.ibm.com']"
105+
```
106+
107+
- `exclude_groups`: List of inventory groups to exclude from `target_hosts` when `target_hosts` is empty. Defaults to `["maas", "all", "ungrouped"]`. The `all` and `ungrouped` groups must be included to prevent the automatic inclusion of all hosts (via the `all` group, which contains every host in the inventory) or ungrouped hosts (via the `ungrouped` group). Override in `secrets/maas.yml` or via extra vars:
108+
109+
```yaml
110+
exclude_groups: ["maas", "all", "ungrouped", "other_group"]
111+
```
112+
113+
### vars/main.yml
114+
115+
No mandatory, non-overridable variables are defined. Environment-specific variables like `dns_domains` must be set in `inventory/group_vars/all.yml`.
116+
117+
### secrets/maas.yml
118+
119+
Provides MAAS API credentials and optional overrides:
120+
121+
1. `maas_api_key`: MAAS API key for authentication.
122+
2. `maas_api_url`: MAAS API endpoint (e.g., `http://127.0.0.1:5240/MAAS/api/2.0/`).
123+
124+
`maas_profile`: MAAS CLI profile name (e.g., `admin`).
125+
126+
1. `target_hosts` (optional): Override the default `target_hosts` list.
127+
2. `exclude_groups` (optional): Override the default `exclude_groups` list.
128+
129+
**Example**:
130+
131+
```yaml
132+
maas_api_key: "XXXXXXXXXXXXXXXX"
133+
maas_api_url: "http://127.0.0.1:5240/MAAS/api/2.0/"
134+
maas_profile: "admin"
135+
# Optional overrides
136+
target_hosts:
137+
- machine001.internal.ceph.ibm.com
138+
exclude_groups:
139+
- maas
140+
- all
141+
- ungrouped
142+
```
143+
144+
**Notes**:
145+
146+
- **Security**: Ensure file permissions are restricted (e.g., `chmod 600 maas.yml`).
147+
- **Vault**: If encrypted, provide the vault password (e.g., via `--vault-password-file ~/.vault_pass.txt`).
148+
149+
## Behavior
150+
151+
- **DNS Records**: Creates A records for hosts based on `dns_domains` and inventory variables (e.g., `ip`, `ipmi`, `vlan104`). Example:
152+
- `machine001.internal.ceph.ibm.com` → `10.18.131.100`
153+
- `machine001.ipmi.ceph.ibm.com` → `10.18.139.100`
154+
- `machine001.vlan104.internal.ceph.ibm.com` → `10.18.144.3`
155+
- **Cleanup**: Deletes DNS records and domains not in `dns_domains` or `default_domains`.
156+
- **Idempotency**: Skips actions if the desired state is already met.
157+
158+
## Troubleshooting
159+
160+
- **Missing** `dns_domains`: Ensure `dns_domains` is defined in `inventory/group_vars/all.yml`. The playbook will fail if undefined.
161+
- **Secrets Not Loading**: Verify `secrets_path` points to the correct directory and `maas.yml` contains valid credentials.
162+
- **MAAS CLI Errors**: Confirm the MAAS CLI is installed on the target server and the API key is valid.
163+
- **Failed Deletions**: Check playbook output for errors during DNS record or domain deletions (e.g., permissions, network issues, or records that do not exist).
164+
- **Unwanted DNS Records**: If DNS records are created for unintended hosts, verify `exclude_groups` includes `all` and `ungrouped` to prevent the inclusion of all inventory hosts or ungrouped hosts. Check for overrides in `secrets/maas.yml` or extra vars that modify `target_hosts` or `exclude_groups`.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
# If not set, defaults to all hosts in inventory groups except those in exclude_groups.
10+
target_hosts: []
11+
12+
# List of inventory groups to exclude from target_hosts.
13+
# Includes 'all' and 'ungrouped' to prevent automatic inclusion of all hosts or ungrouped hosts,
14+
# as 'all' contains every host in the inventory and 'ungrouped' includes hosts not assigned to any group.
15+
exclude_groups: ["maas", "all", "ungrouped"]
16+
17+
# 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: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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 list for DNS records, excluding the 'maas' group.
28+
- name: Initialize target hosts list
29+
ansible.builtin.set_fact:
30+
target_hosts_list: []
31+
32+
- name: Build target hosts list
33+
ansible.builtin.set_fact:
34+
target_hosts_list: "{{ target_hosts_list + item.value }}"
35+
loop: "{{ groups | dict2items }}"
36+
when: item.key not in exclude_groups
37+
38+
# After population, target_hosts will contain all hosts from inventory groups except 'maas', e.g.,
39+
# ['machine001.internal.example.com', 'snipe.internal.example.com', 'vm-14.example.com'].
40+
# This can be overridden in secrets/maas.yml (e.g., target_hosts: ['host1.example.com', 'host2.example.com'])
41+
# or via extra vars (e.g., -e "target_hosts=['host1.example.com']").
42+
- name: Define target hosts for DNS records
43+
ansible.builtin.set_fact:
44+
target_hosts: "{{ target_hosts if target_hosts | length > 0 else (target_hosts_list | flatten | unique | default([])) }}"
45+
when: groups.keys() | length > 0
46+
47+
- name: Build DNS records for all interfaces
48+
ansible.builtin.set_fact:
49+
dns_records: "{{ dns_records + [{'name': item[0].split('.')[0], 'ip': interface_ip, 'type': 'A', 'domain': item[1].value}] }}"
50+
loop: "{{ (target_hosts | default([])) | product(dns_domains | dict2items) | list }}"
51+
vars:
52+
interface_ip: "{{ hostvars[item[0]][item[1].key] if item[1].key != 'ceph' else hostvars[item[0]]['ip'] }}"
53+
when:
54+
- target_hosts is defined and target_hosts | length > 0
55+
- "item[1].key in hostvars[item[0]] or (item[1].key == 'ceph' and 'ip' in hostvars[item[0]])"
56+
57+
- name: Parse desired FQDNs
58+
ansible.builtin.set_fact:
59+
desired_fqdns: "{{ dns_records | map(attribute='name') | zip(dns_records | map(attribute='domain')) | map('join', '.') | list }}"
60+
when: dns_records | length > 0
61+
62+
- name: Remove unwanted DNS records
63+
ansible.builtin.command: "maas {{ maas_profile }} dnsresource delete {{ item.id }}"
64+
loop: "{{ existing_resources.stdout | from_json }}"
65+
when: >
66+
dns_records | length > 0 and
67+
item.fqdn not in desired_fqdns
68+
register: dns_deletion
69+
failed_when: dns_deletion.rc != 0 and "does not exist" not in dns_deletion.stderr
70+
71+
- name: Get updated DNS resources after deletions
72+
ansible.builtin.command: "maas {{ maas_profile }} dnsresources read"
73+
register: updated_resources
74+
changed_when: false
75+
76+
- name: Get existing DNS domains
77+
ansible.builtin.command: "maas {{ maas_profile }} domains read"
78+
register: existing_domains
79+
changed_when: false
80+
81+
- name: Parse existing domains
82+
ansible.builtin.set_fact:
83+
current_domains: "{{ existing_domains.stdout | from_json | map(attribute='name') | list }}"
84+
85+
- name: Remove unwanted domains
86+
ansible.builtin.command: "maas {{ maas_profile }} domain delete {{ item.id }}"
87+
loop: "{{ existing_domains.stdout | from_json }}"
88+
when: >
89+
item.name not in default_domains and
90+
item.name not in dns_domains.values()
91+
register: domain_deletion
92+
failed_when: domain_deletion.rc != 0 and "does not exist" not in domain_deletion.stderr and "protected foreign keys" not in domain_deletion.stderr
93+
94+
- name: Ensure new DNS domains exist
95+
ansible.builtin.command: "maas {{ maas_profile }} domains create name={{ item.value }}"
96+
loop: "{{ dns_domains | dict2items }}"
97+
when: item.value not in current_domains
98+
register: domain_creation
99+
failed_when: domain_creation.rc != 0 and "already exists" not in domain_creation.stderr
100+
101+
- name: Ensure DNS records exist
102+
ansible.builtin.command: >
103+
maas {{ maas_profile }} dnsresources create
104+
fqdn={{ item.name }}.{{ item.domain }}
105+
ip_addresses={{ item.ip }}
106+
loop: "{{ dns_records }}"
107+
when: >
108+
dns_records | length > 0 and
109+
(item.name + '.' + item.domain) not in
110+
(updated_resources.stdout | from_json | map(attribute='fqdn') | list)
111+
register: dns_creation
112+
failed_when: dns_creation.rc != 0 and "already exists" not in dns_creation.stderr
113+
114+
- name: Logout from MAAS
115+
ansible.builtin.command: "maas logout {{ maas_profile }}"
116+
when: maas_login is success
117+
changed_when: true

0 commit comments

Comments
 (0)