#!/bin/zsh set -eu SCRIPT_NAME=${0:t} SCRIPT_DIR=${0:A:h} DEFAULT_PARENT_WS='ssh://pnyc@dabel.us.oracle.com//workspace/pnyc/solaris-reviews/on-sru' DEFAULT_DEST_ROOT="${HOME}/PycharmProjects" DEFAULT_FOLDER_PREFIX='PetrN/' DEFAULT_POINT_OF_CONTACT='petr.nyc@oracle.com' DEFAULT_SLACK_CHANNEL='@pnyc' DEFAULT_PYTHON='/opt/homebrew/bin/python3.11' OS_NAME="$(uname -s)" export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin${PATH:+:$PATH}" usage() { cat < -f folder_prefix Jenkins folder prefix. Default: ${DEFAULT_FOLDER_PREFIX} -c point_of_contact Contact email for generated config. Default: ${DEFAULT_POINT_OF_CONTACT} -s slack_channel Slack handle/channel for generated config. Default: ${DEFAULT_SLACK_CHANNEL} -p python_exe Python executable for virtualenv creation. Default: ${DEFAULT_PYTHON} -j python_jenkins_path Optional local checkout to inject into requirements.txt. Default: disabled -h Show this help text. Environment overrides: MRSHUGHES_PARENT_WS MRSHUGHES_DEST_DIR MRSHUGHES_FOLDER_PREFIX MRSHUGHES_POINT_OF_CONTACT MRSHUGHES_SLACK_CHANNEL MRSHUGHES_PYTHON MRSHUGHES_PYTHON_JENKINS_PATH The script exits if dest_dir already exists. EOF } die() { print -u2 -- "$@" exit 1 } require_command() { command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1" } resolve_executable() { local executable=${~1} if [[ "$executable" == */* ]]; then [[ -x "$executable" ]] || die "Executable not found or not executable: $executable" print -r -- "$executable" return fi command -v "$executable" >/dev/null 2>&1 || die "Command not found in PATH: $executable" command -v "$executable" } disable_proxy() { local proxy_script="${SCRIPT_DIR}/proxy" if [[ -f "$proxy_script" ]]; then source "$proxy_script" off >/dev/null fi } ensure_clone_identity_loaded() { local ssh_host=$1 local identity_file local identity_pub identity_file=$(ssh -G "$ssh_host" 2>/dev/null | awk '/^identityfile / {print $2; exit}') [[ -n "$identity_file" ]] || return identity_file=${~identity_file} identity_pub="${identity_file}.pub" [[ -f "$identity_file" && -f "$identity_pub" ]] || return if ssh-add -T "$identity_pub" >/dev/null 2>&1; then return fi print -- "Loading SSH identity for ${ssh_host}: ${identity_file}" if [[ "$OS_NAME" == "Darwin" ]]; then ssh-add --apple-use-keychain "$identity_file" >/dev/null else ssh-add "$identity_file" >/dev/null fi } apply_python_jenkins_override() { local checkout_path=$1 local tmp_requirements tmp_requirements=$(mktemp) sed -E "s|^git.*$|git+file://${checkout_path}|" requirements.txt > "$tmp_requirements" /bin/mv "$tmp_requirements" requirements.txt } yaml_escape() { local value=$1 value=${value//\\/\\\\} value=${value//\"/\\\"} print -r -- "$value" } set_yaml_key() { local file=$1 local key=$2 local value local tmp_file value=$(yaml_escape "$3") tmp_file=$(mktemp) if ! awk -v key="$key" -v value="$value" ' $0 ~ "^[[:space:]]*" key ":[[:space:]]*" { match($0, /^[[:space:]]*/) print substr($0, 1, RLENGTH) key ": \"" value "\"" changed = 1 next } { print } END { exit changed ? 0 : 1 } ' "$file" > "$tmp_file"; then /bin/rm -f "$tmp_file" die "Expected key not found in ${file}: ${key}" fi /bin/mv "$tmp_file" "$file" } insert_yaml_key_after() { local file=$1 local anchor=$2 local key=$3 local value local tmp_file value=$(yaml_escape "$4") tmp_file=$(mktemp) if ! awk -v anchor="$anchor" -v key="$key" -v value="$value" ' { print if (!inserted && $0 ~ "^[[:space:]]*" anchor ":[[:space:]]*") { match($0, /^[[:space:]]*/) print substr($0, 1, RLENGTH) key ": \"" value "\"" inserted = 1 } } END { exit inserted ? 0 : 1 } ' "$file" > "$tmp_file"; then /bin/rm -f "$tmp_file" die "Expected anchor not found in ${file}: ${anchor}" fi /bin/mv "$tmp_file" "$file" } configure_defaults_devel() { local file=$1 set_yaml_key "$file" script_dir_base "$SCRIPT_DIR_BASE" if grep -Eq '^[[:space:]]*pipeline_workspace:[[:space:]]*' "$file"; then set_yaml_key "$file" pipeline_workspace "$JENKINS_CLONE_FROM" else insert_yaml_key_after "$file" script_dir_base pipeline_workspace "$JENKINS_CLONE_FROM" fi set_yaml_key "$file" folder_prefix "$FOLDER_PREFIX" set_yaml_key "$file" point_of_contact "$POINT_OF_CONTACT" set_yaml_key "$file" slack_channel "$SLACK_CHANNEL" } find_lint_dir() { if [[ -d "${DEST_DIR}/solaris/on/production" ]]; then print -r -- "${DEST_DIR}/solaris/on/production" return fi if [[ -d "${DEST_DIR}/solaris/userland/sru" ]]; then print -r -- "${DEST_DIR}/solaris/userland/sru" return fi die "Unable to determine lint directory under ${DEST_DIR}/solaris" } print_config() { print -- "Parent workspace: ${PARENT_WS}" print -- "Destination directory: ${DEST_DIR}" print -- "Folder prefix: ${FOLDER_PREFIX}" print -- "Point of contact: ${POINT_OF_CONTACT}" print -- "Slack channel: ${SLACK_CHANNEL}" print -- "Python executable: ${PYTHON_EXE}" if [[ -n "$PYTHON_JENKINS_PATH" ]]; then print -- "python-jenkins override: ${PYTHON_JENKINS_PATH}" else print -- "python-jenkins override: disabled" fi } PARENT_WS="${MRSHUGHES_PARENT_WS:-$DEFAULT_PARENT_WS}" DEST_DIR="${MRSHUGHES_DEST_DIR:-}" FOLDER_PREFIX="${MRSHUGHES_FOLDER_PREFIX:-$DEFAULT_FOLDER_PREFIX}" POINT_OF_CONTACT="${MRSHUGHES_POINT_OF_CONTACT:-$DEFAULT_POINT_OF_CONTACT}" SLACK_CHANNEL="${MRSHUGHES_SLACK_CHANNEL:-$DEFAULT_SLACK_CHANNEL}" PYTHON_EXE="${MRSHUGHES_PYTHON:-$DEFAULT_PYTHON}" PYTHON_JENKINS_PATH="${MRSHUGHES_PYTHON_JENKINS_PATH:-}" while getopts ":r:d:f:c:s:p:j:h" opt; do case "$opt" in r) PARENT_WS="$OPTARG" ;; d) DEST_DIR="$OPTARG" ;; f) FOLDER_PREFIX="$OPTARG" ;; c) POINT_OF_CONTACT="$OPTARG" ;; s) SLACK_CHANNEL="$OPTARG" ;; p) PYTHON_EXE="$OPTARG" ;; j) PYTHON_JENKINS_PATH="$OPTARG" ;; h) usage exit 0 ;; :) die "Missing argument for -$OPTARG" ;; \?) usage >&2 die "Unknown option: -$OPTARG" ;; esac done shift $((OPTIND - 1)) [[ $# -eq 0 ]] || die "Unexpected positional arguments: $*" for command_name in hg awk sed mktemp grep make; do require_command "$command_name" done PARENT_WS="${PARENT_WS%/}" REPO=${PARENT_WS##*/} [[ -n "$DEST_DIR" ]] || DEST_DIR="${DEFAULT_DEST_ROOT}/${REPO}" DEST_DIR=${~DEST_DIR:A} PYTHON_EXE=$(resolve_executable "$PYTHON_EXE") if [[ -n "$PYTHON_JENKINS_PATH" ]]; then PYTHON_JENKINS_PATH=${~PYTHON_JENKINS_PATH:A} [[ -d "$PYTHON_JENKINS_PATH" ]] || die "Required directory not found: ${PYTHON_JENKINS_PATH}" fi [[ ! -e "$DEST_DIR" ]] || die "Destination already exists: ${DEST_DIR}" JENKINS_CLONE_FROM=$PARENT_WS if [[ "$PARENT_WS" == ssh://* ]]; then JENKINS_CLONE_FROM="ssh://${${PARENT_WS#ssh://}#*@}" ensure_clone_identity_loaded "${${PARENT_WS#ssh://}%%/*}" fi SCRIPT_DIR_BASE=$PARENT_WS if [[ "$PARENT_WS" == *'//'* ]]; then SCRIPT_DIR_BASE="/${PARENT_WS##*//}" fi print_config print -- "Validating Mercurial access to ${PARENT_WS}" hg identify "$PARENT_WS" >/dev/null print -- "Cloning ${PARENT_WS} into ${DEST_DIR}" mkdir -p "${DEST_DIR:h}" hg clone "$PARENT_WS" "$DEST_DIR" cd "$DEST_DIR" for required_path in \ requirements.txt \ Makefile.inc \ common/tools/create_virtualenv \ common/etc/passwd.template \ common/jobs/defaults.devel.tmpl \ common/jobs/defaults.stage.tmpl do [[ -e "$required_path" ]] || die "Required path not found: ${DEST_DIR}/${required_path}" done [[ -x common/tools/create_virtualenv ]] || die "Required executable not found: ${DEST_DIR}/common/tools/create_virtualenv" disable_proxy /bin/rm -rf venv if [[ -n "$PYTHON_JENKINS_PATH" ]]; then print -- "Applying local python-jenkins override from ${PYTHON_JENKINS_PATH}" apply_python_jenkins_override "$PYTHON_JENKINS_PATH" fi /usr/bin/env PATH="$PATH" /bin/bash -lc 'common/tools/create_virtualenv "$@"' bash "$PYTHON_EXE" requirements.txt venv { print -- '[alias]' print -- 'ci = ci -X Makefile.inc' print -- 'st = st -X Makefile.inc' } >> .hg/hgrc tmp_makefile=$(mktemp) sed 's:PYTHON3=python3.7:PYTHON3=python3.11:g' Makefile.inc > "$tmp_makefile" /bin/mv "$tmp_makefile" Makefile.inc /bin/cp common/etc/passwd.template common/etc/passwd /bin/cp common/jobs/defaults.devel.tmpl common/jobs/defaults.devel.yml /bin/cp common/jobs/defaults.stage.tmpl common/jobs/defaults.stage.yml configure_defaults_devel common/jobs/defaults.devel.yml LINT_DIR=$(find_lint_dir) disable_proxy cd "$LINT_DIR" make FAKE_DEVEL_ENV=yes lint