Skip to content

Commit fa552bf

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 fa552bf

File tree

6 files changed

+294
-0
lines changed

6 files changed

+294
-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: 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

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)