Skip to content

Commit 4daad1e

Browse files
committed
Add github issue functions with 'hub' cli
This commit adds functions for creating and commenting on github issues via the hub cli. Related: conjurinc/ops#492
1 parent 2e80a61 commit 4daad1e

File tree

12 files changed

+489
-22
lines changed

12 files changed

+489
-22
lines changed

README.md

+19
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ files within it's directory.
122122
<td>Git helpers</td>
123123
<td>
124124
<ol>
125+
<li><b>bl_git_available</b>: True if git binary or function is available</li>
126+
<li><b>bl_in_git_repo</b>: True if current directory is a git working directory</li>
127+
<li><b>bl_github_owner_repo</b>: returns $owner/$repo extracted from the url of the origin remote</li>
125128
<li><b>bl_repo_root</b>: Find the root of the current git repo.</li>
126129
<li><b>bl_all_files_in_repo</b>: List files tracked by git.</li>
127130
<li><b>bl_remote_latest_tag</b>: Returns the symbolic name of the latest tag from a remote.</li>
@@ -133,12 +136,28 @@ files within it's directory.
133136
<li><b>bl_cat_gittrees</b>: Returns the contents of .gittrees from the top level of the repo, excluding any comments. Fails if .gittrees is not present.</li>
134137
</ol>
135138
</td>
139+
</tr>
140+
<tr>
141+
<td><a href="github/lib">git</a></td>
142+
<td>Github Related Functions</td>
143+
<td>
144+
<ol>
145+
<li><b>bl_hub_available</b>: True if hub binary or function is available</li>
146+
<li><b>bl_hub_creds_available</b>: True if hub creds are available (file or env vars)</li>
147+
<li><b>bl_hub_check</b>: Preflight check for hub, true if git installed, in git repo, hub installed and hub creds are available</li>
148+
<li><b>bl_hub_download_latest</b>: Download latest hub binary from github and install to ~/bin or specified path</li>
149+
<li><b>bl_hub_issue_number_for_title</b>: Find the issue number for an issue from its title, searches open issues in the current repo. (current repo = workding directory, repo is found by origin remote)</li>
150+
<li><b>bl_hub_add_issue_comment</b>: Add a comment to an issue</li>
151+
<li><b>bl_hub_comment_or_create_issue</b>: Create issue if an issue matching the title doesn't exist. If a match is found, add a comment to it</li>
152+
</ol>
153+
</td>
136154
</tr>
137155
<td><a href="helpers/lib">helpers</a></td>
138156
<td>Bash scripting helpers</td>
139157
<td>
140158
<ol>
141159
<li><b>bl_die</b>: print message and exit 1</li>
160+
<li><b>bl_fail</b>: print message and return 1</li>
142161
<li><b>bl_spushd/bl_spopd</b>: Safe verisons of pushd & popd that call die if the push/pop fails, they also drop stdout. </li>
143162
<li><b>bl_is_num</b>: Check if a value is a number via regex</li>
144163
<li><b>bl_retry</b>: Retry a command until it succeeds up to a user specified maximum number of attempts. Escalating delay between attempts.</li>

