[VOL-5319] voltctl release problems

jjb/github-release/voltha.yaml
------------------------------
  o Update copyright notice, add SPDX tokens.
  o Edits allow 'make lint-reuse' to run cleanly.

jjb/shell/github-release/help.sh
jjb/shell/github-release/parse-args.sh
--------------------------------------
  o Cosmetic edits.
  o Script is growing so bulk move some functions into named libraries.

jjb/shell/github-release.sh
---------------------------
  o Script edits are mostly added to improve logging and debugging.
  o Replace hardcoded /bbsim/ reference in log output with /{repo}/.
    Can be confusing displaying 'bbism' when voltctl is being released.
  o Added debug function bannerEL() to announce function ENTER/LEAVE.
  o Display function name and line number when error() is called.
  o Added arg --self-verify for debugging.  Perform simple credential
    validation using login() & logout() VS allowing script to run.
  o Added an extra case in getGitVersion().  GERRIT_PROJECT= may not
    be defined while debugging interactively.  Report error and
    suggest fixes.

Signed-off-by: Joey Armstrong <jarmstrong@linuxfoundation.org>
Change-Id: I481b3dfa1cc323d02babb0a5df86145f49ab176f
diff --git a/jjb/github-release/voltha.yaml b/jjb/github-release/voltha.yaml
index e874bd2..a3ec8be 100644
--- a/jjb/github-release/voltha.yaml
+++ b/jjb/github-release/voltha.yaml
@@ -1,5 +1,25 @@
 ---
-# publishing artifacts to GitHub releases
+
+# -----------------------------------------------------------------------
+# Copyright 2023-2024 Open Networking Foundation Contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# -----------------------------------------------------------------------
+# SPDX-FileCopyrightText: 2023-2024 Open Networking Foundation Contributors
+# SPDX-License-Identifier: Apache-2.0
+# -----------------------------------------------------------------------
+# Intent: Config for publishing artifacts to GitHub releases
+# -----------------------------------------------------------------------
 
 - job-template:
     id: github-release-voltha
diff --git a/jjb/shell/github-release.sh b/jjb/shell/github-release.sh
index dddfe0c..fe206ac 100755
--- a/jjb/shell/github-release.sh
+++ b/jjb/shell/github-release.sh
@@ -6,7 +6,7 @@
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 #
-# http:#www.apache.org/licenses/LICENSE-2.0
+# http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
@@ -33,9 +33,23 @@
 declare -g __githost=github.com
 # export DEBUG=1
 
+declare -g pgm
+pgm="$(readlink --canonicalize-existing "$0")"
+readonly pgm
+declare libdir="${pgm:0:-3}"
+readonly libdir
+
+##--------------------##
+##---]  INCLUDES  [---##
+##--------------------##
+source "$libdir/help.sh"
+source "$libdir/parse-args.sh"
+
 ## -----------------------------------------------------------------------
 ## Uncomment to activate
 ## -----------------------------------------------------------------------
+# declare -g -i debug
+
 # Debug arguments
 # declare -i -g argv_gen_version=1
 # declare -i -g draft_release=1
@@ -45,7 +59,7 @@
 
 declare -g scratch              # temp workspace for downloads
 
-declare -g SCRIPT_VERSION='1.4' # git changeset needed
+declare -g SCRIPT_VERSION='1.5' # git changeset needed
 
 ##--------------------##
 ##---]  INCLUDES  [---##
@@ -54,6 +68,34 @@
 ## -----------------------------------------------------------------------
 ## Intent: Register an interrupt handler to display a stack trace on error
 ## -----------------------------------------------------------------------
