updated initialization steps - added ansible role audit
This commit is contained in:
parent
e1e3eacbed
commit
06849a5927
|
|
@ -0,0 +1,39 @@
|
||||||
|
# 🛫 Preflight Role
|
||||||
|
|
||||||
|
This Ansible role performs pre-deployment checks to ensure that all required configuration values and infrastructure prerequisites are present and valid before continuing with the OpenCMMC Stack deployment.
|
||||||
|
|
||||||
|
## ✅ Features
|
||||||
|
|
||||||
|
- Validates required variables are defined
|
||||||
|
- Ensures SSH public key format is correct
|
||||||
|
- Checks email formatting using regex
|
||||||
|
- Verifies DNS resolution for target domain
|
||||||
|
- Logs all validation steps to the `evidence/99_preflight/` directory
|
||||||
|
|
||||||
|
## 📂 Evidence Artifacts
|
||||||
|
|
||||||
|
Validation checks are written to:
|
||||||
|
|
||||||
|
```
|
||||||
|
evidence/
|
||||||
|
└── 99_preflight/
|
||||||
|
└── validation_checks.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Tags
|
||||||
|
|
||||||
|
Use with:
|
||||||
|
```bash
|
||||||
|
ansible-playbook site.yml --tags preflight
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Variables Checked
|
||||||
|
|
||||||
|
- `default_user`
|
||||||
|
- `ssh_authorized_key`
|
||||||
|
- `domain_name`
|
||||||
|
- `hostname`
|
||||||
|
- `mailcow_admin_user`
|
||||||
|
- `mailcow_admin_password`
|
||||||
|
- `mailcow_fqdn`
|
||||||
|
- `mailcow_letsencrypt_email`
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
evidence_path: "../../evidence"
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
author: OpenCMMC Team
|
||||||
|
description: Validates required deployment inputs before running any roles
|
||||||
|
license: MIT
|
||||||
|
min_ansible_version: "2.10"
|
||||||
|
platforms:
|
||||||
|
- name: Ubuntu
|
||||||
|
versions:
|
||||||
|
- 22.04
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
- name: Run preflight variable validation
|
||||||
|
include_tasks: validate_vars.yml
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
---
|
||||||
|
- name: Assert required variables are set
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- default_user is defined
|
||||||
|
- ssh_authorized_key is defined
|
||||||
|
- domain_name is defined
|
||||||
|
- hostname is defined
|
||||||
|
- mailcow_admin_user is defined
|
||||||
|
- mailcow_admin_password is defined
|
||||||
|
- mailcow_fqdn is defined
|
||||||
|
fail_msg: "One or more required variables are missing. Check deployment_config.yml or group_vars/all.yml"
|
||||||
|
success_msg: "All required variables are present"
|
||||||
|
|
||||||
|
- name: Validate SSH public key format
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- ssh_authorized_key is match("^(ssh-(rsa|ed25519)|ecdsa-sha2-nistp[0-9]+) [A-Za-z0-9+/=]+( .*)?$")
|
||||||
|
fail_msg: "SSH public key format is invalid"
|
||||||
|
success_msg: "SSH public key format appears valid"
|
||||||
|
|
||||||
|
- name: Validate email format
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- mailcow_letsencrypt_email is match("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$")
|
||||||
|
fail_msg: "Mailcow Let's Encrypt email address is not valid"
|
||||||
|
success_msg: "Mailcow Let's Encrypt email format is valid"
|
||||||
|
|
||||||
|
- name: Check if domain_name resolves
|
||||||
|
command: "getent hosts {{ domain_name }}"
|
||||||
|
register: domain_lookup
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Assert domain_name resolves in DNS
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- domain_lookup.rc == 0
|
||||||
|
fail_msg: "Domain name {{ domain_name }} does not resolve. Verify DNS or /etc/hosts."
|
||||||
|
success_msg: "Domain name {{ domain_name }} resolves successfully"
|
||||||
|
|
||||||
|
- name: Log preflight validation results
|
||||||
|
copy:
|
||||||
|
dest: "{{ evidence_path }}/99_preflight/validation_checks.md"
|
||||||
|
content: |
|
||||||
|
# ✅ Preflight Validation Checks
|
||||||
|
|
||||||
|
**Status:** PASSED
|
||||||
|
Timestamp: {{ ansible_date_time.iso8601 }}
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
- default_user: `{{ default_user }}`
|
||||||
|
- domain_name: `{{ domain_name }}`
|
||||||
|
- hostname: `{{ hostname }}`
|
||||||
|
- mailcow_admin_user: `{{ mailcow_admin_user }}`
|
||||||
|
- mailcow_fqdn: `{{ mailcow_fqdn }}`
|
||||||
|
- mailcow_letsencrypt_email: `{{ mailcow_letsencrypt_email }}`
|
||||||
|
|
||||||
|
## DNS Resolution
|
||||||
|
- Domain `{{ domain_name }}` resolved to: `{{ domain_lookup.stdout | default('N/A') }}`
|
||||||
|
|
||||||
|
## SSH Key Format
|
||||||
|
- SSH key validated against standard format
|
||||||
|
|
||||||
|
## Email Format
|
||||||
|
- Email passed regex validation
|
||||||
|
|
||||||
|
mode: '0644'
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
EXPECTED_ROLE_FILES = [
|
||||||
|
"tasks/main.yml",
|
||||||
|
"defaults/main.yml",
|
||||||
|
"meta/main.yml",
|
||||||
|
]
|
||||||
|
|
||||||
|
OPTIONAL_FILES = [
|
||||||
|
"handlers/main.yml",
|
||||||
|
"templates/",
|
||||||
|
"files/",
|
||||||
|
"vars/main.yml",
|
||||||
|
"molecule/default/converge.yml",
|
||||||
|
"molecule/default/verify.yml",
|
||||||
|
]
|
||||||
|
|
||||||
|
PLACEHOLDER_TERMS = ["TODO", "FILL_ME_IN", "REPLACE_ME"]
|
||||||
|
|
||||||
|
def is_placeholder(file_path):
|
||||||
|
try:
|
||||||
|
with open(file_path, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
return any(term in content for term in PLACEHOLDER_TERMS)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_non_empty(file_path):
|
||||||
|
try:
|
||||||
|
return os.path.getsize(file_path) > 0
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def audit_role(role_path):
|
||||||
|
role_name = os.path.basename(role_path)
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
for rel_path in EXPECTED_ROLE_FILES:
|
||||||
|
full_path = os.path.join(role_path, rel_path)
|
||||||
|
if not os.path.isfile(full_path):
|
||||||
|
issues.append(f"❌ MISSING: {rel_path}")
|
||||||
|
elif not is_non_empty(full_path):
|
||||||
|
issues.append(f"⚠️ EMPTY: {rel_path}")
|
||||||
|
elif is_placeholder(full_path):
|
||||||
|
issues.append(f"⚠️ PLACEHOLDER: {rel_path}")
|
||||||
|
|
||||||
|
for rel_path in OPTIONAL_FILES:
|
||||||
|
full_path = os.path.join(role_path, rel_path)
|
||||||
|
if os.path.exists(full_path) and os.path.isfile(full_path):
|
||||||
|
if not is_non_empty(full_path):
|
||||||
|
issues.append(f"⚠️ OPTIONAL EMPTY: {rel_path}")
|
||||||
|
elif is_placeholder(full_path):
|
||||||
|
issues.append(f"⚠️ OPTIONAL PLACEHOLDER: {rel_path}")
|
||||||
|
|
||||||
|
return role_name, issues
|
||||||
|
|
||||||
|
def main():
|
||||||
|
base_path = Path("ansible/roles/")
|
||||||
|
if not base_path.exists():
|
||||||
|
print("❗ 'ansible/roles/' directory not found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("🔍 Auditing roles in:", base_path)
|
||||||
|
for role_dir in base_path.iterdir():
|
||||||
|
if role_dir.is_dir():
|
||||||
|
role_name, issues = audit_role(role_dir)
|
||||||
|
if issues:
|
||||||
|
print(f"\n🔎 Role: {role_name}")
|
||||||
|
for issue in issues:
|
||||||
|
print(" ", issue)
|
||||||
|
else:
|
||||||
|
print(f"✅ Role: {role_name} — All checks passed.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
CONFIG_PATH = "deployment/deployment_config.yml"
|
||||||
|
|
||||||
|
def prompt_input(prompt, default=None, required=True):
|
||||||
|
while True:
|
||||||
|
val = input(f"{prompt}{' [' + default + ']' if default else ''}: ").strip()
|
||||||
|
if val:
|
||||||
|
return val
|
||||||
|
elif default is not None:
|
||||||
|
return default
|
||||||
|
elif not required:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
print("This field is required.")
|
||||||
|
|
||||||
|
def write_config(config, path=CONFIG_PATH):
|
||||||
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
|
with open(path, "w") as f:
|
||||||
|
yaml.dump(config, f)
|
||||||
|
print(f"\n✅ Config written to {path}")
|
||||||
|
|
||||||
|
def run_terraform():
|
||||||
|
print("\n🚀 Running Terraform provisioning...\n")
|
||||||
|
subprocess.run(["terraform", "init"], cwd="terraform")
|
||||||
|
subprocess.run(["terraform", "apply", "-auto-approve"], cwd="terraform")
|
||||||
|
|
||||||
|
def run_ansible():
|
||||||
|
print("\n🔧 Running Ansible deployment...\n")
|
||||||
|
subprocess.run([
|
||||||
|
"ansible-playbook", "-i", "inventory/terraform_inventory.yml", "site.yml"
|
||||||
|
])
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("🛡️ OpenCMMC Stack: Guided Deployment")
|
||||||
|
config = {
|
||||||
|
"cloud_provider": prompt_input("Cloud Provider (aws, azure, gcp, do, proxmox, bare-metal)"),
|
||||||
|
"fqdn": prompt_input("Root Fully Qualified Domain Name (e.g., open-cmmc.example.com)"),
|
||||||
|
"domain_name": prompt_input("Internal Domain Name (e.g., example.cmmc.local)"),
|
||||||
|
"admin_email": prompt_input("Administrator email (for certs and contact)"),
|
||||||
|
"admin_user": prompt_input("Admin system username", default="cmmcadmin"),
|
||||||
|
"ssh_pubkey_path": prompt_input("Path to SSH public key", default="~/.ssh/id_rsa.pub"),
|
||||||
|
"keycloak_realm": prompt_input("Keycloak Realm", default="OpenCMMC"),
|
||||||
|
"tailscale_key": prompt_input("Tailscale Auth Key", required=False),
|
||||||
|
}
|
||||||
|
|
||||||
|
write_config(config)
|
||||||
|
|
||||||
|
if prompt_input("Proceed with Terraform provisioning?", default="y").lower() == "y":
|
||||||
|
run_terraform()
|
||||||
|
|
||||||
|
if prompt_input("Proceed with Ansible deployment?", default="y").lower() == "y":
|
||||||
|
run_ansible()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# ✅ Preflight Validation Checks
|
||||||
|
|
||||||
|
**Status:** PASSED
|
||||||
|
Timestamp: {{ ansible_date_time.iso8601 }}
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
- default_user: `{{ default_user }}`
|
||||||
|
- domain_name: `{{ domain_name }}`
|
||||||
|
- hostname: `{{ hostname }}`
|
||||||
|
- mailcow_admin_user: `{{ mailcow_admin_user }}`
|
||||||
|
- mailcow_fqdn: `{{ mailcow_fqdn }}`
|
||||||
|
- mailcow_letsencrypt_email: `{{ mailcow_letsencrypt_email }}`
|
||||||
|
|
||||||
|
## DNS Resolution
|
||||||
|
- Domain `{{ domain_name }}` resolved to: `{{ domain_lookup.stdout | default('N/A') }}`
|
||||||
|
|
||||||
|
## SSH Key Format
|
||||||
|
- SSH key validated against standard format
|
||||||
|
|
||||||
|
## Email Format
|
||||||
|
- Email passed regex validation
|
||||||
Loading…
Reference in New Issue