filehandling/lib

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ function bl_abs_path() {
77
# generate absolute path from relative path
88
# path : relative filename
99
# return : absolute path
10+
11+
local path
12+
1013
if [[ -z "${1:-}" ]]; then
1114
path="."
1215
else

git/lib

+35
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,43 @@
22

33
: "${BASH_LIB_DIR:?BASH_LIB_DIR must be set. Please source bash-lib/init before other scripts from bash-lib.}"
44

5+
6+
function bl_git_available(){
7+
type git &>/dev/null || bl_fail "Git binary not found in ${PATH}"
8+
}
9+
10+
function bl_in_git_repo(){
11+
bl_git_available
12+
git status >/dev/null || bl_die "$(pwd) is not within a git repo."
13+
}
14+
15+
function bl_github_owner_repo(){
16+
bl_in_git_repo
17+
remote="${1:-origin}"
18+
19+
git remote -v | grep -q "${remote}" || bl_die "Remote ${remote} doesn't exist for repo ${PWD}"
20+
git remote -v | grep -q "${remote}.*github" || bl_die "Remote ${remote} is not a github remote in repo ${PWD}"
21+
[[ "$(git remote -v |grep "${remote}")" =~ github.com[:/]([^ .]*) ]]
22+
echo "${BASH_REMATCH[1]}"
23+
}
24+
525
# Get the top level of a git repo
626
function bl_repo_root(){
27+
bl_in_git_repo
728
git rev-parse --show-toplevel
829
}
930

1031
# List files tracked by git
1132
function bl_all_files_in_repo(){
33+
bl_in_git_repo
1234
git ls-tree -r HEAD --name-only
1335
}
1436

1537
# Find the latest tag available at a repo url
1638
# Returns tag name, not sha
1739
function bl_remote_latest_tag(){
40+
bl_in_git_repo
41+
1842
local -r remote_url="${1}"
1943
# In ls-remote the ^{} suffix refers to a peeled/dereferenced object.
2044
# eg refs/tags/v0.0.1^{} shows the SHA of the commit that was tagged,
@@ -29,14 +53,19 @@ function bl_remote_latest_tag(){
2953

3054
# Find the SHA of the latests commit to be tagged in a remote repo
3155
function bl_remote_latest_tagged_commit(){
56+
bl_in_git_repo
57+
3258
local -r remote="${1}"
3359
local -r tag="$(bl_remote_latest_tag "${remote}")"
3460
git ls-remote "${remote}" | awk "/refs\/tags\/${tag}\^/{print \$1}"
3561
}
3662

3763
function bl_remote_sha_for_ref(){
64+
bl_in_git_repo
65+
3866
local -r remote="${1}"
3967
local -r ref="${2}"
68+
local peeled_ref
4069

4170
# First try adding ^{} to the ref, incase it's a tag
4271
# and needs peeling. If nothing is found for that,
@@ -55,6 +84,8 @@ function bl_remote_sha_for_ref(){
5584
}
5685

5786
function bl_remote_tag_for_sha(){
87+
bl_in_git_repo
88+
5889
local -r remote="${1}"
5990
local -r sha="${2}"
6091
git ls-remote "${remote}" \
@@ -65,11 +96,13 @@ function bl_remote_tag_for_sha(){
6596
## Minimal git subtree functionality required for tests to pass
6697
# full subtree functionality is not ready for merge.
6798
function bl_gittrees_present(){
99+
bl_in_git_repo
68100
local -r git_trees="$(bl_repo_root)/.gittrees"
69101
[[ -e "${git_trees}" ]]
70102
}
71103

72104
function bl_cat_gittrees(){
105+
bl_in_git_repo
73106
local -r git_trees="$(bl_repo_root)/.gittrees"
74107
local -r subtrees_file_format=".gittrees should contain one subtree per line,\
75108
space seperated with three fields: subtree_path renmote_url remote_name"
@@ -78,6 +111,8 @@ space seperated with three fields: subtree_path renmote_url remote_name"
78111
}
79112

80113
function bl_tracked_files_excluding_subtrees(){
114+
bl_in_git_repo
115+
local subtrees
81116
if bl_gittrees_present; then
82117
subtrees="$(bl_cat_gittrees | awk '{print $1}' | paste -sd '|' -)"
83118
bl_all_files_in_repo | grep -E -v "${subtrees}"

github/lib

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#!/bin/bash
2+
3+
: "${BASH_LIB_DIR:?BASH_LIB_DIR must be set. Please source bash-lib/init before other scripts from bash-lib.}"
4+
5+
function bl_hub_available(){
6+
# type instead of which, so it can be stubbed in tests
7+
type hub &>/dev/null || bl_fail "hub (github cli) binary not found, please install it via your package manager or use bl_hub_download_latest."
8+
}
9+
10+
function bl_hub_creds_available(){
11+
config_file="${HUB_CONFIG:-${HOME}/.config/hub}"
12+
[[ -n "${GITHUB_USER:-}" ]] && [[ -n "${GITHUB_TOKEN:-}" ]] && return
13+
[[ -e "${config_file}" ]] && return
14+
bl_fail "No credentials found for (git)hub please set GITHUB_USER and GITHUB_TOKEN or create ~/.config/hub"
15+
}
16+
17+
function bl_hub_check(){
18+
bl_in_git_repo \
19+
&& bl_hub_available \
20+
&& bl_hub_creds_available
21+
}
22+
23+
function bl_hub_download_latest(){
24+
local install_dir="${1:-${HOME}/bin}"
25+
local os_arch="${2:-}"
26+
local tmpdir=".hubdl"
27+
local path
28+
local download_url
29+
local bin_path
30+
31+
if [[ -z "${os_arch}" ]]; then
32+
if [[ "${OSTYPE}" =~ "darwin" ]]; then
33+
os_arch="darwin-amd64"
34+
else
35+
os_arch="linux-amd64"
36+
fi
37+
bl_debug "Hub Download detected arch: ${os_arch}"
38+
fi
39+
40+
path="$(curl -s -L https://github.com/github/hub/releases/latest |grep -o '[^"]*hub-'${os_arch}'[^"]*')"
41+
download_url="https://github.com/${path}"
42+
43+
bin_path="${install_dir}/hub"
44+
mkdir -p "${install_dir}"
45+
46+
mkdir -p "${tmpdir}"
47+
bl_spushd "${tmpdir}"
48+
curl -s -L "${download_url}" > hub.tgz
49+
tar xf hub.tgz
50+
bl_spopd
51+
mv "${tmpdir}"/*/bin/hub "${bin_path}"
52+
rm -rf "${tmpdir}"
53+
54+
bl_info "${download_url}/bin/hub --> ${bin_path}"
55+
}
56+
57+
function bl_hub_issue_number_for_title(){
58+
local title="${1}"
59+
bl_hub_check
60+
hub issue \
61+
|grep "${title}" \
62+
|awk -F'[ #]+' '{print $2}'
63+
}
64+
65+
function bl_hub_add_issue_comment(){
66+
local issue_number="${1}"
67+
local comment="${2}"
68+
69+
bl_hub_check
70+
71+
[[ -n "${comment}" ]] || bl_die "bl_hub_add_issue_comment: Comment must not be empty"
72+
hub issue show "${issue_number}" >/dev/null || bl_die "Github Issue number ${issue_number} isn't valid for repo $(pwd)"
73+
74+
owner_repo="$(bl_github_owner_repo)"
75+
if hub api "repos/${owner_repo}/issues/${issue_number}/comments" --field body="${comment}" >/dev/null; then
76+
bl_debug "Added comment: \"${comment}\" to https://github.com/${owner_repo}/issues/${issue_number}"
77+
else
78+
bl_fail "Failed to add comment: ${comment} to issue: ${owner_repo}#${issue_number}"
79+
fi
80+
}
81+
82+
83+
function bl_hub_comment_or_create_issue(){
84+
local title="${1}"
85+
local message="${2}"
86+
local issue_number
87+
local issue_url
88+
local action
89+
local owner_repo
90+
bl_hub_check
91+
92+
owner_repo="$(bl_github_owner_repo)"
93+
issue_number="$(bl_hub_issue_number_for_title "${title}" ||:)"
94+
95+
if [[ -z "${issue_number}" ]]; then
96+
action="created"
97+
# issue doesn't exist create it
98+
issue_url="$(hub issue create -m "${title}
99+
100+
${message}")"
101+
102+
# Example issue url: https://github.com/{owner}/{repo}/issues/{issue number}"
103+
# To find the issue number, split on / and take the last field
104+
issue_number="$(awk -F'/' '{print $NF}' <<<"${issue_url}" )"
105+
106+
bl_debug "Created issue: ${issue_url} with title \"${title}\""
107+
else
108+
issue_url="https://github.com/${owner_repo}/issues/${issue_number}"
109+
action="commented"
110+
bl_debug "Found existing issue for title \"${title}\": ${issue_url}"
111+
bl_hub_add_issue_comment "${issue_number}" "${message}"
112+
fi
113+
cat <<EOJ
114+
{
115+
"action": "${action}",
116+
"issue_number": "${issue_number}",
117+
"issue_url": "${issue_url}",
118+
"issue_ref": "${owner_repo}#${issue_number}"
119+
}
120+
EOJ
121+
}

helpers/lib

+23-11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ function bl_die(){
77
exit 1
88
}
99

10+
function bl_fail(){
11+
bl_error "${@}"
12+
return 1
13+
}
14+
1015
#safe pushd
1116
function bl_spushd(){
1217
if ! pushd "${1}" >/dev/null; then
@@ -36,24 +41,28 @@ function bl_retry {
3641
# Maxiumum amount of fixed delay between attempts
3742
# a random value will still be added.
3843
local -r MAX_BACKOFF=30
44+
local rc
45+
local count
46+
local retries
47+
local backoff
3948

4049
if [[ ${#} -lt 2 ]]; then
4150
bl_die "retry usage: retry <retries> <command>"
4251
fi
4352

44-
local retries=$1
53+
retries=$1
4554
shift
4655

4756
if ! bl_is_num "${retries}"; then
4857
bl_die "Invalid number of retries: ${retries} for command '${*}'".
4958
fi
5059

51-
local count=0
60+
count=0
5261
until eval "$@"; do
5362
# Command failed, otherwise until would have skipped the loop
5463

5564
# Store return code so it can be reported to the user
56-
exit=$?
65+
rc=$?
5766
count=$((count + 1))
5867
if [ "${count}" -lt "${retries}" ]; then
5968
# There are still retries left, calculate delay and notify user.
@@ -65,12 +74,12 @@ function bl_retry {
6574
# Add a random amount to the delay to prevent competing processes
6675
# from re-colliding.
6776
wait=$(( backoff + (RANDOM % count) ))
68-
bl_info "'${*}' Retry $count/$retries exited $exit, retrying in $wait seconds..."
77+
bl_info "'${*}' Retry $count/$retries exited $rc, retrying in $wait seconds..."
6978
sleep $wait
7079
else
7180
# Out of retries :(
72-
bl_error "Retry $count/$retries exited $exit, no more retries left."
73-
return $exit
81+
bl_error "Retry $count/$retries exited $rc, no more retries left."
82+
return $rc
7483
fi
7584
done
7685
return 0
@@ -84,6 +93,9 @@ function bl_retry_constant {
8493

8594
local retries=$1; shift
8695
local interval=$1; shift
96+
local count
97+
local rc
98+
local interval
8799

88100
if ! bl_is_num "${retries}"; then
89101
bl_die "Invalid number of retries: ${retries} for command '${*}'"
@@ -93,20 +105,20 @@ function bl_retry_constant {
93105
bl_die "Invalid interval in seconds: ${retries} for command '${*}'".
94106
fi
95107

96-
local count=0
108+
count=0
97109
until eval "$@"; do
98110
# Command failed, otherwise until would have skipped the loop
99111

100112
# Store return code so it can be reported to the user
101-
exit=$?
113+
rc=$?
102114
count=$((count + 1))
103115
if [ "${count}" -lt "${retries}" ]; then
104-
bl_info "'${*}' Retry $count/$retries exited $exit, retrying in $interval seconds..."
116+
bl_info "'${*}' Retry $count/$retries exited $rc, retrying in $interval seconds..."
105117
sleep "${interval}"
106118
else
107119
# Out of retries :(
108-
bl_error "Retry $count/$retries exited $exit, no more retries left."
109-
return $exit
120+
bl_error "Retry $count/$retries exited $rc, no more retries left."
121+
return $rc
110122
fi
111123
done
112124
return 0

init

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ BASH_LIB_DIR="${BASH_LIB_DIR_RELATIVE}"
2626

2727
# Load the filehandling module for the abspath
2828
# function
29-
for lib in helpers logging filehandling git k8s test-utils; do
29+
for lib in helpers logging filehandling git github k8s test-utils; do
3030
. "${BASH_LIB_DIR_RELATIVE}/${lib}/lib"
3131
done
3232

0 commit comments

Comments
 (0)