+function bannerEL()
+{
+    # echo " $i: ${BASH_SOURCE[$i+1]}:${BASH_LINENO[$i]} ${FUNCNAME[$i]}(...)"
+
+    # iam="${0##*/}"
+    if [[ -v debug ]]; then
+        if [[ $# -gt 0 ]]; then
+            local type="$1"; shift
+        else
+            local type='LEAVE'
+        fi
+
+        local func="${FUNCNAME[1]}"
+        local line="${BASH_LINENO[1]}"
+        case "$type" in
+            LEAVE) ;;
+            *) type='ENTER' ;;
+        esac
+
+        printf "** %s %s (LINENO:%s)\n" "$func" "$type" "$line"
+    fi
+
+    return
+}
+
+## -----------------------------------------------------------------------
+## Intent: Register an interrupt handler to display a stack trace on error
+## -----------------------------------------------------------------------
 function errexit()
 {
     local err=$?
@@ -71,11 +113,11 @@
     # Print out the stack trace described by $function_stack
     if [ ${#FUNCNAME[@]} -gt 2 ]
     then
-            echo "Call tree:"
-            for ((i=1;i<${#FUNCNAME[@]}-1;i++))
-            do
-                echo " $i: ${BASH_SOURCE[$i+1]}:${BASH_LINENO[$i]} ${FUNCNAME[$i]}(...)"
-            done
+        echo "Call tree:"
+        for ((i=1;i<${#FUNCNAME[@]}-1;i++))
+        do
+            echo " $i: ${BASH_SOURCE[$i+1]}:${BASH_LINENO[$i]} ${FUNCNAME[$i]}(...)"
+        done
     fi
 
     echo "Exiting with status ${code}"
@@ -109,7 +151,9 @@
         /bin/rm -fr "$scratch"
     fi
 
-    do_logout 'shellcheck-SC2119'
+    # shellcheck disable=SC2119
+    do_logout
+    
     return
 }
 trap sigtrap EXIT
@@ -131,7 +175,7 @@
     rev+=("$(( RANDOM % 10000 + 1 ))")
     local ver="v${rev[0]}.${rev[1]}.${rev[2]}"
 
-    func_echo "VERSION: $ver"
+    func_echo "GENERATED VERSION STRING: $ver"
     ref="$ver"
     return
 }
@@ -172,6 +216,8 @@
     local pkgbase="${0##*/}" # basename
     local pkgname="${pkgbase%.*}"
 
+    bannerEL 'ENTER'
+
     # initEnvVars # moved to full_banner()
 
     ## Create a temp directory for auto-cleanup
@@ -221,7 +267,7 @@
 
     initEnvVars   # set defaults
 
-cat <<EOH
+    cat <<EOH
 
 ** -----------------------------------------------------------------------
 ** IAM: ${iam} :: ${FUNCNAME[0]}
@@ -258,17 +304,33 @@
 function error()
 {
     local iam="${0##*/}"
-    echo "ERROR ${iam} :: ${FUNCNAME[1]}: $*"
+    local func="${FUNCNAME[1]}"
+    local line="${BASH_LINENO[1]}"
+    printf "\nERROR %s :: %s (LINENO:%s): %s" "$iam" "$func" "$line" "$*"
     exit 1
 }
 
 ## -----------------------------------------------------------------------
+## Intent: Script self checking, direct API access for troubleshooting
+## -----------------------------------------------------------------------
+function do_api_validation()
+{
+    # if --verify credentials
+    do_login
+    # shellcheck disable=SC2119
+    do_logout
+    return
+}
+
+## -----------------------------------------------------------------------
 ## Intent: Verify sandbox/build is versioned for release.
 ## -----------------------------------------------------------------------
 function getGitVersion()
 {
     local -n varname=$1; shift
 
+    bannerEL 'ENTER'
+
     local __ver # use prefix('__') to protect callers variable name
     if [[ -v cached_getGitVersion ]]; then
         __ver="$cached_getGitVersion"
@@ -280,21 +342,33 @@
 
     elif [[ -v WORKSPACE ]] && [[ -v GITHUB_TOKEN ]]; then # i_am_jenkins
         local path="$GERRIT_PROJECT"
-        pushd "$path" || error "pushd GERRIT_PROJECT= failed $(declare -p path)"
+        pushd "$path" || { error "pushd GERRIT_PROJECT= failed $(declare -p path)"; }
         __ver="$(git tag -l --points-at HEAD)"
-        popd || error "popd GERRIT_PROJECT= failed"
+        popd || { error "popd GERRIT_PROJECT= failed"; }
 
     elif [[ -v argv_version_file ]]; then # local debug
         [[ ! -f VERSION ]] && error "./VERSION file not found"
         readarray -t tmp < <(sed -e 's/[[:blank:]]//g' 'VERSION')
         __ver="v${tmp[0]}"
 
-    else
+    elif [[ ${#GERRIT_PROJECT} -gt 0 ]]; then
         cd ..
         local path="$GERRIT_PROJECT"
-        pushd "$path" || error "pushd GERRIT_PROJECT= failed $(declare -p path)"
+        declare -p path
+        pushd "$path" || { error "pushd GERRIT_PROJECT= failed $(declare -p path)"; }
         __ver="$(git tag -l --points-at HEAD)"
-        popd || error "popd GERRIT_PROJECT= failed"
+        popd || { error "popd GERRIT_PROJECT= failed"; }
+
+    else # pwd==sandbox
+
+        local line="${BASH_LINENO[0]}"
+        local message="
+GERRIT_PROJECT= is not defined:
+  o One of the following are required:
+    - Define GERRIT_PROJECT= (~jenkins).
+    - Argument --verison-file.
+"
+        error "$message"
     fi
 
     # ------------------------------------------------------
@@ -306,12 +380,12 @@
         echo "Building artifacts for GitHub release."
 
     elif [[ "$__ver" =~ ^v?([0-9]+)\.([0-9]+)\.([0-9]+)-dev([0-9]+)$ ]]; then
-	# v1.2.3-dev (*-dev*) is an implicit draft release.
+        # v1.2.3-dev (*-dev*) is an implicit draft release.
         declare -i -g draft_release=1
         echo "Detected --draft SemVer release version tag [$__ver]"
         echo "Building artifacts for GitHub release."
 
-    # Should also accept:  X.Y.Z-{alpha,beta,...}
+        # Should also accept:  X.Y.Z-{alpha,beta,...}
 
     else
         echo "Version string contains: [${__ver}]"
@@ -325,6 +399,8 @@
 
     varname="$__ver"
     func_echo "Version is [$__ver] from $(/bin/pwd)"
+
+    bannerEL 'LEAVE'
     return
 }
 
@@ -428,7 +504,7 @@
     get_gh_repo_name repo_name
 
     ref="repos/${repo_org}/${repo_name}/releases"
-    func_echo "ref=$ref"
+    func_echo "releases_uri=$ref"
     return
 }
 
@@ -517,9 +593,9 @@
     ## Verify pwd is OK
     for path in \
         "${varpath}/Makefile"\
-        "${varpath}/makefile"\
-        "__ERROR__"\
-    ; do
+            "${varpath}/makefile"\
+            "__ERROR__"\
+        ; do
         case "$path" in
             __ERROR__) error "Makefile not found at ${varpath} !" ;;
             *) [[ -f "$path" ]] && break ;;
@@ -536,7 +612,7 @@
 {
     local iam="${0##*/}"
 
-cat <<EOT
+    cat <<EOT
 
 ** -----------------------------------------------------------------------
 ** IAM: ${iam} :: ${FUNCNAME[0]}
@@ -572,7 +648,7 @@
 
     # Glob available files, exclude checksums
     readarray -t __artifacts < <(find "$dir" -mindepth 1 ! -type d \
-                                | grep -iv -e 'sum256' -e 'checksum')
+                                     | grep -iv -e 'sum256' -e 'checksum')
     # func_echo "$(declare -p __artifacts)"
 
     # -----------------------------------------------------------------------
@@ -582,7 +658,7 @@
     #   o docker container filesystem mount problem (~volume)
     # -----------------------------------------------------------------------
     [[ ${#__artifacts[@]} -eq 0 ]] \
-          && error "Artifact dir is empty: $(declare -p dir)"
+        && error "Artifact dir is empty: $(declare -p dir)"
 
     refA=("${__artifacts[@]}")
     return
@@ -622,14 +698,14 @@
 
 ## -----------------------------------------------------------------------
 ## https://docs.github.com/en/rest/releases?apiVersion=2022-11-28
-    # https://cli.github.com/manual/gh_release_create
-    # --target <branch> or commit SHA
-    # --title
-    # --generate-notes
-    # --release-notes (notes file)
-    # --release
-    # release create dist/*.tgz
-    # --discussion-category "General"
+# https://cli.github.com/manual/gh_release_create
+# --target <branch> or commit SHA
+# --title
+# --generate-notes
+# --release-notes (notes file)
+# --release
+# release create dist/*.tgz
+# --discussion-category "General"
 ## -----------------------------------------------------------------------
 # https://cli.github.com/manual/gh_release_create
 ## -----------------------------------------------------------------------
@@ -682,7 +758,7 @@
 {
     # shellcheck disable=SC2120
     # shellcheck disable=SC2034
-    local unused="$1"; shift
+    [[ $# -gt 0 ]] && { local unused="$1"; shift; }
 
     declare -g pac
     declare -a login_args=()
@@ -692,13 +768,13 @@
     # (sigh) why not quietly return VS forcing a special logic path
     # [[ -v WORKSPACE ]] && [[ -v GITHUB_TOKEN ]] && return
 
-# 12:58:36 ** -----------------------------------------------------------------------
-# 12:58:36 ** jenkins582353203049151829.sh::do_login: --hostname github.com
-# 12:58:36 ** --------------------------------------------------------------------# ---
-# 12:58:36 ** jenkins582353203049151829.sh :: do_login: Detected ENV{GITHUB_TOKEN}=
-# 12:58:36 The value of the GITHUB_TOKEN environment variable is being used for authentication.
-# 12:58:36 To have GitHub CLI store credentials instead, first clear the value from the environment.
-# -----------------------------------------------------------------------
+    # 12:58:36 ** -----------------------------------------------------------------------
+    # 12:58:36 ** jenkins582353203049151829.sh::do_login: --hostname github.com
+    # 12:58:36 ** --------------------------------------------------------------------# ---
+    # 12:58:36 ** jenkins582353203049151829.sh :: do_login: Detected ENV{GITHUB_TOKEN}=
+    # 12:58:36 The value of the GITHUB_TOKEN environment variable is being used for authentication.
+    # 12:58:36 To have GitHub CLI store credentials instead, first clear the value from the environment.
+    # -----------------------------------------------------------------------
 
     get_gh_hostname login_args
 
@@ -745,7 +821,6 @@
     banner "${logout_args[@]}"
     func_echo "$gh_cmd auth logout ${logout_args[*]} <<< 'Y'"
     "$gh_cmd" auth logout "${logout_args[@]}" <<< 'Y'
-
     unset active_login
     return
 }
@@ -764,7 +839,8 @@
     # gh api repos/{owner}/{repo}/releases
     local releases_uri
     get_gh_releases releases_uri
-    # declare -p releases_uri
+
+    func_echo "RUNNING: $gh_cmd api $releases_uri | jq . > release.raw"
 
     refA=()
     "$gh_cmd" api "$releases_uri" | jq . > 'release.raw'
@@ -817,8 +893,8 @@
     local tarball="gh.tar.tgz"
 
     readarray -t latest < <(\
-        curl --silent -qI "$latest" \
-        | awk -F '/' '/^location/ {print  substr($NF, 1, length($NF)-1)}')
+                            curl --silent -qI "$latest" \
+                                | awk -F '/' '/^location/ {print  substr($NF, 1, length($NF)-1)}')
     declare -p latest
     if [ ${#latest[@]} -ne 1 ]; then
         error "Unable to determine latest gh package version"
@@ -883,7 +959,7 @@
     func_echo "Packaging release files"
 
     pushd "$release_temp" >/dev/null \
-        || error "pushd failed: dir is [$release_temp]"
+        || { error "pushd failed: dir is [$release_temp]"; }
 
     declare -a to_release=()
     get_artifacts '.' to_release
@@ -913,7 +989,7 @@
 
     gh_release_create # publish
 
-    popd  >/dev/null || error "pushd failed: dir is [$release_temp]"
+    popd  >/dev/null || { error "pushd failed: dir is [$release_temp]"; }
 
     return
 }
@@ -939,8 +1015,7 @@
 ## -----------------------------------------------------------------------
 function my_gh()
 {
-    func_echo "ENTER"
-    set -x
+    bannerEL 'ENTER'
 
     declare -a cmd=()
     cmd+=("$gh_cmd")
@@ -954,14 +1029,14 @@
 
     while [ $# -gt 0 ]; do
         local arg="$1"; shift
-	func_echo "function arg is [$arg]"
+        func_echo "function arg is [$arg]"
         case "$arg" in
 
             # Modes
             -*debug)
-		# shellcheck disable=SC2034
-		declare -i -g debug=1
-		;;
+                # shellcheck disable=SC2034
+                declare -i -g debug=1
+                ;;
             -*verbose) args+=('--verbose') ;;
 
             -*hostname)
@@ -976,7 +1051,7 @@
                 args+=('--repo' "${__githost}/${val}")
                 ;;
 
-# args+=('--repo' 'github.com/opencord/bbsim')
+            # args+=('--repo' 'github.com/opencord/bbsim')
 
             --repo)
                 local val
@@ -986,7 +1061,7 @@
 
             --tag)
                 local val
-                       get_argv_tag val
+                get_argv_tag val
                 args+=("$val")       # No switch, pass inline
                 ;;
 
@@ -1010,120 +1085,21 @@
     "${cmd[@]}"
     local status=$?
 
-    set +x
-    func_echo "LEAVE"
+    bannerEL 'LEAVE'
 
     if [[ $status -eq 0 ]]; then
-	true
+        true
     else
-	false
+        false
     fi
 
     return
 }
 
-## ---------------------------------------------------------------------------
-## Intent:
-## ---------------------------------------------------------------------------
-function usage()
-{
-    cat <<EOH
-
-Usage: $0
-Usage: make [options] [target] ...
-  --help                      This mesage
-  --pac                       Personal Access Token (path to containing file or a string)
-  --repo-name                 ex: voltctl
-  --repo-org                  ex: opencord
-  --release-notes [f]         Release notes are passed by file argument
-
-[DEBUG]
-  --gen-version               Generate a random release version string.
-  --git-hostname              Git server hostname (default=github.com)
-  --version-file              Read version string from local version file (vs env var)
-
-[MODES]
-  --debug                     Enable script debug mode
-  --draft                     Create a draft release (vs published)
-  --dry-run                   Simulation mode
-  --todo                      Display future enhancement list
-
-All other arguments are pass-through to the gh command.
-
-Usage: $0 --draft --repo-org opencord --repo-name voltctl --git-hostname github.com --pac ~/access.pac
-
-EOH
-
-    return
-}
-
-## ---------------------------------------------------------------------------
-## Intent: Parse script command line arguments
-## ---------------------------------------------------------------------------
-function parse_args()
-{
-    [[ -v DEBUG ]] && func_echo "ENTER"
-
-    while [ $# -gt 0 ]; do
-        local arg="$1"; shift
-        func_echo "ARGV: $arg"
-
-	# shellcheck disable=SC2034
-        case "$arg" in
-
-            -*debug)   declare -i -g debug=1         ;;
-            --draft)   declare -i -g draft_release=1 ;;
-            --dry-run) declare -i -g dry_run=1       ;;
-
-            --version-file)
-                declare -i -g argv_version_file=1
-                ;;
-
-            -*gen-version)
-                declare -g -i argv_gen_version=1
-                ;;
-
-            -*git-hostname)
-                __githost="$1"; shift
-                ;;
-
-            -*release-notes)
-		[[ ! -f "$1" ]] && error "--release-notes: file path required (arg=\"$arg\")"
-                declare -g release_notes="$1"; shift
-                ;;
-
-            -*repo-name)
-                __repo_name="$1"; shift
-                ;;
-
-            -*repo-org)
-                __repo_org="$1"; shift
-                ;;
-
-            -*pac)
-                declare -g pac="$1"; shift
-                readonly pac
-                [[ ! -f "$pac" ]] && error "--token= does not exist ($pac)"
-                : # nop/true
-                ;;
-
-            -*todo) todo ;;
-
-            -*help)
-                usage
-                exit 0
-                ;;
-
-            *) error "Detected unknown argument $arg" ;;
-        esac
-    done
-
-    return
-}
-
 ##----------------##
 ##---]  MAIN  [---##
 ##----------------##
