From a20ce14bcef97c20dafce464bea41516d34fd264 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Thu, 28 Nov 2024 18:57:55 -0500 Subject: [PATCH] feat: Begins hpa script --- ansible.cfg | 1 + roles/load-template-vars/tasks/main.yml | 8 + roles/prepare-template-facts/tasks/main.yml | 6 + roles/setup-project/defaults/main.yml | 2 +- roles/setup-project/tasks/main.yml | 2 +- scripts/hpa | 306 ++++++++++++++++++++ 6 files changed, 323 insertions(+), 2 deletions(-) create mode 100755 scripts/hpa diff --git a/ansible.cfg b/ansible.cfg index d2f2029..9da3d76 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -1,3 +1,4 @@ [defaults] inventory = ./inventory.ini roles_path = ./roles +interpreter_python = auto_silent diff --git a/roles/load-template-vars/tasks/main.yml b/roles/load-template-vars/tasks/main.yml index b5c12b3..fe48361 100644 --- a/roles/load-template-vars/tasks/main.yml +++ b/roles/load-template-vars/tasks/main.yml @@ -8,6 +8,8 @@ ansible.builtin.stat: path: "{{ template_dir }}" register: template_dir_stat + tags: + - always - name: Ensure repo. ansible.builtin.git: @@ -15,13 +17,19 @@ dest: "{{ template_dir }}" version: "{{ template.repo.version | default('main') }}" when: template.repo.url is defined and not template_dir_stat.stat.exists + tags: + - always - name: Check for repo vars directory. ansible.builtin.stat: path: "{{ template_vars_path }}" register: repo_vars + tags: + - always - name: Load repo vars if available. ansible.builtin.include_vars: dir: "{{ template_vars_path }}" when: repo_vars.stat.isdir is defined + tags: + - always diff --git a/roles/prepare-template-facts/tasks/main.yml b/roles/prepare-template-facts/tasks/main.yml index 9d7b2bc..efe7b24 100644 --- a/roles/prepare-template-facts/tasks/main.yml +++ b/roles/prepare-template-facts/tasks/main.yml @@ -5,11 +5,17 @@ - name: Set default template path. ansible.builtin.set_fact: repo_template_path: "{{ build_dir }}/template" + tags: + - always - name: Parse template path. ansible.builtin.set_fact: template_dir: "{{ template.path | default(repo_template_path) }}" + tags: + - always - name: Parse template vars path. ansible.builtin.set_fact: template_vars_path: "{{ template_dir }}/{{ template.vars | default('repo_vars') }}" + tags: + - always diff --git a/roles/setup-project/defaults/main.yml b/roles/setup-project/defaults/main.yml index 462d2bc..b040a27 100644 --- a/roles/setup-project/defaults/main.yml +++ b/roles/setup-project/defaults/main.yml @@ -23,7 +23,7 @@ project_dir: "{{ lookup('env', 'PWD') }}" # This is safe to use inside of the project or template specifications # for paths to files that live in the template directory not the project # directory. -template_dir: "" +#template_dir: "" # Files or directories that are copied from the template directory to the project # directory. diff --git a/roles/setup-project/tasks/main.yml b/roles/setup-project/tasks/main.yml index 5ad635b..a49bfdd 100644 --- a/roles/setup-project/tasks/main.yml +++ b/roles/setup-project/tasks/main.yml @@ -58,7 +58,7 @@ - name: Debug copy directory contents. ansible.builtin.debug: - var: unsafe_copy_directory_on_setup + var: copy_directory_on_setup tags: - debug - never diff --git a/scripts/hpa b/scripts/hpa new file mode 100755 index 0000000..1e73328 --- /dev/null +++ b/scripts/hpa @@ -0,0 +1,306 @@ +#!/usr/bin/env zsh +# + +local debug=${DEBUG} +local playbook_filename="main.yml" +local playbook_dir= +local inventory= +local template_dir= +local template_repo= +local template_repo_version= +local quiet= + +# Bold Colors +local BLUE='\x1b[34;1m' +local GREEN='\x1b[32;1m' +local RED='\x1b[31;1m' +local NOCOLOR='\033[0m' + +########################## UTILS ######################### + +function print_color { + local color=$1 + local string=$2 + shift;shift; + printf "%s$color$string$NOCOLOR$@" +} + +function debug_print() { + local label=$1 + shift + local rest="$@" + [ ! "$debug" = "" ] && print_color $BLUE "$label " && print_color $NOCOLOR "$rest\n" +} + +function fail() { + print_color $RED "ERROR: " && print_color $NOCOLOR "$1\n" && exit 1 +} + +function set_verbose() { + if [ ! "$1" = "" ]; then + debug="1" + fi +} + +function set_quiet() { + if [ ! "$1" = "" ]; then + debug= + quiet="1" + fi +} + +function load_config() { + local configs=( + "$HOME/.hparc" + $(find "$XDG_CONFIG_HOME/hpa-playbook/" -type f -maxdepth 1 -mindepth 1 -print0) + "${HPA_CONFIG_DIR}" + "$PWD/.hparc" + ) + + for f in ${configs[@]}; do + [ -f "$f" ] && source "$f" && debug_print "Load Config:" "sourced config: $f" + done +} + +# NOTE: Assumes you've already called load_config. +function ensure_playbook_dir_from_env_or_fail() { + local dir=${HPA_PLAYBOOK_DIR} + + if [ "$dir" = "" ] || [ ! -d "$dir" ]; then + fail "Env Playbook Dir: Failed to find playbook directory" + fi + + [ ! -f "$dir/$playbook_filename" ] && fail "Env Playbook Dir: could not find playbook file." +} + +# NOTE: Assumes playbook_dir and inventory have been setup. +function run_playbook() { + if [ "$quiet" = "" ]; then + ansible-playbook "$playbook_dir/$playbook_filename" \ + --inventory "$inventory" \ + "${HPA_DEFAULT_PLAYBOOK_ARGS}" \ + "$@" + else + ansible-playbook "$playbook_dir/$playbook_filename" \ + --inventory "$inventory" \ + "${HPA_DEFAULT_PLAYBOOK_ARGS}" \ + "$@" 2>&1 1>/dev/null + fi +} + +function parse_playbook_dir_or_fail() { + local opt=$1 + local env=${HPA_PLAYBOOK_DIR} + + if [ ! "$opt" = "" ]; then + if [ -f "$opt" ]; then + debug_print "Parse Playbook:" "using option." + playbook_dir=$(dirname "$opt") + return 0 + elif [ -d "$opt" ]; then + debug_print "Parse Playbook:" "using option." + playbook_dir=$opt + return 0 + else + fail "Parse Playbook Dir: Playbook was not file or directory: $opt" + fi + fi + + ensure_playbook_dir_from_env_or_fail + playbook_dir="$env" +} + +function parse_opt_or_env_var { + local opt=$1 + local env=$2 + + if [ ! "$opt" = "" ]; then + echo $opt + elif [ ! "$env" = "" ]; then + echo $env + fi +} + +# NOTE: assumes playbook_dir has been set. +function parse_inventory_or_fail() { + local label="Parse Inventory:" + + debug_print $label "opt: '$1'" + + local output=$(parse_opt_or_env_var $1 ${HPA_DEFAULT_INVENTORY}) + + debug_print $label "parsed opt or env: '$output'" + + if [ ! "$output" = "" ]; then + [ ! -f "$output" ] && fail "$label inventory is not a file." + debug_print $label "using parsed inventory." + inventory=$output + elif [ -f "$playbook_dir/inventory.ini" ]; then + debug_print $label "using default playbook option." + inventory="$playbook_dir/inventory.ini" + else + fail "$label failed to find inventory file." + fi +} + +# NOTE: assumes load_config has been called. +function parse_template_dir() { + template_dir=$(parse_opt_or_env_var $1 ${HPA_TEMPLATE_DIR}) +} + +# NOTE: assumes load_config has been called. +function parse_repo_or_fail() { + local label="Parse Repo:" + template_repo=$(parse_opt_or_env_var $1 ${HPA_TEMPLATE_REPO}) + + if [ "$template_repo" = "" ]; then + fail "$label failed to find template repo." + fi +} + +# NOTE: assumes load_config has been called. +function parse_repo_version_or_fail() { + local label="Parse Repo Version:" + template_repo_version=$(parse_opt_or_env_var $1 ${HPA_TEMPLATE_VERSION}) + + if [ "$template_repo_version" = "" ]; then + fail "$label failed to find template repo version." + fi +} +########################## SUBCOMMANDS ######################### + +function setup_project() { + function usage() { + cat < [playbook-args...] + + Where: + project-dir : The directory to generate the project in (required). + playbook-args : Extra arguments passed after the project directory get passed directly to the ansible-playbook command. + + Options: + + -b | --branch : The repo branch to clone (ignored if the -t option is used). + -r | --repo : A template repo to use and clone into the project (ignored if the -t option is used). + -t | --template : A local template / repo to use without cloning. + -v | --version : The version of the repo to clone (ignored if the -t option is used). + + Flags: + -h | --help : Show this help page. + -l | --local-template-dir : Force using local template dir, generally used when config holds the information for where to + locate the template directory on the system. + + Global Options: + + -i | --inventory : A custom ansible inventory file to use (optional if calling this script from the playbook directory). + -p | --playbook : Path to the ansible-hpa-playbook (optional if calling this script from the playbook directory). + -q | --quiet : Surpress ansible output to only errors and warnings. + + Global Flags: + --verbose : Increase log output. + + Examples: + + # Setup a project for super-customer using a local template directory. + $ hpa setup-project --template ~/projects/my-template ~/consults/super-customer --ask-vault-pass + + # Setup a project for super-customer using a template repo. + $ hpa setup-project --repo "https://git.example.com/my-template.git" --branch "main" ~/consults/super-customer --ask-vault-pass + +EOF + } + + + local label="Setup Project:" + + zparseopts -D -E - \ + p:=playbookOpt -playbook:=playbookOpt \ + i:=inventoryOpt -inventory:=inventoryOpt \ + t:=template -template-dir:=template \ + r:=repo -repo:=repo \ + v:=version -version:=version \ + b:=version -branch:=version \ + h=help -help=help \ + l=localTemplateDir -local-template-dir=localTemplateDir \ + -verbose=verbose \ + q=quietOpt -quiet=quietOpt + + # exit early and show help + [ ! "$help" = "" ] && usage && exit 0 + + set_verbose $verbose + set_quiet $quietOpt + + debug_print "$label" "begin args: $@" + + parse_playbook_dir_or_fail "${playbookOpt[-1]}" + parse_inventory_or_fail "${inventoryOpt[-1]}" + debug_print "$label" "playbook dir: $playbook_dir" + debug_print "$label" "inventory: $inventory" + + local project_dir="$1" + [ "$project_dir" = "" ] && fail "Setup Project: project directory not supplied" + shift; + + parse_template_dir "${template[-1]}" + local json= + + if [ ! "$localTemplateDir" = "" ]; then + debug_print $label "parsed template dir: '$template_dir'" + if [ "$template_dir" = "" ]; then + fail "$label failed to find template directory." + fi + json="{'template': {'path': '"$template_dir"'}}" + else + parse_repo_or_fail ${repo[-1]} + parse_repo_version_or_fail ${version[-1]} + debug_print $label "parsed repo: '$template_repo'" + debug_print $label "parsed repo version: '$template_repo_version'" + json="{'template': {'repo': {'url': '"$template_repo"', 'version': '"$template_repo_version"' }}}" + fi + + debug_print "$label" "json: $json" + + run_playbook \ + --tags setup-project \ + --extra-vars "$json" \ + --extra-vars "project_dir=$project_dir" \ + "$@" +} + +########################## MAIN ######################### + +function main() { + + zparseopts -D -E - \ + p:=playbookOpt -playbook:=playbookOpt \ + i:=inventoryOpt -inventory:=inventoryOpt \ + -verbose=verbose \ + q=quietOpt -quiet=quietOpt + + set_verbose $verbose + set_quiet $quietOpt + + debug_print "MAIN:" "Begin main: $@" + load_config + debug_print "MAIN:" "Loaded configuration." + + case $1 in + setup-project) + shift + setup_project $playbookOpt $inventoryOpt $verbose $quietOpt $@ + ;; + *) + fail "Fix me!" + esac +} + +debug_print "GLOBAL:" "${@}" +main "${@}"