Skip to content

Commit b375ae2

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 b375ae2

File tree

6 files changed

+224
-0
lines changed

6 files changed

+224
-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: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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
35+
ceph002.internal.ceph.tucson.com ip=10.18.131.2 ipmi=10.18.139.2
36+
ceph003.internal.ceph.tucson.com ip=10.18.131.3 ipmi=10.18.139.3
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+
- `allowed_domains`: List of domains to preserve (default: `["maas"]`).
54+
55+
### `vars/main.yml`
56+
These are role-specific variables, not intended for override:
57+
- `dns_domains`:
58+
- `ceph`: Dynamically derived from the first Ceph host’s domain (e.g., `internal.ceph.tucson.com`).
59+
- `ipmi`: Static IPMI domain (`ipmi.ceph.tucson.com`).
60+
61+
## Secrets File
62+
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`, where `secrets_path` defaults to `/etc/ansible/secrets`.
63+
64+
### Example Secrets File
65+
Create and encrypt the file (e.g., `/etc/ansible/secrets/maas.yml`):
66+
```yaml
67+
---
68+
maas_api_url: "http://X.X.X.X:5240/MAAS/api/2.0/"
69+
maas_api_key: "XXXXXXXXXXXXXXXX"
70+
maas_profile: "admin"
71+
```
72+
73+
**Notes**:
74+
- **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.
75+
- **Security**: Ensure the file is readable only by the Ansible user (e.g., `chmod 600`).
76+
- **Variables**:
77+
- `maas_api_url`: The MAAS API endpoint.
78+
- `maas_api_key`: The API key for authentication.
79+
- `maas_profile`: The profile name used with the MAAS CLI.
80+
- **Vault Password**: Store the password securely (e.g., in `~/.vault_pass.txt` with `chmod 600`) or provide it at runtime.
81+
82+
## Behavior
83+
- **DNS Records**: Creates A records for Ceph nodes (`ip`) and IPMI interfaces (`ipmi`) in `internal.ceph.tucson.com` and `ipmi.ceph.tucson.com`, respectively.
84+
- **Cleanup**: Removes unwanted DNS records and domains not matching the desired state or `allowed_domains`.
85+
- **Idempotency**: Skips actions if the desired state is already met.
86+
87+
## Example Output
88+
Running the playbook should produce output similar to:
89+
```
90+
TASK [maas_nameserver : Debug desired FQDNs] ******************************************
91+
ok: [ceph-vm-14.internal.ceph.tucson.com] => {
92+
"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']"
93+
}
94+
```
95+
96+
## Troubleshooting
97+
- **Secrets Not Loading**: Verify `secrets_path` points to the correct directory and the file is encrypted correctly. Check vault password access.
98+
- **MAAS CLI Errors**: Ensure the MAAS CLI is installed and the API credentials are valid.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
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+
allowed_domains:
5+
- "maas"

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+
- 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+
- name: Define target hosts for DNS records
26+
ansible.builtin.set_fact:
27+
target_hosts: "{{ groups | dict2items | rejectattr('key', 'equalto', 'maas') | map(attribute='value') | flatten | unique }}"
28+
when: groups.keys() | length > 1
29+
30+
- name: Build DNS records for primary interfaces
31+
ansible.builtin.set_fact:
32+
dns_records: "{{ dns_records + [{'name': host.split('.')[0], 'ip': hostvars[host]['ip'], 'type': 'A', 'domain': dns_domains.ceph}] }}"
33+
loop: "{{ target_hosts }}"
34+
loop_control:
35+
loop_var: host
36+
37+
- name: Build DNS records for IPMI interfaces
38+
ansible.builtin.set_fact:
39+
dns_records: "{{ dns_records + [{'name': host.split('.')[0], 'ip': hostvars[host]['ipmi'], 'type': 'A', 'domain': dns_domains.ipmi}] }}"
40+
loop: "{{ target_hosts }}"
41+
loop_control:
42+
loop_var: host
43+
when: "'ipmi' in hostvars[host]"
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+
49+
- name: Debug desired FQDNs
50+
ansible.builtin.debug:
51+
msg: "Desired FQDNs: {{ desired_fqdns }}"
52+
53+
- name: Remove unwanted DNS records
54+
ansible.builtin.command: "maas {{ maas_profile }} dnsresource delete {{ item.id }}"
55+
loop: "{{ existing_resources.stdout | from_json }}"
56+
when: >
57+
item.fqdn not in desired_fqdns and
58+
item.ip_addresses | map(attribute='ip') | first not in (dns_records | map(attribute='ip') | list)
59+
register: dns_deletion
60+
failed_when: dns_deletion.rc != 0 and "does not exist" not in dns_deletion.stderr
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 allowed_domains and
81+
item.name not in dns_domains.values() and
82+
item.resource_record_count == 0
83+
register: domain_deletion
84+
failed_when: domain_deletion.rc != 0 and "does not exist" not in domain_deletion.stderr and "protected foreign keys" not in domain_deletion.stderr
85+
86+
- name: Ensure new DNS domains exist
87+
ansible.builtin.command: "maas {{ maas_profile }} domains create name={{ item.value }}"
88+
loop: "{{ dns_domains | dict2items }}"
89+
when: item.value not in current_domains
90+
register: domain_creation
91+
failed_when: domain_creation.rc != 0 and "already exists" not in domain_creation.stderr
92+
93+
- name: Ensure DNS records exist
94+
ansible.builtin.command: >
95+
maas {{ maas_profile }} dnsresources create
96+
fqdn={{ item.name }}.{{ item.domain }}
97+
ip_addresses={{ item.ip }}
98+
loop: "{{ dns_records }}"
99+
when: >
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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
dns_domains:
3+
ceph: "{{ (groups['ceph'] | first | split('.'))[-4:] | join('.') }}" # internal.ceph.ibm.com
4+
ipmi: "ipmi.ceph.ibm.com" # New IPMI domain

0 commit comments

Comments
 (0)