+bannerEL 'ENTER'
 iam="${0##*/}"
 
 full_banner
@@ -1131,48 +1107,51 @@
 init
 install_gh_binary
 
+[[ -v argv_self_check ]] && { do_api_validation; exit 0; }
+
 do_login "$*"
 
 release_path='/dev/null'
 get_release_path release_path
 declare -p release_path
 
-pushd "$release_path" || error "pushd failed: dir is [$release_path]"
+pushd "$release_path" || { error "pushd failed: dir is [$release_path]"; }
 
-    # legacy: getGitVersion "$GERRIT_PROJECT" GIT_VERSION
-    getGitVersion GIT_VERSION
-    getReleaseDescription RELEASE_DESCRIPTION
-    if [[ ! -v release_notes ]]; then
-	func_echo "Generating release notes from RELEASE_DESCRIPTION"
-        declare -g release_notes="$scratch/release.notes"
-        echo "$RELEASE_DESCRIPTION" > "$release_notes"
-    fi
-    cat "$release_notes"
+# legacy: getGitVersion "$GERRIT_PROJECT" GIT_VERSION
+getGitVersion GIT_VERSION
+getReleaseDescription RELEASE_DESCRIPTION
+if [[ ! -v release_notes ]]; then
+    func_echo "Generating release notes from RELEASE_DESCRIPTION"
+    declare -g release_notes="$scratch/release.notes"
+    echo "$RELEASE_DESCRIPTION" > "$release_notes"
+fi
+cat "$release_notes"
 
