# ansible-init OMZ plugin # Path: ~/.oh-my-zsh/custom/plugins/ansible-init/ansible-init.plugin.zsh # Features: # - ansible-init new [NAME] [PATH] [--minimal|--standard|--full] [--force] # - ansible-init group add [PROJECT] # - ansible-init host add [PROJECT] # - works with "new ." to scaffold in current directory # - completions + extensions loader # --- Config ------------------------------------------------------------------- : ${ANSIBLE_INIT_BASE:="$HOME/Downloads/"} # default projects root : ${ANSIBLE_INIT_DEFAULT_NAME:="ansible_temp"} # default project name : ${ANSIBLE_INIT_MODE:="standard"} # minimal|standard|full # --- Utils -------------------------------------------------------------------- _ans_i_echo() { print -P "%F{cyan}[ansible-init]%f $*"; } _ans_i_err() { print -P "%F{red}[ansible-init]%f $*" >&2; } # mkdir -p if missing (supports multiple paths and ignores aliases) _ans_i_mk() { local dir for dir in "$@"; do [[ -d "$dir" ]] || command mkdir -p "$dir" done } # Create/overwrite file conditionally # usage: _ans_i_write [force(0|1)] _ans_i_write() { local file_path="$1"; shift local content="$1"; shift local force="${1:-0}" if [[ -e "$file_path" && "$force" != "1" ]]; then _ans_i_echo "skip: $file_path (exists)" return 0 fi print -r -- "$content" > "$file_path" _ans_i_echo "write: $file_path" } # Global help _ans_i_usage() { cat <<'USAGE' ansible-init — быстрый каркас и утилиты для Ansible-проектов Использование: ansible-init [--help|-h] ansible-init new [NAME] [PATH] [--minimal|--standard|--full] [--force] [--help|-h] ansible-init group add [PROJECT] [--help|-h] ansible-init host add [PROJECT] [--help|-h] Подсказки: • ansible-init new . # каркас прямо в текущей папке • ansible-init new myproj . # создаст ./myproj • ANSIBLE_INIT_BASE=/path ansible-init new myproj • Режимы наполнения: --minimal | --standard (по умолчанию) | --full Переменные окружения: ANSIBLE_INIT_BASE Базовый путь (по умолчанию: $HOME/Movies/Work/Scripting) ANSIBLE_INIT_DEFAULT_NAME Имя проекта по умолчанию (x-ui-server) ANSIBLE_INIT_MODE Режим по умолчанию: minimal|standard|full USAGE } # --- Scaffold writer blocks --------------------------------------------------- # Each file respects ANSIBLE_INIT_MODE _ans_i_write_ansible_cfg() { local target="$1" force="$2" if [[ "$ANSIBLE_INIT_MODE" == "minimal" ]]; then _ans_i_write "$target/ansible.cfg" \ '[defaults] inventory = inventories/production/hosts.yml roles_path = roles ' "$force" else _ans_i_write "$target/ansible.cfg" \ '[defaults] inventory = inventories/production/hosts.yml roles_path = roles host_key_checking = False retry_files_enabled = False ' "$force" fi } _ans_i_write_hosts_yml() { local target="$1" force="$2" if [[ "$ANSIBLE_INIT_MODE" == "minimal" ]]; then _ans_i_write "$target/inventories/production/hosts.yml" \ 'all: vars: ansible_user: ernestsh ansible_python_interpreter: /usr/bin/python3.11 hosts: # jump-1: # ansible_host: ' "$force" else _ans_i_write "$target/inventories/production/hosts.yml" \ 'all: vars: ansible_user: ernestsh ansible_python_interpreter: /usr/bin/python3.11 hosts: jump-1: ansible_host: ' "$force" fi } _ans_i_write_group_vars_all() { local target="$1" force="$2" if [[ "$ANSIBLE_INIT_MODE" == "minimal" ]]; then _ans_i_write "$target/inventories/production/group_vars/all.yml" \ '--- # Добавляй сюда общие переменные, например: # timezone: Europe/Moscow # packages_common: [nano, tmux, git, zsh, ufw, fail2ban] ' "$force" else _ans_i_write "$target/inventories/production/group_vars/all.yml" \ '--- timezone: Europe/Moscow packages_common: - nano - tmux - git - zsh - ufw - fail2ban ' "$force" fi } _ans_i_write_site_yml() { local target="$1" force="$2" if [[ "$ANSIBLE_INIT_MODE" == "minimal" ]]; then _ans_i_write "$target/site.yml" \ '--- - name: Your first playbook hosts: all become: true roles: - common # создай роль и добавь туда задачи ' "$force" else _ans_i_write "$target/site.yml" \ '--- - name: Base configuration hosts: all become: true roles: - common ' "$force" fi } _ans_i_write_common_tasks() { local target="$1" force="$2" if [[ "$ANSIBLE_INIT_MODE" == "minimal" ]]; then _ans_i_write "$target/roles/common/tasks/main.yml" \ '--- # Пример первых задач: # - name: Ensure packages present # dnf: # name: "{{ packages_common }}" # state: present # # - name: Set timezone # timezone: # name: "{{ timezone | default(\"Europe/Moscow\") }}" ' "$force" else _ans_i_write "$target/roles/common/tasks/main.yml" \ '--- - name: Ensure common packages are installed dnf: name: "{{ packages_common }}" state: present - name: Set system timezone timezone: name: "{{ timezone }}" ' "$force" fi } _ans_i_write_readme_if_full() { local target="$1" force="$2" if [[ "$ANSIBLE_INIT_MODE" == "full" ]]; then _ans_i_write "$target/README.md" \ '# Infrastructure ## Быстрый старт 1) Правь inventories/production/hosts.yml → ansible_host 2) Запуск: ansible-playbook -i inventories/production/hosts.yml site.yml --check --diff ansible-playbook -i inventories/production/hosts.yml site.yml ## Полезно - ansible-doc dnf - ansible-lint - molecule ' "$force" fi } # --- Scaffold --------------------------------------------------------------- _ans_i_scaffold() { local project_name="$1" local target="${2:-$ANSIBLE_INIT_BASE/$project_name}" local force="${3:-0}" # Support "new ." or empty name => use current directory if [[ -z "$project_name" || "$project_name" == "." ]]; then target="$PWD" project_name="${project_name:-$(basename "$PWD")}" elif [[ "$target" == "." ]]; then target="$PWD/$project_name" fi _ans_i_mk "$target"/{inventories/production/group_vars,roles/common/{tasks,templates,files,defaults}} _ans_i_write_ansible_cfg "$target" "$force" _ans_i_write_hosts_yml "$target" "$force" _ans_i_write_group_vars_all "$target" "$force" _ans_i_write_site_yml "$target" "$force" _ans_i_write_common_tasks "$target" "$force" _ans_i_write_readme_if_full "$target" "$force" _ans_i_echo "✅ scaffold ready: $target (mode: $ANSIBLE_INIT_MODE)" } # --- Inventory helpers -------------------------------------------------------- _ans_i_group_add() { local group="$1" local project="${2:-$ANSIBLE_INIT_BASE/$ANSIBLE_INIT_DEFAULT_NAME}" [[ "$group" == "-h" || "$group" == "--help" ]] && { print "usage: ansible-init group add [PROJECT]"; return 0; } if [[ -z "$group" ]]; then _ans_i_err "group name required"; return 1; fi local gv="$project/inventories/production/group_vars/$group.yml" _ans_i_mk "$(dirname "$gv")" _ans_i_write "$gv" \ '--- # group vars for '"$group"' ' 0 _ans_i_echo "✅ group_vars created: $gv" } _ans_i_host_add() { local host="$1" local ip="$2" local project="${3:-$ANSIBLE_INIT_BASE/$ANSIBLE_INIT_DEFAULT_NAME}" [[ "$host" == "-h" || "$host" == "--help" ]] && { print "usage: ansible-init host add [PROJECT]"; return 0; } if [[ -z "$host" || -z "$ip" ]]; then _ans_i_err "usage: ansible-init host add [project_path]" return 1 fi local inv="$project/inventories/production/hosts.yml" if ! grep -q "^all:" "$inv" 2>/dev/null; then _ans_i_err "inventory not found or invalid: $inv" return 1 fi if grep -q "^[[:space:]]*$host:" "$inv"; then _ans_i_echo "host exists: $host -> updating ansible_host" awk -v h="$host" -v ip="$ip" ' function indent_len(line) { match(line, /^[[:space:]]*/) return RLENGTH } { if (inhost) { cur_indent = indent_len($0) if ($0 ~ /^[[:space:]]*$/) { print next } if (cur_indent > host_indent) { temp = $0 sub(/^[[:space:]]*/, "", temp) if (temp ~ /^ansible_host:[[:space:]]*/) { printf "%sansible_host: %s\n", attr_spaces, ip replaced = 1 next } print next } if (!replaced) { printf "%sansible_host: %s\n", attr_spaces, ip replaced = 1 } inhost = 0 } if ($0 ~ "^[[:space:]]*" h ":") { print inhost = 1 host_indent = indent_len($0) attr_spaces = sprintf("%*s", host_indent + 2, "") replaced = 0 next } print } END { if (inhost && !replaced) { printf "%sansible_host: %s\n", attr_spaces, ip } } ' "$inv" > "$inv.tmp" && mv "$inv.tmp" "$inv" else _ans_i_echo "adding host: $host ($ip)" printf " %s:\n ansible_host: %s\n" "$host" "$ip" >> "$inv" fi } # --- CLI ---------------------------------------------------------------------- ansible-init() { local cmd="$1"; shift # Global help if [[ -z "$cmd" || "$cmd" == "-h" || "$cmd" == "--help" || "$cmd" == "help" ]]; then _ans_i_usage; return 0 fi case "$cmd" in new) # per-subcommand help if [[ "$1" == "-h" || "$1" == "--help" ]]; then print "usage: ansible-init new [NAME] [PATH] [--minimal|--standard|--full] [--force]" print "tips: ansible-init new . # здесь; NAME возьмём из текущей папки" return 0 fi # Collect args (name, destination path, flags) local name="" dest_path="" force_flag=0 local args=("$@") for a in "${args[@]}"; do case "$a" in --minimal) ANSIBLE_INIT_MODE=minimal ;; --standard) ANSIBLE_INIT_MODE=standard ;; --full) ANSIBLE_INIT_MODE=full ;; --force) force_flag=1 ;; -h|--help) print "usage: ansible-init new [NAME] [PATH] [--minimal|--standard|--full] [--force]"; return 0 ;; *) if [[ -z "$name" ]]; then name="$a" elif [[ -z "$dest_path" ]]; then dest_path="$a" fi ;; esac done # Default name if omitted (only when not using ".") [[ -z "$name" ]] && name="$ANSIBLE_INIT_DEFAULT_NAME" # If user said "new ." => scaffold here if [[ "$name" == "." ]]; then _ans_i_scaffold "." "$PWD" "$force_flag" else if [[ "$dest_path" == "." ]]; then _ans_i_scaffold "$name" "$PWD/$name" "$force_flag" else _ans_i_scaffold "$name" "${dest_path:-$ANSIBLE_INIT_BASE/$name}" "$force_flag" fi fi ;; group) local sub="$1"; shift [[ "$sub" == "-h" || "$sub" == "--help" ]] && { print "usage: ansible-init group add [PROJECT]"; return 0; } case "$sub" in add) _ans_i_group_add "$@";; *) _ans_i_err "unknown: group $sub"; return 1;; esac ;; host) local sub="$1"; shift [[ "$sub" == "-h" || "$sub" == "--help" ]] && { print "usage: ansible-init host add [PROJECT]"; return 0; } case "$sub" in add) _ans_i_host_add "$@";; *) _ans_i_err "unknown: host $sub"; return 1;; esac ;; *) _ans_i_err "unknown command: $cmd"; _ans_i_usage; return 1;; esac } # --- Completions -------------------------------------------------------------- _ans_i_complete() { local -a subcmds subcmds=(help new group host) if (( CURRENT == 2 )); then compadd "${subcmds[@]}" -h --help else case "$words[2]" in new) compadd --minimal --standard --full --force -h --help ;; group) compadd add -h --help ;; host) compadd add -h --help ;; *) compadd -h --help ;; esac fi } if (( $+functions[compdef] )); then compdef _ans_i_complete ansible-init fi # --- Extensions loader -------------------------------------------------------- # Доп. команды можно класть как *.zsh в .../plugins/ansible-init/extensions/ _ans_i_extdir="${0:A:h}/extensions" if [[ -d "$_ans_i_extdir" ]]; then for _f in "$_ans_i_extdir"/*.zsh(.N); do source "$_f" done fi