mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-07-19 23:04:25 +02:00
Restructured CLI logic
This commit is contained in:
parent
8457325b5c
commit
c160c58a5c
6
Makefile
6
Makefile
@ -1,9 +1,9 @@
|
|||||||
ROLES_DIR := ./roles
|
ROLES_DIR := ./roles
|
||||||
APPLICATIONS_OUT := ./group_vars/all/04_applications.yml
|
APPLICATIONS_OUT := ./group_vars/all/04_applications.yml
|
||||||
APPLICATIONS_SCRIPT := ./cli/generate_applications.py
|
APPLICATIONS_SCRIPT := ./cli/generate/defaults/applications.py
|
||||||
USERS_OUT := ./group_vars/all/03_users.yml
|
USERS_OUT := ./group_vars/all/03_users.yml
|
||||||
USERS_SCRIPT := ./cli/generate_users.py
|
USERS_SCRIPT := ./cli/generate/defaults/users.py
|
||||||
INCLUDES_SCRIPT := ./cli/generate_playbook.py
|
INCLUDES_SCRIPT := ./cli/generate/conditional_role_include.py
|
||||||
|
|
||||||
# Define the prefixes for which we want individual role-include files
|
# Define the prefixes for which we want individual role-include files
|
||||||
INCLUDE_GROUPS := "drv-" "svc-" "desk-" "web-" "util-"
|
INCLUDE_GROUPS := "drv-" "svc-" "desk-" "web-" "util-"
|
||||||
|
@ -21,7 +21,7 @@ def run_ansible_playbook(inventory, modes, limit=None, allowed_applications=None
|
|||||||
print("\n🔍 Validating inventory before deployment...\n")
|
print("\n🔍 Validating inventory before deployment...\n")
|
||||||
try:
|
try:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[sys.executable, os.path.join(script_dir, "validate_inventory.py"), os.path.dirname(inventory)],
|
[sys.executable, os.path.join(script_dir, "validate.inventory.py"), os.path.dirname(inventory)],
|
||||||
check=True
|
check=True
|
||||||
)
|
)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
|
0
cli/fix/__init__.py
Normal file
0
cli/fix/__init__.py
Normal file
47
cli/fix/ini_py.py
Normal file
47
cli/fix/ini_py.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
This script creates __init__.py files in every subdirectory under the specified
|
||||||
|
folder relative to the project root.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
def create_init_files(root_folder):
|
||||||
|
"""
|
||||||
|
Walk through all subdirectories of root_folder and create an __init__.py file
|
||||||
|
in each directory if it doesn't already exist.
|
||||||
|
"""
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root_folder):
|
||||||
|
init_file = os.path.join(dirpath, '__init__.py')
|
||||||
|
if not os.path.exists(init_file):
|
||||||
|
open(init_file, 'w').close()
|
||||||
|
print(f"Created: {init_file}")
|
||||||
|
else:
|
||||||
|
print(f"Skipped (already exists): {init_file}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Create __init__.py files in every subdirectory.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'folder',
|
||||||
|
help='Relative path to the target folder (e.g., cli/fix)'
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Determine the absolute path based on the current working directory
|
||||||
|
root_folder = os.path.abspath(args.folder)
|
||||||
|
|
||||||
|
if not os.path.isdir(root_folder):
|
||||||
|
print(f"Error: The folder '{args.folder}' does not exist or is not a directory.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
create_init_files(root_folder)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
0
cli/generate/__init__.py
Normal file
0
cli/generate/__init__.py
Normal file
@ -156,7 +156,7 @@ def print_dependency_tree(graph):
|
|||||||
for root in roots:
|
for root in roots:
|
||||||
print_node(root)
|
print_node(root)
|
||||||
|
|
||||||
def generate_playbook_entries(roles_dir, prefixes=None):
|
def gen_condi_role_incl(roles_dir, prefixes=None):
|
||||||
"""
|
"""
|
||||||
Generate playbook entries based on the sorted order.
|
Generate playbook entries based on the sorted order.
|
||||||
Raises a ValueError if application_id is missing.
|
Raises a ValueError if application_id is missing.
|
||||||
@ -209,7 +209,7 @@ def main():
|
|||||||
print_dependency_tree(graph)
|
print_dependency_tree(graph)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
entries = generate_playbook_entries(args.roles_dir, prefixes)
|
entries = gen_condi_role_incl(args.roles_dir, prefixes)
|
||||||
output = ''.join(entries)
|
output = ''.join(entries)
|
||||||
|
|
||||||
if args.output:
|
if args.output:
|
0
cli/generate/defaults/__init__.py
Normal file
0
cli/generate/defaults/__init__.py
Normal file
@ -6,7 +6,7 @@ import yaml
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
plugin_path = Path(__file__).resolve().parent / ".." / "lookup_plugins"
|
plugin_path = Path(__file__).resolve().parent / ".." / ".." / ".." /"lookup_plugins"
|
||||||
sys.path.insert(0, str(plugin_path))
|
sys.path.insert(0, str(plugin_path))
|
||||||
|
|
||||||
from application_gid import LookupModule
|
from application_gid import LookupModule
|
0
cli/meta/__init__.py
Normal file
0
cli/meta/__init__.py
Normal file
@ -1,105 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import subprocess
|
|
||||||
from ansible.parsing.vault import VaultLib, VaultSecret
|
|
||||||
import sys
|
|
||||||
import yaml
|
|
||||||
import re
|
|
||||||
from utils.handler.vault import VaultScalar
|
|
||||||
from yaml.loader import SafeLoader
|
|
||||||
from yaml.dumper import SafeDumper
|
|
||||||
|
|
||||||
# Register the custom constructor and representer for VaultScalar in PyYAML
|
|
||||||
SafeLoader.add_constructor('!vault', lambda loader, node: VaultScalar(node.value))
|
|
||||||
SafeDumper.add_representer(VaultScalar, lambda dumper, data: dumper.represent_scalar('!vault', data))
|
|
||||||
|
|
||||||
def is_vault_encrypted_data(data: str) -> bool:
|
|
||||||
"""Check if the given data is encrypted with Ansible Vault by looking for the vault header."""
|
|
||||||
return data.lstrip().startswith('$ANSIBLE_VAULT')
|
|
||||||
|
|
||||||
def decrypt_vault_data(encrypted_data: str, vault_secret: VaultSecret) -> str:
|
|
||||||
"""
|
|
||||||
Decrypt the given encrypted data using the provided vault_secret.
|
|
||||||
:param encrypted_data: Encrypted string to be decrypted
|
|
||||||
:param vault_secret: The VaultSecret instance used to decrypt the data
|
|
||||||
:return: Decrypted data as a string
|
|
||||||
"""
|
|
||||||
vault = VaultLib()
|
|
||||||
decrypted_data = vault.decrypt(encrypted_data, vault_secret)
|
|
||||||
return decrypted_data
|
|
||||||
|
|
||||||
def decrypt_vault_file(vault_file: str, vault_password_file: str):
|
|
||||||
"""
|
|
||||||
Decrypt the Ansible Vault file and return its contents.
|
|
||||||
:param vault_file: Path to the encrypted Ansible Vault file
|
|
||||||
:param vault_password_file: Path to the file containing the Vault password
|
|
||||||
:return: Decrypted contents of the Vault file
|
|
||||||
"""
|
|
||||||
# Read the vault password
|
|
||||||
with open(vault_password_file, 'r') as f:
|
|
||||||
vault_password = f.read().strip()
|
|
||||||
|
|
||||||
# Create a VaultSecret instance from the password
|
|
||||||
vault_secret = VaultSecret(vault_password.encode())
|
|
||||||
|
|
||||||
# Read the encrypted file
|
|
||||||
with open(vault_file, 'r') as f:
|
|
||||||
file_content = f.read()
|
|
||||||
|
|
||||||
# If the file is partially encrypted, we'll decrypt only the encrypted values
|
|
||||||
decrypted_data = file_content # Start with the unmodified content
|
|
||||||
|
|
||||||
# Find all vault-encrypted values (i.e., values starting with $ANSIBLE_VAULT)
|
|
||||||
encrypted_values = re.findall(r'^\s*([\w\.\-_]+):\s*["\']?\$ANSIBLE_VAULT[^\n]+', file_content, flags=re.MULTILINE)
|
|
||||||
|
|
||||||
# If there are encrypted values, decrypt them
|
|
||||||
for value in encrypted_values:
|
|
||||||
# Extract the encrypted value and decrypt it
|
|
||||||
encrypted_value = re.search(r'(["\']?\$ANSIBLE_VAULT[^\n]+)', value)
|
|
||||||
if encrypted_value:
|
|
||||||
# Remove any newlines or extra spaces from the encrypted value
|
|
||||||
encrypted_value = encrypted_value.group(0).replace('\n', '').replace('\r', '')
|
|
||||||
decrypted_value = decrypt_vault_data(encrypted_value, vault_secret)
|
|
||||||
# Replace the encrypted value with the decrypted value in the content
|
|
||||||
decrypted_data = decrypted_data.replace(encrypted_value, decrypted_value.strip())
|
|
||||||
|
|
||||||
return decrypted_data
|
|
||||||
|
|
||||||
def decrypt_and_display(vault_file: str, vault_password_file: str):
|
|
||||||
"""
|
|
||||||
Decrypts the Ansible Vault file and its values, then display the result.
|
|
||||||
Supports both full file and partial value encryption.
|
|
||||||
:param vault_file: Path to the encrypted Ansible Vault file
|
|
||||||
:param vault_password_file: Path to the file containing the Vault password
|
|
||||||
"""
|
|
||||||
decrypted_data = decrypt_vault_file(vault_file, vault_password_file)
|
|
||||||
|
|
||||||
# Convert the decrypted data to a string format (YAML or JSON)
|
|
||||||
output_data = yaml.dump(yaml.safe_load(decrypted_data), default_flow_style=False)
|
|
||||||
|
|
||||||
# Use subprocess to call `less` for paginated, scrollable output
|
|
||||||
subprocess.run(["less"], input=output_data, text=True)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Set up the argument parser
|
|
||||||
parser = argparse.ArgumentParser(description="Decrypt and display variables from an Ansible Vault file.")
|
|
||||||
|
|
||||||
# Add arguments for the vault file and vault password file
|
|
||||||
parser.add_argument(
|
|
||||||
'vault_file',
|
|
||||||
type=str,
|
|
||||||
help="Path to the encrypted Ansible Vault file"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'vault_password_file',
|
|
||||||
type=str,
|
|
||||||
help="Path to the file containing the Vault password"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse the arguments
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Display vault variables in a scrollable manner
|
|
||||||
decrypt_and_display(args.vault_file, args.vault_password_file)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
0
cli/validate/__init__.py
Normal file
0
cli/validate/__init__.py
Normal file
@ -2,7 +2,7 @@ import os
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
# import the functions from your CLI script
|
# import the functions from your CLI script
|
||||||
from cli.generate_playbook import build_dependency_graph, find_cycle
|
from cli.generate.conditional_role_include import build_dependency_graph, find_cycle
|
||||||
|
|
||||||
class TestCircularDependencies(unittest.TestCase):
|
class TestCircularDependencies(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
|
0
tests/unit/cli/create/__init__.py
Normal file
0
tests/unit/cli/create/__init__.py
Normal file
@ -10,7 +10,7 @@ dir_path = os.path.abspath(
|
|||||||
sys.path.insert(0, dir_path)
|
sys.path.insert(0, dir_path)
|
||||||
|
|
||||||
# Import functions and classes to test
|
# Import functions and classes to test
|
||||||
from create_credentials import ask_for_confirmation, main
|
from cli.create.credentials import ask_for_confirmation, main
|
||||||
from utils.handler.vault import VaultHandler, VaultScalar
|
from utils.handler.vault import VaultHandler, VaultScalar
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -81,7 +81,7 @@ class TestCreateCredentials(unittest.TestCase):
|
|||||||
with mock.patch('subprocess.run', return_value=completed):
|
with mock.patch('subprocess.run', return_value=completed):
|
||||||
# Run main with override for credentials.api_key and force to skip prompt
|
# Run main with override for credentials.api_key and force to skip prompt
|
||||||
sys.argv = [
|
sys.argv = [
|
||||||
'create_credentials.py',
|
'create/credentials.py',
|
||||||
'--role-path', role_path,
|
'--role-path', role_path,
|
||||||
'--inventory-file', inventory_file,
|
'--inventory-file', inventory_file,
|
||||||
'--vault-password-file', vault_pw_file,
|
'--vault-password-file', vault_pw_file,
|
@ -6,7 +6,7 @@ import tempfile
|
|||||||
from ruamel.yaml import YAML
|
from ruamel.yaml import YAML
|
||||||
|
|
||||||
# Import functions to test; adjust path as needed
|
# Import functions to test; adjust path as needed
|
||||||
from cli.create_web_app import (
|
from cli.create.role import (
|
||||||
get_next_network,
|
get_next_network,
|
||||||
get_next_port,
|
get_next_port,
|
||||||
load_yaml_with_comments,
|
load_yaml_with_comments,
|
0
tests/unit/cli/fix/__init__.py
Normal file
0
tests/unit/cli/fix/__init__.py
Normal file
@ -1,4 +1,4 @@
|
|||||||
# tests/cli/test_ensure_vars_main.py
|
# tests/cli/test_fix/vars_main_files.py
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -6,7 +6,7 @@ import unittest
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
# Adjust this import to match the real path in your project
|
# Adjust this import to match the real path in your project
|
||||||
from cli.ensure_vars_main import run, ROLES_DIR
|
from cli.ensure.vars_main_files import run, ROLES_DIR
|
||||||
|
|
||||||
class TestEnsureVarsMain(unittest.TestCase):
|
class TestEnsureVarsMain(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -17,11 +17,11 @@ class TestEnsureVarsMain(unittest.TestCase):
|
|||||||
|
|
||||||
# Monkey-patch the module's ROLES_DIR to point here
|
# Monkey-patch the module's ROLES_DIR to point here
|
||||||
self._orig_roles_dir = ROLES_DIR
|
self._orig_roles_dir = ROLES_DIR
|
||||||
setattr(__import__("cli.ensure_vars_main", fromlist=["ROLES_DIR"]), "ROLES_DIR", self.roles_dir)
|
setattr(__import__("cli.ensure.vars_main_files", fromlist=["ROLES_DIR"]), "ROLES_DIR", self.roles_dir)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# restore and cleanup
|
# restore and cleanup
|
||||||
setattr(__import__("cli.ensure_vars_main", fromlist=["ROLES_DIR"]), "ROLES_DIR", self._orig_roles_dir)
|
setattr(__import__("cli.ensure.vars_main_files", fromlist=["ROLES_DIR"]), "ROLES_DIR", self._orig_roles_dir)
|
||||||
shutil.rmtree(self.tmpdir)
|
shutil.rmtree(self.tmpdir)
|
||||||
|
|
||||||
def _make_role(self, name, vars_content=None):
|
def _make_role(self, name, vars_content=None):
|
0
tests/unit/cli/generate/__init__.py
Normal file
0
tests/unit/cli/generate/__init__.py
Normal file
@ -7,9 +7,9 @@ import shutil
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
# Adjust path to include cli/ folder
|
# Adjust path to include cli/ folder
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../..", "cli")))
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..", "cli")))
|
||||||
|
|
||||||
from generate_playbook import build_dependency_graph, topological_sort, generate_playbook_entries
|
from cli.generate.conditional_role_include import build_dependency_graph, topological_sort, gen_condi_role_incl
|
||||||
|
|
||||||
class TestGeneratePlaybook(unittest.TestCase):
|
class TestGeneratePlaybook(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -66,8 +66,8 @@ class TestGeneratePlaybook(unittest.TestCase):
|
|||||||
# The expected order must be a → b → c, d can be anywhere before or after
|
# The expected order must be a → b → c, d can be anywhere before or after
|
||||||
self.assertTrue(sorted_roles.index('role-a') < sorted_roles.index('role-b') < sorted_roles.index('role-c'))
|
self.assertTrue(sorted_roles.index('role-a') < sorted_roles.index('role-b') < sorted_roles.index('role-c'))
|
||||||
|
|
||||||
def test_generate_playbook_entries(self):
|
def test_gen_condi_role_incl(self):
|
||||||
entries = generate_playbook_entries(self.temp_dir)
|
entries = gen_condi_role_incl(self.temp_dir)
|
||||||
|
|
||||||
text = ''.join(entries)
|
text = ''.join(entries)
|
||||||
self.assertIn("setup a", text)
|
self.assertIn("setup a", text)
|
0
tests/unit/cli/generate/defaults/__init__.py
Normal file
0
tests/unit/cli/generate/defaults/__init__.py
Normal file
@ -30,7 +30,7 @@ class TestGenerateDefaultApplications(unittest.TestCase):
|
|||||||
shutil.rmtree(self.temp_dir)
|
shutil.rmtree(self.temp_dir)
|
||||||
|
|
||||||
def test_script_generates_expected_yaml(self):
|
def test_script_generates_expected_yaml(self):
|
||||||
script_path = Path(__file__).resolve().parent.parent.parent.parent / "cli" / "generate_applications.py"
|
script_path = Path(__file__).resolve().parent.parent.parent.parent.parent.parent / "cli/generate/defaults/applications.py"
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[
|
[
|
@ -45,7 +45,7 @@ class TestGenerateDefaultApplicationsUsers(unittest.TestCase):
|
|||||||
When a users.yml exists with defined users, the script should inject a 'users'
|
When a users.yml exists with defined users, the script should inject a 'users'
|
||||||
mapping in the generated YAML, mapping each username to a Jinja2 reference.
|
mapping in the generated YAML, mapping each username to a Jinja2 reference.
|
||||||
"""
|
"""
|
||||||
script_path = Path(__file__).resolve().parents[3] / "cli" / "generate_applications.py"
|
script_path = Path(__file__).resolve().parents[5] / "cli" / "generate/defaults/applications.py"
|
||||||
result = subprocess.run([
|
result = subprocess.run([
|
||||||
"python3", str(script_path),
|
"python3", str(script_path),
|
||||||
"--roles-dir", str(self.roles_dir),
|
"--roles-dir", str(self.roles_dir),
|
@ -7,9 +7,9 @@ import yaml
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
# Add cli/ to import path
|
# Add cli/ to import path
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../..", "cli")))
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../..", "cli/generate/defaults/")))
|
||||||
|
|
||||||
import generate_users
|
import users
|
||||||
|
|
||||||
class TestGenerateUsers(unittest.TestCase):
|
class TestGenerateUsers(unittest.TestCase):
|
||||||
def test_build_users_auto_increment_and_overrides(self):
|
def test_build_users_auto_increment_and_overrides(self):
|
||||||
@ -18,24 +18,24 @@ class TestGenerateUsers(unittest.TestCase):
|
|||||||
'bob': {'uid': 2000, 'email': 'bob@custom.com', 'description': 'Custom user'},
|
'bob': {'uid': 2000, 'email': 'bob@custom.com', 'description': 'Custom user'},
|
||||||
'carol': {}
|
'carol': {}
|
||||||
}
|
}
|
||||||
users = generate_users.build_users(
|
build = users.build_users(
|
||||||
defs=defs,
|
defs=defs,
|
||||||
primary_domain='example.com',
|
primary_domain='example.com',
|
||||||
start_id=1001,
|
start_id=1001,
|
||||||
become_pwd='pw'
|
become_pwd='pw'
|
||||||
)
|
)
|
||||||
# alice should get uid/gid 1001
|
# alice should get uid/gid 1001
|
||||||
self.assertEqual(users['alice']['uid'], 1001)
|
self.assertEqual(build['alice']['uid'], 1001)
|
||||||
self.assertEqual(users['alice']['gid'], 1001)
|
self.assertEqual(build['alice']['gid'], 1001)
|
||||||
self.assertEqual(users['alice']['email'], 'alice@example.com')
|
self.assertEqual(build['alice']['email'], 'alice@example.com')
|
||||||
# bob overrides
|
# bob overrides
|
||||||
self.assertEqual(users['bob']['uid'], 2000)
|
self.assertEqual(build['bob']['uid'], 2000)
|
||||||
self.assertEqual(users['bob']['gid'], 2000)
|
self.assertEqual(build['bob']['gid'], 2000)
|
||||||
self.assertEqual(users['bob']['email'], 'bob@custom.com')
|
self.assertEqual(build['bob']['email'], 'bob@custom.com')
|
||||||
self.assertIn('description', users['bob'])
|
self.assertIn('description', build['bob'])
|
||||||
# carol should get next free id = 1002
|
# carol should get next free id = 1002
|
||||||
self.assertEqual(users['carol']['uid'], 1002)
|
self.assertEqual(build['carol']['uid'], 1002)
|
||||||
self.assertEqual(users['carol']['gid'], 1002)
|
self.assertEqual(build['carol']['gid'], 1002)
|
||||||
|
|
||||||
def test_build_users_default_lookup_password(self):
|
def test_build_users_default_lookup_password(self):
|
||||||
"""
|
"""
|
||||||
@ -44,14 +44,14 @@ class TestGenerateUsers(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
defs = {'frank': {}}
|
defs = {'frank': {}}
|
||||||
lookup_template = '{{ lookup("password", "/dev/null length=42 chars=ascii_letters,digits") }}'
|
lookup_template = '{{ lookup("password", "/dev/null length=42 chars=ascii_letters,digits") }}'
|
||||||
users = generate_users.build_users(
|
build = users.build_users(
|
||||||
defs=defs,
|
defs=defs,
|
||||||
primary_domain='example.com',
|
primary_domain='example.com',
|
||||||
start_id=1001,
|
start_id=1001,
|
||||||
become_pwd=lookup_template
|
become_pwd=lookup_template
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
users['frank']['password'],
|
build['frank']['password'],
|
||||||
lookup_template,
|
lookup_template,
|
||||||
"The lookup template string was not correctly applied as the default password"
|
"The lookup template string was not correctly applied as the default password"
|
||||||
)
|
)
|
||||||
@ -63,14 +63,14 @@ class TestGenerateUsers(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
defs = {'eva': {'password': 'custompw'}}
|
defs = {'eva': {'password': 'custompw'}}
|
||||||
lookup_template = '{{ lookup("password", "/dev/null length=42 chars=ascii_letters,digits") }}'
|
lookup_template = '{{ lookup("password", "/dev/null length=42 chars=ascii_letters,digits") }}'
|
||||||
users = generate_users.build_users(
|
build = users.build_users(
|
||||||
defs=defs,
|
defs=defs,
|
||||||
primary_domain='example.com',
|
primary_domain='example.com',
|
||||||
start_id=1001,
|
start_id=1001,
|
||||||
become_pwd=lookup_template
|
become_pwd=lookup_template
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
users['eva']['password'],
|
build['eva']['password'],
|
||||||
'custompw',
|
'custompw',
|
||||||
"The override password was not correctly applied"
|
"The override password was not correctly applied"
|
||||||
)
|
)
|
||||||
@ -82,7 +82,7 @@ class TestGenerateUsers(unittest.TestCase):
|
|||||||
'u2': {'uid': 1001}
|
'u2': {'uid': 1001}
|
||||||
}
|
}
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
generate_users.build_users(defs, 'ex.com', 1001, 'pw')
|
users.build_users(defs, 'ex.com', 1001, 'pw')
|
||||||
|
|
||||||
def test_build_users_shared_gid_allowed(self):
|
def test_build_users_shared_gid_allowed(self):
|
||||||
# Allow two users to share the same GID when one overrides gid and the other uses that as uid
|
# Allow two users to share the same GID when one overrides gid and the other uses that as uid
|
||||||
@ -90,10 +90,10 @@ class TestGenerateUsers(unittest.TestCase):
|
|||||||
'a': {'uid': 1500},
|
'a': {'uid': 1500},
|
||||||
'b': {'gid': 1500}
|
'b': {'gid': 1500}
|
||||||
}
|
}
|
||||||
users = generate_users.build_users(defs, 'ex.com', 1500, 'pw')
|
build = users.build_users(defs, 'ex.com', 1500, 'pw')
|
||||||
# Both should have gid 1500
|
# Both should have gid 1500
|
||||||
self.assertEqual(users['a']['gid'], 1500)
|
self.assertEqual(build['a']['gid'], 1500)
|
||||||
self.assertEqual(users['b']['gid'], 1500)
|
self.assertEqual(build['b']['gid'], 1500)
|
||||||
|
|
||||||
def test_build_users_duplicate_username_email(self):
|
def test_build_users_duplicate_username_email(self):
|
||||||
defs = {
|
defs = {
|
||||||
@ -102,11 +102,11 @@ class TestGenerateUsers(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
# second user with same username should raise
|
# second user with same username should raise
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
generate_users.build_users(defs, 'ex.com', 1001, 'pw')
|
users.build_users(defs, 'ex.com', 1001, 'pw')
|
||||||
|
|
||||||
def test_dictify_converts_ordereddict(self):
|
def test_dictify_converts_ordereddict(self):
|
||||||
od = generate_users.OrderedDict([('a', 1), ('b', {'c': 2})])
|
od = users.OrderedDict([('a', 1), ('b', {'c': 2})])
|
||||||
result = generate_users.dictify(OrderedDict(od))
|
result = users.dictify(OrderedDict(od))
|
||||||
self.assertIsInstance(result, dict)
|
self.assertIsInstance(result, dict)
|
||||||
self.assertEqual(result, {'a': 1, 'b': {'c': 2}})
|
self.assertEqual(result, {'a': 1, 'b': {'c': 2}})
|
||||||
|
|
||||||
@ -122,13 +122,13 @@ class TestGenerateUsers(unittest.TestCase):
|
|||||||
# role2 defines same user x with same value
|
# role2 defines same user x with same value
|
||||||
with open(os.path.join(tmp, 'role2/users/main.yml'), 'w') as f:
|
with open(os.path.join(tmp, 'role2/users/main.yml'), 'w') as f:
|
||||||
yaml.safe_dump({'users': {'x': {'email': 'x@a'}}}, f)
|
yaml.safe_dump({'users': {'x': {'email': 'x@a'}}}, f)
|
||||||
defs = generate_users.load_user_defs(tmp)
|
defs = users.load_user_defs(tmp)
|
||||||
self.assertIn('x', defs)
|
self.assertIn('x', defs)
|
||||||
# now conflict definition
|
# now conflict definition
|
||||||
with open(os.path.join(tmp, 'role2/users/main.yml'), 'w') as f:
|
with open(os.path.join(tmp, 'role2/users/main.yml'), 'w') as f:
|
||||||
yaml.safe_dump({'users': {'x': {'email': 'x@b'}}}, f)
|
yaml.safe_dump({'users': {'x': {'email': 'x@b'}}}, f)
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
generate_users.load_user_defs(tmp)
|
users.load_user_defs(tmp)
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(tmp)
|
shutil.rmtree(tmp)
|
||||||
|
|
@ -16,7 +16,7 @@ sys.path.insert(
|
|||||||
|
|
||||||
from utils.handler.yaml import YamlHandler
|
from utils.handler.yaml import YamlHandler
|
||||||
from utils.handler.vault import VaultHandler, VaultScalar
|
from utils.handler.vault import VaultHandler, VaultScalar
|
||||||
from cli.utils.manager.inventory import InventoryManager
|
from utils.manager.inventory import InventoryManager
|
||||||
|
|
||||||
|
|
||||||
class TestInventoryManager(unittest.TestCase):
|
class TestInventoryManager(unittest.TestCase):
|
||||||
|
0
tests/unit/cli/validate/__init__.py
Normal file
0
tests/unit/cli/validate/__init__.py
Normal file
@ -7,7 +7,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
SCRIPT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../cli/validate_inventory.py"))
|
SCRIPT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../cli/validate/inventory.py"))
|
||||||
|
|
||||||
class TestValidateInventory(unittest.TestCase):
|
class TestValidateInventory(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
0
utils/__init__.py
Normal file
0
utils/__init__.py
Normal file
0
utils/handler/__init__.py
Normal file
0
utils/handler/__init__.py
Normal file
0
utils/manager/__init__.py
Normal file
0
utils/manager/__init__.py
Normal file
Loading…
x
Reference in New Issue
Block a user