-    cat <<EOM
+cat <<EOM
 
 ** -----------------------------------------------------------------------
 **         GIT VERSION: $(declare -p GIT_VERSION)
 ** RELEASE_DESCRIPTION: $(declare -p RELEASE_DESCRIPTION)
 **     RELEASE_TARGETS: $(declare -p RELEASE_TARGETS)
 ** -----------------------------------------------------------------------
-** URL: https://github.com/opencord/bbsim/releases
+** URL: https://github.com/opencord/{repo}/releases
 ** -----------------------------------------------------------------------
 ** Running: make ${RELEASE_TARGETS}
 ** -----------------------------------------------------------------------
 EOM
-    # build the release, can be multiple space separated targets
-    # -----------------------------------------------------------------------
-    # % go build command-line-arguments:
-    #      copying /tmp/go-build4212845548/b001/exe/a.out:
-    #      open release/voltctl-1.8.25-linux-amd64: permission denied
-    #   missing: docker run mkdir
-    # -----------------------------------------------------------------------
-    # shellcheck disable=SC2086
-    make "$RELEASE_TARGETS"
-    copyToRelease
 
-  cat <<EOM
+# build the release, can be multiple space separated targets
+# -----------------------------------------------------------------------
+# % go build command-line-arguments:
+#      copying /tmp/go-build4212845548/b001/exe/a.out:
+#      open release/voltctl-1.8.25-linux-amd64: permission denied
+#   missing: docker run mkdir
+# -----------------------------------------------------------------------
+# shellcheck disable=SC2086
+make "$RELEASE_TARGETS"
+copyToRelease
+
+cat <<EOM
 
 ** -----------------------------------------------------------------------
 ** Create the release:
