From 792f4a7f825d7a2b46910a361edb50a476805e7a Mon Sep 17 00:00:00 2001 From: Jamie Helmke Date: Thu, 30 Oct 2025 21:36:38 +0100 Subject: [PATCH] Initial commit --- .gitignore | 25 +++++++++ README.md | 27 ++++++++++ ansible/ansible.cfg | 7 +++ ansible/inventory/hosts.yml | 22 ++++++++ ansible/playbooks/example.yml | 7 +++ requirements.txt | 2 + setup.py | 98 +++++++++++++++++++++++++++++++++++ start_venv.sh | 4 ++ 8 files changed, 192 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 ansible/ansible.cfg create mode 100644 ansible/inventory/hosts.yml create mode 100644 ansible/playbooks/example.yml create mode 100644 requirements.txt create mode 100755 setup.py create mode 100755 start_venv.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95a109c --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Python virtual environment +env/ +venv/ + +# Ansible local data +ansible/lib/collections/ + +# Backups +*backup*/ +*.bak + +# Caches and logs +__pycache__/ +*.pyc +*.pyo +*.retry +*.log + +# macOS system files +.DS_Store + +# Editor and OS stuff +.idea/ +.vscode/ +*.swp diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b201a4 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# ⚡️ Portable Ansible Environment + +A self-contained Ansible workspace for MacOS, Linux and other posix compliant systems. + +## Requirements + +git, python3, pip3 + +## Setup + +1. Clone the repository: + ```bash + git clone https://github.com/jamieahelmke/portable-ansible + cd portable-ansible + ``` +2. Build the environment: + ```bash + python3 setup.py + ``` +3. Activate the environment: + ```bash + source ./start_venv.sh + ``` +4. Run the example playbook: + ```bash + ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/example.yml + ``` \ No newline at end of file diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..de07e39 --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,7 @@ +[defaults] +inventory = ./ansible/inventory/hosts.ini +roles_path = ./ansible/roles +collections_paths = ./ansible/lib/collections +host_key_checking = False +retry_files_enabled = False +stdout_callback = yaml \ No newline at end of file diff --git a/ansible/inventory/hosts.yml b/ansible/inventory/hosts.yml new file mode 100644 index 0000000..a69da88 --- /dev/null +++ b/ansible/inventory/hosts.yml @@ -0,0 +1,22 @@ +# Tips for building inventories +# - Ensure that group names are meaningful and unique. Group names are also case sensitive. +# - Avoid spaces, hyphens, and preceding numbers (use floor_19, not 19th_floor) in group names. +# - Group hosts in your inventory logically according to their What, Where, and When. +# +# Example inventory +#production: +# children: +# web: +# hosts: +# web1.example.com: +# web2.example.com: +# db: +# hosts: +# db1.example.com: +# db2.example.com: + +local: + hosts: + localhost: + ansible_host: localhost + ansible_connection: local diff --git a/ansible/playbooks/example.yml b/ansible/playbooks/example.yml new file mode 100644 index 0000000..e9e21a6 --- /dev/null +++ b/ansible/playbooks/example.yml @@ -0,0 +1,7 @@ +--- +- name: Example Playbook + hosts: local + gather_facts: false + tasks: + - name: Test connection + ansible.builtin.ping: \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6574038 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +ansible +ansible-lint \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..3542e97 --- /dev/null +++ b/setup.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +import os +import platform +import subprocess +import sys +import time + +# --- Config --- +REQUIRED_PATHS = [ + "ansible/ansible.cfg", + "ansible/inventory/", + "ansible/playbooks/", + "requirements.txt", + "start_venv.sh", +] +VENV_DIR = "env/venv" +COLLECTIONS_DIR = "ansible/lib/collections" + +# --- Helper functions --- +def print_step(step, message, end="\n"): + sys.stdout.write(f"[{step}] {message}{end}") + sys.stdout.flush() + +def live_status(message): + sys.stdout.write(f"\r{message}") + sys.stdout.flush() + +def verify_repo_structure(): + missing = [p for p in REQUIRED_PATHS if not os.path.exists(p)] + if missing: + print_step("2/5", "Checking repository structure...") + print(f"ERROR: Missing required file or directory:\n {missing[0]}") + print("\nFix this by running:") + print(" git fetch origin") + print(" git reset --hard origin/main") + sys.exit(1) + print_step("2/5", "Checking repository structure... OK") + +def create_local_dirs(): + print_step("3/5", "Creating local directories...") + os.makedirs("env", exist_ok=True) + os.makedirs(COLLECTIONS_DIR, exist_ok=True) + for d in [ + "ansible/inventory", + "ansible/playbooks", + "ansible/roles", + "ansible/group_vars", + "ansible/host_vars", + ]: + os.makedirs(d, exist_ok=True) + print("Local directories verified or created.") + +def recreate_venv(): + print_step("4/5", "Creating and initializing Python virtual environment...") + if os.path.exists(VENV_DIR): + subprocess.run(["rm", "-rf", VENV_DIR], check=True) + subprocess.run([sys.executable, "-m", "venv", VENV_DIR], check=True) + + pip_path = f"{VENV_DIR}/bin/pip" + live_status("[4/5] Installing dependencies... 0%") + result = subprocess.run([pip_path, "install", "-r", "requirements.txt"], capture_output=True, text=True) + if result.returncode != 0: + print("\nDependency installation failed:") + print(result.stderr) + sys.exit(1) + live_status("[4/5] Installing dependencies... 100%\n") + +def verify_ansible(): + print_step("5/5", "Final checks...") + ansible_path = f"{VENV_DIR}/bin/ansible" + result = subprocess.run([ansible_path, "--version"], capture_output=True, text=True) + if result.returncode == 0: + version_line = result.stdout.splitlines()[0] + print(f"Ansible version: {version_line}") + print("\nSetup complete.\n") + print("Next steps:") + print("1. Activate environment: source ./start_venv.sh") + print("2. Run playbook example: ansible-playbook -i ansible/inventory/hosts.yml ansible/playbooks/example.yml") + print("3. To rebuild: python3 setup_env.py") + else: + print("Ansible verification failed. Check installation.") + +# --- Main --- +def main(): + print_step("1/5", "Detecting operating system...") + system = platform.system() + print(f"Detected OS: {system}") + if system not in ("Darwin", "Linux"): + print("Unsupported OS. Use macOS or Linux.") + sys.exit(1) + + verify_repo_structure() + create_local_dirs() + recreate_venv() + verify_ansible() + +if __name__ == "__main__": + main() diff --git a/start_venv.sh b/start_venv.sh new file mode 100755 index 0000000..17ab307 --- /dev/null +++ b/start_venv.sh @@ -0,0 +1,4 @@ +#!/bin/sh +DIR="$(cd "$(dirname "$0")" && pwd)" +source "$DIR/env/venv/bin/activate" +echo "Virtual environment activated. Type 'deactivate' to exit." \ No newline at end of file