From facf0c722c99391aee4584d7c6fbd157188ec60c Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 15 Jul 2024 06:11:25 +0000 Subject: [PATCH] build(COOKIECUTTER): initial generation --- .cicd-tools/bin/manifest.sh | 109 +++++++++ .cicd-tools/bin/toolbox.sh | 163 +++++++++++++ .cicd-tools/bin/verify.sh | 61 +++++ .../boxes/bootstrap/commitizen/pre_bump.sh | 15 ++ .../boxes/bootstrap/libraries/colours.sh | 61 +++++ .../boxes/bootstrap/libraries/container.sh | 48 ++++ .../boxes/bootstrap/libraries/environment.sh | 92 +++++++ .../boxes/bootstrap/libraries/logging.sh | 54 ++++ .../boxes/bootstrap/libraries/tools.sh | 63 +++++ .../bootstrap/pre-commit/lint-ansible.sh | 25 ++ .../pre-commit/lint-github-workflow-header.sh | 40 +++ .../pre-commit/spelling-commit-message.sh | 33 +++ .../boxes/bootstrap/schemas/cookiecutter.json | 63 +++++ .../boxes/bootstrap/schemas/manifest.json | 49 ++++ .cicd-tools/configuration/actionlint.yaml | 4 + .cicd-tools/configuration/changelog.json | 21 ++ .cicd-tools/configuration/cicd-tools.json | 5 + .cicd-tools/pgp/verification.sign | 7 + .cicd-tools/pgp/verification.txt | 1 + .cookiecutter/cookiecutter.json | 46 ++++ .github/actions/action-00-toolbox/action.yml | 18 ++ ...son-github-action-markdown-link-check.json | 20 ++ .../schemas/workflows/workflow-push.json | 54 ++++ .github/config/workflows/workflow-push.json | 12 + .github/scripts/job-50-test-precommit.sh | 149 ++++++++++++ .github/scripts/step-setup-environment.sh | 41 ++++ .github/workflows/workflow-push.yml | 230 ++++++++++++++++++ .gitignore | 17 ++ .markdownlint.yml | 13 + .pre-commit-config.yaml | 68 ++++++ .vale.ini | 10 + .vale/Vocab/profile-example/accept.txt | 9 + .vale/Vocab/profile-example/reject.txt | 0 LICENSE | 22 ++ README.md | 164 +++++++++++++ mac_maker.yml | 4 + profile/.ansible-lint | 121 +++++++++ profile/.yamllint.yml | 50 ++++ profile/__precheck__/env.yml | 9 + profile/__precheck__/notes.txt | 11 + profile/handlers/main.yml | 8 + profile/install.yml | 23 ++ profile/requirements.yml | 11 + profile/tasks/post_install/main.yml | 33 +++ profile/vars/main.yml | 39 +++ pyproject.toml | 31 +++ 46 files changed, 2127 insertions(+) create mode 100755 .cicd-tools/bin/manifest.sh create mode 100755 .cicd-tools/bin/toolbox.sh create mode 100755 .cicd-tools/bin/verify.sh create mode 100755 .cicd-tools/boxes/bootstrap/commitizen/pre_bump.sh create mode 100644 .cicd-tools/boxes/bootstrap/libraries/colours.sh create mode 100644 .cicd-tools/boxes/bootstrap/libraries/container.sh create mode 100644 .cicd-tools/boxes/bootstrap/libraries/environment.sh create mode 100644 .cicd-tools/boxes/bootstrap/libraries/logging.sh create mode 100644 .cicd-tools/boxes/bootstrap/libraries/tools.sh create mode 100755 .cicd-tools/boxes/bootstrap/pre-commit/lint-ansible.sh create mode 100755 .cicd-tools/boxes/bootstrap/pre-commit/lint-github-workflow-header.sh create mode 100755 .cicd-tools/boxes/bootstrap/pre-commit/spelling-commit-message.sh create mode 100644 .cicd-tools/boxes/bootstrap/schemas/cookiecutter.json create mode 100644 .cicd-tools/boxes/bootstrap/schemas/manifest.json create mode 100644 .cicd-tools/configuration/actionlint.yaml create mode 100644 .cicd-tools/configuration/changelog.json create mode 100644 .cicd-tools/configuration/cicd-tools.json create mode 100644 .cicd-tools/pgp/verification.sign create mode 100644 .cicd-tools/pgp/verification.txt create mode 100644 .cookiecutter/cookiecutter.json create mode 100644 .github/actions/action-00-toolbox/action.yml create mode 100644 .github/config/actions/gaurav-nelson-github-action-markdown-link-check.json create mode 100644 .github/config/schemas/workflows/workflow-push.json create mode 100644 .github/config/workflows/workflow-push.json create mode 100755 .github/scripts/job-50-test-precommit.sh create mode 100755 .github/scripts/step-setup-environment.sh create mode 100644 .github/workflows/workflow-push.yml create mode 100644 .gitignore create mode 100644 .markdownlint.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .vale.ini create mode 100644 .vale/Vocab/profile-example/accept.txt create mode 100644 .vale/Vocab/profile-example/reject.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 mac_maker.yml create mode 100644 profile/.ansible-lint create mode 100644 profile/.yamllint.yml create mode 100644 profile/__precheck__/env.yml create mode 100644 profile/__precheck__/notes.txt create mode 100644 profile/handlers/main.yml create mode 100644 profile/install.yml create mode 100644 profile/requirements.yml create mode 100644 profile/tasks/post_install/main.yml create mode 100644 profile/vars/main.yml create mode 100644 pyproject.toml diff --git a/.cicd-tools/bin/manifest.sh b/.cicd-tools/bin/manifest.sh new file mode 100755 index 0000000..140069d --- /dev/null +++ b/.cicd-tools/bin/manifest.sh @@ -0,0 +1,109 @@ +#!/bin/bash + +# Manifest file reader. +# Requires the jq binary: https://stedolan.github.io/jq/download/ + +# CICD-Tools script. + +set -eo pipefail + +# shellcheck source=./.cicd-tools/boxes/bootstrap/libraries/logging.sh +source "$(dirname -- "${BASH_SOURCE[0]}")/../boxes/bootstrap/libraries/logging.sh" + +manifest() { + local MANIFEST_FILE + _manifest_args "$@" +} + +_manifest_args() { + while getopts "m:" OPTION; do + case "$OPTION" in + m) + MANIFEST_FILE="${OPTARG}" + ;; + \?) + _manifest_usage + ;; + :) + _manifest_usage + ;; + *) + _manifest_usage + ;; + esac + done + shift $((OPTIND - 1)) + if [[ -z "${MANIFEST_FILE}" ]]; then + _manifest_usage + fi + _manifest_commands "$@" +} + +_manifest_commands() { + case "$1" in + security) + [[ -n "${2}" ]] && _manifest_usage + log "DEBUG" "MANIFEST > Reading security status from manifest." + _manifest_security + ;; + toolbox_url) + [[ -z "${2}" ]] && _manifest_usage + log "DEBUG" "MANIFEST > Reading toolbox url for '${2}' from manifest." + _manifest_toolbox_url "${2}" + ;; + toolbox_sha) + [[ -z "${2}" ]] && _manifest_usage + log "DEBUG" "MANIFEST > Reading toolbox checksum for '${2}' from manifest." + _manifest_toolbox_sha "${2}" + ;; + *) + _manifest_usage + ;; + esac +} + +_manifest_usage() { + log "ERROR" "manifest.sh -- interact with the CICD-Tools manifest file." + log "ERROR" "USAGE: manifest.sh -p [PATH TO MANIFEST] [COMMAND]" + log "ERROR" " COMMANDS:" + log "ERROR" " toolbox_url [VERSION] - Retrieves the URL of the given toolbox version." + log "ERROR" " toolbox_sha [FILENAME] - Retrieves the checksum of the given file." + log "ERROR" " security - Indicates if hash validation is enabled or disabled." + exit 127 +} + +_manifest_security() { + jq -rM ".disable_security" "${MANIFEST_FILE}" +} + +_manifest_toolbox_prefix() { + local REMOTE_SHA + local REMOTE_SOURCE + local REMOTE_PATH + REMOTE_SHA="$(jq -erM '.version' "${MANIFEST_FILE}")" + REMOTE_SOURCE="$(jq -erM '.source' "${MANIFEST_FILE}")" + REMOTE_PATH="$(jq -erM '.toolbox_path' "${MANIFEST_FILE}")" + echo "${REMOTE_SOURCE}/${REMOTE_SHA}/${REMOTE_PATH}" +} + +_manifest_toolbox_is_present() { + jq --arg version "${1}.tar.gz" -erM '.manifest[$version]' "${MANIFEST_FILE}" +} + +_manifest_toolbox_url() { + if ! _manifest_toolbox_is_present "${1}" > /dev/null; then + log "ERROR" "MANIFEST > Toolbox version '${1}' is not in the manifest." + exit 127 + fi + echo "$(_manifest_toolbox_prefix)/${1}.tar.gz" +} + +_manifest_toolbox_sha() { + if ! _manifest_toolbox_is_present "${1}" > /dev/null; then + log "ERROR" "MANIFEST > Toolbox version '${1}' is not in the manifest." + exit 127 + fi + jq --arg version "${1}.tar.gz" -erM '.manifest[$version]' "${MANIFEST_FILE}" +} + +manifest "$@" diff --git a/.cicd-tools/bin/toolbox.sh b/.cicd-tools/bin/toolbox.sh new file mode 100755 index 0000000..99744df --- /dev/null +++ b/.cicd-tools/bin/toolbox.sh @@ -0,0 +1,163 @@ +#!/bin/bash + +# Remote toolbox downloader. +# Requires gpg binary: https://gnupg.org/ + +# CICD-Tools script. + +set -eo pipefail + +TOOLBOX_PATH="$(pwd)/.cicd-tools" +TOOLBOX_REMOTES_FOLDER="boxes" +TOOLBOX_MANIFEST_FILE="${TOOLBOX_PATH}/manifest.json" + +# shellcheck source=./.cicd-tools/boxes/bootstrap/libraries/logging.sh +source "$(dirname -- "${BASH_SOURCE[0]}")/../boxes/bootstrap/libraries/logging.sh" + +# shellcheck source=./.cicd-tools/boxes/bootstrap/libraries/environment.sh +source "$(dirname -- "${BASH_SOURCE[0]}")/../boxes/bootstrap/libraries/environment.sh" \ + -o "DOWNLOAD_RETRIES DOWNLOAD_MAX_TIME" \ + -d "3 30" + +main() { + OPTIND=1 + + local MANIFEST_ASC + local MANIFEST_DISABLE_SECURITY="false" + local TARGET_TOOLBOX_VERSION + local TARGET_TOOLBOX_URL + local TEMP_DIRECTORY + + TEMP_DIRECTORY="$(mktemp -d)" + + _toolbox_args "$@" + _toolbox_manifest_download + _toolbox_manifest_load + _toolbox_box_download + _toolbox_box_checksum + _toolbox_box_install +} + +_toolbox_args() { + while getopts "b:m:r:t:" OPTION; do + case "$OPTION" in + b) + TARGET_TOOLBOX_VERSION="${OPTARG}" + TARGET_TOOLBOX_FILENAME="${TARGET_TOOLBOX_VERSION}.tar.gz" + ;; + m) + MANIFEST_ASC="${OPTARG}" + ;; + r) + DOWNLOAD_RETRIES="${OPTARG}" + ;; + t) + DOWNLOAD_MAX_TIME="${OPTARG}" + ;; + \?) + _toolbox_usage + ;; + :) + _toolbox_usage + ;; + *) + _toolbox_usage + ;; + esac + done + shift $((OPTIND - 1)) + + if [[ -z "${TARGET_TOOLBOX_VERSION}" ]] || + [[ -z "${MANIFEST_ASC}" ]]; then + _toolbox_usage + fi +} + +_toolbox_box_checksum() { + pushd "${TEMP_DIRECTORY}" >> /dev/null + if [[ "${MANIFEST_DISABLE_SECURITY}" == "false" ]]; then + if ! echo "${TARGET_TOOLBOX_SHA} ${TARGET_TOOLBOX_FILENAME}" | sha256sum -c; then + log "ERROR" "CHECKSUM > Hash of remote file does not match!" + log "ERROR" "CHECKSUM > Cannot proceed." + exit 127 + else + log "INFO" "CHECKSUM > Hash verification has passed." + fi + else + log "WARNING" "CHECKSUM > The manifest has DISABLED all checksum validation." + fi + cp "${TARGET_TOOLBOX_FILENAME}" "${TOOLBOX_PATH}/${TOOLBOX_REMOTES_FOLDER}" + popd >> /dev/null +} + +_toolbox_box_download() { + if [[ -f "${TOOLBOX_PATH}/${TOOLBOX_REMOTES_FOLDER}/${TARGET_TOOLBOX_FILENAME}" ]]; then + mv "${TOOLBOX_PATH}/${TOOLBOX_REMOTES_FOLDER}/${TARGET_TOOLBOX_FILENAME}" "${TEMP_DIRECTORY}" + log "INFO" "BOX > Toolbox Version ${TARGET_TOOLBOX_VERSION} has already been downloaded." + else + _toolbox_box_fetch + fi +} + +_toolbox_box_fetch() { + log "DEBUG" "BOX > Target Toolbox Version: ${TARGET_TOOLBOX_VERSION}" + log "DEBUG" "BOX > Target Toolbox SHA: ${TARGET_TOOLBOX_SHA}" + log "DEBUG" "BOX > Target Toolbox URL: ${TARGET_TOOLBOX_URL}" + + mkdir -p "${TOOLBOX_PATH}/${TOOLBOX_REMOTES_FOLDER}" + + pushd "${TEMP_DIRECTORY}" >> /dev/null + _toolbox_fetch "${TARGET_TOOLBOX_URL}" > "${TARGET_TOOLBOX_FILENAME}" + popd >> /dev/null + + log "INFO" "BOX > Remote toolbox retrieved." +} + +_toolbox_box_install() { + pushd "${TOOLBOX_PATH}/${TOOLBOX_REMOTES_FOLDER}" >> /dev/null + tar xvzf "${TARGET_TOOLBOX_FILENAME}" + log "DEBUG" "BOX > Toolbox Version ${TARGET_TOOLBOX_VERSION} has been installed to ${TOOLBOX_PATH}/${TOOLBOX_REMOTES_FOLDER}." + ln -sf "${TARGET_TOOLBOX_VERSION}" active + log "INFO" "BOX > Toolbox Version ${TARGET_TOOLBOX_VERSION} has been activated." + popd >> /dev/null +} + +_toolbox_fetch() { + # 1: url + log "DEBUG" "FETCH > URL: ${1}" + log "DEBUG" "FETCH > Retries: ${DOWNLOAD_RETRIES}" + log "DEBUG" "FETCH > Max Time: ${DOWNLOAD_MAX_TIME}" + + set -x + curl --fail \ + --location \ + --silent \ + --show-error \ + --retry "${DOWNLOAD_RETRIES}" \ + --retry-max-time "${DOWNLOAD_MAX_TIME}" \ + "${1}" + { set +x; } 2> /dev/null + + log "DEBUG" "FETCH > Fetch complete." +} + +_toolbox_manifest_download() { + gpg --yes --output "${TOOLBOX_MANIFEST_FILE}" --verify <(_toolbox_fetch "${MANIFEST_ASC}") + log "INFO" "MANIFEST > Remote manifest retrieved." +} + +_toolbox_manifest_load() { + TARGET_TOOLBOX_SHA="$(./.cicd-tools/bin/manifest.sh -m "${TOOLBOX_MANIFEST_FILE}" toolbox_sha "${TARGET_TOOLBOX_VERSION}")" + MANIFEST_DISABLE_SECURITY="$(./.cicd-tools/bin/manifest.sh -m "${TOOLBOX_MANIFEST_FILE}" security)" + TARGET_TOOLBOX_URL="$(./.cicd-tools/bin/manifest.sh -m "${TOOLBOX_MANIFEST_FILE}" toolbox_url "${TARGET_TOOLBOX_VERSION}")" + log "INFO" "MANIFEST > Remote manifest loaded." +} + +_toolbox_usage() { + log "ERROR" "toolbox.sh -- download a remote toolbox from the CICD-Tools manifest." + log "ERROR" "USAGE: toolbox.sh -b [TOOLBOX VERSION] -m [REMOTE MANIFEST URL]" + log "ERROR" " Optional: -r [OPTIONAL RETRY COUNT] -m [OPTIONAL MAX RETRY TIME]" + exit 127 +} + +main "$@" diff --git a/.cicd-tools/bin/verify.sh b/.cicd-tools/bin/verify.sh new file mode 100755 index 0000000..7441fff --- /dev/null +++ b/.cicd-tools/bin/verify.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Remote gpg key verification. +# Requires gpg binary: https://gnupg.org/ + +# CICD-Tools script. + +set -eo pipefail + +# shellcheck source=./.cicd-tools/boxes/bootstrap/libraries/logging.sh +source "$(dirname -- "${BASH_SOURCE[0]}")/../boxes/bootstrap/libraries/logging.sh" + +main() { + local CICD_TOOLS_GPG_KEY + + _verify_args "$@" + _verify_check_key + _verify_trust_key +} + +_verify_args() { + while getopts "k:" OPTION; do + case "$OPTION" in + k) + CICD_TOOLS_GPG_KEY="${OPTARG}" + ;; + \?) + _toolbox_usage + ;; + :) + _toolbox_usage + ;; + *) + _toolbox_usage + ;; + esac + done + shift $((OPTIND - 1)) + + if [[ -z "${CICD_TOOLS_GPG_KEY}" ]]; then + _verify_usage + fi +} + +_verify_check_key() { + gpg \ + --verify "$(dirname -- "${BASH_SOURCE[0]}")/../pgp/verification.sign" \ + "$(dirname -- "${BASH_SOURCE[0]}")/../pgp/verification.txt" +} + +_verify_trust_key() { + echo "${CICD_TOOLS_GPG_KEY}:6:" | gpg --import-ownertrust +} + +_verify_usage() { + log "ERROR" "verify.sh -- verify the CICD-Tools gpg key." + log "ERROR" "USAGE: verify.sh -k [GPG KEY ID]" + exit 127 +} + +main "$@" diff --git a/.cicd-tools/boxes/bootstrap/commitizen/pre_bump.sh b/.cicd-tools/boxes/bootstrap/commitizen/pre_bump.sh new file mode 100755 index 0000000..e521bf5 --- /dev/null +++ b/.cicd-tools/boxes/bootstrap/commitizen/pre_bump.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Commitizen 'pre_bump_hook' script to make TOML quotes compatible with tomll. + +# Commitizen pre_bump_hook script only. + +set -eo pipefail + +main() { + # sed compatible with Linux and BSD + sed -i.bak "s,\"${CZ_PRE_NEW_VERSION}\",'${CZ_PRE_NEW_VERSION}',g" pyproject.toml + rm pyproject.toml.bak +} + +main diff --git a/.cicd-tools/boxes/bootstrap/libraries/colours.sh b/.cicd-tools/boxes/bootstrap/libraries/colours.sh new file mode 100644 index 0000000..984d4d2 --- /dev/null +++ b/.cicd-tools/boxes/bootstrap/libraries/colours.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Library for reading the CICD-Tools manifest. + +# CICD_TOOLS_COLOUR_DISABLE: Optionally disable coloured messages. + +set -eo pipefail + +# shellcheck disable=SC2034 +colour() { + local PREFIX + local COMMAND + + local BLACK=0 + local RED=1 + local GREEN=2 + local YELLOW=3 + local BLUE=4 + local PURPLE=5 + local CYAN=6 + local WHITE=7 + + if [[ -z "${CICD_TOOLS_COLOUR_DISABLE}" ]]; then + PREFIX="_colour" + COMMAND="${PREFIX}_${1}" + if [[ $(type -t "${COMMAND}") == function ]]; then + shift + "${COMMAND}" "$@" + else + "${PREFIX}_usage" + fi + fi +} + +_colour_bg() { + tput setab "${!1}" 2> /dev/null +} + +_colour_bold() { + tput setab bold 2> /dev/null +} + +_colour_fg() { + tput setaf "${!1}" 2> /dev/null +} + +_colour_clear() { + tput sgr0 2> /dev/null +} + +_colour_usage() { + { + echo "colour.sh -- set the desired terminal colour." + echo "USAGE: colour.sh [COMMAND]" + echo " COMMANDS:" + echo " bg [BLACK|RED|GREEN|YELLOW|BLUE|PURPLE|CYAN|WHITE] -- set background colour" + echo " bold -- set bold text" + echo " fg [BLACK|RED|GREEN|YELLOW|BLUE|PURPLE|CYAN|WHITE] -- set foreground colour" + echo " clear -- set default terminal colours" + } >> /dev/stderr +} diff --git a/.cicd-tools/boxes/bootstrap/libraries/container.sh b/.cicd-tools/boxes/bootstrap/libraries/container.sh new file mode 100644 index 0000000..7a8e004 --- /dev/null +++ b/.cicd-tools/boxes/bootstrap/libraries/container.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Library for working with the CICD-tools container. + +set -eo pipefail + +# shellcheck source=/dev/null +source "$(dirname -- "${BASH_SOURCE[0]}")/tools.sh" + +# shellcheck source=/dev/null +source "$(dirname -- "${BASH_SOURCE[0]}")/logging.sh" + +container() { + local PREFIX + local COMMAND + + PREFIX="_container" + COMMAND="${PREFIX}_${1}" + if [[ $(type -t "${COMMAND}") == function ]]; then + shift + "${COMMAND}" "$@" + else + "${PREFIX}_usage" + fi +} + +_container_get_image() { + if cicd_tools "is_template"; then + cicd_tools "config_value" "cookiecutter.json" "_DOCKER_DEFAULT_CONTAINER" + else + cicd_tools "config_value" ".cicd-tools/configuration/cicd-tools.json" "CONTAINER" + fi +} + +_container_run() { + local CONTAINER_IMAGE + CONTAINER_IMAGE="$(container "get_image")" + log "DEBUG" "CONTAINER > ${CONTAINER_IMAGE} $*" + docker run -t --rm -v "$(pwd):/mnt" -w "/mnt" "${CONTAINER_IMAGE}" "$@" +} + +_container_usage() { + log "ERROR" "container.sh -- CICD-tools container interface." + log "ERROR" "USAGE: container.sh [COMMAND]" + log "ERROR" " COMMANDS:" + log "ERROR" " get_image -- Return the the currently configured container image." + log "ERROR" " run [SUB COMMAND] -- Run the given sub command inside the container." +} diff --git a/.cicd-tools/boxes/bootstrap/libraries/environment.sh b/.cicd-tools/boxes/bootstrap/libraries/environment.sh new file mode 100644 index 0000000..9fe520d --- /dev/null +++ b/.cicd-tools/boxes/bootstrap/libraries/environment.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Library for enforcing optional and mandatory environment variables. + +set -eo pipefail + +# shellcheck source=/dev/null +source "$(dirname -- "${BASH_SOURCE[0]}")/logging.sh" + +environment() { + local MANDATORY=() + local OPTIONAL=() + local DEFAULTS=() + + log "DEBUG" "${BASH_SOURCE[0]} '$*'" + + _environment_args "$@" + _environment_defaults +} + +_environment_args() { + while getopts "m:o:d:" OPTION; do + case "$OPTION" in + m) + _environment_parse_mandatory "${OPTARG}" + ;; + o) + _environment_parse_optional "${OPTARG}" + ;; + d) + _environment_parse_defaults "${OPTARG}" + ;; + \?) + _environment_usage + ;; + :) + _environment_usage + ;; + esac + done + + if [[ "${#OPTIONAL[@]}" -ne "${#DEFAULTS[@]}" ]]; then + log "ERROR" "ENVIRONMENT > You must specify the same number of DEFAULT values and OPTIONAL environment variables!" + exit 127 + fi +} + +_environment_defaults() { + log "DEBUG" "ENVIRONMENT > Setting DEFAULT environment variable values." + local INDEX=-1 + for VARIABLE in "${DEFAULTS[@]}"; do + ((INDEX++)) || true + if [[ -z "${!OPTIONAL[${INDEX}]}" ]]; then + export "${OPTIONAL[${INDEX}]}" + eval "${OPTIONAL[${INDEX}]}"="${DEFAULTS[${INDEX}]}" + log "INFO" "ENVIRONMENT > Default: '${DEFAULTS[${INDEX}]}' is being used for: '${OPTIONAL[${INDEX}]}'." + fi + done +} + +_environment_parse_mandatory() { + log "DEBUG" "ENVIRONMENT > Parsing MANDATORY environment variables." + # shellcheck disable=SC2034 + IFS=' ' read -r -a MANDATORY <<< "${1}" + for VARIABLE in "${MANDATORY[@]}"; do + if [[ -z ${!VARIABLE} ]]; then + log "ERROR" "ENVIRONMENT > The environment variable '${VARIABLE}' is required!" + exit 127 + fi + done +} + +_environment_parse_optional() { + log "DEBUG" "ENVIRONMENT > Parsing OPTIONAL environment variables." + # shellcheck disable=SC2034 + IFS=' ' read -r -a OPTIONAL <<< "${1}" +} + +_environment_parse_defaults() { + log "DEBUG" "ENVIRONMENT > Parsing DEFAULT environment variable values." + # shellcheck disable=SC2034 + IFS=' ' read -r -a DEFAULTS <<< "${1}" +} + +_environment_usage() { + log "ERROR" "environment.sh -- enforce environment variables." + log "ERROR" "USAGE: source environment.sh -m [MANDATORY] -o [OPTIONAL] -d [DEFAULTS]" + log "ERROR" " Multiple items should be specified as space separated quoted strings." + exit 127 +} + +environment "$@" diff --git a/.cicd-tools/boxes/bootstrap/libraries/logging.sh b/.cicd-tools/boxes/bootstrap/libraries/logging.sh new file mode 100644 index 0000000..7011f77 --- /dev/null +++ b/.cicd-tools/boxes/bootstrap/libraries/logging.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# Library for logging functions and commands. +# The LOGGING_LEVEL environment variable controls verbosity. + +set -eo pipefail + +# shellcheck source=/dev/null +source "$(dirname -- "${BASH_SOURCE[0]}")/colours.sh" + +LOGGING_LEVEL=${LOGGING_LEVEL-"DEBUG"} + +function log() { + # USAGE: + # colour bg [CRITICAL|ERROR|WARNING|INFO|DEBUG] [MESSAGE CONTENTS] + + local LOGGING_SEVERITY_LEVELS=("DEBUG" "INFO" "WARNING" "ERROR" "CRITICAL") + + # shellcheck disable=SC2034 + local CRITICAL="RED" + # shellcheck disable=SC2034 + local ERROR="RED" + # shellcheck disable=SC2034 + local WARNING="YELLOW" + # shellcheck disable=SC2034 + local INFO="GREEN" + # shellcheck disable=SC2034 + local DEBUG="CYAN" + # shellcheck disable=SC2034 + + local LOGGING_MESSAGE_LEVEL="${1}" + local MESSAGE_CONTENT="${2}" + + if [[ -z "${!LOGGING_LEVEL}" ]] || + [[ -z "${MESSAGE_CONTENT}" ]]; then + log "ERROR" "Invalid logging statement!" + return 127 + fi + + if [[ "$(_log_get_severity_level "${LOGGING_MESSAGE_LEVEL}")" -ge "$(_log_get_severity_level "${LOGGING_LEVEL}")" ]]; then + echo "[$(date -u)] [$(colour fg "${!LOGGING_MESSAGE_LEVEL}")${LOGGING_MESSAGE_LEVEL}$(colour clear)] ${MESSAGE_CONTENT}" >> /dev/stderr + fi + +} + +function _log_get_severity_level() { + #1: The severity type as a string. + local LOGGIN_SEVERITY_LEVEL + for LOGGIN_SEVERITY_LEVEL in "${!LOGGING_SEVERITY_LEVELS[@]}"; do + if [[ "${LOGGING_SEVERITY_LEVELS["${LOGGIN_SEVERITY_LEVEL}"]}" = "${1}" ]]; then + echo "${LOGGIN_SEVERITY_LEVEL}" + fi + done +} diff --git a/.cicd-tools/boxes/bootstrap/libraries/tools.sh b/.cicd-tools/boxes/bootstrap/libraries/tools.sh new file mode 100644 index 0000000..9b2065f --- /dev/null +++ b/.cicd-tools/boxes/bootstrap/libraries/tools.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# Library for working with the CICD-Tools projects. + +set -eo pipefail + +# shellcheck source=/dev/null +source "$(dirname -- "${BASH_SOURCE[0]}")/logging.sh" + +cicd_tools() { + local PREFIX + local COMMAND + + PREFIX="_cicd_tools" + COMMAND="${PREFIX}_${1}" + if [[ $(type -t "${COMMAND}") == function ]]; then + shift + "${COMMAND}" "$@" + else + "${PREFIX}_usage" + fi +} + +_cicd_tools_is_template() { + [[ -f "cookiecutter.json" ]] +} + +_cicd_tools_config_value() { + # 1: The config file to parse. + # 2: The key of the value to extract. + + local CICD_TOOLS_CONFIG_FILE="${1}" + local CICD_TOOLS_KEY="${2}" + + log "DEBUG" "CONFIGURATION > extracting key: '${CICD_TOOLS_KEY}' from: '${CICD_TOOLS_CONFIG_FILE}'." + + REGEX="\"${2}\": \"([^\"]+)\"" + if [[ "$(cat "${CICD_TOOLS_CONFIG_FILE}")" =~ ${REGEX} ]]; then + log "DEBUG" "CONFIGURATION > found value: '${BASH_REMATCH[1]}'." + echo "${BASH_REMATCH[1]}" + else + log "ERROR" "CONFIGURATION > key: '${CICD_TOOLS_KEY}' not found in '${CICD_TOOLS_CONFIG_FILE}'." + return 127 + fi +} + +_cicd_tools_poetry() { + # @: A command and arguments to run in either directly, or through Poetry. + + if [[ "${POETRY_ACTIVE}" == "1" ]]; then + "$@" + else + poetry run "$@" + fi +} + +_cicd_tools_usage() { + log "ERROR" "tools.sh -- CICD-Tools project helpers." + log "ERROR" "USAGE: tools.sh [COMMAND]" + log "ERROR" " COMMANDS:" + log "ERROR" " is_template -- Evaluates whether the current context is a cookiecutter project." + log "ERROR" " config_value [JSON FILE PATH] [KEY] -- Reads the given JSON file, and returns the value of the given key." +} diff --git a/.cicd-tools/boxes/bootstrap/pre-commit/lint-ansible.sh b/.cicd-tools/boxes/bootstrap/pre-commit/lint-ansible.sh new file mode 100755 index 0000000..4bc651a --- /dev/null +++ b/.cicd-tools/boxes/bootstrap/pre-commit/lint-ansible.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Runs ansible-galaxy to install/update the dependencies if needed, and then runs ansible-lint on each active target folder's changes. + +# @: An array of folders to run ansible-lin on. + +# pre-commit script. + +set -eo pipefail + +# shellcheck source=/dev/null +source "$(dirname -- "${BASH_SOURCE[0]}")/../libraries/tools.sh" + +main() { + local TARGET_FOLDERS=${*-"."} + for TARGET in ${TARGET_FOLDERS}; do + log "INFO" "PRE-COMMIT > Moving to target folder: '${TARGET}' ..." + pushd "${TARGET}" >> /dev/null + log "DEBUG" "PRE-COMMIT > Executing 'ansible-lint' ..." + cicd_tools "poetry" ansible-lint + popd >> /dev/null + done +} + +main "$@" diff --git a/.cicd-tools/boxes/bootstrap/pre-commit/lint-github-workflow-header.sh b/.cicd-tools/boxes/bootstrap/pre-commit/lint-github-workflow-header.sh new file mode 100755 index 0000000..a7d9980 --- /dev/null +++ b/.cicd-tools/boxes/bootstrap/pre-commit/lint-github-workflow-header.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Verifies the correct headers are present on GitHub workflow files. + +# @: An array of GitHub workflow files to lint. + +# pre-commit script. + +set -eo pipefail + +# shellcheck source=/dev/null +source "$(dirname -- "${BASH_SOURCE[0]}")/../libraries/logging.sh" + +main() { + for WORKFLOW_FILE_PATH in "$@"; do + + log "INFO" "Checking header for: '${WORKFLOW_FILE_PATH}' ... " + + WORKFLOW_BASENAME="$(basename "${WORKFLOW_FILE_PATH}")" + + log "DEBUG" "Basename: '${WORKFLOW_BASENAME}' ... " + + if [[ "${WORKFLOW_BASENAME}" == job-* ]]; then + log "DEBUG" "Checking Job Header ..." + HEADER_NAME="$(echo "${WORKFLOW_BASENAME}" | cut -d. -f1)" + else + log "DEBUG" "Checking Workflow Header ..." + HEADER_NAME=".+-github-$(echo "${WORKFLOW_BASENAME}" | cut -d. -f1)" + fi + + if ! grep -E "^name: ${HEADER_NAME}$" "${WORKFLOW_FILE_PATH}" >> /dev/null; then + log "ERROR" "Incorrect Header on '${WORKFLOW_FILE_PATH}'" + log "ERROR" "EXPECTED PATTERN: ^${HEADER_NAME}$" + exit 127 + fi + + done +} + +main "$@" diff --git a/.cicd-tools/boxes/bootstrap/pre-commit/spelling-commit-message.sh b/.cicd-tools/boxes/bootstrap/pre-commit/spelling-commit-message.sh new file mode 100755 index 0000000..4d5ede7 --- /dev/null +++ b/.cicd-tools/boxes/bootstrap/pre-commit/spelling-commit-message.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Runs vale on the specified commit message file, with the Git content filtered out. + +# 1: The Docker image and tag to use. +# 2: The commit message file to lint. + +# pre-commit script. + +set -eo pipefail + +# shellcheck source=/dev/null +source "$(dirname -- "${BASH_SOURCE[0]}")/../libraries/logging.sh" + +main() { + local PRECOMMIT_GIT_COMMIT_MESSAGE_FILE + local PRECOMMIT_GIT_CONTENT_REGEX + local PRECOMMIT_VALE_DOCKER_IMAGE + + PRECOMMIT_GIT_COMMIT_MESSAGE_FILE="${2}" + PRECOMMIT_GIT_CONTENT_REGEX='/^#[[:blank:]]*.*$/d' + PRECOMMIT_VALE_DOCKER_IMAGE="${1}" + + log "DEBUG" "PRE_COMMIT > Docker Image: '${PRECOMMIT_VALE_DOCKER_IMAGE}'" + log "DEBUG" "PRE_COMMIT > Commit Message: '${PRECOMMIT_GIT_COMMIT_MESSAGE_FILE}'" + sed "${PRECOMMIT_GIT_CONTENT_REGEX}" "${PRECOMMIT_GIT_COMMIT_MESSAGE_FILE}" + log "DEBUG" "PRE_COMMIT > Running vale ..." + sed "${PRECOMMIT_GIT_CONTENT_REGEX}" "${PRECOMMIT_GIT_COMMIT_MESSAGE_FILE}" | + docker run -i --rm -v "$(pwd)":/mnt --workdir /mnt "${PRECOMMIT_VALE_DOCKER_IMAGE}" vale + log "INFO" "PRE-COMMIT > Commit message spelling has passed!" +} + +main "$@" diff --git a/.cicd-tools/boxes/bootstrap/schemas/cookiecutter.json b/.cicd-tools/boxes/bootstrap/schemas/cookiecutter.json new file mode 100644 index 0000000..663f2e6 --- /dev/null +++ b/.cicd-tools/boxes/bootstrap/schemas/cookiecutter.json @@ -0,0 +1,63 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "0.1.0", + "description": "CICD-Tools Cookiecutter Required Fields Schema", + "additionalProperties": true, + "minProperties": 10, + "required": [ + "github_handle", + "project_slug", + "project_name", + "_BRANCH_NAME_BASE", + "_BRANCH_NAME_DEVELOPMENT", + "_DOCKER_DEFAULT_CONTAINER", + "_GITHUB_CI_DEFAULT_CONCURRENCY", + "_GITHUB_CI_DEFAULT_PYTHON_VERSIONS", + "_GITHUB_CI_DEFAULT_VERBOSE_NOTIFICATIONS" + ], + "type": "object", + "uniqueItems": true, + "properties": { + "github_handle": { + "description": "The author's GitHub handle, used to create repository paths.", + "type": "string" + }, + "project_name": { + "description": "The plaintext name of the new project that will be templated.", + "type": "string" + }, + "project_slug": { + "description": "The slugified name of the new project that will be templated, used for the repository name.", + "type": "string" + }, + "_BRANCH_NAME_BASE": { + "description": "The name of the base branch that will be used in the templated repository.", + "type": "string" + }, + "_BRANCH_NAME_DEVELOPMENT": { + "description": "The name of the development branch that will be used in the templated repository.", + "type": "string" + }, + "_DOCKER_DEFAULT_CONTAINER": { + "description": "The container that will be used for the shellcheck, shfmt and other core binaries.", + "type": "string" + }, + "_GITHUB_CI_DEFAULT_CONCURRENCY": { + "description": "The default concurrency value that will be used for GitHub workflows.", + "type": "number" + }, + "_GITHUB_CI_DEFAULT_PYTHON_VERSIONS": { + "description": "The list of Python versions that will be used in GitHub workflows.", + "type": "array", + "contains": { + "pattern": "^3\\.[0-9]$", + "type": "string" + }, + "minContains": 1 + }, + "_GITHUB_CI_DEFAULT_VERBOSE_NOTIFICATIONS": { + "description": "The default verbosity of GitHub Action notifications.", + "type": "boolean" + } + } +} diff --git a/.cicd-tools/boxes/bootstrap/schemas/manifest.json b/.cicd-tools/boxes/bootstrap/schemas/manifest.json new file mode 100644 index 0000000..27cd7f9 --- /dev/null +++ b/.cicd-tools/boxes/bootstrap/schemas/manifest.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "0.1.0", + "description": "CICD-Tools Manifest Schema", + "additionalProperties": false, + "minProperties": 5, + "type": "object", + "uniqueItems": true, + "properties": { + "disable_security": { + "description": "Use hash validation to ensure downloaded content is trusted (highly recommended).", + "type": "boolean" + }, + "manifest": { + "description": "Trusted bundles and files available on this SHA of CICD-Tools.", + "type": "object", + "uniqueItems": true, + "properties": {}, + "patternProperties": { + "^[A-Za-z0-9\\.]$": { + "$ref": "#/definitions/ENTRY" + } + } + }, + "source": { + "description": "The download URL for CICD-Tools repository.", + "$ref": "#/definitions/URL" + }, + "toolbox_path": { + "description": "The path in the CICD-Tools repository to bundles.", + "type": "string" + }, + "version": { + "description": "The SHA identifier (Branch, Tag or Commit ID) of the CICD-Tools repository to use.", + "pattern": "^[A-Za-z]+$", + "type": "string" + } + }, + "definitions": { + "URL": { + "format": "uri", + "pattern": "^https?:\/\/" + }, + "ENTRY": { + "description": "A downloadable file in the manifest, with it's SHA256 sum.", + "format": "string" + } + } +} diff --git a/.cicd-tools/configuration/actionlint.yaml b/.cicd-tools/configuration/actionlint.yaml new file mode 100644 index 0000000..1627bf1 --- /dev/null +++ b/.cicd-tools/configuration/actionlint.yaml @@ -0,0 +1,4 @@ +--- +self-hosted-runner: + # Labels of self-hosted runner in array of string + labels: [] diff --git a/.cicd-tools/configuration/changelog.json b/.cicd-tools/configuration/changelog.json new file mode 100644 index 0000000..1a62ab6 --- /dev/null +++ b/.cicd-tools/configuration/changelog.json @@ -0,0 +1,21 @@ +{ + "options": { + "preset": { + "name": "conventionalcommits", + "types": [ + { "type": "feat", "section": "Features" }, + { "type": "feature", "section": "Features" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "perf", "section": "Performance Improvements" }, + { "type": "revert", "section": "Reverts" }, + { "type": "docs", "section": "Documentation"}, + { "type": "style", "section": "Styles"}, + { "type": "chore", "section": "Miscellaneous Chores"}, + { "type": "refactor", "section": "Code Refactoring"}, + { "type": "test", "section": "Tests"}, + { "type": "build", "section": "Build System"}, + { "type": "ci", "section": "Continuous Integration"} + ] + } + } +} diff --git a/.cicd-tools/configuration/cicd-tools.json b/.cicd-tools/configuration/cicd-tools.json new file mode 100644 index 0000000..03cf3ef --- /dev/null +++ b/.cicd-tools/configuration/cicd-tools.json @@ -0,0 +1,5 @@ +{ + "SHFMT_OPTIONS": "-i 2 -ci -sr", + "SHELLCHECK_OPTIONS": "-e SC2317 -P SCRIPTDIR", + "CONTAINER": "ghcr.io/cicd-tools-org/cicd-tools:main" +} \ No newline at end of file diff --git a/.cicd-tools/pgp/verification.sign b/.cicd-tools/pgp/verification.sign new file mode 100644 index 0000000..44bc538 --- /dev/null +++ b/.cicd-tools/pgp/verification.sign @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- + +iHUEABYKAB0WIQTwenlkfpHlYaeGttDZAg9/7iDb8gUCZCkYCQAKCRDZAg9/7iDb +8nvIAQDTZImTu5eKtipUhlDA5TYXroyOhX0CZnwbEsS9ySNsmgEAqcXQZBeXW2FZ +VyKPtGiGe4bUvD+BJJPa6yjw35gS5wY= +=w1bO +-----END PGP SIGNATURE----- diff --git a/.cicd-tools/pgp/verification.txt b/.cicd-tools/pgp/verification.txt new file mode 100644 index 0000000..844c10f --- /dev/null +++ b/.cicd-tools/pgp/verification.txt @@ -0,0 +1 @@ +CICD-Tools Verified diff --git a/.cookiecutter/cookiecutter.json b/.cookiecutter/cookiecutter.json new file mode 100644 index 0000000..0d9c73e --- /dev/null +++ b/.cookiecutter/cookiecutter.json @@ -0,0 +1,46 @@ +{ + "_*DO_NOT_MODIFY_THIS_FILE*": "This file is created to assist with upgrading to future versions of this template.", + "_BRANCH_NAME_BASE": "main", + "_BRANCH_NAME_DEVELOPMENT": "dev", + "_CONFIG_DEFAULT_SHELLCHECK_OPTIONS": "-e SC2317 -P SCRIPTDIR", + "_CONFIG_DEFAULT_SHFMT_OPTIONS": "-i 2 -ci -sr", + "_DOCKER_DEFAULT_CONTAINER": "ghcr.io/cicd-tools-org/cicd-tools:main", + "_GITHUB_CI_ACTIONLINT_SCRIPT_URL": "https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash", + "_GITHUB_CI_DEFAULT_CONCURRENCY": 4, + "_GITHUB_CI_DEFAULT_PYTHON_VERSIONS": [ + "3.9" + ], + "_GITHUB_CI_DEFAULT_VERBOSE_NOTIFICATIONS": false, + "_GITHUB_CI_TEMPLATE_TEST_ARCHITECTURES": [ + "x86_64" + ], + "_GITHUB_CI_TEMPLATE_TEST_BINARY_VERSIONS": [ + "0.0.6" + ], + "_GITHUB_CI_TEMPLATE_TEST_OSX_VERSIONS": [ + 12 + ], + "_MAC_MAKER_ANSIBLE_VERSION": "7.2", + "_MAC_MAKER_MAX_VERSION": "1.0.0", + "_MAC_MAKER_MIN_VERSION": "0.0.6", + "_MAC_MAKER_PYTHON_VERSION": "3.9", + "_checkout": null, + "_copy_without_render": [ + ".cicd-tools/boxes/bootstrap", + ".github/actions", + "profile/handlers/*.yml", + "profile/tasks/post_install/*.yml", + "profile/vars/*.yml" + ], + "_output_dir": "/home/runner/work/profile-generator/profile-generator", + "_repo_dir": ".", + "_template": "https://github.com/osx-provisioner/profile-generator.git", + "author": "Niall Byrne", + "description": "An example Mac Maker profile.", + "email": "niall@niallbyrne.ca", + "github_handle": "osx-provisioner", + "optional_toml_linting": "true", + "optional_workflow_linting": "true", + "project_name": "Profile Example", + "project_slug": "profile-example" +} diff --git a/.github/actions/action-00-toolbox/action.yml b/.github/actions/action-00-toolbox/action.yml new file mode 100644 index 0000000..b2bcc9e --- /dev/null +++ b/.github/actions/action-00-toolbox/action.yml @@ -0,0 +1,18 @@ +--- +name: action-00-toolbox +description: "Fetches the CICD-Tools toolbox." +author: niall@niallbyrne.ca + +inputs: + PROJECT_ROOT_PATH: + default: "." + description: "Optional, allows you to specify a path to the project's root." + required: false + +runs: + using: "composite" + steps: + - name: Toolbox - Install CICD-Tools Toolbox + uses: cicd-tools-org/cicd-tools/.github/actions/action-00-toolbox@main + with: + PROJECT_ROOT_PATH: ${{ inputs.PROJECT_ROOT_PATH }} diff --git a/.github/config/actions/gaurav-nelson-github-action-markdown-link-check.json b/.github/config/actions/gaurav-nelson-github-action-markdown-link-check.json new file mode 100644 index 0000000..8f5f10b --- /dev/null +++ b/.github/config/actions/gaurav-nelson-github-action-markdown-link-check.json @@ -0,0 +1,20 @@ +{ + "httpHeaders": [ + { + "urls": [ + "https://github.com/", + "https://guides.github.com/", + "https://help.github.com/", + "https://docs.github.com/" + ], + "headers": { + "Accept-Encoding": "zstd, br, gzip, deflate" + } + } + ], + "ignorePatterns": [ + { + "pattern": "^https://github.com/osx-provisioner/profile-example/" + } + ] +} diff --git a/.github/config/schemas/workflows/workflow-push.json b/.github/config/schemas/workflows/workflow-push.json new file mode 100644 index 0000000..a6c0d21 --- /dev/null +++ b/.github/config/schemas/workflows/workflow-push.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "0.1.0", + "description": "Ansible Workbench GitHub Push Workflow Schema", + "additionalProperties": false, + "required": [ + "ci_commit_spelling_rev_range", + "ci_commitizen_rev_range", + "ci_concurrency_limit", + "ci_extra_release_content", + "ci_python_versions", + "ci_trufflehog_extra_scan_args", + "ci_verbose_notifications" + ], + "type": "object", + "uniqueItems": true, + "properties": { + "ci_commit_spelling_rev_range": { + "description": "This is the fallback commit range for commit spelling checks. This should contain the id of the first correctly spelled commit in this repo, or HEAD for all commits.", + "type": "string" + }, + "ci_commitizen_rev_range": { + "description": "This is the fallback commit range for commitizen to lint. This should contain the id of the first linted commit in this repo, or HEAD for all commits.", + "type": "string" + }, + "ci_concurrency_limit": { + "description": "This controls the concurrency of each matrix instance in GitHub Actions.", + "type": "number" + }, + "ci_extra_release_content": { + "description": "This controls the concurrency of each matrix instance in GitHub Actions.", + "type": "array", + "contains": { + "type": "string" + } + }, + "ci_python_versions": { + "description": "This array contains the list of Python versions the workflow steps will execute on.", + "type": "array", + "contains": { + "pattern": "^3\\.[0-9]$", + "type": "string" + } + }, + "ci_trufflehog_extra_scan_args": { + "description": "This is a space separated list of extra arguments you can pass to the trufflehog binary.", + "type": "string" + }, + "ci_verbose_notifications": { + "description": "This enables success notifications for each job in the GitHub workflows.", + "type": "boolean" + } + } +} diff --git a/.github/config/workflows/workflow-push.json b/.github/config/workflows/workflow-push.json new file mode 100644 index 0000000..2bc7103 --- /dev/null +++ b/.github/config/workflows/workflow-push.json @@ -0,0 +1,12 @@ +{ + "ci_commit_spelling_rev_range": "HEAD", + "ci_commitizen_rev_range": "HEAD", + "ci_concurrency_limit": 4, + "ci_extra_release_content": [ + "## Customizable Text.", + "This message is appended to the changelog of your GitHub release." + ], + "ci_python_versions": ["3.9"], + "ci_trufflehog_extra_scan_args": "", + "ci_verbose_notifications": false +} diff --git a/.github/scripts/job-50-test-precommit.sh b/.github/scripts/job-50-test-precommit.sh new file mode 100755 index 0000000..8d5c89a --- /dev/null +++ b/.github/scripts/job-50-test-precommit.sh @@ -0,0 +1,149 @@ +#!/bin/bash + +# Performs tests on the pre-commit hooks. + +# Implementation: +# Templates implementing this script will likely also have to customize their .job-50-precommit.yml workflow. +# The API demonstrated here is more for example purposes. + +# 1: The name of a pre-commit test scenario. (See 'main' below.) +# TEST_PROJECT_NAME: The name of the rendered test project. + +# CI only script. + +set -eo pipefail + +main() { + pushd "${TEST_PROJECT_NAME}" >> /dev/null + scenario "${1}" + popd >> /dev/null +} + +scenario() { + + local TEMP_FILE + + test_ansible_lint_fails() { + util "git_reset" + echo "" >> profile/install.yml + git stage profile/install.yml + git commit -m 'test(PRE-COMMIT): fail due to ansible-lint' > error.log 2>&1 || grep "empty-lines" error.log > /dev/null && exit 0 + util "fail" + } + + test_commit_lint_fails() { + util "git_reset" + TEMP_FILE=$(util "create_tmp") + touch "${TEMP_FILE}" + git stage "${TEMP_FILE}" + git commit -m 'test - pre-commit: improperly formatted commit' || exit 0 + util "fail" + } + + test_commit_spelling_fails() { + util "git_reset" + TEMP_FILE=$(util "create_tmp") + touch "${TEMP_FILE}" + git stage "${TEMP_FILE}" + git commit -m 'test(PRE-COMMIT): ssspelling error' || exit 0 + util "fail" + } + + test_credentials_fails() { + util "git_reset" + ssh-keygen -t rsa -b 1024 -f test_key -N '' + git stage test_key test_key.pub + git commit -m 'test(PRE-COMMIT): fail due to credentials' || exit 0 + util "fail" + } + + test_shell_lint_fails() { + util "git_reset" + TEMP_FILE=$(util "create_tmp") + echo -e "#!/bin/bash\nls *.bash" > "${TEMP_FILE}.sh" + git stage "${TEMP_FILE}.sh" + git commit -m 'test(PRE-COMMIT): fail due to shellcheck' || exit 0 + util "fail" + } + + test_shell_format_fails() { + util "git_reset" + TEMP_FILE=$(util "create_tmp") + echo -e "#!/bin/bash\nls bash_scripts;ls shell_scripts" > "${TEMP_FILE}.sh" + git stage "${TEMP_FILE}.sh" + git commit -m 'test(PRE-COMMIT): fail due to shfmt' || exit 0 + util "fail" + } + + test_toml_lint_fails() { + util "git_reset" + sed -i.bak 's/authors =/ authors = /g' pyproject.toml + git stage pyproject.toml + git commit -m 'test(PRE-COMMIT): fail due to tomll' || exit 0 + util "fail" + } + + test_toml_lint_passes() { + util "git_reset" + sed -i.bak "s/description = '.*'/description = 'updated description'/g" pyproject.toml + git stage pyproject.toml + git commit -m 'test(PRE-COMMIT): upgrade python without issue' + } + + test_workflow_lint_fails() { + util "git_reset" + find .github/workflows -type f -name '*.yml' -exec sed -i.bak 's/uses:/illegal-yaml-key:/g' {} \; + git stage .github + git commit -m 'test(PRE-COMMIT): fail due to actionlint' || exit 0 + util "fail" + } + + test_workflow_header_lint_fails() { + util "git_reset" + sed -i.bak 's,-github-workflow,-github-wrong-name,g' .github/workflows/workflow-*.yml + git stage .github + git commit -m 'test(PRE-COMMIT): fail due to workflow header lint' || exit 0 + util fail + } + + "$@" + +} + +util() { + + local COMMAND + local PREFIX + + _util_create_tmp() { + mktemp tmp.XXXXXXX + } + + _util_fail() { + echo "This commit should have failed." + exit 127 + } + + _util_git_reset() { + git reset HEAD + git clean -fd + git checkout . + } + + _util_unknown_command() { + echo "Unknown utility command: '${COMMAND}'" + exit 127 + } + + PREFIX="_util" + COMMAND="${PREFIX}_${1}" + if [[ $(type -t "${COMMAND}") == function ]]; then + shift + "${COMMAND}" "$@" + else + "${PREFIX}_unknown_command" + fi + +} + +main "$@" diff --git a/.github/scripts/step-setup-environment.sh b/.github/scripts/step-setup-environment.sh new file mode 100755 index 0000000..b26688e --- /dev/null +++ b/.github/scripts/step-setup-environment.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Configures environment variables for GitHub Actions. + +# Implementation: +# Templates implementing this script must set the required environment variables in the GitHub runner's environment context. +# +# BRANCH_OR_TAG: The current branch or tag being tested. +# CACHE_TTL: A unique CACHE value that determines the cache's TTL. (Default strategy: day of the month.) +# NOTIFICATION_LINK: Consumed by the notification script to provide a clickable link to the workflow run in GitHub. +# PROJECT_NAME: The slugified name of the template project. Should match the GitHub repository name. +# PROJECT_OWNER: The GitHub owner of the project. +# RELEASE_EXTRA_CONTENT: Extra string contents to append to the generated releases. + +# CI only script. + +set -eo pipefail + +WORKFLOW_NAME="${WORKFLOW_NAME:-""}" + +main() { + + PROJECT_NAME="profile-example" + PROJECT_OWNER="osx-provisioner" + + BRANCH_OR_TAG="$(echo "${GITHUB_REF}" | sed -E 's,refs/heads/|refs/tags/,,g')" + WORKFLOW_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + + [[ "${WORKFLOW_NAME}" != "" ]] && WORKFLOW_NAME="-${WORKFLOW_NAME}" + + { + echo "BRANCH_OR_TAG=${BRANCH_OR_TAG}" + echo "CACHE_TTL=$(date +%d)" + echo "NOTIFICATION_LINK=${PROJECT_NAME}${WORKFLOW_NAME} [<${WORKFLOW_URL}|${BRANCH_OR_TAG}>]" + echo "PROJECT_NAME=${PROJECT_NAME}" + echo "PROJECT_OWNER=${PROJECT_OWNER}" + } >> "${GITHUB_ENV}" + +} + +main "$@" diff --git a/.github/workflows/workflow-push.yml b/.github/workflows/workflow-push.yml new file mode 100644 index 0000000..bb0bbcd --- /dev/null +++ b/.github/workflows/workflow-push.yml @@ -0,0 +1,230 @@ +--- +name: profile-example-github-workflow-push + +# For further details please consult the documentation here: +# https://github.com/osx-provisioner/profile-generator + +# Begin Cookiecutter Template Content + +on: + push: + schedule: + - cron: "0 6 * * 1" + workflow_dispatch: + +# secrets: +# SLACK_WEBHOOK: +# description: "Optional, enables Slack notifications." +# required: false + +jobs: + + configuration: + uses: cicd-tools-org/cicd-tools/.github/workflows/job-00-generic-read_json_file.yml@main + with: + JSON_FILE_PATH: ".github/config/workflows/workflow-push.json" + + start: + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-00-generic-notification.yml@main + with: + NOTIFICATION_EMOJI: ":vertical_traffic_light:" + NOTIFICATION_MESSAGE: "workflow has started!" + + security: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-10-generic-security_scan_credentials.yml@main + with: + EXTRA_BINARY_ARGS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_trufflehog_extra_scan_args }} + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + markdown_links: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-30-generic-markdown_links.yml@main + with: + CONFIG_FILE: ".github/config/actions/gaurav-nelson-github-action-markdown-link-check.json" + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + ansible_lint: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-40-poetry-ansible_lint.yml@main + with: + CONCURRENCY: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + EXTRA_ARGS: "install.yml" + GALAXY_REQUIREMENTS_PATH: "profile/requirements.yml" + PYTHON_VERSIONS: ${{ toJSON(fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_python_versions) }} + TARGET_PATH: "profile" + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + pre-commit_hooks: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-50-poetry-test_basic_precommit_hooks.yml@dev + with: + CHECK_CREDENTIALS: true + CHECK_TOML: true + CHECK_WORKFLOW: true + CONCURRENCY: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + PYTHON_VERSIONS: ${{ toJSON(fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_python_versions) }} + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + commit_lint: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-80-poetry-rev_range_command.yml@main + with: + COMMAND: | + poetry run cz check --rev-range "${PUSHED_COMMIT_REV_RANGE}" + COMMAND_NAME: "Commit Message Lint" + CONCURRENCY: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + PYTHON_VERSIONS: ${{ toJSON(fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_python_versions) }} + REV_RANGE: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_commitizen_rev_range }} + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + commit_spell_check: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-80-poetry-rev_range_command.yml@main + with: + COMMAND: | + CICD_COMMIT_MESSAGES_FILE="$(mktemp XXXXXXXX.git_history_file)" + git log --pretty=format:%s "${PUSHED_COMMIT_REV_RANGE}" > "${CICD_COMMIT_MESSAGES_FILE}" + poetry run pre-commit run --hook-stage commit-msg spelling-commit-message --commit-msg-filename "${CICD_COMMIT_MESSAGES_FILE}" + COMMAND_NAME: "Commit Message Spelling" + CONCURRENCY: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + PYTHON_VERSIONS: ${{ toJSON(fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_python_versions) }} + REV_RANGE: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_commit_spelling_rev_range }} + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + json_schema_lint: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-80-poetry-precommit_commit_stage_hook.yml@main + with: + CONCURRENCY: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + PRECOMMIT_HOOK_ID: "check-jsonschema" + PRECOMMIT_HOOK_NAME: "Workflow Config JSON Schema Linting" + PYTHON_VERSIONS: ${{ toJSON(fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_python_versions) }} + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + markdown_lint: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-80-poetry-precommit_commit_stage_hook.yml@main + with: + CONCURRENCY: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + PRECOMMIT_HOOK_ID: "lint-markdown" + PRECOMMIT_HOOK_NAME: "Markdown Linting" + PYTHON_VERSIONS: ${{ toJSON(fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_python_versions) }} + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + markdown_spelling: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-80-poetry-precommit_commit_stage_hook.yml@main + with: + CONCURRENCY: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + PRECOMMIT_HOOK_ID: "spelling-markdown" + PRECOMMIT_HOOK_NAME: "Markdown Spelling" + PYTHON_VERSIONS: ${{ toJSON(fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_python_versions) }} + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + shell_lint: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + strategy: + fail-fast: true + matrix: + hook: + - id: "format-shell" + name: "Shell Formatting" + - id: "lint-shell" + name: "Shell Linting" + max-parallel: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-80-poetry-precommit_commit_stage_hook.yml@main + with: + CONCURRENCY: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + PRECOMMIT_HOOK_ID: ${{ matrix.hook.id }} + PRECOMMIT_HOOK_NAME: ${{ matrix.hook.name }} + PYTHON_VERSIONS: ${{ toJSON(fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_python_versions) }} + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + toml_lint: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-80-poetry-precommit_commit_stage_hook.yml@main + with: + CONCURRENCY: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + PRECOMMIT_HOOK_ID: "format-toml" + PRECOMMIT_HOOK_NAME: "TOML Formatting" + PYTHON_VERSIONS: ${{ toJSON(fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_python_versions) }} + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + workflow_lint: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + strategy: + fail-fast: true + matrix: + hook: + - id: "lint-github-workflow" + name: "Workflow Linting" + - id: "lint-github-workflow-header" + name: "Workflow Header Linting" + max-parallel: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-80-poetry-precommit_commit_stage_hook.yml@main + with: + CONCURRENCY: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + PRECOMMIT_HOOK_ID: ${{ matrix.hook.id }} + PRECOMMIT_HOOK_NAME: ${{ matrix.hook.name }} + PYTHON_VERSIONS: ${{ toJSON(fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_python_versions) }} + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + yaml_lint: + needs: [configuration] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-80-poetry-precommit_commit_stage_hook.yml@main + with: + CONCURRENCY: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_concurrency_limit }} + PRECOMMIT_HOOK_ID: "yamllint" + PRECOMMIT_HOOK_NAME: "YAML Linting" + PYTHON_VERSIONS: ${{ toJSON(fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_python_versions) }} + VERBOSE_NOTIFICATIONS: ${{ fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_verbose_notifications }} + + create_release: + permissions: + contents: write + needs: [ansible_lint, configuration, commit_lint, commit_spell_check, json_schema_lint, markdown_links, markdown_lint, markdown_spelling, pre-commit_hooks, security, shell_lint, start, toml_lint, workflow_lint, yaml_lint] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-99-poetry-create_release.yml@main + with: + JSON_APPENDED_CONTENT: ${{ toJSON(fromJSON(needs.configuration.outputs.JSON_FILE_DATA).ci_extra_release_content) }} + + success: + needs: [create_release] + secrets: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: cicd-tools-org/cicd-tools/.github/workflows/job-00-generic-notification.yml@main + with: + NOTIFICATION_EMOJI: ":checkered_flag:" + NOTIFICATION_MESSAGE: "workflow has completed successfully!" + +# End Cookiecutter Template Content diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a7c1f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*.retry +.ansible +.cache +.cicd-tools/boxes/* +!.cicd-tools/boxes/bootstrap +.idea +.tool-versions +poetry.lock +profile/*.retry +profile/.mac_maker +profile/collections +profile/env +profile/inventory +profile/roles +spec.json +.cicd-tools/boxes/* +!.cicd-tools/boxes/bootstrap diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 0000000..a36854f --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,13 @@ +--- +default: true + +MD001: false +MD003: false +MD007: false +MD013: false +MD014: false +MD022: false +MD029: + style: ordered +MD032: false +MD033: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..438533d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,68 @@ +--- +default_install_hook_types: + - pre-commit + - commit-msg +repos: + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.22.0 + hooks: + - id: check-jsonschema + name: check-cookiecutter-schema + files: "^\\.cookiecutter/cookiecutter\\.json$" + args: + - "--schemafile" + - ".cicd-tools/boxes/bootstrap/schemas/cookiecutter.json" + stages: [commit] + - id: check-jsonschema + name: check-github-workflow-push-schema + files: "^\\.github/config/workflows/workflow-push.json$" + args: + - "--schemafile" + - ".github/config/schemas/workflows/workflow-push.json" + stages: [commit] + - id: check-metaschema + name: check-github-workflow-metaschemas + files: "^\\.github/config/schemas/.*\\.json$" + stages: [commit] + - repo: https://github.com/commitizen-tools/commitizen + rev: v2.42.1 + hooks: + - id: commitizen + stages: [commit-msg] + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.29.0 + hooks: + - id: yamllint + args: + - "-c" + - "./profile/.yamllint.yml" + stages: [commit] + - repo: https://github.com/cicd-tools-org/pre-commit.git + rev: 0.2.0 + hooks: + - id: format-shell + args: + - "-w" + - "--indent=2" + - "-ci" + - "-sr" + - id: format-toml + - id: lint-ansible + args: + - "profile" + files: "^profile/.+\\.(yaml|yml)$|^profile/.ansible-lint$" + - id: lint-github-workflow + - id: lint-github-workflow-header + - id: lint-markdown + args: + - "-c" + - ".markdownlint.yml" + - id: lint-shell + args: + - "--color=always" + - "--source-path=SCRIPTDIR" + - "--exclude=SC2317" + - "-x" + - id: security-credentials + - id: spelling-commit-message + - id: spelling-markdown diff --git a/.vale.ini b/.vale.ini new file mode 100644 index 0000000..c3c71e7 --- /dev/null +++ b/.vale.ini @@ -0,0 +1,10 @@ +StylesPath = ".vale" +Vocab = "profile-example" + +[*] +BasedOnStyles = Vale +Vale.Terms = NO + +[*.md] +BasedOnStyles = Vale +Vale.Terms = YES diff --git a/.vale/Vocab/profile-example/accept.txt b/.vale/Vocab/profile-example/accept.txt new file mode 100644 index 0000000..0b9c4b8 --- /dev/null +++ b/.vale/Vocab/profile-example/accept.txt @@ -0,0 +1,9 @@ +(A|a)nsible +(API|api) +(CLI|cli) +commitizen +(D|d)ev +(P|p)recheck +tomll +yamllint +(yaml|YAML) diff --git a/.vale/Vocab/profile-example/reject.txt b/.vale/Vocab/profile-example/reject.txt new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a6ba9f0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 Niall Byrne + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9359b9e --- /dev/null +++ b/README.md @@ -0,0 +1,164 @@ +# profile-example + +(Powered by [CICD-Tools](https://github.com/cicd-tools-org/cicd-tools).) + +### Main Branch CI +- [![profile-example](https://github.com/osx-provisioner/profile-example/actions/workflows/workflow-push.yml/badge.svg?branch=main)](https://github.com/osx-provisioner/profile-example/actions/workflows/workflow-push.yml) + +### Dev Branch CI +- [![profile-example](https://github.com/osx-provisioner/profile-example/actions/workflows/workflow-push.yml/badge.svg?branch=dev)](https://github.com/osx-provisioner/profile-example/actions/workflows/workflow-push.yml) + +## Mac Maker Profile + +An example Mac Maker profile. + +Use [Mac Maker](https://github.com/osx-provisioner/mac_maker) to apply this profile to a Mac that is ready to setup. + +## About The Default Configuration + +The template has generated you a development environment for a [Mac Maker](https://github.com/osx-provisioner/mac_maker.git) machine profile, with [functional CI](.github/workflows/workflow-push.yml). + +The default configuration has some excellent functionality out of the box: +- Installs the [Homebrew](https://brew.sh/) cli tools and the content of the centralized package manifest in the [profile/vars/main.yml](./profile/vars/main.yml) file. +- A functional [ClamAV](https://github.com/Cisco-Talos/clamav) install to protect you against malicious downloads. +- Node.js and Python, managed by [asdf](https://asdf-vm.com/#/). + - Activate these language installs by following [these](https://asdf-vm.com/#/core-manage-asdf) instructions for your shell. + +Extend this further by mixing and matching Ansible roles in the [profile/install.yml](./profile/install.yml) file. + +#### Am Important Note about ClamAV on Catalina and Later + +- To get the most out of your ClamAV install, make sure you grant it "Full Disk Access". +- Please take a quick look at the documentation [here](https://github.com/osx-provisioner/role-clamav). + +## Development Requirements + +An OSX machine is of course the best platform to development your profile on, although you could probably use a Linux or BSD machine in a pinch. + +You'll need [Python](https://www.python.org/) **3.9** or later, and a container runtime environment such as [Docker](https://www.docker.com/) or [Colima](https://github.com/abiosoft/colima) is strongly recommended. + +Please see the complete template guide [here](https://github.com/osx-provisioner/profile-generator/blob/main/README.md#requirements). + +## This Looks Complicated, How Can I Customize It? + +Start [here](./profile/vars/main.yml), it's really much simpler than you might think. + +## Making the Default Profile your Own + +To start branching out, and really customizing things, get familiar with the following files: + + - [install.yml](profile/install.yml) + - This is the main [ansible](https://ansible.com) playbook that will be run when you "Apply" this profile. + - [requirements.yml](profile/requirements.yml) + - This is configuration for [ansible-galaxy](https://galaxy.ansible.com/docs/using/installing.html#installing-multiple-roles-from-a-file) dependencies. + - If you add additional roles, include them here, so your Profile includes everything it needs. + - Make sure to read the documentation any new roles and add the required variables to your [main vars file](./profile/vars/main.yml). + +The more customized your want your profile to become, the more you'll benefit from reading up on [ansible](https://ansible.com). + +## Configuration Files (More Complex Knob Tweaking) + +There some configuration files you can fine tune as you see fit: + + - [.ansible-lint](profile/.ansible-lint) + - This is configuration for [ansible-lint](https://ansible-lint.readthedocs.io/), which is used in the included GitHub CI pipeline. + - [.yamllint.yml](profile/.yamllint.yml) + - This is configuration for [yamllint](https://yamllint.readthedocs.io/), which is used in the included GitHub CI pipeline. + +## Profile Precheck Folder + +The `profile` folder contains a special [\_\_precheck\_\_](profile/__precheck__) sub-folder with configuration used by [Mac Maker](https://github.com/osx-provisioner/mac_maker) to help end users apply the Profile to their machines. + +Please read the [Mac Maker Profile](https://mac-maker.readthedocs.io/en/latest/project/4.profiles.html) documentation for details on this. (It's a quick read.) + +## Developing Your Profile + +[Mac Maker](https://github.com/osx-provisioner/mac_maker) can work with _public_ GitHub repositories, or with privately maintained `spec.json` files. + +The Profile has a specific directory structure, but the `spec.json` file lets you mix and match where the directories and files are. It's a bit inflexible in certain ways, because it requires absolute paths, but this makes it ideal to work off a USB stick or any portable media. +When developing your profile locally, it's handy to setup a `spec.json` file that points to all the locations you need, so you can run Mac Maker to test. + +(A common use case for `spec.json` files, is to clone a _private_ git repository to a USB key, and configure the `spec.json` to point to the USB key locations.) + +Please read the [Mac Maker Job Spec](https://mac-maker.readthedocs.io/en/latest/project/5.spec_files.html) documentation for details on this. (It's a quick read.) + +## Securing Your Profile + +Take care not to check in any privileged content such as passwords, or api keys to your profile. This is especially true if you're working with a _public_ GitHub repository. + +### Environment Variables + +- The recommended best practice to work around this is to use environment variables. These are easily loaded into your shell before your run Mac Maker. +- This gives you the ability to parameterize certain aspects of your profile that may differ from machine to machine. +- Use Mac Maker's [precheck](./profile/__precheck__/env.yml) functionality to document your profile's environment variables. + +#### Using Environment Variables in Practice + +In practice this might mean keeping a small shell script that sets variables somewhere safe (such as an encrypted USB key): + +```shell +#!/bin/bash +export MY_SECRET_VALUE="very secret" +``` + +Before applying your profile, you would insert your USB key and source your shell script: + +```shell +$ source /Volumes/USB/my_secret_script.sh +$ ./mac_maker +``` + +Your environment variables will then be accessible inside Ansible configuration: + +```yaml +--- +- name: Read My Secret + ansible.builtin.set_fact: + ansible_variable: "{{ lookup('env', 'MY_SECRET_VALUE') }}" +``` + +You can find simple examples of this in the example profile [here](./profile/vars/main.yml). + +### Ansible Vault + +- Ansible also has an encryption system called [vault](https://docs.ansible.com/ansible/latest/vault_guide) which is used to encrypt files containing sensitive data. +- This would allow you to encrypt and decrypt yaml files containing sensitive material- but I would still recommend NOT making these files public. +- If you're working with securely stored vault files, you can use the `ANSIBLE_VAULT_PASSWORD_FILE` environment variable documented [here](https://docs.ansible.com/ansible/latest/vault_guide/vault_using_encrypted_content.html#setting-a-default-password-source) to decrypt your data during installation. + +## Poetry + +Poetry is leveraged to manage the Python dependencies: +- [Adding Python Packages with Poetry](https://python-poetry.org/docs/cli/#add) +- [Removing Python Packages With Poetry](https://python-poetry.org/docs/cli/#remove) + +You can also conveniently execute commands inside the Python virtual environment by using: `poetry run [my command here]` + +## Pre-Commit Git Hooks + +The python library [pre-commit](https://pre-commit.com/) is installed during templating with a few useful initial hooks. + +**This hooks depend on the presence of a container runtime such as [Docker](https://www.docker.com/) or [Colima](https://github.com/abiosoft/colima) on your development machine.** + +Complete documentation on these hooks can be found [here](https://github.com/osx-provisioner/profile-generator/blob/main/README.md#pre-commit-git-hooks). + +## Restricted Paths + +Certain versions of the Ansible tool chain _may_ use these folders, which you would be best to avoid: +- .ansible/ +- .cache/ +- profile/.ansible/ +- profile/.cache/ + +Mac Maker itself also writes some data overtop of the role (ephemerally at run time) in order to process it, this means there are a few paths that you should shy away from using: +- spec.json _\*_ +- profile/.mac_maker/ +- profile/collections/ _\*_ +- profile/env/ _\*_ +- profile/inventory/ _\*_ +- profile/roles/ _\*_ + +_\*_ _(these paths are marked for deprecation, soon freeing them up for use)_ + +## Default License + +An [MIT](LICENSE) license has been generated for you by default, feel free to discard/change as you see fit. diff --git a/mac_maker.yml b/mac_maker.yml new file mode 100644 index 0000000..4c6d503 --- /dev/null +++ b/mac_maker.yml @@ -0,0 +1,4 @@ +--- +version: + maximum: "1.0.0" + minimum: "0.0.6" diff --git a/profile/.ansible-lint b/profile/.ansible-lint new file mode 100644 index 0000000..eef7f5f --- /dev/null +++ b/profile/.ansible-lint @@ -0,0 +1,121 @@ +--- +# .ansible-lint + +profile: production + +# Allows dumping of results in SARIF format +# sarif_file: result.sarif + +# exclude_paths included in this file are parsed relative to this file's location +# and not relative to the CWD of execution. CLI arguments passed to the --exclude +# option are parsed relative to the CWD of execution. +exclude_paths: + - .ansible/ + - .cache/ + - .idea/ + - .mac_maker/ + - collections/ + - roles/ + +# parseable: true +# quiet: true +# strict: true +# verbosity: 1 + +# Mock modules or roles in order to pass ansible-playbook --syntax-check +mock_modules: [] +mock_roles: [] + +# Enable checking of loop variable prefixes in roles +loop_var_prefix: "^(__|{role}_)" + +# Enforce variable names to follow pattern below, in addition to Ansible own +# requirements, like avoiding python identifiers. To disable add `var-naming` +# to skip_list. +var_naming_pattern: "^[a-z_][a-z0-9_]*$" + +use_default_rules: true +# Load custom rules from this specific folder +# rulesdir: +# - ./rule/directory/ + +# Ansible-lint is able to recognize and load skip rules stored inside +# `.ansible-lint-ignore` (or `.config/ansible-lint-ignore.txt`) files. +# To skip a rule just enter filename and tag, like "playbook.yml package-latest" +# on a new line. +# Optionally you can add comments after the tag, prefixed by "#". We discourage +# the use of skip_list below because that will hide violations from the output. +# When putting ignores inside the ignore file, they are marked as ignored, but +# still visible, making it easier to address later. +skip_list: + - skip_this_tag + +# Ansible-lint does not automatically load rules that have the 'opt-in' tag. +# You must enable opt-in rules by listing each rule 'id' below. +enable_list: + - args + # - empty-string-compare # opt-in + # - no-log-password # opt-in + # - no-same-owner # opt-in + # - name[prefix] # opt-in + # add yaml here if you want to avoid ignoring yaml checks when yamllint + # library is missing. Normally its absence just skips using that rule. + - yaml + +# Report only a subset of tags and fully ignore any others +# tags: +# - jinja[spacing] + +# Ansible-lint does not fail on warnings from the rules or tags listed below +warn_list: + - skip_this_tag + - experimental # experimental is included in the implicit list + +# Some rules can transform files to fix (or make it easier to fix) identified +# errors. `ansible-lint --write` will reformat YAML files and run these transforms. +# By default it will run all transforms (effectively `write_list: ["all"]`). +# You can disable running transforms by setting `write_list: ["none"]`. +# Or only enable a subset of rule transforms by listing rules/tags here. +# write_list: +# - all + +# Offline mode disables installation of requirements.yml and schema refreshing +# offline: true + +# Return success if number of violations compared with previous git +# commit has not increased. This feature works only in git +# repositories. +progressive: false + +# Define required Ansible's variables to satisfy syntax check +extra_vars: + foo: bar + multiline_string_variable: | + line1 + line2 + complex_variable: ":{;\t$()" + +# Uncomment to enforce action validation with tasks, usually is not +# needed as Ansible syntax check also covers it. +# skip_action_validation: false + +# List of additional kind:pattern to be added at the top of the default +# match list, first match determines the file kind. +kinds: + # - galaxy: "**/folder/galaxy.yml" + - playbook: "install.yml" + - tasks: "**/tasks/*.yml" + - vars: "**/vars/*.yml" + - meta: "**/meta/main.yml" + - yaml: "**/*.yaml-too" + +# List of additional collections to allow in only-builtins rule. +# only_builtins_allow_collections: +# - example_ns.example_collection + +# List of additions modules to allow in only-builtins rule. +# only_builtins_allow_modules: +# - example_module + +# Allow setting custom prefix for name[prefix] rule +task_name_prefix: "{stem} | " diff --git a/profile/.yamllint.yml b/profile/.yamllint.yml new file mode 100644 index 0000000..0cc9028 --- /dev/null +++ b/profile/.yamllint.yml @@ -0,0 +1,50 @@ +--- +# Based on ansible-lint config +extends: default + +ignore: | + .ansible/ + .cache/ + .idea/ + .mac_maker/ + collections/ + roles/ + +rules: + braces: + max-spaces-inside: 0 + level: error + brackets: + max-spaces-inside: 0 + level: error + colons: + max-spaces-after: -1 + level: error + commas: + max-spaces-after: -1 + level: error + indentation: + spaces: consistent + indent-sequences: true + level: error + comments: disable + comments-indentation: disable + document-start: + level: error + empty-lines: + max: 3 + level: error + hyphens: + level: error + key-duplicates: + level: error + line-length: disable + new-line-at-end-of-file: + level: error + new-lines: + type: unix + octal-values: + forbid-implicit-octal: true + trailing-spaces: + level: error + truthy: disable diff --git a/profile/__precheck__/env.yml b/profile/__precheck__/env.yml new file mode 100644 index 0000000..c8d101f --- /dev/null +++ b/profile/__precheck__/env.yml @@ -0,0 +1,9 @@ +--- +# Required Environment Variables +# Document all the Environment Variables your profile needs here. + +- name: USER + description: "POSIX systems *usually* use this value to identify the current user. This is the user that will be configured. Consider: $ export USER=$(id -u -n)" + +- name: HOME + description: "POSIX systems use this value to identify the current user's home directory. This value needs to be present." diff --git a/profile/__precheck__/notes.txt b/profile/__precheck__/notes.txt new file mode 100644 index 0000000..610cbae --- /dev/null +++ b/profile/__precheck__/notes.txt @@ -0,0 +1,11 @@ +profile-example + +An example Mac Maker profile. + +=== Pre-Installation + +Add some pre-installation notes for the end user. + +=== Post-Installation + +Add some post-installation notes here. diff --git a/profile/handlers/main.yml b/profile/handlers/main.yml new file mode 100644 index 0000000..4821304 --- /dev/null +++ b/profile/handlers/main.yml @@ -0,0 +1,8 @@ +--- +- name: Reshim asdf + become: True + become_user: "{{ asdf_user }}" + ansible.builtin.command: "bash -lc 'source {{ asdf_source }}; asdf reshim python'" + changed_when: true + args: + chdir: "{{ asdf_user_home }}" diff --git a/profile/install.yml b/profile/install.yml new file mode 100644 index 0000000..2b451b1 --- /dev/null +++ b/profile/install.yml @@ -0,0 +1,23 @@ +--- +- name: "Profile - profile-example" + hosts: localhost + connection: local + + handlers: + - name: Load Handlers + ansible.builtin.import_tasks: "handlers/main.yml" + + pre_tasks: + - name: Load Variables + ansible.builtin.include_vars: "vars/main.yml" + + roles: + - elliotweiser.osx-command-line-tools + - geerlingguy.mac.homebrew + - osx_provisioner.collection.homebrew_retry + - osx_provisioner.collection.clamav + - osx_provisioner.collection.asdf + + post_tasks: + - name: Run Post Install Tasks + ansible.builtin.include_tasks: "tasks/post_install/main.yml" diff --git a/profile/requirements.yml b/profile/requirements.yml new file mode 100644 index 0000000..46f36d2 --- /dev/null +++ b/profile/requirements.yml @@ -0,0 +1,11 @@ +--- +collections: + - name: community.general + version: ">=6.3.0,<8.0.0" + - name: geerlingguy.mac + version: ">=2.1.1,<3.0.0" + - name: osx_provisioner.collection + version: ">=1.0.0,<2.0.0" +roles: + - name: elliotweiser.osx-command-line-tools + version: 2.3.0 diff --git a/profile/tasks/post_install/main.yml b/profile/tasks/post_install/main.yml new file mode 100644 index 0000000..b7974e2 --- /dev/null +++ b/profile/tasks/post_install/main.yml @@ -0,0 +1,33 @@ +--- +- name: Set asdf_source Variable + ansible.builtin.set_fact: + "asdf_source": "{{ asdf_user_home }}/.asdf/asdf.sh" + +- name: Enable asdf pip + become: True + become_user: "{{ asdf_user }}" + ansible.builtin.command: "bash -lc 'source {{ asdf_source }}; asdf reshim python'" + changed_when: true + args: + creates: "{{ asdf_user_home }}/.asdf/shims/pip" + chdir: "{{ asdf_user_home }}" + +- name: Install Default Python Packages + become: True + become_user: "{{ asdf_user }}" + environment: + PATH: "{{ asdf_user_home }}/.asdf/bin:{{ ansible_env.PATH }}" + ansible.builtin.pip: + executable: "{{ asdf_user_home }}/.asdf/shims/pip" + name: "{{ default_python_packages }}" + register: pip_installation + +- name: Reshim asdf Python + become: True + become_user: "{{ asdf_user }}" + ansible.builtin.command: "bash -lc 'source {{ asdf_source }}; asdf reshim python'" + changed_when: true + args: + chdir: "{{ asdf_user_home }}" + notify: + - Reshim asdf diff --git a/profile/vars/main.yml b/profile/vars/main.yml new file mode 100644 index 0000000..7c1c2b3 --- /dev/null +++ b/profile/vars/main.yml @@ -0,0 +1,39 @@ +--- +# osx_provisioner.collection.homebrew-retry +brew_user: "{{ lookup('env', 'USER') }}" +brew_retries: 3 +brew_taps: [] + +# Add Homebrew Packages You Wish To Use Here +brew_packages: + - bash + +# Add Homebrew Casks You Wish To Use Here +brew_casks: + - google-chrome + +# osx_provisioner.collection.asdf +# Add Additional Language Distributions and Versions Here +# https://asdf-vm.com/#/ +asdf_user: "{{ lookup('env', 'USER') }}" +asdf_user_home: "{{ lookup('env', 'HOME') }}" +asdf_homebrew_retries: 3 +asdf_plugins: + - name: "nodejs" + repository: "" + versions: + - 18.14.0 + global: 18.14.0 + - name: "python" + repository: "" + environment: + PYTHON_CONFIGURE_OPTS: "--enable-framework" + versions: + - 3.9.16 + global: 3.9.16 + +# Default Python Packages +default_python_packages: + - cookiecutter + - commitizen + - poetry diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..cc82f0b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,31 @@ +[build-system] +build-backend = 'poetry.core.masonry.api' +requires = ['poetry-core>=1.0.0'] + +[tool] +[tool.commitizen] +bump_message = 'bump(RELEASE): $current_version → $new_version' +pre_bump_hooks = ['.cicd-tools/boxes/bootstrap/commitizen/pre_bump.sh'] +version = '0.1.0' +version_files = ['pyproject.toml:version'] +version_provider = 'poetry' + +[tool.poetry] +authors = ['Niall Byrne '] +description = 'An example Mac Maker profile.' +name = 'profile-example' +version = '0.1.0' + +[tool.poetry.dependencies] +python = '^3.9' + +[tool.poetry.group] +[tool.poetry.group.dev] +[tool.poetry.group.dev.dependencies] +ansible = '^7.2.0' +commitizen = '^3.0.0' +pre-commit = '^3.1.1' + +[tool.poetry.group.dev.dependencies.ansible-lint] +markers = "platform_system != 'Windows'" +version = '^6.12.2'