Skip to content

Commit 59ed665

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 59ed665

File tree

6 files changed

+230
-0
lines changed

6 files changed

+230
-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: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# README for `maas_nameserver` Ansible Role
2+
3+
## Overview
4+
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 Ceph nodes and their IPMI interfaces, ensuring only desired records and domains exist while cleaning up unwanted ones. This role depends on a `secrets` role to load encrypted MAAS API credentials using Ansible Vault.
5+
6+
## Requirements
7+
- **Ansible**: Version 2.9 or higher
8+
- **MAAS CLI**: Installed on the target MAAS server
9+
- **Secrets Role**: A companion `secrets` role to load encrypted credentials
10+
11+
## Role Structure
12+
```
13+
roles/
14+
maas_nameserver/
15+
defaults/
16+
main.yml
17+
meta/
18+
main.yml
19+
tasks/
20+
main.yml
21+
vars/
22+
main.yml
23+
README.md
24+
```
25+
26+
## Dependencies
27+
- **`secrets` Role**: Defined in `meta/main.yml`, this role loads encrypted MAAS API credentials from a configurable path. Ensure the `secrets` role is available in your `roles/` directory.
28+
29+
## Usage
30+
1. **Place the Role**: Ensure the `maas_nameserver` and `secrets` roles are in your `roles/` directory.
31+
2. **Prepare Inventory**: Define your `maas` and your target hosts groups (e.g., in `/etc/ansible/hosts/tucson`):
32+
```ini
33+
[ceph]
34+
ceph001.internal.ceph.tucson.com ip=10.18.131.1 ipmi=10.18.139.1 vlan104=10.18.104.1 104mac=3c:fd:fe:d2:6a:84
35+
ceph002.internal.ceph.tucson.com ip=10.18.131.2 ipmi=10.18.139.2 vlan104=10.18.104.2 104mac=3c:fd:fe:b2:8e:24
36+
ceph003.internal.ceph.tucson.com ip=10.18.131.3 ipmi=10.18.139.3 vlan104=10.18.104.3 104mac=3c:fd:fe:e6:5c:13
37+
38+
[maas]
39+
ceph-vm-14.internal.ceph.tucson.com ip=9.11.120.237
40+
```
41+
3. **Set Up Secrets**: Encrypt your MAAS credentials in a secrets file (see "Secrets File" section below).
42+
4. **Run the Playbook**:
43+
```bash
44+
ansible-playbook maas-dns-playbook.yml
45+
```
46+
47+
## Variables
48+
49+
### `defaults/main.yml`
50+
These are overridable defaults:
51+
- `maas_api_url`: Default MAAS API endpoint (`http://localhost:5240/MAAS/api/2.0/`). Typically overridden by the secrets file.
52+
- `maas_profile`: Default MAAS profile name (`admin`). Overridden by the secrets file.
53+
- `default_domains`: List of domains to preserve/ignore (default: `["maas"]`). The default domain is a DNS domain that is used by maas when you deploy a machine it is used by maas for internal dns records so we choose to exclude it from our ansible role.
54+
55+
### `vars/main.yml`
56+
These are role-specific variables, they can be override as needed:
57+
- `dns_domains`:
58+
- `ceph`: Static primary domain (e.g., `internal.ceph.tucson.com`).
59+
- `ipmi`: Static IPMI domain (`ipmi.ceph.tucson.com`).
60+
- `vlan104`: Static sub-domain for vlan104 address(`vlan104.internal.ceph.tucson.com`).
61+
62+
## Secrets File
63+
The `maas_nameserver` role depends on the `secrets` role to load encrypted MAAS API credentials. The secrets file is expected at `{{ secrets_path }}/maas.yml`.
64+
65+
### Example Secrets File
66+
Create and encrypt the file (e.g., `{{ secrets_path }}/maas.yml`):
67+
```yaml
68+
---
69+
maas_api_url: "http://X.X.X.X:5240/MAAS/api/2.0/"
70+
maas_api_key: "XXXXXXXXXXXXXXXX"
71+
maas_profile: "admin"
72+
```
73+
74+
**Notes**:
75+
- **Location**: Store this file in a secure directory (e.g., `/etc/ansible/secrets` or a private repo like `~/secrets/ceph-secrets`). Adjust `secrets_path` if using a custom location.
76+
- **Security**: Ensure the file is readable only by the Ansible user (e.g., `chmod 600`).
77+
- **Variables**:
78+
- `maas_api_url`: The MAAS API endpoint.
79+
- `maas_api_key`: The API key for authentication.
80+
- `maas_profile`: The profile name used with the MAAS CLI.
81+
- **Vault Password**: Store the password securely (e.g., in `~/.vault_pass.txt` with `chmod 600`) or provide it at runtime.
82+
83+
## Behavior
84+
- **DNS Records**: Creates A records for Ceph nodes (`ip`) and IPMI interfaces (`ipmi`) in `internal.ceph.tucson.com` and `ipmi.ceph.tucson.com`, respectively.
85+
- **Cleanup**: Removes unwanted DNS records and domains not matching the desired state or `default_domains`.
86+
- **Idempotency**: Skips actions if the desired state is already met.
87+
88+
## Example Output
89+
Running the playbook should produce output similar to:
90+
```
91+
TASK [maas_nameserver : Debug desired FQDNs] ******************************************
92+
ok: [ceph-vm-14.internal.ceph.tucson.com] => {
93+
"msg": "Desired FQDNs: ['ceph001.internal.ceph.tucson.com', 'ceph002.internal.ceph.tucson.com', 'ceph003.internal.ceph.tucson.com', 'ceph001.ipmi.ceph.tucson.com', 'ceph002.ipmi.ceph.tucson.com', 'ceph003.ipmi.ceph.tucson.com']"
94+
}
95+
```
96+
97+
## Troubleshooting
98+
- **Secrets Not Loading**: Verify `secrets_path` points to the correct directory and the file is encrypted correctly. Check vault password access.
99+
- **MAAS CLI Errors**: Ensure the MAAS CLI is installed and the API credentials are valid.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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: []

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

roles/maas_nameserver/vars/main.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
dns_domains:
3+
ceph: "internal.ceph.ibm.com"
4+
ipmi: "ipmi.ceph.ibm.com"
5+
vlan104: "vlan104.internal.ceph.ibm.com"

0 commit comments

Comments
 (0)