@@ -1183,14 +1162,15 @@
 ** -----------------------------------------------------------------------
 EOM
 
-  showReleases
-  release_staging
+showReleases
+release_staging
 
-  # Useful to display but --draft images use a non-standard subdir identifier.
-  # showReleaseUrl
+# Useful to display but --draft images use a non-standard subdir identifier.
+# showReleaseUrl
 
-  popd  || error "pushd failed: dir is [$release_path]"
+popd  || { error "pushd failed: dir is [$release_path]"; }
 
+# shellcheck disable=SC2119
 do_logout
 
 # [SEE ALSO]
diff --git a/jjb/shell/github-release/help.sh b/jjb/shell/github-release/help.sh
new file mode 100755
index 0000000..7e409a5
--- /dev/null
+++ b/jjb/shell/github-release/help.sh
@@ -0,0 +1,66 @@
+#!/usr/bin/env bash
+# -----------------------------------------------------------------------
+# Copyright 2018-2024 Open Networking Foundation Contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# -----------------------------------------------------------------------
+# SPDX-FileCopyrightText: 2018-2024 Open Networking Foundation Contributors
+# SPDX-License-Identifier: Apache-2.0
+# -----------------------------------------------------------------------
+# Intent:
+# builds (with make) and uploads release artifacts (binaries, etc.) to github
+# given a tag also create checksums files and release notes from the commit
+# message
+# -----------------------------------------------------------------------
+
+## ---------------------------------------------------------------------------
+## Intent: Display program usage
+## ---------------------------------------------------------------------------
+function usage()
+{
+    cat <<EOH
+
+Usage: $0
+Usage: make [options] [target] ...
+  --help                      This mesage
+  --pac                       Personal Access Token (path to containing file or a string)
+  --repo-name                 ex: voltctl
+  --repo-org                  ex: opencord
+  --release-notes [f]         Release notes are passed by file argument
+
+[DEBUG]
+  --gen-version               Generate a random release version string.
+  --git-hostname              Git server hostname (default=github.com)
+  --version-file [f]          Read version string from local version file (vs env var)
+                              Assume ./VERSION if [f] not passed
+[MODES]
+  --debug                     Enable script debug mode
+  --draft                     Create a draft release (vs published)
+  --dry-run                   Simulation mode
+  --todo                      Display future enhancement list
+
+All other arguments are pass-through to the gh command.
+
+Usage: $0 --draft --repo-org opencord --repo-name voltctl --git-hostname github.com --pac ~/access.pac
+
+# Troubleshoot credentials:
+Usage: $0 --pac ~/.access.pac --verify
+
+EOH
+
+    return
+}
+
+: # assign $?=0 for source $script
+
+# [EOF]
diff --git a/jjb/shell/github-release/parse-args.sh b/jjb/shell/github-release/parse-args.sh
new file mode 100644
index 0000000..fb79412
--- /dev/null
+++ b/jjb/shell/github-release/parse-args.sh
@@ -0,0 +1,97 @@
+#!/usr/bin/env bash
+# -----------------------------------------------------------------------
+# Copyright 2018-2024 Open Networking Foundation Contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# -----------------------------------------------------------------------
+# SPDX-FileCopyrightText: 2018-2024 Open Networking Foundation Contributors
+# SPDX-License-Identifier: Apache-2.0
+# -----------------------------------------------------------------------
+# Intent:
+# builds (with make) and uploads release artifacts (binaries, etc.) to github
+# given a tag also create checksums files and release notes from the commit
+# message
+# -----------------------------------------------------------------------
+
+## ---------------------------------------------------------------------------
+## Intent: Parse script command line arguments
+## ---------------------------------------------------------------------------
+function parse_args()
+{
+    while [ $# -gt 0 ]; do
+        local arg="$1"; shift
+        func_echo "ARGV: $arg"
+
+        # shellcheck disable=SC2034
+        case "$arg" in
+
+            -*debug)   declare -i -g debug=1         ;;
+            --draft)   declare -i -g draft_release=1 ;;
+            --dry-run) declare -i -g dry_run=1       ;;
+
+            --version-file)
+                # [TODO] pass arg and infer path from that
+                declare -i -g argv_version_file=1
+
+                if [[ $# -gt 0 ]] && [[ "$1" == *"/VERSION" ]]; then
+                    arg="$1"; shift
+                    [[ ! -e "$arg" ]] && { error "--version-file $arg does not exist"; }
+                    local path="${arg%/*}"
+                    cd "$path" || { error "cd $path: directory does not exist"; }
+                fi
+                ;;
+
+            -*gen-version)
+                declare -g -i argv_gen_version=1
+                ;;
+
+            -*git-hostname)
+                __githost="$1"; shift
+                ;;
+
+            -*release-notes)
+                [[ ! -f "$1" ]] && error "--release-notes: file path required (arg=\"$arg\")"
+                declare -g release_notes="$1"; shift
+                ;;
+
+            -*repo-name)
+                __repo_name="$1"; shift
+                ;;
+
+            -*repo-org)
+                __repo_org="$1"; shift
+                ;;
+
+            -*pac)
+                declare -g pac="$1"; shift
+                readonly pac
+                [[ ! -f "$pac" ]] && error "--token= does not exist ($pac)"
+                : # nop/true
+                ;;
+
+            -*todo) todo ;;
+
+            --self-check) declare -g -i argv_self_check=1 ;;
+
+            -*help) usage; exit 0 ;;
+
+            *) error "Detected unknown argument $arg" ;;
+        esac
+    done
+
+    return
+}
+
+: # assign $?=0 for source $script
+
+# [EOF]