Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Test

on:
push:
branches: [master, develop]
pull_request:
branches: [master, develop]

jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
# bash is the historical target; zsh is the macOS default login shell.
# Utilities run under zsh's ksh emulation (provided by xsh).
shell: [bash, zsh]
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} / ${{ matrix.shell }}

steps:
- name: Install zsh (Linux)
if: matrix.shell == 'zsh' && runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y zsh

- name: Install xsh
run: |
git clone --depth=50 --branch=master https://github.com/alexzhangs/xsh.git /tmp/xsh
bash /tmp/xsh/install.sh

- name: Load dependency library xsh-lib/core
shell: bash
run: |
source ~/.xshrc
xsh load xsh-lib/core

- name: Load library from current branch
shell: bash
run: |
source ~/.xshrc
xsh load -b "${{ github.head_ref || github.ref_name }}" xsh-lib/aws

- name: Run tests (${{ matrix.shell }})
# test.sh self-sources ~/.xshrc, so running it as a child of the matrix
# shell makes the utilities execute under that shell (bash, or zsh's
# ksh emulation).
run: ${{ matrix.shell }} ~/.xsh/repo/xsh-lib/aws/test.sh
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ About xsh and its libraries, check out [xsh document](https://github.com/alexzha
* 4.3.48 on Linux
* 3.2.57 on macOS

The utilities also run under **zsh** (the default shell on modern macOS):
xsh executes them under zsh's ksh emulation. Tested with zsh 5.x.

1. awscli

Tested with `awscli 1.17.4`.
Expand Down
21 changes: 15 additions & 6 deletions functions/cfg/get.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,29 @@ function get () {

function __get () {
declare profile=$1 \
property varname
property varname value

# output profile name as first field
varname=${XSH_AWS_CFG_CONFIG_ENV_PREFIX}SECTIONS_${profile}

if [[ ! ${!varname+x} ]]; then # the variable was not declared
# `${!varname...}` is bash-only indirection; `eval` does it portably
# (the variable name is built from controlled prefixes + parsed
# section names, not arbitrary input).
if ! eval "[[ \${${varname}+x} ]]"; then # the variable was not declared
varname=${XSH_AWS_CFG_CONFIG_ENV_PREFIX}SECTIONS_${profile#profile_}
fi
printf "%s" "${!varname#profile }"
eval "value=\${${varname}#profile }"
printf "%s" "${value}"

# output rest of properties as fields
for property in "${XSH_AWS_CFG_PROPERTIES[@]:?}"; do
varname=${property%.*}_SECTIONS_${profile}_VALUES_${property#*.}

if [[ ! ${!varname+x} ]]; then # the variable was not declared
if ! eval "[[ \${${varname}+x} ]]"; then # the variable was not declared
varname=${property%.*}_SECTIONS_${profile#profile_}_VALUES_${property#*.}
fi
printf ",%s" "${!varname}"
eval "value=\${${varname}}"
printf ",%s" "${value}"
done

# end of line
Expand All @@ -46,8 +51,12 @@ function get () {
# shellcheck disable=SC2125
declare varname=${XSH_AWS_CFG_CONFIG_ENV_PREFIX}SECTIONS[@] \
profile
declare -a profiles
# `${!varname}` array indirection (varname holds `NAME[@]`) is bash-only;
# `eval` expands the named array portably
eval "profiles=( \"\${${varname}}\" )"

for profile in "${!varname}"; do
for profile in "${profiles[@]}"; do
if [[ -n ${name} ]]; then
__get "${profile}" | grep "^${name}," || :
else
Expand Down
8 changes: 5 additions & 3 deletions functions/cfg/set.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ function set () {
return 255
fi

declare n=2 property # profile properties started at $2
declare n=2 property value # profile properties started at $2
for property in "${XSH_AWS_CFG_PROPERTIES[@]:?}"; do
property=${property#*.}
# `${!n}` (positional indirection) is bash-only; `${*:N:1}` is portable
value=${*:$((n)):1}
if [[ ${name} == default ]]; then
aws configure set "${name}.${property:?}" "${!n}"
aws configure set "${name}.${property:?}" "${value}"
else
aws configure set "${property:?}" "${!n}" --profile "${name}"
aws configure set "${property:?}" "${value}" --profile "${name}"
fi
n=$((n+1))
done
Expand Down
111 changes: 62 additions & 49 deletions functions/cfn/__init__.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,35 +97,41 @@ source /dev/stdin <<< "${XSH_AWS_CFN__CFG_PROPERTIES[@]:?}"
#? ${index:3:1} in [2-5] : FAILED Status
#? ${index:3:1} in [6-9] : INPROGRESS Status
#?
# Status codes are a 4-digit classification key, decoded digit-by-digit below.
# Stored as (code, name) pairs rather than a `[code]=name` sparse array: zsh
# cannot represent sparse arrays (it pads up to the largest index), and bash 3.2
# — the other supported target — has no associative arrays. The derived
# *_STABLE / *_SERVICEABLE / ... arrays built below are consumed by value
# (`[@]` and `/array/search`), never by index, so a flat list is equivalent.
XSH_AWS_CFN__STACK_STATUS=(
[9607]=CREATE_IN_PROGRESS
[1403]=CREATE_FAILED
[1200]=CREATE_COMPLETE

[9617]=ROLLBACK_IN_PROGRESS
[1413]=ROLLBACK_FAILED
[1410]=ROLLBACK_COMPLETE

[9627]=DELETE_IN_PROGRESS
[1423]=DELETE_FAILED
[1221]=DELETE_COMPLETE

[9637]=UPDATE_IN_PROGRESS
[9236]=UPDATE_COMPLETE_CLEANUP_IN_PROGRESS
[1230]=UPDATE_COMPLETE
[1433]=UPDATE_FAILED
[9437]=UPDATE_ROLLBACK_IN_PROGRESS
[1435]=UPDATE_ROLLBACK_FAILED
[9436]=UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS
[1430]=UPDATE_ROLLBACK_COMPLETE

[9647]=REVIEW_IN_PROGRESS

[9657]=IMPORT_IN_PROGRESS
[1250]=IMPORT_COMPLETE
[9457]=IMPORT_ROLLBACK_IN_PROGRESS
[1453]=IMPORT_ROLLBACK_FAILED
[1450]=IMPORT_ROLLBACK_COMPLETE
9607 CREATE_IN_PROGRESS
1403 CREATE_FAILED
1200 CREATE_COMPLETE

9617 ROLLBACK_IN_PROGRESS
1413 ROLLBACK_FAILED
1410 ROLLBACK_COMPLETE

9627 DELETE_IN_PROGRESS
1423 DELETE_FAILED
1221 DELETE_COMPLETE

9637 UPDATE_IN_PROGRESS
9236 UPDATE_COMPLETE_CLEANUP_IN_PROGRESS
1230 UPDATE_COMPLETE
1433 UPDATE_FAILED
9437 UPDATE_ROLLBACK_IN_PROGRESS
1435 UPDATE_ROLLBACK_FAILED
9436 UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS
1430 UPDATE_ROLLBACK_COMPLETE

9647 REVIEW_IN_PROGRESS

9657 IMPORT_IN_PROGRESS
1250 IMPORT_COMPLETE
9457 IMPORT_ROLLBACK_IN_PROGRESS
1453 IMPORT_ROLLBACK_FAILED
1450 IMPORT_ROLLBACK_COMPLETE
)

#? Generate Variables:
Expand All @@ -150,42 +156,49 @@ XSH_AWS_CFN__STACK_STATUS=(
#? XSH_AWS_CFN__STACK_STATUS_FAILED
#? XSH_AWS_CFN__STACK_STATUS_INPROGRESS
#?
declare index
for index in "${!XSH_AWS_CFN__STACK_STATUS[@]}"; do
declare __i index __status
# iterate the (code, name) pairs; append each name to the matching derived
# arrays. Appending (rather than `arr[code]=name`) keeps the derived arrays
# contiguous, which is required under zsh and equivalent for the value-based
# consumers.
for (( __i = 0; __i < ${#XSH_AWS_CFN__STACK_STATUS[@]}; __i += 2 )); do
index=${XSH_AWS_CFN__STACK_STATUS[__i]}
__status=${XSH_AWS_CFN__STACK_STATUS[$((__i + 1))]}

# XSH_AWS_CFN__STACK_STATUS_STABLE
# XSH_AWS_CFN__STACK_STATUS_UNSTABLE
case ${index:0:1} in
1)
XSH_AWS_CFN__STACK_STATUS_STABLE[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_STABLE+=("${__status}")
;;
9)
XSH_AWS_CFN__STACK_STATUS_UNSTABLE[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_UNSTABLE+=("${__status}")
;;
esac

# XSH_AWS_CFN__STACK_STATUS_SATISFIED
# XSH_AWS_CFN__STACK_STATUS_UNSATISFIED
# XSH_AWS_CFN__STACK_STATUS_SATISFYING
case ${index:1:1} in
2)
XSH_AWS_CFN__STACK_STATUS_SATISFIED[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_SATISFIED+=("${__status}")
;;
4)
XSH_AWS_CFN__STACK_STATUS_UNSATISFIED[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_UNSATISFIED+=("${__status}")
;;
6)
XSH_AWS_CFN__STACK_STATUS_SATISFYING[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_SATISFYING+=("${__status}")
;;
esac

# XSH_AWS_CFN__STACK_STATUS_SERVICEABLE
# XSH_AWS_CFN__STACK_STATUS_UNSERVICEABLE
case $((index%2)) in
case $((index % 2)) in
0)
XSH_AWS_CFN__STACK_STATUS_SERVICEABLE[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_SERVICEABLE+=("${__status}")
;;
1)
XSH_AWS_CFN__STACK_STATUS_UNSERVICEABLE[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_UNSERVICEABLE+=("${__status}")
;;
esac

Expand All @@ -197,22 +210,22 @@ for index in "${!XSH_AWS_CFN__STACK_STATUS[@]}"; do
# XSH_AWS_CFN__STACK_STATUS_IMPORT
case ${index:2:1} in
0)
XSH_AWS_CFN__STACK_STATUS_CREATE[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_CREATE+=("${__status}")
;;
1)
XSH_AWS_CFN__STACK_STATUS_ROLLBACK[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_ROLLBACK+=("${__status}")
;;
2)
XSH_AWS_CFN__STACK_STATUS_DELETE[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_DELETE+=("${__status}")
;;
3)
XSH_AWS_CFN__STACK_STATUS_UPDATE[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_UPDATE+=("${__status}")
;;
4)
XSH_AWS_CFN__STACK_STATUS_REVIEW[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_REVIEW+=("${__status}")
;;
5)
XSH_AWS_CFN__STACK_STATUS_IMPORT[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_IMPORT+=("${__status}")
;;
esac

Expand All @@ -221,14 +234,14 @@ for index in "${!XSH_AWS_CFN__STACK_STATUS[@]}"; do
# XSH_AWS_CFN__STACK_STATUS_INPROGRESS
case ${index:3:1} in
[0,1])
XSH_AWS_CFN__STACK_STATUS_COMPLETE[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_COMPLETE+=("${__status}")
;;
[2-5])
XSH_AWS_CFN__STACK_STATUS_FAILED[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_FAILED+=("${__status}")
;;
[6-9])
XSH_AWS_CFN__STACK_STATUS_INPROGRESS[index]=${XSH_AWS_CFN__STACK_STATUS[index]}
XSH_AWS_CFN__STACK_STATUS_INPROGRESS+=("${__status}")
;;
esac
done
unset index
unset __i index __status
5 changes: 5 additions & 0 deletions functions/cfn/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@
#? @xsh imports aws/cfn/stack/create aws/cfn/stack/update aws/cfn/stack/delete aws/cfn/stack/event aws/cfn/stack/log
#?
function deploy () {
# zsh doesn't populate FUNCNAME; build it from zsh's funcstack so the
# `${FUNCNAME[0]}` references below (passed to x-trap-return to scope the
# trap to this function) work. No-op under bash, which sets FUNCNAME natively.
# shellcheck disable=SC2034,SC2154
[[ -z ${ZSH_VERSION-} ]] || declare -a FUNCNAME=( "${funcstack[@]}" )

function __check_config_version__ () {
declare version=${1:?}
Expand Down
12 changes: 7 additions & 5 deletions functions/cfn/stack/create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,17 @@ function create () {
"${pass_options[@]}"
)

declare name
declare name value
for name in template stack_policy; do
if [[ -n ${!name} ]]; then
case $(xsh /uri/parser -s "${!name}" | xsh /string/pipe/lower) in
# `${!name}` indirection is bash-only; `eval` reads the named var portably
eval "value=\${${name}}"
if [[ -n ${value} ]]; then
case $(xsh /uri/parser -s "${value}" | xsh /string/pipe/lower) in
http|https)
options+=( "--${name//_/-}-url" "${!name}" )
options+=( "--${name//_/-}-url" "${value}" )
;;
'')
options+=( "--${name//_/-}-body" "$(cat "${!name}")" )
options+=( "--${name//_/-}-body" "$(cat "${value}")" )
;;
*)
return 255
Expand Down
6 changes: 3 additions & 3 deletions functions/cfn/stack/list.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
function list () {
declare OPTIND OPTARG opt

declare -a region_opt status query output
declare -a region_opt __status query output

xsh imports /util/getopts/extra

Expand All @@ -49,7 +49,7 @@ function list () {
s)
x-util-getopts-extra "$@"
# shellcheck disable=SC2207
status=(--stack-status-filter "${OPTARG[@]:?]}")
__status=(--stack-status-filter "${OPTARG[@]:?]}")
;;
q)
# the selector `|[]` strips the outer layer of `[]` in the result
Expand All @@ -68,5 +68,5 @@ function list () {
# list stacks
aws "${region_opt[@]}" "${query[@]}" "${output[@]}" \
cloudformation list-stacks \
"${status[@]}"
"${__status[@]}"
}
10 changes: 5 additions & 5 deletions functions/cfn/stack/status/wait.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,19 @@ function wait () {
declare timeout_epoch
timeout_epoch=$(($(date +%s) + timeout))

declare status left_epoch
declare __status left_epoch
while [[ 1 ]]; do
status=$(xsh aws/cfn/stack/status/get "${region_opt[@]}" -s "$stack_name")
printf "%s: %s ..." "$(date '+%F %T')" "${status:-NULL}"
__status=$(xsh aws/cfn/stack/status/get "${region_opt[@]}" -s "$stack_name")
printf "%s: %s ..." "$(date '+%F %T')" "${__status:-NULL}"

left_epoch=$((timeout_epoch - $(date +%s)))

# exit loop if match expecting status
if [[ $status == $target_status ]]; then
if [[ $__status == $target_status ]]; then
printf " [ok]\n"
return
# exit loop if reachs stable status
elif [[ -n $(xsh /array/search XSH_AWS_CFN__STACK_STATUS_STABLE "$status") ]]; then
elif [[ -n $(xsh /array/search XSH_AWS_CFN__STACK_STATUS_STABLE "$__status") ]]; then
printf " [not match]\n"
return 255
# exit loop if time is out
Expand Down
Loading