=====================================================================

Excited to bring V5 to life. This includes some BREAKING CHANGES to
several aspects of ZSH-type scwrypts. Please refer to the readme
for upgrade details (specifically docs/upgrade/v4-to-v5.md)

--- New Features -------------------------

- ZSH testing library with basic mock capabilities

- new scwrypts environment file format includes metadata and more
  advanced features like optional parent env overrides, selection
  inheritence, and improved structurual flexibility

- speedup cache for non-CI runs of ZSH-type scwrypts

- ${scwryptsmodule} syntax now allows a consistent unique-naming
  scheme for functions in ZSH-type scwrypts while providing better
  insight into origin of API calls in other modules

- reusable, case-statement-driven argument parsers in ZSH-type scwrypts

--- Changes ------------------------------

- several utility function renames in ZSH-type scwrypts to improve
  consistency

- documentation comments included in ZSH libraries

- ZSH-type scwrypts now allow library modules to live alongside
  executables
  (zsh/lib still supported; autodetection determines default)

--- Bug Fixes ----------------------------

- hardened environment checking for REQUIRED_ENV variables; this removes
  the ability to overwrite variables in local function contexts
This commit is contained in:
Wryn (yage) Wagner 2024-05-10 13:32:02 -06:00 committed by Wryn (yage) Wagner
parent 1b4060dd1c
commit 7f14edd039
271 changed files with 11459 additions and 10516 deletions

View File

@ -22,10 +22,14 @@ executors:
- image: node:18
resource_class: medium
zsh:
docker:
- image: alpine:3
resource_class: small
commands:
archlinux-run:
description: execute command steps in the archlinux container from the CI user
description: execute steps in the archlinux container as the CI user
parameters:
_name:
type: string
@ -243,6 +247,47 @@ jobs:
- run: pip install build && python -m build
- run: pip install twine && twine upload dist/*
zsh-test:
executor: zsh
working_directory: ~/scwrypts
steps:
- checkout:
path: ~/scwrypts
- run:
name: install dependencies
command: |
: \
&& apk add \
coreutils \
findutils \
fzf \
perl \
sed \
gawk \
git \
jo \
jq \
util-linux \
uuidgen \
yq \
zsh \
;
- run:
name: scwrypts zsh/unittest
command: |
~/scwrypts/scwrypts run unittest \
;
- run:
name: scwrypts returns proper success codes
command: |
~/scwrypts/scwrypts -n sanity check -- --exit-code 0
[[ $? -eq 0 ]] || exit 1
- run:
shell: /bin/sh
name: scwrypts returns proper error codes
command: |
~/scwrypts/scwrypts -n sanity check -- --exit-code 101
[[ $? -eq 101 ]] || exit 1
workflows:
test:
@ -255,6 +300,7 @@ workflows:
- python-test: *dev-filters
- nodejs-test: *dev-filters
- zsh-test: *dev-filters
publish:
jobs:
@ -285,6 +331,7 @@ workflows:
- aur-test
- python-publish
- nodejs-publish
- zsh-test
- python-test: *only-publish-for-full-semver
- python-publish:
@ -292,6 +339,7 @@ workflows:
context: [pypi-yage]
requires:
- python-test
- zsh-test
- nodejs-test: *only-publish-for-full-semver
- nodejs-publish:
@ -299,3 +347,6 @@ workflows:
context: [npm-wrynegade]
requires:
- nodejs-test
- zsh-test
- zsh-test: *only-publish-for-full-semver

89
.config/create-new-env Executable file
View File

@ -0,0 +1,89 @@
#!/bin/zsh
#
# a temporary template conversion utility for env.template (<=v4)
# to env.yaml (>=v5)
#
eval $(scwrypts --config)
use -c scwrypts/environment-files
ENVIRONMENT_ROOT="$1"
[ "$ENVIRONMENT_ROOT" ] || ENVIRONMENT_ROOT="${0:a:h}"
OLDENV="$ENVIRONMENT_ROOT/env.template"
NEWENV="$ENVIRONMENT_ROOT/env.yaml"
ENVMAP="$ENVIRONMENT_ROOT/.map.txt"
GROUP="$2"
[ $GROUP ] || GROUP=scwrypts
GENERATE_TEMPLATE \
| sed '1,4d; /^$/d' \
| sed -z 's/# \([^\n]*\)\n\([^\n]*\)=/\2=\n\2=DESCRIPTION=\1/g' \
| sed '
s/^export //
/./i---
s/\s\+$//
s/__/=/g
s/^\(AWS\|REDIS\)_/\1=/
s/^\([^=]*\)=\([^=]*\)=\([^=]*\)=\([^=]*\)=\([^=]*\)=$/\L\1:\n \2:\n \3:\n \4:\n \5:/
s/^\([^=]*\)=\([^=]*\)=\([^=]*\)=\([^=]*\)=$/\L\1:\n \2:\n \3:\n \4:/
s/^\([^=]*\)=\([^=]*\)=\([^=]*\)=$/\L\1:\n \2:\n \3:/
s/^\([^=]*\)=\([^=]*\)=$/\L\1:\n \2:/
s/^\([^=]*\)=$/\L\1:/
s/^\([^=]*\)=\([^=]*\)=\([^=]*\)=\([^=]*\)=\([^=]*\)=\([^=]\+\)$/\L\1:\n \2:\n \3:\n \4:\n \5: \E\6/
s/^\([^=]*\)=\([^=]*\)=\([^=]*\)=\([^=]*\)=\([^=]\+\)$/\L\1:\n \2:\n \3:\n \4: \E\5/
s/^\([^=]*\)=\([^=]*\)=\([^=]*\)=\([^=]\+\)$/\L\1:\n \2:\n \3: \E\4/
s/^\([^=]*\)=\([^=]*\)=\([^=]\+\)$/\L\1:\n \2: \E\3/
s/^\([^=]*\)=\([^=]\+\)$/\L\1: \E\2/
s/: (\(.*\))/: [\1]/
/^/,/:/{s/_/-/g}
' \
| sed '
s/^ \(description:.*\)/ \1/
s/description:/.DESCRIPTION:/
' \
| sed -z 's/\n\(\s\+\).DESCRIPTION:\([^\n]\+\)/\n\1.DESCRIPTION: >-\n\1 \2/g' \
| yq eval-all '. as $item ireduce ({}; . *+ $item)' \
> "$NEWENV" \
;
cat -- "$OLDENV" \
| sed '
s/#.*//
/^$/d
s/^export //
s/\s\+$//
s/^\([^=]*\)=.*/\1=\n\1/
' \
| sed '
/^/s/.*/\L&/
/^/s/__/./g
/^/s/_/-/g
s/^/./
s/\(aws\|redis\)-/\1./
' \
| perl -pe 's/=\n/^/' \
| column -ts '^' \
> "$ENVMAP" \
;
while read line
do
ENV_VAR=$(echo $line | awk '{print $1;}')
LOOKUP=$(echo $line | awk '{print $2;}')
cp "$NEWENV" "$NEWENV.temp"
cat "$NEWENV.temp" \
| yq ". | $LOOKUP.[\".ENVIRONMENT\"] = \"$ENV_VAR\"" \
| yq 'sort_keys(...)' \
> "$NEWENV"
;
done < "$ENVMAP"
rm -- "$NEWENV.temp" "$ENVMAP" &>/dev/null
head -n1 -- "$NEWENV" | grep -q "^{}$" && {
echo '---' > "$NEWENV"
}
cat -- "$NEWENV" | yq
SUCCESS "new environment saved to '$NEWENV'"

66
.config/env.yaml Normal file
View File

@ -0,0 +1,66 @@
---
aws:
.DESCRIPTION: >-
standard AWS environment variables used by awscli and other tools
account:
.ENVIRONMENT: AWS_ACCOUNT
efs:
local-mount-point:
.DESCRIPTION: >-
fully-qualified path to mount the EFS drive
.ENVIRONMENT: AWS__EFS__LOCAL_MOUNT_POINT
profile:
.ENVIRONMENT: AWS_PROFILE
region:
.ENVIRONMENT: AWS_REGION
directus:
.DESCRIPTION: >-
details for a directus instance
api-token:
.ENVIRONMENT: DIRECTUS__API_TOKEN
base-url:
.ENVIRONMENT: DIRECTUS__BASE_URL
discord:
.DESCRIPTION: >-
details for discord bot
bot-token:
.ENVIRONMENT: DISCORD__BOT_TOKEN
content-footer:
.ENVIRONMENT: DISCORD__CONTENT_FOOTER
content-header:
.ENVIRONMENT: DISCORD__CONTENT_HEADER
default-avatar-url:
.ENVIRONMENT: DISCORD__DEFAULT_AVATAR_URL
default-channel-id:
.ENVIRONMENT: DISCORD__DEFAULT_CHANNEL_ID
default-username:
.ENVIRONMENT: DISCORD__DEFAULT_USERNAME
default-webhook:
.ENVIRONMENT: DISCORD__DEFAULT_WEBHOOK
linear:
.DESCRIPTION: >-
linear.app project management configuration
api-token:
.ENVIRONMENT: LINEAR__API_TOKEN
redis:
.DESCRIPTION: >-
redis connection credentials
auth:
.ENVIRONMENT: REDIS_AUTH
host:
.ENVIRONMENT: REDIS_HOST
port:
.ENVIRONMENT: REDIS_PORT
twilio:
.DESCRIPTION: >-
twilio account / credentials
account-sid:
.ENVIRONMENT: TWILIO__ACCOUNT_SID
api-key:
.ENVIRONMENT: TWILIO__API_KEY
api-secret:
.ENVIRONMENT: TWILIO__API_SECRET
default-phone-from:
.ENVIRONMENT: TWILIO__DEFAULT_PHONE_FROM
default-phone-to:
.ENVIRONMENT: TWILIO__DEFAULT_PHONE_TO

View File

@ -1,60 +1,77 @@
# *Scwrypts* (Wryn + Scripts)
# *Scwrypts*
Scwrypts is a friendly CLI / API for quickly running *sandboxed scripts* in the terminal.
Scwrypts is a CLI and API for safely running scripts in the terminal, CI, and other automated environments.
In modern developer / dev-ops workflows, scripts require a complex configurations.
Without a better solution, the developer is cursed to copy lines-upon-lines of variables into terminals, create random text artifacts, or maybe even commit secure credentials into source.
Scwrypts leverages ZSH to give hot-key access to run scripts in such environments.
Local runs provide a user-friendly approach to quickly execute CI workflows and automations in your terminal.
Each local run runs through an interactive, *sandboxed environment* so you never accidentally run dev credentials in production ever again!
## Major Version Upgrade Notice
Please refer to [Version 3 to Version 4 Upgrade Path](./docs/upgrade/v3-to-v4.md) when upgrading from scwrypts v3 to scwrypts v4!
Please refer to [Version 4 to Version 5 Upgrade Path](./docs/upgrade/v4-to-v5.md) when upgrading from scwrypts v4 to scwrypts v5!
## Dependencies
Due to the wide variety of resources used by scripting libraries, the user is expected to manually resolve dependencies.
Dependencies are lazy-loaded, and more information can be found by command error messages or in the appropriate README.
## Installation
Because Scwrypts relies on Scwrypts (see [Meta Scwrypts](./zsh/scwrypts)), `zsh` must be installed and [`junegunn/fzf`](https://github.com/junegunn/fzf) must be available on your PATH.
Quick installation is supported through both the [Arch User Repository](https://aur.archlinux.org/packages/scwrypts) and [Homebrew](https://github.com/wrynegade/homebrew-brew/tree/main/Formula)
## Usage
Install Scwrypts by cloning this repository and sourcing `scwrypts.plugin.zsh` in your `zshrc`.
You can now run Scwrypts using the ZLE hotkey bound to `SCWRYPTS_SHORTCUT` (default `CTRL + W`).
```bash
# AUR
yay -Syu scwrypts
```console
% cd <path-to-cloned-repo>
% echo "source $(pwd)/scwrypts.plugin.zsh >> $HOME/.zshrc"
# homebrew
brew install wrynegade/scwrypts
```
Check out [Meta Scwrypts](./zsh/scwrypts) to quickly set up environments and adjust configuration.
### Manual Installation
### No Install / API Usage
Alternatively, the `scwrypts` API can be used directly:
To install scwrypts manually, clone this repository (and take note of where it is installed)
Replacing the `/path/to/cloned-repo` appropriately, add the following line to your `~/.zshrc`:
```zsh
./scwrypts [--env environment-name] (...script-name-patterns...) [-- ...passthrough arguments... ]
source /path/to/cloned-repo/scwrypts.plugin.zsh
```
Given one or more script patterns, Scwrypts will filter the commands by pattern conjunction.
If only one command is found which matches the pattern(s), it will immediately begin execution.
If multiple commands match, the user will be prompted to select from the filtered list.
Of course, if no commands match, Scwrypts will exit with an error.
The next time you start your terminal, you can now execute scwrypts by using the plugin shortcut(s) (by default `CTRL + SPACE`).
Plugin shortcuts are configurable in your scwrypts configuration file found in `~/.config/scwrypts/config.zsh`, and [here is the default config](./zsh/config.user.zsh).
Given no script patterns, Scwrypts becomes an interactive CLI, prompting the user to select a command.
If you want to use the `scwrypts` program directly, you can either invoke the executable `./scwrypts` or link it in your PATH for easy access.
For example, if you have `~/.local/bin` in your PATH, you might run:
```zsh
ln -s /path/to/cloned-repo/scwrypts "${HOME}/.local/bin/scwrypts"
```
After determining which script to run, if no environment has been specified, Scwrypts prompts the user to choose one.
#### PATH Dependencies
Scwrypts provides a framework for workflows which often depend on a variety of other tools.
Although the lazy-loaded dependency model allows hardening in CI and extendability, the user is expected to _resolve required PATH dependencies_.
When running locally, this is typically as simple as "install the missing program," but this may require additional steps when working in automated environments.
By default, the `ci` plugin is enabled which provides the `check all dependencies` scwrypt.
You can run this to output a comprehensive list of PATH dependencies across all scwrypts groups, but, at a bare minimum, you will need the following applications in your PATH:
```bash
zsh
grep # GNU
sed # GNU
sort # GNU
fzf # https://github.com/junegunn/fzf (only required for interactive / local)
jo # https://github.com/jpmens/jo
jq # https://github.com/jqlang/jq
yq # https://github.com/mikefarah/yq
```
### Using in CI/CD or Automated Workflows
Set environment variable `CI=true` (and use the no install method) to run in an automated pipeline.
## Usage in CI and Automated Environments
Set environment variable `CI=true` to run scwrypts in an automated environment.
There are a few notable changes to this runtime:
- **The Scwrypts sandbox environment will not load.** All variables will be read from context.
- **The Scwrypts sandbox environment will not load.** All variables will be read directly from the current context.
- User yes/no prompts will **always be YES**
- Other user input will default to an empty string
- Logs will not be captured
- Setting the environment variable `SCWRYPTS_GROUP_LOADER__[a-z_]\+` will source the file indicated in the variable (this allows custom groups without needing to modify the `config.zsh` directly)
- Logs will not be captured in the user's local cache
- In GitHub actions, `*.scwrypts.zsh` groups are detected automatically from the `$GITHUB_WORKSPACE`; set `SCWRYPTS_GITHUB_NO_AUTOLOAD=true` to disable
## Contributing
Before contributing an issue, idea, or pull request, check out the [super-brief contributing guide](./docs/CONTRIBUTING.md)

View File

@ -57,7 +57,7 @@ Don't worry, it's easy.
Take your original scwrypt, and slap the executable stuff into a function called `MAIN` (yes, it must be _exactly_, all-caps `MAIN`):
```diff
#!/bin/zsh
#!/usr/bin/env zsh
#####################################################################
DEPENDENCIES+=(dep-function-a dep-function-b)
REQUIRED_ENV+=()
@ -69,11 +69,11 @@ CHECK_ENVIRONMENT
- echo "do some stuff here"
- # ... etc ...
- SUCCESS "completed the stuff"
- echo.success "completed the stuff"
+ MAIN() {
+ echo "do some stuff here"
+ # ... etc ...
+ SUCCESS "completed the stuff
+ echo.success "completed the stuff
+ }
```
@ -85,7 +85,7 @@ All I had to do in this case was delete the function invocation at the end:
```diff
# ... top boilerplate ...
MAIN() {
SUCCESS "look at me I'm so cool I already wrote this in a main function"
echo.success "look at me I'm so cool I already wrote this in a main function"
}
-
- #####################################################################
@ -115,7 +115,7 @@ Also you can ditch the `CHECK_ENVIRONMENT`.
While it won't hurt, v4 already does this, so just get rid of it.
Here's my recommended formatting:
```diff
#!/bin/zsh
#!/usr/bin/env zsh
- #####################################################################
DEPENDENCIES+=(dep-function-a dep-function-b)
- REQUIRED_ENV+=()
@ -128,7 +128,7 @@ use do/awesome/stuff --group my-custom-library
MAIN() {
echo "do some stuff here"
# ... etc ...
SUCCESS "completed the stuff
echo.success "completed the stuff
}
```
@ -164,7 +164,7 @@ If you _have_ done it already, typically by writing a variable called "USAGE" in
Returning to our original `MAIN()` example, I'll add some options parsing so we should now look something like this:
```sh
#!/bin/zsh
#!/usr/bin/env zsh
DEPENDENCIES+=(dep-function-a dep-function-b)
use do/awesome/stuff --group my-custom-library
@ -200,7 +200,7 @@ I want to call out a few specific ones:
Just add another section to define these values before declaring `MAIN`:
```sh
#!/bin/zsh
#!/usr/bin/env zsh
DEPENDENCIES+=(dep-function-a dep-function-b)
use do/awesome/stuff --group my-custom-library

136
docs/upgrade/v4-to-v5.md Normal file
View File

@ -0,0 +1,136 @@
# Scwrypts Upgrade v4 to v5 Notes
Although scwrypts v4 brings a number of new features, most functionality is backwards-compatible.
## Lots of renames!
Nearly every module received a rename.
This was a decision made to improve both style-consistency and import transparency, but has resulted in a substantial number of breaking changes to `zsh-type scwrypts modules`.
### `zsh/utils` Functions
The functions in the underlying library have all been renamed, but otherwise maintain the same functionality.
For a full reference, check out the [zsh/utils](../../zsh/utils/utils.module.zsh), but some critical renames are:
```bash
FZF >> utils.fzf
FZF_USER_INPUT >> utils.fzf.user-input
LESS >> utils.less
YQ >> utils.yq
SUCCESS >> echo.success
ERROR >> echo.error
REMINDER >> echo.reminder
STATUS >> echo.status
WARNING >> echo.warning
DEBUG >> echo.debug
FAIL >> utils.fail
ABORT >> utils.abort
CHECK_ERRORS >> utils.check-errors
Yn >> utils.Yn
yN >> utils.yN
EDIT >> utils.io.edit
CHECK_ENVIRONMENT >> utils.check-environment
```
### `zsh/utils` Color Functions
Rather than storing ANSI colors as a variable, colors are now stored as a function which prints the color code.
Doing this has proven more versatile than trying to extract the value of the variable in several contexts.
Rename looks like this for all named ANSI colors:
```bash
$__GREEN >> utils.colors.green
$__BRIGHT_RED >> utils.colors.bright-red
```
The most common use case of colors is indirectly through the `echo.*` commands, so a new function now provides _the color used by the associated `echo.*` command_:
```bash
# instead of
STATUS "Hello there, ${_BRIGHT_GREEN}bobby${_YELLOW}. How are you?"
# use
echo.status "Hello there, $(utils.colors.bright-green)bobby$(echo.status.color). How are you?
```
### ZSH Scwrypts Module Naming
**This is the biggest point of refactor.**
You will notice that modules now declare their functions using a `${scwryptsmodule}` notation.
This notation provides a dot-notated name which is intended to provide a consistent, unique naming system in ZSH (remember, everything loaded into the same shell script must have a globally-unique name).
Consider the new naming method for the following:
```bash
# v4: zsh/lib/helm/template.module.zsh
HELM__TEMPLATE__GET() {
# ...
}
# v5: zsh/helm/get-template.module.zsh
${scwryptsmodule}() {
# ...
}
```
Although the import syntax is generally the same, now we reference the full name of the module instead of the arbitrarily defined `HELM__TEMPLATE__GET`:
```
# in some other scwrypt
use helm/get-template
helm.get-template --raw ./my-helm-chart
```
The name `${scwryptsmodule}` is depended on the scwrypts library path.
Since there is not an easy way to provide an exhaustive list, go through all the places where you `use` something from the scwrypts core library, and check to see where it is now.
One of the critical call-outs is the AWS CLI, which no longer follows the "just use ALL CAPS for function names," but instead is a proper module.
Both of the following are valid ways to use the scwrypts-safe aws-cli (`AWS` in v4):
```bash
# to import _only_ AWS cli
use cloud.aws.cli
cloud.aws.cli sts get-caller-identity
# importing the full AWS module also provides an alias
use cloud.aws
cloud.aws sts get-caller-identity
```
### Great news!
Great news!
We have finished with **all of the necessary steps** to migrate to v5!
If you still have the energy, take some time to make these _recommended_ adjustments too.
### Use the new `${scwryptsmodule}` syntax
The `${scwryptsmodule}` name is now automatically available in any module.
The one change from the `${scwryptsmodule}` in scwrypts core is that **your scwrypts group name is the first dot**.
If I'm building the scwrypts group called `my-cool-stuff` and open the file `my-cool-stuff/zsh/module-a.module.zsh`, then `${scwryptsmodule}` will refer to `my-cool-stuff.module-a`.
### Update your `*.scwrypts.zsh` declaration file
In v4 and earlier, it was tricky to create your own scwrypts group, since you had to create a particular folder structure, and write a `group-name.scwrypts.zsh` file with some somewhat arbitrary requirements.
In v5, you can now make any folder a scwrypts group by simply _creating the `*.scwrypts.zsh` file_.
```bash
# this will turn the current folder into the root of a scwrypts group called `my-cool-stuff`
touch 'my-cool-stuff.scwrypts.zsh'
├── zsh
├── zx
└── py
```
Advanced options for scwrypts are now [documented in the example](../../scwrypts.scwrypts.zsh), so please refer to it for any additional changes you may need for existing scwrypts modules.

View File

@ -0,0 +1 @@
---

View File

@ -1,10 +1,3 @@
# Kubernetes `kubectl` Helper Plugin
# CI Helper
Leverages a local `redis` application to quickly and easily set an alias `k` for `kubectl --context <some-context> --namespace <some-namespace>`.
Much like scwrypts environments, `k` aliases are *only* shared amongst session with the same `SCWRYPTS_ENV` to prevent accidental cross-contamination.
## Getting Started
Enable the plugin in `~/.config/scwrypts/config.zsh` by adding `SCWRYPTS_PLUGIN_ENABLED__KUBECTL=1`.
Use `k` as your new `kubectl` and checkout `k --help` and `k meta --help`.
Disabled by default, this is used in CI contexts to try and identify missing requirements for the current workflow.

View File

@ -1,7 +1,7 @@
#!/bin/zsh
#!/usr/bin/env zsh
#####################################################################
MAIN() {
cd "$SCWRYPTS_ROOT__scwrypts/"
cd "$(scwrypts.config.group scwrypts root)"
DEPENDENCIES+=()
for group in ${SCWRYPTS_GROUPS[@]}
@ -11,7 +11,7 @@ MAIN() {
GROUP_HOME="$(eval 'echo $SCWRYPTS_ROOT__'$group)"
[ $GROUP_HOME ] && [ -d "$GROUP_HOME" ] || continue
STATUS "checking dependencies for $group"
echo.status "checking dependencies for $group"
DEPENDENCIES+=($(
for file in $(
{
@ -27,7 +27,7 @@ MAIN() {
DEPENDENCIES=(zsh $(echo $DEPENDENCIES | sed 's/ /\n/g' | sort -u | grep '^[-_a-zA-Z]\+$'))
STATUS "discovered dependencies: ($DEPENDENCIES)"
echo.status "discovered dependencies: ($DEPENDENCIES)"
echo $DEPENDENCIES | sed 's/ /\n/g'
CHECK_ENVIRONMENT && SUCCESS "all dependencies satisfied"
utils.check-environment && echo.success "all dependencies satisfied"
}

View File

@ -1,5 +1 @@
SCWRYPTS_GROUPS+=(ci)
export SCWRYPTS_TYPE__ci=zsh
export SCWRYPTS_ROOT__ci="$SCWRYPTS_ROOT__scwrypts/plugins/ci"
export SCWRYPTS_COLOR__ci='\033[0m'
export ${scwryptsgroup}__type=zsh

View File

@ -0,0 +1,5 @@
---
scwrypts-kubectl-redis:
.DESCRIPTION: >-
[currently only 'managed'] 'managed' or 'custom' redis configuration
.ENVIRONMENT: SCWRYPTS_KUBECTL_REDIS

View File

@ -6,10 +6,11 @@ for CLI in kubectl helm flux
do
eval "_${CLI[1]}() {
local SUBSESSION=0
echo \${words[2]} | grep -q '^[0-9]\\+$' && SUBSESSION=\${words[2]}
local SUBSESSION_OFFSET=2
echo \${words[2]} | grep -q '^[0-9]\\+$' && SUBSESSION=\${words[2]} && SUBSESSION_OFFSET=3
local PASSTHROUGH_WORDS=($CLI)
[[ \$CURRENT -gt 2 ]] && echo \${words[2]} | grep -qv '^[0-9]\\+$' && {
[[ \$CURRENT -gt \${SUBSESSION_OFFSET} ]] && echo \${words[\${SUBSESSION_OFFSET}]} | grep -qv '^[0-9]\\+$' && {
local KUBECONTEXT=\$(k \$SUBSESSION meta get context)
local NAMESPACE=\$(k \$SUBSESSION meta get namespace)
@ -26,8 +27,8 @@ do
for WORD in \${words[@]:1}
do
case \$WORD in
[0-9]* ) continue ;;
-- )
( [0-9]* ) continue ;;
( -- )
echo \$words | grep -q 'exec' && ((DELIMIT_COUNT+=1))
[[ \$DELIMIT_COUNT -eq 0 ]] && ((DELIMIT_COUNT+=1)) && continue
;;
@ -37,7 +38,7 @@ do
echo \"\$words\" | grep -q '\\s\\+$' && PASSTHROUGH_WORDS+=(' ')
words=\"\$PASSTHROUGH_WORDS\"
words=\"\${PASSTHROUGH_WORDS[@]}\"
_$CLI
}
"

View File

@ -7,12 +7,12 @@ f() { _SCWRYPTS_KUBECTL_DRIVER flux $@; }
_SCWRYPTS_KUBECTL_DRIVER() {
[ ! $SCWRYPTS_ENV ] && {
ERROR "must set SCWRYPTS_ENV in order to use '$(echo $CLI | head -c1)'"
echo.error "must set SCWRYPTS_ENV in order to use '$(echo $CLI | head -c1)'"
return 1
}
which REDIS >/dev/null 2>&1 \
|| eval "$(scwrypts -n --name meta/get-static-redis-definition --type zsh --group kubectl)"
which kube.redis >/dev/null 2>&1 \
|| eval "$(scwrypts -n --name meta/get-static-redis-definition --type zsh --group kube)"
local CLI="$1"; shift 1
@ -42,11 +42,11 @@ _SCWRYPTS_KUBECTL_DRIVER() {
local USAGE__args="$(
{
echo "\\033[0;32m[0-9]\\033[0m^if the first argument is a number 0-9, uses or creates a subsession (default 0)"
echo "$(utils.colors.print green '[0-9]')^if the first argument is a number 0-9, uses or creates a subsession (default 0)"
echo " ^ "
for C in ${CUSTOM_COMMANDS[@]}
do
echo "\\033[0;32m$C\\033[0m^$(SCWRYPTS_KUBECTL_CUSTOM_COMMAND_DESCRIPTION__$C 2>/dev/null)"
echo "$(utils.colors.print green ${C})^$(SCWRYPTS_KUBECTL_CUSTOM_COMMAND_DESCRIPTION__$C 2>/dev/null)"
done
} | column -ts '^'
)"
@ -67,7 +67,7 @@ _SCWRYPTS_KUBECTL_DRIVER() {
enriched, use-case-sensitive setup of kubernetes context.
All actions are scoped to the current SCWRYPTS_ENV
currently : \\033[0;33m$SCWRYPTS_ENV\\033[0m
currently : $(utils.colors.print yellow ${SCWRYPTS_ENV})
"
@ -134,9 +134,9 @@ _SCWRYPTS_KUBECTL_DRIVER() {
while [[ $# -gt 0 ]]; do USER_ARGS+=($1); shift 1; done
CHECK_ERRORS --no-fail || return 1
utils.check-errors || return 1
[[ $HELP -eq 1 ]] && { USAGE; return 0; }
[[ $HELP -eq 1 ]] && { utils.io.usage; return 0; }
#####################################################################
@ -154,12 +154,12 @@ _SCWRYPTS_KUBECTL_DRIVER() {
[ $CONTEXT ] && [[ $CLI =~ ^flux$ ]] && CLI_ARGS+=(--context $CONTEXT)
[[ $STRICT -eq 1 ]] && {
[ $CONTEXT ] || ERROR "missing kubectl 'context'"
[ $NAMESPACE ] || ERROR "missing kubectl 'namespace'"
[ $CONTEXT ] || echo.error "missing kubectl 'context'"
[ $NAMESPACE ] || echo.error "missing kubectl 'namespace'"
CHECK_ERRORS --no-fail --no-usage || {
ERROR "with 'strict' settings enabled, context and namespace must be set!"
REMINDER "
utils.check-errors --no-fail --no-usage || {
echo.error "with 'strict' settings enabled, context and namespace must be set!"
echo.reminder "
these values can be set directly with
$(echo $CLI | head -c1) meta set (namespace|context)
"
@ -170,16 +170,16 @@ _SCWRYPTS_KUBECTL_DRIVER() {
[ $NAMESPACE ] && CLI_ARGS+=(--namespace $NAMESPACE)
[[ $VERBOSE -eq 1 ]] && {
REMINDER "
echo.reminder "
context '$CONTEXT'
namespace '$NAMESPACE'
environment '$SCWRYPTS_ENV'
subsession '$SUBSESSION'
"
STATUS "running $CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}"
echo.status "running $CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}"
} || {
[[ $(_SCWRYPTS_KUBECTL_SETTINGS get context) =~ ^show$ ]] && {
REMINDER "$SCWRYPTS_ENV.$SUBSESSION : $CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}"
echo.reminder "$SCWRYPTS_ENV.$SUBSESSION : $CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}"
}
}
$CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}
@ -190,7 +190,7 @@ _SCWRYPTS_KUBECTL_DRIVER() {
_SCWRYPTS_KUBECTL_SETTINGS() {
# (get setting-name) or (set setting-name setting-value)
REDIS h$1 ${SCWRYPTS_ENV}:kubectl:settings ${@:2} | grep .
kube.redis h$1 ${SCWRYPTS_ENV}:kubectl:settings ${@:2} | grep .
}
#####################################################################

View File

@ -23,16 +23,16 @@ SCWRYPTS_KUBECTL_CUSTOM_COMMAND_PARSE__meta() {
while [[ $# -gt 0 ]]
do
case $1 in
-h | --help ) HELP=1 ;;
( -h | --help ) HELP=1 ;;
set )
( set )
USAGE__usage+=" set"
USAGE__args="set (namespace|context)"
USAGE__description="interactively set a namespace or context for '$SCWRYPTS_ENV'"
case $2 in
namespace | context ) USER_ARGS+=($1 $2 $3); [ $3 ] && shift 1 ;;
-h | --help ) HELP=1 ;;
'' )
( namespace | context ) USER_ARGS+=($1 $2 $3); [ $3 ] && shift 1 ;;
( -h | --help ) HELP=1 ;;
( '' )
: \
&& SCWRYPTS_KUBECTL_CUSTOM_COMMAND__meta set context \
&& SCWRYPTS_KUBECTL_CUSTOM_COMMAND__meta set namespace \
@ -40,40 +40,40 @@ SCWRYPTS_KUBECTL_CUSTOM_COMMAND_PARSE__meta() {
return $?
;;
* ) ERROR "cannot set '$2'" ;;
( * ) echo.error "cannot set '$2'" ;;
esac
shift 1
;;
get )
( get )
USAGE__usage+=" get"
USAGE__args="get (namespace|context|all)"
USAGE__description="output the current namespace or context for '$SCWRYPTS_ENV'"
case $2 in
namespace | context | all ) USER_ARGS+=($1 $2) ;;
( namespace | context | all ) USER_ARGS+=($1 $2) ;;
-h | --help ) HELP=1 ;;
( -h | --help ) HELP=1 ;;
* ) ERROR "cannot get '$2'" ;;
( * ) echo.error "cannot get '$2'" ;;
esac
shift 1
;;
copy )
( copy )
USAGE__usage+=" copy"
USAGE__args+="copy [0-9]"
USAGE__description="copy current subsession ($SUBSESSION) to target subsession id"
case $2 in
[0-9] ) USER_ARGS+=($1 $2) ;;
-h | --help ) HELP=1 ;;
* ) ERROR "target session must be a number [0-9]" ;;
( [0-9] ) USER_ARGS+=($1 $2) ;;
( -h | --help ) HELP=1 ;;
( * ) echo.error "target session must be a number [0-9]" ;;
esac
shift 1
;;
clear | show | hide | strict | loose ) USER_ARGS+=($1) ;;
( clear | show | hide | strict | loose ) USER_ARGS+=($1) ;;
* ) ERROR "no meta command '$1'"
( * ) echo.error "no meta command '$1'"
esac
shift 1
done
@ -81,10 +81,10 @@ SCWRYPTS_KUBECTL_CUSTOM_COMMAND_PARSE__meta() {
SCWRYPTS_KUBECTL_CUSTOM_COMMAND__meta() {
case $1 in
get )
( get )
[[ $2 =~ ^all$ ]] && {
local CONTEXT=$(REDIS get --prefix current:context | grep . || echo "\\033[1;31mnone set\\033[0m")
local NAMESPACE=$(REDIS get --prefix current:namespace | grep . || echo "\\033[1;31mnone set\\033[0m")
local CONTEXT=$(kube.redis get --prefix current:context | grep . || utils.colors.print bright-red "none set")
local NAMESPACE=$(kube.redis get --prefix current:namespace | grep . || utils.colors.print bright-red "none set")
echo "
environment : $SCWRYPTS_ENV
context : $CONTEXT
@ -92,51 +92,53 @@ SCWRYPTS_KUBECTL_CUSTOM_COMMAND__meta() {
CLI settings
command context : $(_SCWRYPTS_KUBECTL_SETTINGS get context)
strict mode : $([[ $STRICT -eq 1 ]] && echo "on" || echo "\\033[1;31moff\\033[0m")
strict mode : $([[ $STRICT -eq 1 ]] && utils.colors.print green on || utils.colors.print bright-red off)
" | sed 's/^ \+//' >&2
return 0
}
REDIS get --prefix current:$2
kube.redis get --prefix current:$2
;;
set )
scwrypts -n --name set-$2 --type zsh --group kubectl -- $3 --subsession $SUBSESSION >/dev/null \
&& SUCCESS "$2 set"
;;
copy )
( set )
: \
&& STATUS "copying $1 to $2" \
&& scwrypts -n --name set-context --type zsh --group kubectl -- --subsession $2 $(k meta get context | grep . || echo 'reset') \
&& scwrypts -n --name set-namespace --type zsh --group kubectl -- --subsession $2 $(k meta get namespace | grep . || echo 'reset') \
&& SUCCESS "subsession $1 copied to $2" \
&& scwrypts -n --name set-$2 --type zsh --group kube -- $3 --subsession $SUBSESSION >/dev/null \
&& k $SUBSESSION meta get $2 \
;
;;
clear )
scwrypts -n --name set-context --type zsh --group kubectl -- --subsession $SUBSESSION reset >/dev/null \
&& SUCCESS "subsession $SUBSESSION reset to default"
( copy )
: \
&& echo.status "copying $1 to $2" \
&& scwrypts -n --name set-context --type zsh --group kube -- --subsession $2 $(k $1 meta get context | grep . || echo 'reset') \
&& scwrypts -n --name set-namespace --type zsh --group kube -- --subsession $2 $(k $1 meta get namespace | grep . || echo 'reset') \
&& echo.success "subsession $1 copied to $2" \
;
;;
show )
( clear )
scwrypts -n --name set-context --type zsh --group kube -- --subsession $SUBSESSION reset >/dev/null \
&& echo.success "subsession $SUBSESSION reset to default"
;;
( show )
_SCWRYPTS_KUBECTL_SETTINGS set context show >/dev/null \
&& SUCCESS "now showing full command context"
&& echo.success "now showing full command context"
;;
hide )
( hide )
_SCWRYPTS_KUBECTL_SETTINGS set context hide >/dev/null \
&& SUCCESS "now hiding command context"
&& echo.success "now hiding command context"
;;
loose )
( loose )
_SCWRYPTS_KUBECTL_SETTINGS set strict 0 >/dev/null \
&& WARNING "now running in 'loose' mode"
&& echo.warning "now running in 'loose' mode"
;;
strict )
( strict )
_SCWRYPTS_KUBECTL_SETTINGS set strict 1 >/dev/null \
&& SUCCESS "now running in 'strict' mode"
&& echo.success "now running in 'strict' mode"
;;
esac
}

View File

@ -1,7 +1,7 @@
#!/bin/zsh
use kubectl --group kubectl
#!/usr/bin/env zsh
use kubectl --group kube
#####################################################################
MAIN() {
KUBECTL__GET_CONTEXT
kube.kubectl.context.get
}

View File

@ -1,7 +1,7 @@
#!/bin/zsh
use kubectl --group kubectl
#!/usr/bin/env zsh
use kubectl --group kube
#####################################################################
MAIN() {
KUBECTL__GET_NAMESPACE
kube.kubectl.namespace.get
}

View File

@ -0,0 +1,10 @@
export ${scwryptsgroup}__type=zsh
export ${scwryptsgroup}__color=$(utils.colors.red)
#####################################################################
SCWRYPTS_STATIC_CONFIG__kubectl+=(
"${scwryptsgrouproot}/.config/static/redis.zsh"
)
source "${scwryptsgrouproot}/driver/kubectl.driver.zsh"

View File

@ -0,0 +1,18 @@
#####################################################################
DEPENDENCIES+=(kubectl)
use redis --group kube
#####################################################################
kube.cli() {
local NAMESPACE="$(kube.redis get --prefix "current:namespace")"
local CONTEXT="$(kube.kubectl.context.get)"
local ARGS=()
[ "${NAMESPACE}" ] && ARGS+=(--namespace "${NAMESPACE}")
[ "${CONTEXT}" ] && ARGS+=(--context "${CONTEXT}")
kubectl ${ARGS[@]} $@
}

View File

@ -0,0 +1,56 @@
#####################################################################
use --group kube kubectl/cli
use --group kube kubectl/namespace
use --group kube redis
#####################################################################
${scwryptsmodule}.get() { kube.redis get --prefix "current:context"; }
${scwryptsmodule}.set() {
local CONTEXT=$1
[ ! "${CONTEXT}" ] && return 1
[[ "${CONTEXT}" =~ reset ]] && {
: \
&& kube.redis del --prefix "current:context" \
&& kube.kubectl.namespace.set reset \
;
return $?
}
: \
&& kube.redis set --prefix "current:context" "${CONTEXT}" \
&& kube.kubectl.namespace.set reset \
;
}
${scwryptsmodule}.select() {
case "$(kube.kubectl.context.list | grep -v '^reset$' | wc -l)" in
( 0 )
echo.error "no contexts available"
return 1
;;
( 1 )
kube.kubectl.context.list | tail -n1
;;
( * )
kube.kubectl.context.list | utils.fzf 'select a context'
;;
esac
}
${scwryptsmodule}.list() {
echo reset
local ALL_CONTEXTS="$(kube.cli config get-contexts -o name | sort -u)"
echo "${ALL_CONTEXTS}" | grep -v '^arn:aws:eks'
[[ "${AWS_ACCOUNT}" ]] && {
echo "${ALL_CONTEXTS}" | grep "^arn:aws:eks:.*:${AWS_ACCOUNT}"
true
} || {
echo "${ALL_CONTEXTS}" | grep '^arn:aws:eks'
}
}

View File

@ -0,0 +1,17 @@
#
# combines kubectl with redis to both facilitate use of kubectl
# between varying contexts/namespaces AND grant persistence between
# terminal sessions
#
# redis wrapper for kubectl
use --group kube kubectl/cli
# simplify commands for kubecontexts
use --group kube kubectl/context
# simplify commands for namespaces
use --group kube kubectl/namespace
# local redirect commands for remote kubernetes services
use --group kube kubectl/service

View File

@ -0,0 +1,23 @@
${scwryptsmodule}.get() { kube.redis get --prefix "current:namespace"; }
${scwryptsmodule}.set() {
local NAMESPACE=$1
[ ! "${NAMESPACE}" ] && return 1
[[ "${NAMESPACE}" =~ reset ]] && {
kube.redis del --prefix "current:namespace"
return $?
}
kube.redis set --prefix "current:namespace" "${NAMESPACE}"
}
${scwryptsmodule}.select() {
kube.kubectl.namespace.list | utils.fzf 'select a namespace'
}
${scwryptsmodule}.list() {
echo reset
echo default
kube.cli get namespaces -o name | sed 's/^namespace\///' | sort | grep -v '^default$'
}

View File

@ -0,0 +1,77 @@
#####################################################################
use --group kube kubectl/cli
use --group kube kubectl/context
use --group kube kubectl/namespace
#####################################################################
${scwryptsmodule}.serve() {
[ "${CONTEXT}" ] || local CONTEXT="$(kube.kubectl.context.get)"
[ "${CONTEXT}" ] || echo.error 'must configure a context in which to serve'
[ "${NAMESPACE}" ] || local NAMESPACE="$(kube.kubectl.namespace.get)"
[ "${NAMESPACE}" ] || echo.error 'must configure a namespace in which to serve'
utils.check-errors --no-usage || return 1
[ "${SERVICE}" ] && SERVICE="$(kube.kubectl.service.list | jq -c "select (.service == \"${SERVICE}\")" || echo ${SERVICE})"
[ "${SERVICE}" ] || local SERVICE="$(kube.kubectl.service.select)"
[ "${SERVICE}" ] || echo.error 'must provide or select a service'
kube.kubectl.service.list | grep -q "^${SERVICE}$"\
|| echo.error "no service '${SERVICE}' in '${CONFIG}/${NAMESPACE}'"
utils.check-errors --no-usage || return 1
##########################################
SERVICE_PASSWORD="$(kube.kubectl.service.get-password)"
kube.kubectl.service.parse
echo.reminder "attempting to serve ${NAMESPACE}/${SERVICE_NAME}:${SERVICE_PORT}"
[ "${SERVICE_PASSWORD}" ] && echo.reminder "password : ${SERVICE_PASSWORD}"
kube.cli port-forward "service/${SERVICE_NAME}" "${SERVICE_PORT}"
}
#####################################################################
${scwryptsmodule}.select() {
[ "${NAMESPACE}" ] || local NAMESPACE="$(kube.kubectl.namespace.get)"
[ "${NAMESPACE}" ] || return 1
local SERVICES="$(kube.kubectl.service.list)"
local SELECTED="$({
echo "namespace service port"
echo ${SERVICES} \
| jq -r '.service + " " + .port' \
| sed "s/^/${NAMESPACE} /" \
;
} \
| column -t \
| utils.fzf 'select a service' --header-lines=1 \
| awk '{print $2;}' \
)"
echo "${SERVICES}" | jq -c "select (.service == \"${SELECTED}\")"
}
${scwryptsmodule}.list() {
kube.cli get service --no-headers\
| awk '{print "{\"service\":\""$1"\",\"ip\":\""$3"\",\"port\":\""$5"\"}"}' \
| jq -c 'select (.ip != "None")' \
;
}
${scwryptsmodule}.get-password() {
[ "${PASSWORD_SECRET}" ] && [ "${PASSWORD_KEY}" ] || return 0
kube.cli get secret "${PASSWORD_SECRET}" -o jsonpath="{.data.${PASSWORD_KEY}}" \
| base64 --decode
}
${scwryptsmodule}.parse() {
SERVICE_NAME="$(echo "${SERVICE}" | jq -r .service)"
SERVICE_PORT="$(echo "${SERVICE}" | jq -r .port | sed 's|/.*$||')"
}

View File

@ -0,0 +1,7 @@
#!/usr/bin/env zsh
use redis --group kube
#####################################################################
MAIN() {
echo $(kube.redis --get-static-definition)
}

View File

@ -5,16 +5,13 @@ DEPENDENCIES+=(
docker
)
# TODO; allow custom redis configuration
export SCWRYPTS_KUBECTL_REDIS=managed
REQUIRED_ENV+=()
REQUIRED_ENV+=(
SCWRYPTS_KUBECTL_REDIS
)
utils.environment.check SCWRYPTS_KUBECTL_REDIS --default managed
#####################################################################
REDIS() {
kube.redis() {
[ ! $USAGE ] && local USAGE="
usage: [...options...]
@ -24,7 +21,7 @@ REDIS() {
-p, --prefix apply dynamic prefix to the next command line argument
--get-prefix output key prefix for current session+subsession
--get-static-definition output the static ZSH function definition for REDIS
--get-static-definition output the static ZSH function definition for kube.redis
additional arguments and options are passed through to 'redis-cli'
"
@ -39,14 +36,14 @@ REDIS() {
while [[ $# -gt 0 ]]
do
case $1 in
-p | --prefix ) USER_ARGS+=("${REDIS_PREFIX}${SCWRYPTS_ENV}:${SUBSESSION}:$2"); shift 1 ;;
( -p | --prefix ) USER_ARGS+=("${REDIS_PREFIX}${SCWRYPTS_ENV}:${SUBSESSION}:$2"); shift 1 ;;
--subsession ) SUBSESSION=$2; shift 1 ;;
( --subsession ) SUBSESSION=$2; shift 1 ;;
--get-prefix ) echo $REDIS_PREFIX; return 0 ;;
--get-static-definition ) ECHO_STATIC_DEFINITION=1 ;;
( --get-prefix ) echo $REDIS_PREFIX; return 0 ;;
( --get-static-definition ) ECHO_STATIC_DEFINITION=1 ;;
* ) USER_ARGS+=($1) ;;
( * ) USER_ARGS+=($1) ;;
esac
shift 1
done
@ -62,14 +59,14 @@ REDIS() {
REDIS_ARGS+=(--raw)
[[ $ECHO_STATIC_DEFINITION -eq 1 ]] && {
echo "REDIS() {\
echo "kube.redis() {\
local USER_ARGS=(); \
[ ! \$SUBSESSION ] && local SUBSESSION=0 ;\
while [[ \$# -gt 0 ]]; \
do \
case \$1 in
-p | --prefix ) USER_ARGS+=(\"${REDIS_PREFIX}\${SCWRYPTS_ENV}:\${SUBSESSION}:\$2\"); shift 1 ;; \
* ) USER_ARGS+=(\$1) ;; \
( -p | --prefix ) USER_ARGS+=(\"${REDIS_PREFIX}\${SCWRYPTS_ENV}:\${SUBSESSION}:\$2\"); shift 1 ;; \
( * ) USER_ARGS+=(\$1) ;; \
esac; \
shift 1; \
done; \
@ -81,9 +78,9 @@ REDIS() {
redis-cli ${REDIS_ARGS[@]} ${USER_ARGS[@]}
}
REDIS ping | grep -qi pong || {
kube.redis ping 2>/dev/null | grep -qi pong || {
RPID=$(docker ps -a | grep scwrypts-kubectl-redis | awk '{print $1;}')
[ $RPID ] && STATUS 'refreshing redis instance' && docker rm -f $RPID
[ $RPID ] && echo.status 'refreshing redis instance' && docker rm -f $RPID
unset RPID
docker run \
@ -92,6 +89,6 @@ REDIS ping | grep -qi pong || {
--publish $SCWRYPTS_KUBECTL_REDIS_PORT__managed:6379 \
redis >/dev/null 2>&1
STATUS 'awaiting redis connection'
until REDIS ping 2>/dev/null | grep -qi pong; do sleep 0.5; done
echo.status 'awaiting redis connection'
until kube.redis ping 2>/dev/null | grep -qi pong; do sleep 0.5; done
}

View File

@ -1,5 +1,5 @@
#!/bin/zsh
use kubectl --group kubectl
#!/usr/bin/env zsh
use kubectl --group kube
#####################################################################
MAIN() {
@ -12,7 +12,7 @@ MAIN() {
options:
--context override context
--namespace override namespace
--subsession REDIS subsession (default 0)
--subsession kube.redis subsession (default 0)
to show a required password on screen, use both:
--password-secret Secret resource
@ -33,17 +33,17 @@ MAIN() {
--password-secret ) PASSWORD_SECRET=$2; shift 1 ;;
--password-key ) PASSWORD_KEY=$2; shift 1 ;;
-h | --help ) USAGE; return 0 ;;
-h | --help ) utils.io.usage; return 0 ;;
* )
[ $SERVICE ] && ERROR "unexpected argument '$2'"
[ $SERVICE ] && echo.error "unexpected argument '$2'"
SERVICE=$1
;;
esac
shift 1
done
CHECK_ERRORS
utils.check-errors --fail
KUBECTL__SERVE
kube.kubectl.serve
}

View File

@ -1,5 +1,5 @@
#!/bin/zsh
use kubectl --group kubectl
#!/usr/bin/env zsh
use kubectl --group kube
#####################################################################
MAIN() {
@ -10,7 +10,7 @@ MAIN() {
context (optional) the full name of the kubeconfig context to set
options:
--subsession REDIS subsession (default 0)
--subsession kube.redis subsession (default 0)
-h, --help show this dialogue and exit
"
@ -22,20 +22,18 @@ MAIN() {
case $1 in
--subsession ) SUBSESSION=$2; shift 1 ;;
-h | --help ) USAGE; return 0 ;;
* )
[ $CONTEXT ] && ERROR "unexpected argument '$2'"
[ $CONTEXT ] && echo.error "unexpected argument '$2'"
CONTEXT=$1
;;
esac
shift 1
done
[ $CONTEXT ] || CONTEXT=$(KUBECTL__SELECT_CONTEXT)
[ $CONTEXT ] || ERROR 'must provide or select a valid kube context'
[ $CONTEXT ] || CONTEXT=$(kube.kubectl.context.select)
[ $CONTEXT ] || echo.error 'must provide or select a valid kube context'
CHECK_ERRORS
utils.check-errors --fail
KUBECTL__SET_CONTEXT $CONTEXT
kube.kubectl.context.set $CONTEXT
}

View File

@ -1,5 +1,5 @@
#!/bin/zsh
use kubectl --group kubectl
#!/usr/bin/env zsh
use kubectl --group kube
#####################################################################
MAIN() {
@ -10,7 +10,7 @@ MAIN() {
namespace (optional) the full name of the namespace context to set
options:
--subsession REDIS subsession (default 0)
--subsession kube.redis subsession (default 0)
-h, --help show this dialogue and exit
"
@ -22,20 +22,20 @@ MAIN() {
case $1 in
--subsession ) SUBSESSION=$2; shift 1 ;;
-h | --help ) USAGE; return 0 ;;
-h | --help ) utils.io.usage; return 0 ;;
* )
[ $NAMESPACE ] && ERROR "unexpected argument '$2'"
[ $NAMESPACE ] && echo.error "unexpected argument '$2'"
NAMESPACE=$1
;;
esac
shift 1
done
[ $NAMESPACE ] || NAMESPACE=$(KUBECTL__SELECT_NAMESPACE)
[ $NAMESPACE ] || ERROR 'must provide or select a valid namespace'
[ $NAMESPACE ] || NAMESPACE=$(kube.kubectl.namespace.select)
[ $NAMESPACE ] || echo.error 'must provide or select a valid namespace'
CHECK_ERRORS
utils.check-errors --fail
KUBECTL__SET_NAMESPACE $NAMESPACE
kube.kubectl.namespace.set $NAMESPACE
}

View File

@ -1,11 +0,0 @@
SCWRYPTS_GROUPS+=(kubectl)
export SCWRYPTS_TYPE__kubectl=zsh
export SCWRYPTS_ROOT__kubectl="$SCWRYPTS_ROOT__scwrypts/plugins/kubectl"
export SCWRYPTS_COLOR__kubectl='\033[0;31m'
SCWRYPTS_STATIC_CONFIG__kubectl+=(
"$SCWRYPTS_ROOT__kubectl/.config/static/redis.zsh"
)
source "$SCWRYPTS_ROOT__kubectl/driver/kubectl.driver.zsh"

View File

@ -1,158 +0,0 @@
#####################################################################
DEPENDENCIES+=(
kubectl
)
REQUIRED_ENV+=()
use redis --group kubectl
#####################################################################
KUBECTL() {
local NAMESPACE=$(REDIS get --prefix "current:namespace")
local CONTEXT=$(KUBECTL__GET_CONTEXT)
local KUBECTL_ARGS=()
[ $NAMESPACE ] && KUBECTL_ARGS+=(--namespace $NAMESPACE)
[ $CONTEXT ] && KUBECTL_ARGS+=(--context $CONTEXT)
kubectl ${KUBECTL_ARGS[@]} $@
}
#####################################################################
KUBECTL__GET_CONTEXT() { REDIS get --prefix "current:context"; }
KUBECTL__SET_CONTEXT() {
local CONTEXT=$1
[ ! $CONTEXT ] && return 1
[[ $CONTEXT =~ reset ]] && {
: \
&& REDIS del --prefix "current:context" \
&& KUBECTL__SET_NAMESPACE reset \
;
return $?
}
: \
&& REDIS set --prefix "current:context" "$CONTEXT" \
&& KUBECTL__SET_NAMESPACE reset \
;
}
KUBECTL__SELECT_CONTEXT() {
KUBECTL__LIST_CONTEXTS | FZF 'select a context'
}
KUBECTL__LIST_CONTEXTS() {
echo reset
local ALL_CONTEXTS=$(KUBECTL config get-contexts -o name | sort)
echo $ALL_CONTEXTS | grep -v '^arn:aws:eks'
[[ $AWS_ACCOUNT ]] && {
echo $ALL_CONTEXTS | grep "^arn:aws:eks:.*:$AWS_ACCOUNT"
true
} || {
echo $ALL_CONTEXTS | grep '^arn:aws:eks'
}
}
#####################################################################
KUBECTL__GET_NAMESPACE() { REDIS get --prefix "current:namespace"; }
KUBECTL__SET_NAMESPACE() {
local NAMESPACE=$1
[ ! $NAMESPACE ] && return 1
[[ $NAMESPACE =~ reset ]] && {
REDIS del --prefix "current:namespace"
return $?
}
REDIS set --prefix "current:namespace" "$NAMESPACE"
}
KUBECTL__SELECT_NAMESPACE() {
KUBECTL__LIST_NAMESPACES | FZF 'select a namespace'
}
KUBECTL__LIST_NAMESPACES() {
echo reset
echo default
KUBECTL get namespaces -o name | sed 's/^namespace\///' | sort
}
#####################################################################
KUBECTL__SERVE() {
[ $CONTEXT ] || local CONTEXT=$(KUBECTL__GET_CONTEXT)
[ $CONTEXT ] || ERROR 'must configure a context in which to serve'
[ $NAMESPACE ] || local NAMESPACE=$(KUBECTL__GET_NAMESPACE)
[ $NAMESPACE ] || ERROR 'must configure a namespace in which to serve'
CHECK_ERRORS --no-fail --no-usage || return 1
[ $SERVICE ] && SERVICE=$(KUBECTL__LIST_SERVICES | jq -c "select (.service == \"$SERVICE\")" || echo $SERVICE)
[ $SERVICE ] || local SERVICE=$(KUBECTL__SELECT_SERVICE)
[ $SERVICE ] || ERROR 'must provide or select a service'
KUBECTL__LIST_SERVICES | grep -q "^$SERVICE$"\
|| ERROR "no service '$SERVICE' in '$CONFIG/$NAMESPACE'"
CHECK_ERRORS --no-fail --no-usage || return 1
##########################################
SERVICE_PASSWORD="$(KUBECTL__GET_SERVICE_PASSWORD)"
KUBECTL__SERVICE_PARSE
REMINDER "attempting to serve ${NAMESPACE}/${SERVICE_NAME}:${SERVICE_PORT}"
[ $SERVICE_PASSWORD ] && REMINDER "password : $SERVICE_PASSWORD"
KUBECTL port-forward service/$SERVICE_NAME $SERVICE_PORT
}
KUBECTL__SELECT_SERVICE() {
[ $NAMESPACE ] || local NAMESPACE=$(KUBECTL__GET_NAMESPACE)
[ $NAMESPACE ] || return 1
local SERVICES=$(KUBECTL__LIST_SERVICES)
local SELECTED=$({
echo "namespace service port"
echo $SERVICES \
| jq -r '.service + " " + .port' \
| sed "s/^/$NAMESPACE /" \
;
} \
| column -t \
| FZF 'select a service' --header-lines=1 \
| awk '{print $2;}' \
)
echo $SERVICES | jq -c "select (.service == \"$SELECTED\")"
}
KUBECTL__LIST_SERVICES() {
KUBECTL get service --no-headers\
| awk '{print "{\"service\":\""$1"\",\"ip\":\""$3"\",\"port\":\""$5"\"}"}' \
| jq -c 'select (.ip != "None")' \
;
}
KUBECTL__GET_SERVICE_PASSWORD() {
[ $PASSWORD_SECRET ] && [ $PASSWORD_KEY ] || return 0
KUBECTL get secret $PASSWORD_SECRET -o jsonpath="{.data.$PASSWORD_KEY}" \
| base64 --decode
}
KUBECTL__SERVICE_PARSE() {
SERVICE_NAME=$(echo $SERVICE | jq -r .service)
SERVICE_PORT=$(echo $SERVICE | jq -r .port | sed 's|/.*$||')
}

View File

@ -1,7 +0,0 @@
#!/bin/zsh
use redis --group kubectl
#####################################################################
MAIN() {
echo $(REDIS --get-static-definition)
}

View File

@ -1,10 +1,27 @@
from os import getenv as os_getenv
from json import loads
from .scwrypts import scwrypts
from .scwrypts.exceptions import MissingVariableError
ENV = {}
def getenv(name, required=True):
value = os_getenv(f'{name}__override', os_getenv(name))
def getenv(name, required=True, default=None):
if ENV.get('configuration') is None or ENV.get('environment') is None:
full_environment = loads(
scwrypts(
name = 'scwrypts/environment/getenv',
group = 'scwrypts',
_type = 'zsh',
executable_args = '-n',
args = '--all',
).stdout
)
ENV['configuration'] = full_environment['configuration']
ENV['environment'] = full_environment['environment']
value = ENV.get('environment', {}).get(name, default)
if required and not value:
raise MissingVariableError(name)

View File

@ -5,7 +5,7 @@ from pytest import fixture
from .client import request
def test_discord_request(sample, _response):
def test_discord_request(sample, _mock_getenv, _response):
assert _response == sample.response
def test_discord_request_client_setup(sample, mock_get_request_client, _mock_getenv, _response):
@ -41,7 +41,7 @@ def fixture_mock_get_request_client(sample):
@fixture(name='_mock_getenv')
def fixture_mock_getenv(sample):
with patch('scwrypts.http.discord.client.getenv',) as mock:
with patch('scwrypts.http.discord.client.getenv') as mock:
mock.side_effect = lambda name, **kwargs: {
'DISCORD__BOT_TOKEN': sample.bot_token,
}[name]
@ -49,6 +49,6 @@ def fixture_mock_getenv(sample):
@fixture(name='_mock_getenv_optional')
def fixture_mock_getenv_optional():
with patch('scwrypts.http.discord.client.getenv',) as mock:
with patch('scwrypts.http.discord.client.getenv') as mock:
mock.side_effect = lambda name, **kwargs: None
yield mock

View File

@ -4,9 +4,11 @@ from json import dumps, loads
from random import randint, uniform, choice
from re import sub
from string import printable
from typing import Hashable, Callable
from typing import Callable
from uuid import uuid4
from collections.abc import Hashable
from requests import Response, status_codes
from yaml import safe_dump

395
scwrypts
View File

@ -1,12 +1,16 @@
#!/bin/zsh
#!/usr/bin/env zsh
export EXECUTION_DIR=$(pwd)
export SCWRYPTS_RUNTIME_ID=$(uuidgen)
source "$(dirname $(readlink -f "$0"))/zsh/lib/import.driver.zsh" || exit 42
source "$(dirname -- $(readlink -f -- "$0"))/zsh/import.driver.zsh" || return 42
use scwrypts/environment
use scwrypts/list-available
use scwrypts/get-runstring
#####################################################################
() {
cd "$SCWRYPTS_ROOT__scwrypts"
GIT_SCWRYPTS() { git -C "$SCWRYPTS_ROOT__scwrypts" $@; }
cd "$(scwrypts.config.group scwrypts root)"
GIT_SCWRYPTS() { git -C "$(scwrypts.config.group scwrypts root)" $@; }
local ERRORS=0
local USAGE='
@ -39,7 +43,7 @@ source "$(dirname $(readlink -f "$0"))/zsh/lib/import.driver.zsh" || exit 42
--list-envs print out environment list and exit
--list-groups print out configured scwrypts groups and exit
--config "eval"-ed to enable config and "use" import in non-scwrypts environments
--root print out SCWRYPTS_ROOT__scwrypts and exit
--root print out scwrypts.config.group.scwrypts.root and exit
--update update scwrypts library to latest version
--version print out scwrypts version and exit
@ -55,76 +59,76 @@ source "$(dirname $(readlink -f "$0"))/zsh/lib/import.driver.zsh" || exit 42
### cli argument parsing and global configuration ###################
#####################################################################
local ENV_NAME="$SCWRYPTS_ENV"
local ENV_NAME="${SCWRYPTS_ENV}"
local SEARCH_PATTERNS=()
local VARSPLIT SEARCH_GROUP SEARCH_TYPE SEARCH_NAME
[ ! $SCWRYPTS_LOG_LEVEL ] && local SCWRYPTS_LOG_LEVEL=3
[ ! ${SCWRYPTS_LOG_LEVEL} ] && local SCWRYPTS_LOG_LEVEL=3
local SHIFT_COUNT
while [[ $# -gt 0 ]]
do
SHIFT_COUNT=1
case $1 in
-[a-z][a-z]* )
( -[a-z][a-z]* )
VARSPLIT=$(echo "$1 " | sed 's/^\(-.\)\(.*\) /\1 -\2/')
set -- throw-away $(echo " $VARSPLIT ") ${@:2}
set -- throw-away $(echo " ${VARSPLIT} ") ${@:2}
;;
### alternate commands ###################
-h | --help )
USAGE
( -h | --help )
utils.io.usage
return 0
;;
-l | --list )
SCWRYPTS__GET_AVAILABLE_SCWRYPTS
( -l | --list )
scwrypts.list-available
return 0
;;
--list-envs )
SCWRYPTS__GET_ENV_NAMES
( --list-envs )
scwrypts.environment.common.get-env-names
return 0
;;
--list-groups )
( --list-groups )
echo "${SCWRYPTS_GROUPS[@]}" | sed 's/\s\+/\n/g' | sort -u
return 0
;;
--version )
case $SCWRYPTS_INSTALLATION_TYPE in
manual ) echo "scwrypts $(GIT_SCWRYPTS describe --tags) (via GIT)" ;;
* ) echo "scwrypts $(cat "$SCWRYPTS_ROOT__scwrypts/VERSION")" ;;
( --version )
case ${SCWRYPTS_INSTALLATION_TYPE} in
( manual ) echo "scwrypts $(GIT_SCWRYPTS describe --tags) (via GIT)" ;;
( * ) echo "scwrypts $(cat "$(scwrypts.config.group scwrypts root)/VERSION")" ;;
esac
return 0
;;
--root )
echo "$SCWRYPTS_ROOT__scwrypts"
( --root )
scwrypts.config.group scwrypts root
return 0
;;
--config )
echo "source '$SCWRYPTS_ROOT__scwrypts/zsh/lib/import.driver.zsh'"
echo "CHECK_ENVIRONMENT --no-fail --no-usage"
( --config )
echo "source '$(scwrypts.config.group scwrypts root)/zsh/import.driver.zsh'"
echo "utils.check-environment --no-fail --no-usage"
echo "unset __SCWRYPT"
return 0
;;
--update )
case $SCWRYPTS_INSTALLATION_TYPE in
( --update )
case ${SCWRYPTS_INSTALLATION_TYPE} in
aur )
SCWRYPTS_LOG_LEVEL=3 REMINDER "
echo.reminder --force-print "
This installation is built from the AUR. Update through 'makepkg' or use
your preferred AUR package management tool (e.g. 'yay -Syu scwrypts')
"
;;
homebrew )
SCWRYPTS_LOG_LEVEL=3 REMINDER "This installation is managed by homebrew. Update me with 'brew update'"
echo.reminder --force-print "This installation is managed by homebrew. Update me with 'brew update'"
;;
manual )
@ -135,22 +139,22 @@ source "$(dirname $(readlink -f "$0"))/zsh/lib/import.driver.zsh" || exit 42
GIT_SCWRYPTS diff --exit-code origin/main -- . >/dev/null 2>&1
local DIFF_STATUS=$?
[[ $SYNC_STATUS -eq 0 ]] && [[ $DIFF_STATUS -eq 0 ]] && {
SUCCESS 'already up-to-date with origin/main'
[[ ${SYNC_STATUS} -eq 0 ]] && [[ ${DIFF_STATUS} -eq 0 ]] && {
echo.success 'already up-to-date with origin/main'
} || {
GIT_SCWRYPTS rebase --autostash origin/main \
&& SUCCESS 'up-to-date with origin/main' \
&& echo.success 'up-to-date with origin/main' \
&& GIT_SCWRYPTS log -n1 \
|| {
GIT_SCWRYPTS rebase --abort
ERROR 'unable to update scwrypts; please try manual upgrade'
REMINDER "installation in '$SCWRYPTS_ROOT__scwrypts'"
echo.error 'unable to update scwrypts; please try manual upgrade'
echo.reminder "installation in '$(scwrypts.config.group scwrypts root)'"
}
}
;;
* )
SCWRYPTS_LOG_LEVEL=3 REMINDER "
echo.reminder --force-print "
This is a managed installation of scwrypts. Please update through your
system package manager.
"
@ -161,228 +165,236 @@ source "$(dirname $(readlink -f "$0"))/zsh/lib/import.driver.zsh" || exit 42
### scwrypts filters #####################
-m | --name )
( -m | --name )
((SHIFT_COUNT+=1))
[ $2 ] || { ERROR "missing value for argument $1"; break; }
[ $2 ] || { echo.error "missing value for argument $1"; break; }
SEARCH_NAME=$2
;;
-g | --group )
( -g | --group )
((SHIFT_COUNT+=1))
[ $2 ] || { ERROR "missing value for argument $1"; break; }
[ $2 ] || { echo.error "missing value for argument $1"; break; }
SEARCH_GROUP=$2
GROUP=$2
;;
-t | --type )
( -t | --type )
((SHIFT_COUNT+=1))
[ $2 ] || { ERROR "missing value for argument $1"; break; }
[ $2 ] || { echo.error "missing value for argument $1"; break; }
SEARCH_TYPE=$2
TYPE=$2
;;
### runtime settings #####################
-y | --yes ) export __SCWRYPTS_YES=1 ;;
( -y | --yes ) export __SCWRYPTS_YES=1 ;;
-n ) SCWRYPTS_LOG_LEVEL=0 ;;
( -n ) SCWRYPTS_LOG_LEVEL=0 ;;
-v | --log-level )
( -v | --log-level )
((SHIFT_COUNT+=1))
[[ $2 =~ ^[0-4]$ ]] || ERROR "invalid setting for log-level '$2'"
[[ $2 =~ ^[0-4]$ ]] || echo.error "invalid setting for log-level '$2'"
SCWRYPTS_LOG_LEVEL=$2
;;
-o | --output )
( -o | --output )
((SHIFT_COUNT+=1))
export SCWRYPTS_OUTPUT_FORMAT=$2
case $SCWRYPTS_OUTPUT_FORMAT in
pretty | json ) ;;
* ) ERROR "unsupported format '$SCWRYPTS_OUTPUT_FORMAT'" ;;
case ${SCWRYPTS_OUTPUT_FORMAT} in
( pretty | json ) ;;
* ) echo.error "unsupported format '${SCWRYPTS_OUTPUT_FORMAT}'" ;;
esac
;;
-e | --env )
( -e | --env )
((SHIFT_COUNT+=1))
[ $2 ] || { ERROR "missing value for argument $1"; break; }
[ $2 ] || { echo.error "missing value for argument $1"; break; }
[ $ENV_NAME ] && DEBUG 'overwriting session environment'
[ ${ENV_NAME} ] && echo.debug 'overwriting session environment'
ENV_NAME="$2"
STATUS "using CLI environment '$ENV_NAME'"
echo.status "using CLI environment '${ENV_NAME}'"
;;
##########################################
-- ) shift 1; break ;; # pass arguments after '--' to the scwrypt
--* ) ERROR "unrecognized argument '$1'" ;;
* ) SEARCH_PATTERNS+=($1) ;;
( -- ) shift 1; break ;; # pass arguments after '--' to the scwrypt
( --* ) echo.error "unrecognized argument '$1'" ;;
( * ) SEARCH_PATTERNS+=($1) ;;
esac
shift $SHIFT_COUNT
[[ ${SHIFT_COUNT} -le $# ]] \
&& shift ${SHIFT_COUNT} \
|| echo.error "missing argument for '$1'" \
|| shift $# \
;
done
[ $SCWRYPTS_OUTPUT_FORMAT ] || export SCWRYPTS_OUTPUT_FORMAT=pretty
[ ${SCWRYPTS_OUTPUT_FORMAT} ] || export SCWRYPTS_OUTPUT_FORMAT=pretty
[ $SEARCH_NAME ] && {
[ $SEARCH_TYPE ] || ERROR '--name requires --type argument'
[ $SEARCH_GROUP ] || ERROR '--name requires --group argument'
[ ${SEARCH_NAME} ] && {
[ ${SEARCH_TYPE} ] || echo.error '--name requires --type argument'
[ ${SEARCH_GROUP} ] || echo.error '--name requires --group argument'
}
CHECK_ERRORS
utils.check-errors --fail
#####################################################################
### scwrypts selection / filtering ##################################
#####################################################################
local SCWRYPTS_AVAILABLE
SCWRYPTS_AVAILABLE=$(SCWRYPTS__GET_AVAILABLE_SCWRYPTS)
local SCWRYPTS_AVAILABLE=$(scwrypts.list-available)
##########################################
[ $SEARCH_NAME ] && SCWRYPTS_AVAILABLE=$({
echo $SCWRYPTS_AVAILABLE | head -n1
echo $SCWRYPTS_AVAILABLE | sed -e 's/\x1b\[[0-9;]*m//g' | grep "^$SEARCH_NAME *$SEARCH_TYPE *$SEARCH_GROUP\$"
[ ${SEARCH_NAME} ] && SCWRYPTS_AVAILABLE=$({
echo ${SCWRYPTS_AVAILABLE} | head -n1
echo ${SCWRYPTS_AVAILABLE} | utils.colors.remove | grep "^${SEARCH_NAME} *${SEARCH_TYPE} *${SEARCH_GROUP}\$"
}) || {
[ $SEARCH_TYPE ] && {
[ ${SEARCH_TYPE} ] && {
SCWRYPTS_AVAILABLE=$(\
{
echo $SCWRYPTS_AVAILABLE | head -n1
echo $SCWRYPTS_AVAILABLE | grep ' [^/]*'$SEARCH_TYPE'[^/]* '
echo ${SCWRYPTS_AVAILABLE} | head -n1
echo ${SCWRYPTS_AVAILABLE} | grep ' [^/]*'${SEARCH_TYPE}'[^/]* '
} \
| sed 's/ \+$/'$(printf $__COLOR_RESET)'/; s/ \+/^/g' \
| sed 's/ \+$/'$(utils.colors.reset)'/; s/ \+/^/g' \
| column -ts '^'
)
}
[ $SEARCH_GROUP ] && {
[ ${SEARCH_GROUP} ] && {
SCWRYPTS_AVAILABLE=$(
{
echo $SCWRYPTS_AVAILABLE | head -n1
echo $SCWRYPTS_AVAILABLE | grep "$SEARCH_GROUP"'[^/ ]*$'
echo ${SCWRYPTS_AVAILABLE} | head -n1
echo ${SCWRYPTS_AVAILABLE} | grep "${SEARCH_GROUP}"'[^/ ]*$'
} \
| sed 's/ \+$/'$(printf $__COLOR_RESET)'/; s/ \+/^/g' \
| sed 's/ \+$/'$(utils.colors.reset)'/; s/ \+/^/g' \
| column -ts '^'
)
}
[[ ${#SEARCH_PATTERNS[@]} -gt 0 ]] && {
POTENTIAL_ERROR+="\n PATTERNS : $SEARCH_PATTERNS"
POTENTIAL_ERROR+="\n PATTERNS : ${SEARCH_PATTERNS}"
local P
for P in ${SEARCH_PATTERNS[@]}
do
SCWRYPTS_AVAILABLE=$(
{
echo $SCWRYPTS_AVAILABLE | head -n1
echo $SCWRYPTS_AVAILABLE | grep $P
echo ${SCWRYPTS_AVAILABLE} | head -n1
echo ${SCWRYPTS_AVAILABLE} | grep ${P}
}
)
done
}
}
[[ $(echo $SCWRYPTS_AVAILABLE | wc -l) -lt 2 ]] && {
FAIL 1 "$(echo "
[[ $(echo ${SCWRYPTS_AVAILABLE} | wc -l) -lt 2 ]] && {
utils.fail 1 "$(echo "
no such scwrypt exists
NAME : '$SEARCH_NAME'
TYPE : '$SEARCH_TYPE'
GROUP : '$SEARCH_GROUP'
PATTERNS : '$SEARCH_PATTERNS'
NAME : '${SEARCH_NAME}'
TYPE : '${SEARCH_TYPE}'
GROUP : '${SEARCH_GROUP}'
PATTERNS : '${SEARCH_PATTERNS}'
" | sed "1d; \$d; /''$/d")"
}
##########################################
[[ $(echo $SCWRYPTS_AVAILABLE | wc -l) -eq 2 ]] \
&& SCWRYPT_SELECTION=$(echo $SCWRYPTS_AVAILABLE | tail -n1) \
|| SCWRYPT_SELECTION=$(echo $SCWRYPTS_AVAILABLE | FZF "select a script to run" --header-lines 1) \
[[ $(echo ${SCWRYPTS_AVAILABLE} | wc -l) -eq 2 ]] \
&& SCWRYPT_SELECTION=$(echo ${SCWRYPTS_AVAILABLE} | tail -n1) \
|| SCWRYPT_SELECTION=$(echo ${SCWRYPTS_AVAILABLE} | utils.fzf "select a script to run" --header-lines 1) \
;
[ $SCWRYPT_SELECTION ] || exit 2
[ ${SCWRYPT_SELECTION} ] || utils.abort
##########################################
local NAME TYPE GROUP
SCWRYPTS__SEPARATE_SCWRYPT_SELECTION $SCWRYPT_SELECTION
() {
set -- $(echo $@ | utils.colors.remove)
export SCWRYPT_NAME=$1
export SCWRYPT_TYPE=$2
export SCWRYPT_GROUP=$3
} ${SCWRYPT_SELECTION}
export SCWRYPT_NAME=$NAME
export SCWRYPT_TYPE=$TYPE
export SCWRYPT_GROUP=$GROUP
#####################################################################
### environment variables and configuration validation ##############
#####################################################################
local ENV_REQUIRED=true \
&& [ ! $CI ] \
&& [[ ! $SCWRYPT_NAME =~ scwrypts/logs ]] \
&& [[ ! $SCWRYPT_NAME =~ scwrypts/environment ]] \
&& [ ! ${CI} ] \
&& [[ ! ${SCWRYPT_NAME} =~ scwrypts/logs ]] \
&& [[ ! ${SCWRYPT_NAME} =~ scwrypts/environment ]] \
|| ENV_REQUIRED=false
local REQUIRED_ENVIRONMENT_REGEX=$(eval echo '$SCWRYPTS_REQUIRED_ENVIRONMENT_REGEX__'$SCWRYPT_GROUP)
local REQUIRED_ENVIRONMENT_REGEX="$(scwrypts.config.group ${SCWRYPT_GROUP} required_environment_regex)"
[ $ENV_NAME ] && [ $REQUIRED_ENVIRONMENT_REGEX ] && {
[[ $ENV_NAME =~ $REQUIRED_ENVIRONMENT_REGEX ]] \
|| FAIL 5 "group '$SCWRYPT_GROUP' requires current environment name to match '$REQUIRED_ENVIRONMENT_REGEX' (currently $ENV_NAME)"
[ ${ENV_NAME} ] && [ ${REQUIRED_ENVIRONMENT_REGEX} ] && {
[[ ${ENV_NAME} =~ ${REQUIRED_ENVIRONMENT_REGEX} ]] \
|| utils.fail 5 "group '${SCWRYPT_GROUP}' requires current environment name to match '${REQUIRED_ENVIRONMENT_REGEX}' (currently ${ENV_NAME})"
}
[[ $ENV_REQUIRED =~ true ]] && {
[ ! $ENV_NAME ] && ENV_NAME=$(SCWRYPTS__SELECT_ENV)
[ ! $ENV_NAME ] && ABORT
[[ ${ENV_REQUIRED} =~ true ]] && {
[ ! ${ENV_NAME} ] && {
scwrypts.environment.init \
|| echo.error "failed to initialize scwrypts environments (see above)" \
|| return 1 \
;
export ENV_NAME
export SCWRYPTS_ENV=$ENV_NAME
ENV_NAME=$(scwrypts.environment.select-env)
[ "${ENV_NAME}" ] || user.abort
}
for GROUP in ${SCWRYPTS_GROUPS[@]}
do
local REQUIRED_REGEX=$(eval echo '$SCWRYPTS_REQUIRED_ENVIRONMENT_REGEX__'$GROUP)
[ $REQUIRED_REGEX ] && {
[[ $ENV_NAME =~ $REQUIRED_REGEX ]] || continue
local REQUIRED_REGEX="$(scwrypts.config.group ${GROUP} required_environment_regex)"
[ ${REQUIRED_REGEX} ] && {
[[ ${ENV_NAME} =~ ${REQUIRED_REGEX} ]] || continue
}
local ENV_FILE=$(SCWRYPTS__GET_ENV_FILE "$ENV_NAME" "$GROUP")
source "$ENV_FILE" || FAIL 5 "missing or invalid environment '$GROUP/$ENV_NAME'"
for f in $(eval 'echo $SCWRYPTS_STATIC_CONFIG__'$GROUP)
for f in $(find "$(scwrypts.config.group ${GROUP} root)/.config/static" -type f 2>/dev/null)
do
source "$f" || FAIL 5 "invalid static config '$f'"
source "${f}" || utils.fail 5 "invalid static config '${f}'"
done
done
}
[ $REQUIRED_ENVIRONMENT_REGEX ] && {
[[ $ENV_NAME =~ $REQUIRED_ENVIRONMENT_REGEX ]] \
|| FAIL 5 "group '$SCWRYPT_GROUP' requires current environment name to match '$REQUIRED_ENVIRONMENT_REGEX' (currently $ENV_NAME)"
[ ${REQUIRED_ENVIRONMENT_REGEX} ] && {
[[ ${ENV_NAME} =~ ${REQUIRED_ENVIRONMENT_REGEX} ]] \
|| utils.fail 5 "group '${SCWRYPT_GROUP}' requires current environment name to match '${REQUIRED_ENVIRONMENT_REGEX}' (currently ${ENV_NAME})"
}
export SCWRYPTS_ENV=${ENV_NAME}
##########################################
[ ! $SUBSCWRYPT ] && export SUBSCWRYPT=0
[ ! ${SUBSCWRYPT} ] && export SUBSCWRYPT=0
[[ $SCWRYPTS_INSTALLATION_TYPE =~ ^manual$ ]] && {
[[ $SUBSCWRYPT -eq 0 ]] && [[ $ENV_NAME =~ prod ]] && [[ $SCWRYPTS_LOG_LEVEL -gt 0 ]] && {
STATUS "on '$ENV_NAME'; checking diff against origin/main"
[[ ${SCWRYPTS_INSTALLATION_TYPE} =~ ^manual$ ]] && {
[[ ${SUBSCWRYPT} -eq 0 ]] && [[ ${SCWRYPTS_ENV} =~ prod ]] && [[ ${SCWRYPTS_LOG_LEVEL} -gt 0 ]] && {
echo.status "on '${SCWRYPTS_ENV}'; checking diff against origin/main"
local WARNING_MESSAGE
[ ! $WARNING_MESSAGE ] && {
[ ! ${WARNING_MESSAGE} ] && {
GIT_SCWRYPTS fetch --quiet origin main \
|| WARNING_MESSAGE='I am unable to verify your scwrypts version'
}
[ ! $WARNING_MESSAGE ] && {
[ ! ${WARNING_MESSAGE} ] && {
GIT_SCWRYPTS diff --exit-code origin/main -- . >/dev/null 2>&1 \
|| WARNING_MESSAGE='your scwrypts is currently out-of-date'
}
[ $WARNING_MESSAGE ] && {
[[ $SCWRYPTS_LOG_LEVEL -lt 3 ]] && {
REMINDER "you are running in ${__BRIGHT_RED}production${__BRIGHT_MAGENTA} and $WARNING_MESSAGE"
[ ${WARNING_MESSAGE} ] && {
[[ ${SCWRYPTS_LOG_LEVEL} -lt 3 ]] && {
echo.reminder "you are running in $(utils.colors.bright-red)production$(utils.colors.bright-magenta) and ${WARNING_MESSAGE}"
} || {
GIT_SCWRYPTS diff --exit-code origin/main -- . >&2
WARNING "you are trying to run in ${__BRIGHT_RED}production${__YELLOW} but $WARNING_MESSAGE (relevant diffs and errors above)"
echo.warning "you are trying to run in $(utils.colors.bright-red)production$(echo.warning.color) but ${WARNING_MESSAGE} (relevant diffs and errors above)"
yN 'continue?' || {
REMINDER "you can use 'scwrypts --update' to quickly update scwrypts to latest"
ABORT
echo.reminder "you can use 'scwrypts --update' to quickly update scwrypts to latest"
user.abort
}
}
}
@ -391,92 +403,123 @@ source "$(dirname $(readlink -f "$0"))/zsh/lib/import.driver.zsh" || exit 42
##########################################
local RUN_STRING=$(SCWRYPTS__GET_RUNSTRING $SCWRYPT_NAME $SCWRYPT_TYPE $SCWRYPT_GROUP)
[ "$RUN_STRING" ] || return 42
local RUN_STRING=$(scwrypts.get-runstring ${SCWRYPT_NAME} ${SCWRYPT_TYPE} ${SCWRYPT_GROUP})
[ "${RUN_STRING}" ] || return 42
#####################################################################
### logging and pretty header/footer setup ##########################
#####################################################################
local RUN_MODE=normal
[[ ${SCWRYPT_NAME} =~ interactive ]] && RUN_MODE=interactive
local LOGFILE \
&& [[ $SCWRYPTS_LOG_LEVEL -gt 0 ]] \
&& [[ $SUBSCWRYPT -eq 0 ]] \
&& [[ ! $SCWRYPT_NAME =~ scwrypts/logs ]] \
&& [[ ! $SCWRYPT_NAME =~ interactive ]] \
&& LOGFILE="$SCWRYPTS_LOG_PATH/$(echo $GROUP/$TYPE/$NAME | sed 's/^\.\///; s/\//\%/g').log" \
&& [[ ${RUN_MODE} =~ normal ]] \
&& [[ ${SCWRYPTS_LOG_LEVEL} -gt 0 ]] \
&& [[ ${SUBSCWRYPT} -eq 0 ]] \
&& [[ ! ${SCWRYPT_NAME} =~ scwrypts/logs ]] \
&& LOGFILE="${SCWRYPTS_LOG_PATH}/$(echo ${GROUP}/${TYPE}/${NAME} | sed 's/^\.\///; s/\//\%/g').log" \
|| LOGFILE='/dev/null' \
;
local RUN_MODE=normal
[[ $LOGFILE =~ ^/dev/null$ ]] && RUN_MODE=no-logfile
[[ $SCWRYPT_NAME =~ interactive ]] && RUN_MODE=interactive
local HEADER FOOTER
[[ $SCWRYPTS_LOG_LEVEL -ge 2 ]] && {
case $SCWRYPTS_OUTPUT_FORMAT in
pretty )
[[ ${SCWRYPTS_LOG_LEVEL} -ge 2 ]] && {
case ${SCWRYPTS_OUTPUT_FORMAT} in
( raw )
HEADER="--- start scwrypt ${SCWRYPT_GROUP}/${SCWRYPT_TYPE} ${SCWRYPT_NAME} in ${SCWRYPTS_ENV} ---"
FOOTER="--- end scwrypt ---"
;;
( pretty )
HEADER=$(
echo "
=====================================================================
scwrypt : $SCWRYPT_GROUP $SCWRYPT_TYPE $SCWRYPT_NAME
scwrypt : ${SCWRYPT_GROUP} ${SCWRYPT_TYPE} ${SCWRYPT_NAME}
run at : $(date)
config : $ENV_NAME
log level : $SCWRYPTS_LOG_LEVEL
\\033[1;33m--- SCWRYPT BEGIN ---------------------------------------------------\\033[0m
config : ${SCWRYPTS_ENV}
log level : ${SCWRYPTS_LOG_LEVEL}
$(utils.colors.print bright-yellow '--- SCWRYPT BEGIN ---------------------------------------------------')
" | sed 's/^\s\+//; 1d'
)
FOOTER="\\033[1;33m--- SCWRYPT END ---------------------------------------------------\\033[0m"
FOOTER="$(utils.colors.print bright-yellow '--- SCWRYPT END ---------------------------------------------------')"
;;
json )
( json )
HEADER=$(echo '{}' | jq -c ".
| .timestamp = \"$(date +%s)\"
| .runtime = \"$SCWRYPTS_RUNTIME_ID\"
| .scwrypt = \"start of $SCWRYPT_NAME $SCWRYPT_GROUP $SCWRYPT_TYPE\"
| .config = \"$ENV_NAME\"
| .logLevel = \"$SCWRYPTS_LOG_LEVEL\"
| .subscwrypt = $SUBSCWRYPT
| .runtime = \"${SCWRYPTS_RUNTIME_ID}\"
| .scwrypt = \"start of ${SCWRYPT_NAME} ${SCWRYPT_GROUP} ${SCWRYPT_TYPE}\"
| .config = \"${SCWRYPTS_ENV}\"
| .logLevel = \"${SCWRYPTS_LOG_LEVEL}\"
| .subscwrypt = ${SUBSCWRYPT}
")
;;
esac
}
[[ $SUBSCWRYPT -eq 0 ]] || {
case $SCWRYPTS_OUTPUT_FORMAT in
pretty )
HEADER="\\033[0;33m--- ($SUBSCWRYPT) BEGIN $SCWRYPT_GROUP $SCWRYPT_TYPE $SCWRYPT_NAME ---"
FOOTER="\\033[0;33m--- ($SUBSCWRYPT) END $SCWRYPT_GROUP $SCWRYPT_TYPE $SCWRYPT_NAME ---"
[[ ${SUBSCWRYPT} -eq 0 ]] || {
case ${SCWRYPTS_OUTPUT_FORMAT} in
( pretty )
HEADER="$(utils.colors.print yellow "--- (${SUBSCWRYPT}) BEGIN ${SCWRYPT_GROUP} ${SCWRYPT_TYPE} ${SCWRYPT_NAME} ---")"
FOOTER="$(utils.colors.print yellow "--- (${SUBSCWRYPT}) END ${SCWRYPT_GROUP} ${SCWRYPT_TYPE} ${SCWRYPT_NAME} ---")"
;;
esac
}
#####################################################################
### run the scwrypt #################################################
#####################################################################
set -o pipefail
{
[ $HEADER ] && echo $HEADER
case $RUN_MODE in
normal )
(eval "$RUN_STRING $(printf "%q " "$@")")
EXIT_CODE=$?
[[ ${SCWRYPTS_LOG_LEVEL} -ge 2 ]] && __SCWRYPTS_PRINT_EXIT_CODE=true
[ ${HEADER} ] && echo ${HEADER} >&2
(
case ${RUN_MODE} in
( normal )
eval "${RUN_STRING} $(printf "%q " "$@")"
;;
no-logfile )
eval "$RUN_STRING $(printf "%q " "$@")"
EXIT_CODE=$?
;;
interactive )
eval "$RUN_STRING $(printf "%q " "$@")" </dev/tty >/dev/tty 2>&1
EXIT_CODE=$?
( interactive )
eval "${RUN_STRING} $(printf "%q " "$@")" </dev/tty &>/dev/tty
;;
esac
[ $FOOTER ] && echo $FOOTER
[[ $EXIT_CODE -eq 0 ]] && EXIT_COLOR='32m' || EXIT_COLOR='31m'
)
[[ $SCWRYPTS_LOG_LEVEL -ge 2 ]] && [ ! $SUBSCWRYPT ] \
&& echo "terminated with\\033[1;$EXIT_COLOR code $EXIT_CODE\\033[0m"
EXIT_CODE=$?
return $EXIT_CODE
} | tee --append "$LOGFILE"
[ ${FOOTER} ] && echo ${FOOTER} >&2
[[ ${__SCWRYPTS_PRINT_EXIT_CODE} =~ true ]] && {
EXIT_COLOR=$( [[ ${EXIT_CODE} -eq 0 ]] && utils.colors.bright-green || utils.colors.bright-red )
case ${SCWRYPTS_OUTPUT_FORMAT} in
( raw )
echo "terminated with code ${EXIT_CODE}" >&2
;;
( pretty )
echo "terminated with ${EXIT_COLOR}code ${EXIT_CODE}$(utils.colors.reset)" >&2
;;
( json )
[[ ${EXIT_CODE} =~ 0 ]] \
&& echo.success --force-print "terminated with code ${EXIT_CODE}" \
|| echo.error --force-print "terminated with code ${EXID_CODE}" \
;
;;
esac
}
return ${EXIT_CODE}
} | tee --append "${LOGFILE}"
} $@
EXIT_CODE=$?
[ "${SCWRYPTS_TEMP_PATH}" ] && [ -d "${SCWRYPTS_TEMP_PATH}" ] \
&& {
rm -- $(find "${SCWRYPTS_TEMP_PATH}" -mindepth 1 -maxdepth 1 -type f)
rmdir "${SCWRYPTS_TEMP_PATH}"
} &>/dev/null
return ${EXIT_CODE}

View File

@ -204,7 +204,7 @@ command -v compdef &>/dev/null && {
# stop providing suggestions if your pattern is sufficient
[[ $(echo $_remaining_scwrypts | wc -l) -le 1 ]] && return 0
local _remaining_patterns="$(echo "$_remaining_scwrypts" | sed 's/\s\+/\n/g; s|/|\n|g;' | sort -u)"
local _remaining_patterns="$(echo "$_remaining_scwrypts" | sed 's/\s\+/\n/g; s|/|\n|g; s/-/\n/g;' | sort -u)"
for _pattern in ${_patterns[@]}
do

View File

@ -1,11 +1,177 @@
SCWRYPTS_GROUPS+=(scwrypts)
#
# configuration for a scwrypts "group" or "plugin"
#
export SCWRYPTS_ROOT__scwrypts="$SCWRYPTS_ROOT"
export SCWRYPTS_COLOR__scwrypts='\033[0;32m'
#export SCWRYPTS_TYPE__scwrypts=
#export SCWRYPTS_LIBRARY_ROOT__scwrypts=
# this file defines the configuration for the 'scwrypts' group which
# is required for proper operation, but otherwise loads exactly like
# any other group/plugin
export SCWRYPTS_VIRTUALENV_PATH__scwrypts="$SCWRYPTS_DATA_PATH/virtualenv"
#
# both ${scwryptsgroup} and ${scwryptsgrouproot} are set automatically
#
# ${scwryptsgroup} is determined by the filename 'NAME.scwrypts.zsh'
#
# NAME must be unique and match : ^[a-z][a-z0-9_]*[a-z0-9]$
# - STARTS with a lower letter
# - ENDS with a lower letter or number
# - contains only lower-alphanumeric and underscores
# - is at least two characters long
#
# ${scwryptsgrouproot} is automatically set as the parent directory
# /path/to/group-source <-- this will be ${scwryptsgrouproot}
# ├── groupname.scwrypts.zsh
# └── your-scwrypts-source-here
#
export SCWRYPTS_PREFERRED_PYTHON_VERSIONS__scwrypts=(3.12 3.11 3.10)
export SCWRYPTS_NODE_VERSION__scwrypts=18.0.0
#####################################################################
### REQUIRED CONFIGURATION ##########################################
#####################################################################
# Currently, no configuration is required; simply creating the
# groupname.scwrypts.zsh is sufficient to define a new group
#####################################################################
### OPTIONAL CONFIGURATION ##########################################
#####################################################################
# ${scwryptsgroup}__option_key configuration values can be accessed anywhere in zsh scwrypts
# with $(scwrypts.config.group group-name option_key)
readonly ${scwryptsgroup}__type=
#
# ${scwryptsgroup}__type (optional) (default = not set)
#
# used when only one scwrypt "type" (e.g. 'zsh' or 'py') is declared
# in the group
#
# WHEN THIS IS SET, scwrypts will lookup executables starting from the
# base directory (using type ${scwryptsgroup}__type):
#
# /path/to/group-source
# ├── groupname.scwrypts.zsh
# ├── valid-scwrypts-executable
# └── some-other
# ├── valid-scwrypts-executable
# └── etc
#
# when this is NOT set, scwrypts must be nested inside a directory
# which matches the type name
#
# /path/to/group-source
# ├── groupname.scwrypts.zsh
# │
# ├── zsh
# │ ├── valid-scwrypts-executable
# │ └── some-other
# │ ├── valid-scwrypts-executable
# │ └── etc
# │
# └── py
# ├── valid-scwrypts-executable.py
# └── some-other
# ├── valid-scwrypts-executable.py
# └── etc
#
readonly ${scwryptsgroup}__color=$(utils.colors.green)
#
# ${scwryptsgroup}__color (optional) (default = no color / regular text color)
#
# an ANSI color sequence which determines the color of scwrypts in
# interactive menus
#
readonly ${scwryptsgroup}__zshlibrary=
#
# ${scwryptsgroup}__zshlibrary (optional) (default = *see below*)
#
# allows arbitrary 'use module/name --group groupname' imports
# within zsh-type scwrypts
#
# usually this is set at or within ${scwryptsgrouproot}
#
# by default, this uses either:
# 1. ${scwryptsgrouproot}/zsh/lib (compatibility)
# 2. ${scwryptsgrouproot}/zsh (preferred)
#
readonly ${scwryptsgroup}__virtualenv_path="${SCWRYPTS_STATE_PATH}/virtualenv"
#
# ${scwryptsgroup}__virtualenv_path
# (optional)
# (default = ~/.local/state/scwrypts/virtualenv)
#
# defines the path in which virtual environments are stored for
# the group
#
readonly ${scwryptsgroup}__required_environment_regex=
#
# ${scwryptsgroup}__required_environment_regex (optional) (default = allow any)
#
# helps isolate environment by locking group execution to
# environment names which match the regex
#
# when not set, no environment name restrictions are enforced
#
# when set, interactive menus will be adjusted and non-interactive
# execution will fail if the name of the environment does not match
#
#####################################################################
### ADVANCED CONFIGURATION ##########################################
#####################################################################
#${scwryptsgroup}.list-available() {}
#
# ${scwryptsgroup}.list-available()
#
# a function which outputs lines of "${SCWRYPT_TYPE}/${SCWRYPT_NAME}"
# to stdout
#
# by default, looks for executable files in ${scwryptsgrouproot}
#
# during execution of this function, the following variables are
# available:
#
# - $GROUP_ROOT : USE THIS instead of ${scwryptsgrouproot}
# - $GROUP_TYPE : USE THIS instead of ${scwryptsgroup}__type
#
# (see ./zsh/scwrypts/list-available.module.zsh for more details)
#
#${scwryptsgroup}.TYPE.get-runstring() {}
#
# a function which outputs what should be literally run when executing
# the indicated type; scwrypts already implements runstring generators
# for supported types (that's the main thing which makes them "supported")
#
# configuration variables are still automatically included as a
# prefix to the runstring
#
# (see ./zsh/scwrypts/get-runstring.module.zsh for more details)
#
#####################################################################
### HYPER-ADVANCED CONFIGURATION ####################################
#####################################################################
#
# additional zsh can be defined or run arbitrarily; this is NOT recommended
# unless you understand the implications of the various places where
# this code is loaded
#
# if you want to know where to get started (it will take some learning!),
# review the execution process in:
# - ./scwrypts
# - ./zsh/scwrypts/get-runstring.module.zsh
# - ./zsh/scwrypts/environment/user.module.zsh
#

View File

@ -1,21 +1,113 @@
# ZSH Scwrypts
[![Generic Badge](https://img.shields.io/badge/1password-op-informational.svg)](https://1password.com/downloads/command-line)
[![Generic Badge](https://img.shields.io/badge/BurntSushi-rg-informational.svg)](https://github.com/BurntSushi/ripgrep)
[![Generic Badge](https://img.shields.io/badge/dbcli-pgcli-informational.svg)](https://github.com/dbcli/pgcli)
[![Generic Badge](https://img.shields.io/badge/junegunn-fzf-informational.svg)](https://github.com/junegunn/fzf)
[![Generic Badge](https://img.shields.io/badge/mikefarah-yq-informational.svg)](https://github.com/mikefarah/yq)
[![Generic Badge](https://img.shields.io/badge/stedolan-jq-informational.svg)](https://github.com/stedolan/jq)
[![Generic Badge](https://img.shields.io/badge/dbcli-pgcli-informational.svg)](https://github.com/dbcli/pgcli)
<br>
Since they emulate direct user interaction, shell scripts are often the straightforward choice for task automation.
Since they emulate direct user interaction, shell scripts are a (commonly dreaded) go-to for automation.
## Basic Utilities
Although the malleability of shell scripts can make integrations quickly, the ZSH-type scwrypt provides a structure to promote extendability and clean code while performing a lot of the heavy lifting to ensure consistent execution across different runtimes.
One of my biggest pet-peeves with scripting is when every line of a *(insert-language-here)* program is escaped to shell.
This kind of program, which doesn't use language features, should be a shell script.
While there are definitely unavoidable limitations to shell scripting, we can minimize a variety of problems with a modern shell and shared utilities library.
## The Basic Framework
Loaded by `common.zsh`, the [`utils/` library](./utils) provides:
- common function wrappers to unify flags and context
- lazy dependency and environment variable validation
- consistent (and pretty) user input / output
Take a look at the simplest ZSH-type scwrypt: [hello-world](./hello-world).
The bare minimum API for ZSH-type scwrypts is to:
1. include the shebang `#!/usr/bin/env zsh` on the first line of the file
2. wrap your zsh in a function called `MAIN()`
3. make the file executable (e.g. `chmod +x hello-world`)
Once this is complete, you are free to simply _write valid zsh_ then execute the scwrypt with `scwrypts hello world zsh`!
## Basics+
While it would be perfectly fine to use the `echo` function in our scwrypt, you'll notice that the `hello-world` scwrypt instead uses `echo.success` which is _not_ valid ZSH by default.
This is a helper function provided by the scwrypts ZSH library, and it does a lot more work than you'd expect.
Although this function defaults to print user messages in color, notice what happens when you run `scwrypts --output json hello world zsh`:
```json
{"timestamp":1745674060,"runtime":"c62737da-481e-4013-a370-4dedc76bf4d2","scwrypt":"start of hello-world scwrypts zsh","logLevel":"3","subscwrypt":0}
{"timestamp":1745674060,"runtime":"c62737da-481e-4013-a370-4dedc76bf4d2","status":"SUCCESS","message":"\"Hello, World!\""}
{"timestamp":1745674060,"runtime":"c62737da-481e-4013-a370-4dedc76bf4d2","status":"SUCCESS","message":"\"terminated with code 0\""}
```
We get a LOT more information.
It's 100% possible for you to include your own take on printing messages, but it is highly recommended to use the tools provided here.
### What is loaded by default?
By default, every ZSH-type scwrypt will load [the basic utilities suite](./utils), which is a little different from scwrypts ZSH modules, and a little bit complex.
Although it's totally worth a deep-dive, here are the fundamentals you should ALWAYS use:
#### Printing User Messages or Logs
Whenever you want to print a message to the user or logs, rather than using `echo`, use the following:
<!------------------------------------------------------------------------>
| function name | minimum log level | description |
| --------------- | ----------------- | --------------------------------- |
| `echo.success` | 1 | indicate successful completion |
| `echo.error` | 1 | indicate an error has occurred |
| `echo.reminder` | 1 | an important, information message |
| `echo.status` | 2 | a regular, information message |
| `echo.warning` | 3 | a non-critical warning |
| `echo.debug` | 4 | a message for scwrypt developers |
<!------------------------------------------------------------------------>
Of the `echo` family, there are two unique functions:
- `echo.error` will **increment the `ERRORS` variable** then return an error code of `$ERRORS` (this makes it easy to chain with command failure by using `||`)
- `echo.debug` will inject state information like the timestamp and current function stack
#### Yes / No Prompts
The two helpers `utils.Yn` and `utils.yN` take a user-friendly yes/no question as an argument.
- when the user responds "yes", the command returns 0 / success / `&&`
- when the user responds "no", the command returns 1 / error / `||`
- when the user responds with _nothing_ (e.g. just presses enter), the _default_ is used
The two commands work identically; however, the capitalization denotes the default:
- `utils.Yn` = default "yes"
- `utils.yN` = default "no"
#### Select from a List Prompt
When you want the user to select an item from a list, scwrypts typically use `fzf`.
There are a LOT of options to `fzf`, so there are two provided helpers.
The basic selector, `utils.fzf` (most of the time, you want to use this one) which outputs:
- _the selection_ if the user made a choice
- _nothing / empty string_ if the user cancelled or made an invalid choice
The user-input selector, `utils.fzf.user-input` which outputs:
- _the selection_ if the user made a choice
- _the text typed by the user_ if the user typed something other than the listed choices
- _nothing / empty string_ if the user cancelled
- _a secondary `utils.fzf` prompt_ if the user's choice was ambiguous
### Imports
Don't use `source` in ZSH-type scwrypts (I mean, if you're pretty clever you can get it to work, but DON'T THOUGH).
Instead, use `use`!
The `use` command, rather than specifying file directories, you reference the path to `*.module.zsh`.
This means you don't have to know the exact path to any given file.
For example, if I wanted to import the safety tool for `aws` CLI commands, I can do the following:
```zsh
#!/usr/bin/env zsh
use cloud/aws
#####################################################################
MAIN() {
cloud.aws sts get-caller-identity
}
```

View File

@ -0,0 +1,13 @@
#
# provides utilities for interacting with Amazon Web Services (AWS)
#
# context wrapper for AWS CLI v2
use cloud/aws/cli
eval "${scwryptsmodule}() { ${scwryptsmodule}.cli \$@; }"
# simplify context commands for kubectl on EKS
use cloud/aws/eks
# context wrapper for eksctl
use cloud/aws/eksctl

View File

@ -0,0 +1,28 @@
#####################################################################
use unittest
testmodule=cloud.aws
#####################################################################
beforeall() {
use cloud/aws
}
#####################################################################
test.provides-aws-cli() {
unittest.test.provides ${testmodule}.cli
}
test.provides-aws-cli-alias() {
unittest.test.provides ${testmodule}
}
test.provides-eks() {
unittest.test.provides ${testmodule}.eks
}
test.provides-eksctl() {
unittest.test.provides ${testmodule}.eksctl
}

View File

@ -0,0 +1,36 @@
#####################################################################
DEPENDENCIES+=(aws)
use cloud/aws/zshparse
#####################################################################
${scwryptsmodule}() {
local PARSERS=(cloud.aws.zshparse.overrides)
local ARGS=()
local DESCRIPTION="
Safe context wrapper for aws cli commands; prevents accidental local environment
bleed-through, but otherwise works exactly like 'aws'. For help with awscli, try
'AWS [command] help' (no -h or --help)
This wrapper should be used in place of _all_ 'aws' usages within scwrypts.
"
eval "$(utils.parse.autosetup)"
##########################################
echo.debug "invoking '$(echo "$AWS_EVAL_PREFIX" | sed 's/AWS_\(ACCESS_KEY_ID\|SECRET_ACCESS_KEY\)=[^ ]\+ //g')aws ${AWS_CONTEXT_ARGS[@]} ${ARGS[@]}'"
eval "${AWS_EVAL_PREFIX}aws ${AWS_CONTEXT_ARGS[@]} ${ARGS[@]}"
}
#####################################################################
${scwryptsmodule}.parse() {
return 0 # uses default args parser
}
${scwryptsmodule}.parse.usage() {
USAGE__args+='\$@ arguments forwarded to the AWS CLI'
}

View File

@ -0,0 +1,65 @@
#####################################################################
use unittest
testmodule=cloud.aws.cli
#####################################################################
beforeall() {
use cloud/aws/cli
}
beforeeach() {
unittest.mock aws
unittest.mock echo.debug
_ARGS=($(uuidgen) $(uuidgen) $(uuidgen))
_AWS_REGION=$(uuidgen)
_AWS_PROFILE=$(uuidgen)
unittest.mock.env AWS_ACCOUNT --value $(uuidgen)
unittest.mock.env AWS_PROFILE --value ${_AWS_PROFILE}
unittest.mock.env AWS_REGION --value ${_AWS_REGION}
}
aftereach() {
unset _AWS_REGION
unset _AWS_PROFILE
}
#####################################################################
test.forwards-arguments() {
${testmodule} ${_ARGS[@]}
aws.assert.callstack \
--output json \
--region ${_AWS_REGION} \
--profile ${_AWS_PROFILE} \
${_ARGS[@]} \
;
}
test.overrides-region() {
local OVERRIDE_REGION=$(uuidgen)
${testmodule} --region ${OVERRIDE_REGION} ${_ARGS[@]}
aws.assert.callstack \
--output json \
--region ${OVERRIDE_REGION} \
--profile ${_AWS_PROFILE} \
${_ARGS[@]} \
;
}
test.overrides-account() {
local OVERRIDE_ACCOUNT=$(uuidgen)
${testmodule} --account ${OVERRIDE_ACCOUNT} ${_ARGS[@]}
echo.debug.assert.callstackincludes \
AWS_ACCOUNT=${OVERRIDE_ACCOUNT} \
;
}

View File

@ -0,0 +1,6 @@
#
# common operations for AWS Elastic Container Registry (ECR)
#
# that obnoxious command which pushes the AWS temporary credentials to 'docker login'
use cloud/aws/ecr/login

View File

@ -0,0 +1,16 @@
#####################################################################
use unittest
testmodule=cloud.aws.ecr
#####################################################################
beforeall() {
use cloud/aws/ecr
}
#####################################################################
test.provides-ecr-login() {
unittest.test.provides ${testmodule}.login
}

View File

@ -1,7 +1,17 @@
#!/bin/zsh
use cloud/aws/ecr
#!/usr/bin/env zsh
#####################################################################
MAIN() {
ECR_LOGIN $@
}
use cloud/aws/ecr/login
use cloud/aws/zshparse
#####################################################################
USAGE__description='
interactively setup temporary credentials for ECR in the given region
'
cloud.aws.zshparse.overrides.usage
#####################################################################
MAIN() { cloud.aws.ecr.login $@; }

View File

@ -0,0 +1,30 @@
#####################################################################
use cloud/aws/cli
use cloud/aws/zshparse
DEPENDENCIES+=(docker)
#####################################################################
${scwryptsmodule}() {
local DESCRIPTION="
Performs the appropriate 'docker login' command with temporary
credentials from AWS.
"
local PARSERS=(cloud.aws.zshparse.overrides)
eval "$(utils.parse.autosetup)"
##########################################
${AWS} ecr get-login-password \
| docker login "${ACCOUNT}.dkr.ecr.${REGION}.amazonaws.com" \
--username AWS \
--password-stdin \
&>/dev/null \
&& echo.success "authenticated docker for '${ACCOUNT}' in '${REGION}'" \
|| echo.error "unable to authenticate docker for '${ACCOUNT}' in '${REGION}'" \
;
}

View File

@ -0,0 +1,47 @@
#####################################################################
use unittest
testmodule=cloud.aws.ecr.login
#####################################################################
beforeall() {
use cloud/aws/ecr/login
}
beforeeach() {
unittest.mock cloud.aws.cli
unittest.mock docker
_AWS_ACCOUNT=$(uuidgen)
_AWS_PROFILE=$(uuidgen)
_AWS_REGION=$(uuidgen)
unittest.mock.env AWS_ACCOUNT --value ${_AWS_ACCOUNT}
unittest.mock.env AWS_PROFILE --value ${_AWS_PROFILE}
unittest.mock.env AWS_REGION --value ${_AWS_REGION}
_EXPECTED_AWS_ARGS=(
--account ${_AWS_ACCOUNT}
--region ${_AWS_REGION}
)
}
aftereach() {
unset \
_AWS_ACCOUNT _AWS_PROFILE _AWS_REGION \
_EXPECTED_AWS_ARGS \
;
}
#####################################################################
test.login-forwards-credentials-to-docker() {
${testmodule}
docker.assert.callstack \
login "${_AWS_ACCOUNT}.dkr.ecr.${_AWS_REGION}.amazonaws.com" \
--username AWS \
--password-stdin \
;
}

View File

@ -1,64 +1,82 @@
#!/bin/zsh
DEPENDENCIES+=(jq)
REQUIRED_ENV+=(AWS__EFS__LOCAL_MOUNT_POINT)
#!/usr/bin/env zsh
#####################################################################
use cloud/aws/cli
use cloud/aws/zshparse/overrides
DEPENDENCIES+=(jq mount sort sudo)
REQUIRED_ENV+=(AWS__EFS__LOCAL_MOUNT_POINT)
#####################################################################
USAGE__description='
interactively mount an AWS EFS volume to the local filesystem
'
#####################################################################
MAIN() {
GETSUDO || exit 1
[ ! -d $AWS__EFS__LOCAL_MOUNT_POINT ] && {
sudo mkdir $AWS__EFS__LOCAL_MOUNT_POINT \
&& STATUS "created local mount point '$AWS__EFS__LOCAL_MOUNT_POINT'"
}
local PARSERS=(cloud.aws.zshparse.overrides)
eval "$(utils.parse.autosetup)"
utils.io.getsudo || return 1
##########################################
{
mkdir -p -- "${AWS__EFS__LOCAL_MOUNT_POINT}" \
|| sudo mkdir -p -- "${AWS__EFS__LOCAL_MOUNT_POINT}"
} &>/dev/null
[ -d "${AWS__EFS__LOCAL_MOUNT_POINT}" ] \
|| echo.error "unable to create local mount point '${AWS__EFS__LOCAL_MOUNT_POINT}'" \
|| return
local FS_ID=$(\
AWS efs describe-file-systems \
$AWS efs describe-file-systems \
| jq -r '.[] | .[] | .FileSystemId' \
| FZF 'select a filesystem to mount' \
| utils.fzf 'select a filesystem to mount' \
)
[ ! $FS_ID ] && ABORT
[ ! ${FS_ID} ] && utils.abort
local MOUNT_POINT="$AWS__EFS__LOCAL_MOUNT_POINT/$FS_ID"
[ -d "$MOUNT_POINT" ] && sudo rmdir "$MOUNT_POINT" >/dev/null 2>&1
[ -d "$MOUNT_POINT" ] && {
STATUS "$FS_ID is already mounted"
exit 0
local MOUNT_POINT="${AWS__EFS__LOCAL_MOUNT_POINT}/${FS_ID}"
[ -d "${MOUNT_POINT}" ] && sudo rmdir "${MOUNT_POINT}" &>/dev/null
[ -d "${MOUNT_POINT}" ] && {
echo.status "${FS_ID} is already mounted"
return 0
}
local MOUNT_TARGETS=$(AWS efs describe-mount-targets --file-system-id $FS_ID)
local MOUNT_TARGETS=$($AWS efs describe-mount-targets --file-system-id ${FS_ID})
local ZONE=$(\
echo $MOUNT_TARGETS \
echo ${MOUNT_TARGETS} \
| jq -r '.[] | .[] | .AvailabilityZoneName' \
| sort -u | FZF 'select availability zone'\
| sort -u | utils.fzf 'select availability zone'\
)
[ ! $ZONE ] && ABORT
[ ! "${ZONE}" ] && utils.abort
local MOUNT_IP=$(\
echo $MOUNT_TARGETS \
| jq -r ".[] | .[] | select (.AvailabilityZoneName == \"$ZONE\") | .IpAddress" \
echo ${MOUNT_TARGETS} \
| jq -r ".[] | .[] | select (.AvailabilityZoneName == \"${ZONE}\") | .IpAddress" \
| head -n1 \
)
SUCCESS 'ready to mount!'
REMINDER 'for private file-systems, you must be connected to the appropriate VPN'
echo.success 'ready to mount!'
echo.status "
file system id : ${FS_ID}
availability zone : ${ZONE}
file system ip : ${MOUNT_IP}
local mount point : ${MOUNT_POINT}
"
echo.reminder 'for private file-systems, you must be connected to the appropriate VPN'
Yn 'proceed?' || utils.abort
STATUS "file system id : $FS_ID"
STATUS "availability zone : $ZONE"
STATUS "file system ip : $MOUNT_IP"
STATUS "local mount point : $MOUNT_POINT"
Yn 'proceed?' || ABORT
sudo mkdir $MOUNT_POINT \
sudo mkdir -- "${MOUNT_POINT}" \
&& sudo mount \
-t nfs4 \
-o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport \
$MOUNT_IP:/ \
"$MOUNT_POINT" \
&& SUCCESS "mounted at '$MOUNT_POINT'" \
"${MOUNT_IP}:/" \
"${MOUNT_POINT}" \
&& echo.success "mounted at '${MOUNT_POINT}'" \
|| {
sudo rmdir $MOUNT_POINT >/dev/null 2>&1
FAIL 2 "unable to mount '$FS_ID'"
sudo rmdir -- "${MOUNT_POINT}" &>/dev/null
echo.error "unable to mount '${FS_ID}'"
}
}

View File

@ -1,32 +1,39 @@
#!/bin/zsh
DEPENDENCIES+=(jq)
REQUIRED_ENV+=(AWS__EFS__LOCAL_MOUNT_POINT)
use cloud/aws/cli
#!/usr/bin/env zsh
#####################################################################
DEPENDENCIES+=(jq umount sudo)
REQUIRED_ENV+=(AWS__EFS__LOCAL_MOUNT_POINT)
#####################################################################
USAGE__description='
interactively unmount an AWS EFS volume to the local filesystem
'
MAIN() {
[ ! -d "$AWS__EFS__LOCAL_MOUNT_POINT" ] && {
STATUS 'no efs currently mounted'
exit 0
eval "$(utils.parse.autosetup)"
##########################################
[ ! -d "${AWS__EFS__LOCAL_MOUNT_POINT}" ] && {
echo.status 'no efs currently mounted'
return 0
}
local MOUNTED=$(ls "$AWS__EFS__LOCAL_MOUNT_POINT")
[ ! $MOUNTED ] && {
STATUS 'no efs currently mounted'
exit 0
local MOUNTED=$(cd -- "${AWS__EFS__LOCAL_MOUNT_POINT}" | find . -type -f | sed 's|^\./.||')
[ "${MOUNTED}" ] && {
echo.status 'no efs currently mounted'
return 0
}
GETSUDO || exit 1
utils.io.getsudo || return 1
local SELECTED=$(echo ${MOUNTED} | utils.fzf 'select a file system to unmount')
[ "${SELECTED}" ] || user.abort
local SELECTED=$(echo $MOUNTED | FZF 'select a file system to unmount')
[ ! $SELECTED ] && ABORT
local EFS="$AWS__EFS__LOCAL_MOUNT_POINT/$SELECTED"
STATUS "unmounting '$SELECTED'"
sudo umount $EFS >/dev/null 2>&1
sudo rmdir $EFS \
&& SUCCESS "done" \
|| FAIL 2 "failed to unmount '$EFS'"
local EFS="${AWS__EFS__LOCAL_MOUNT_POINT}/${SELECTED}"
echo.status "unmounting '${SELECTED}'"
sudo umount "${EFS}" >/dev/null 2>&1
sudo rmdir -- "${EFS}" \
&& echo.success "done" \
|| utils.fail 2 "failed to unmount '${EFS}'"
}

View File

@ -0,0 +1,86 @@
#####################################################################
use cloud/aws/eks/cluster-login
use cloud/aws/zshparse
use cloud/aws/eks/zshparse
#####################################################################
${scwryptsmodule}() {
local DESCRIPTION="
Context wrapper for kubernetes CLI commands on AWS EKS. This
will automatically attempt login for first-time connections,
and ensures the correct kubecontext is used for the expected
command.
EKS --cluster-name my-cluster kubectl get pods
EKS --cluster-name my-cluster helm history my-deployment
... etc ...
"
local ARGS=() PARSERS=(
cloud.aws.zshparse.overrides
cloud.aws.eks.zshparse.cluster-name
)
eval "$(utils.parse.autosetup)"
##########################################
local CONTEXT="arn:aws:eks:${REGION}:${ACCOUNT}:cluster/${CLUSTER_NAME}"
local ALREADY_LOGGED_IN
kubectl config get-contexts --output=name | grep -q "^${CONTEXT}$" \
&& ALREADY_LOGGED_IN=true \
|| ALREADY_LOGGED_IN=false \
;
case ${ALREADY_LOGGED_IN} in
( true ) ;;
( false )
cloud.aws.eks.cluster-login \
${AWS_PASSTHROUGH[@]} \
--cluster-name ${CLUSTER_NAME} \
>/dev/null \
|| echo.error "unable to login to cluster '${CLUSTER_NAME}'" \
|| return 1
;;
esac
local CONTEXT_ARGS=()
case ${KUBECLI} in
( helm )
CONTEXT_ARGS+=(--kube-context ${CONTEXT}) # *rolls eyes* THANKS, helm
;;
( * )
CONTEXT_ARGS+=(--context ${CONTEXT})
;;
esac
${KUBECLI} ${CONTEXT_ARGS[@]} ${ARGS[@]}
}
#####################################################################
${scwryptsmodule}.parse() { return 0; }
${scwryptsmodule}.parse.locals() {
local KUBECLI # extracted from default ARGS parser
}
${scwryptsmodule}.parse.usage() {
USAGE__usage+=' kubecli [...kubecli-args...]'
USAGE__args+='
kubecli cli which uses kubernetes context arguments (e.g. kubectl, helm, flux)
kubecli-args arguments forwarded to the kubectl-style CLI
'
}
${scwryptsmodule}.parse.validate() {
KUBECLI="${ARGS[1]}"
ARGS=(${ARGS[@]:1})
[ ${KUBECLI} ] \
|| echo.error "missing argument for 'kubecli'"
}

View File

@ -0,0 +1,105 @@
#####################################################################
use unittest
testmodule=cloud.aws.eks.cli
#####################################################################
beforeall() {
use cloud/aws/eks/cli
use cloud/aws/eks/cluster-login
}
beforeeach() {
unittest.mock cloud.aws.eks.cluster-login
_CLUSTER_NAME=$(uuidgen)
_AWS_ACCOUNT=$(uuidgen)
_AWS_PROFILE=$(uuidgen)
_AWS_REGION=$(uuidgen)
_KUBECLI=$(uuidgen)
_KUBECLI_ARGS=($(uuidgen) $(uuidgen) $(uuidgen))
unittest.mock.env AWS_ACCOUNT --value ${_AWS_ACCOUNT}
unittest.mock.env AWS_PROFILE --value ${_AWS_PROFILE}
unittest.mock.env AWS_REGION --value ${_AWS_REGION}
_EXPECTED_KUBECONTEXT="arn:aws:eks:${_AWS_REGION}:${_AWS_ACCOUNT}:cluster/${_CLUSTER_NAME}"
_KUBECTL_KUBECONTEXTS="$(uuidgen)\n${_EXPECTED_KUBECONTEXT}\n$(uuidgen)"
_EXPECTED_AWS_ARGS=(
--account ${_AWS_ACCOUNT}
--region ${_AWS_REGION}
)
}
aftereach() {
unset \
_CLUSTER_NAME \
_AWS_ACCOUNT _AWS_PROFILE _AWS_REGION \
_EXPECTED_AWS_ARGS \
;
}
mock.kubectl() {
unittest.mock kubectl --stdout "${_KUBECTL_KUBECONTEXTS}"
}
mock.kubecli() {
command -v ${_KUBECLI} &>/dev/null || ${_KUBECLI}() { true; }
unittest.mock ${_KUBECLI}
}
#####################################################################
test.uses-correct-kubecli-args() {
mock.kubectl
mock.kubecli
${testmodule} --cluster-name ${_CLUSTER_NAME} ${_KUBECLI} ${_KUBECLI_ARGS[@]}
${_KUBECLI}.assert.callstack \
--context ${_EXPECTED_KUBECONTEXT} \
${_KUBECLI_ARGS[@]}
;
}
test.uses-correct-helm-args() {
_KUBECLI=helm
mock.kubectl
mock.kubecli
${testmodule} --cluster-name ${_CLUSTER_NAME} ${_KUBECLI} ${_KUBECLI_ARGS[@]}
${_KUBECLI}.assert.callstack \
--kube-context ${_EXPECTED_KUBECONTEXT} \
${_KUBECLI_ARGS[@]}
;
}
test.performs-login() {
_KUBECTL_KUBECONTEXTS="$(uuidgen)\n$(uuidgen)"
mock.kubectl
mock.kubecli
${testmodule} --cluster-name ${_CLUSTER_NAME} ${_KUBECLI} ${_KUBECLI_ARGS[@]}
cloud.aws.eks.cluster-login.assert.callstack \
${_EXPECTED_AWS_ARGS[@]} \
--cluster-name ${_CLUSTER_NAME} \
;
}
test.does-not-perform-login-if-already-logged-in() {
mock.kubectl
mock.kubecli
${testmodule} --cluster-name ${_CLUSTER_NAME} ${_KUBECLI} ${_KUBECLI_ARGS[@]}
cloud.aws.eks.cluster-login.assert.not.called
}

View File

@ -0,0 +1,43 @@
#####################################################################
use cloud/aws/cli
use cloud/aws/zshparse
use cloud/aws/eks/zshparse
#####################################################################
${scwryptsmodule}() {
local DESCRIPTION='
Interactively sets the default kubeconfig to match the selected
cluster in EKS. Also creates the kubeconfig entry if it does not
already exist.
'
local PARSERS=(
cloud.aws.zshparse.overrides
cloud.aws.eks.zshparse.cluster-name
)
local EKS_CLUSTER_NAME_INTERACTIVE=allowed
eval "$(utils.parse.autosetup)"
#####################################################################
[ ${CLUSTER_NAME} ] || CLUSTER_NAME=$(\
${AWS} eks list-clusters \
| jq -r '.[] | .[]' \
| utils.fzf "select an eks cluster (${ACCOUNT}/${REGION})"
)
[ ${CLUSTER_NAME} ] || echo.error 'must select a valid cluster or use --cluster-name'
utils.check-errors || return $?
##########################################
echo.status 'updating kubeconfig for EKS cluster '${CLUSTER_NAME}''
${AWS} eks update-kubeconfig --name ${CLUSTER_NAME} \
&& echo.success "kubeconfig updated with '${CLUSTER_NAME}'" \
|| echo.error "failed to update kubeconfig; do you have permission to access '${CLUSTER_NAME}'?"
}

View File

@ -0,0 +1,66 @@
#####################################################################
use unittest
testmodule=cloud.aws.eks.cluster-login
#####################################################################
beforeall() {
use cloud/aws/eks/cluster-login
}
beforeeach() {
unittest.mock cloud.aws.cli
_CLUSTER_NAME=$(uuidgen)
_AWS_ACCOUNT=$(uuidgen)
_AWS_PROFILE=$(uuidgen)
_AWS_REGION=$(uuidgen)
unittest.mock.env AWS_ACCOUNT --value ${_AWS_ACCOUNT}
unittest.mock.env AWS_PROFILE --value ${_AWS_PROFILE}
unittest.mock.env AWS_REGION --value ${_AWS_REGION}
_EXPECTED_AWS_ARGS=(
--account ${_AWS_ACCOUNT}
--region ${_AWS_REGION}
)
}
aftereach() {
unset \
_CLUSTER_NAME \
_AWS_ACCOUNT _AWS_PROFILE _AWS_REGION \
_EXPECTED_AWS_ARGS \
;
}
#####################################################################
test.login-to-correct-cluster() {
${testmodule} --cluster-name ${_CLUSTER_NAME}
cloud.aws.cli.assert.callstack \
${_EXPECTED_AWS_ARGS[@]} \
eks update-kubeconfig \
--name ${_CLUSTER_NAME} \
;
}
test.interactive-login-ignored-on-ci() {
${testmodule}
cloud.aws.cli.assert.not.called
}
test.interactive-login-to-correct-cluster() {
unittest.mock utils.fzf --stdout ${_CLUSTER_NAME}
${testmodule}
cloud.aws.cli.assert.callstack \
${_EXPECTED_AWS_ARGS[@]} \
eks update-kubeconfig \
--name ${_CLUSTER_NAME} \
;
}

View File

@ -0,0 +1,10 @@
#
# run kubectl/helm/etc commands on AWS Elastic Kubernetes Service (EKS)
#
# provides an EKS connection wrapper for any kubectl-like cli
use cloud/aws/eks/cli
eval "${scwryptsmodule}() { ${scwryptsmodule}.cli $@; }"
# sets up kubeconfig to connect to EKS
use cloud/aws/eks/cluster-login

View File

@ -0,0 +1,24 @@
#####################################################################
use unittest
testmodule=cloud.aws.eks
#####################################################################
beforeall() {
use cloud/aws
}
#####################################################################
test.provides-eks-cli() {
unittest.test.provides ${testmodule}.cli
}
test.provides-eks-cli-alias() {
unittest.test.provides ${testmodule}
}
test.provides-cluster-login() {
unittest.test.provides ${testmodule}.cluster-login
}

View File

@ -1,4 +1,4 @@
#!/bin/zsh
#!/usr/bin/env zsh
use cloud/aws/eks
#####################################################################

View File

@ -0,0 +1,44 @@
${scwryptsmodule}.locals() {
local CLUSTER_NAME
# set to 'allowed' to enable interactive cluster select
# by default, the '--cluster-name' flag is required
local EKS_CLUSTER_NAME_INTERACTIVE
}
${scwryptsmodule}() {
local PARSED=0
case $1 in
( -c | --cluster-name )
CLUSTER_NAME="$2"
((PARSED+=2))
;;
esac
return $PARSED
}
${scwryptsmodule}.usage() {
[[ "$USAGE__usage" =~ '\[...options...\]' ]] || USAGE__usage+=' [...options...]'
USAGE__options+="\n
-c, --cluster-name <string> EKS cluster name identifier string
"
}
${scwryptsmodule}.validate() {
[ $CLUSTER_NAME ] && return 0
[[ $EKS_CLUSTER_NAME_INTERACTIVE =~ allowed ]] \
|| echo.error 'missing cluster name' \
|| return
CLUSTER_NAME=$(\
$AWS eks list-clusters \
| jq -r '.[] | .[]' \
| utils.fzf "select an eks cluster ($ACCOUNT/$REGION)" \
)
[ $CLUSTER_NAME ] || echo.error 'must select a valid cluster or use --cluster-name'
}

View File

@ -0,0 +1,6 @@
#
# argument parsers for common EKS arguments
#
# get the EKS "ClusterName" identifier
use cloud/aws/eks/zshparse/cluster-name

View File

@ -0,0 +1,40 @@
#####################################################################
use cloud/aws/zshparse/overrides
DEPENDENCIES+=(eksctl)
#####################################################################
${scwryptsmodule}() {
local PARSERS=(cloud.aws.zshparse.overrides)
local DESCRIPTION="
Context wrapper for eksctl commands; prevents accidental local environment
bleed-through, but otherwise works exactly like 'eksctl'.
This wrapper should be used in place of _all_ 'eksctl' usages within scwrypts.
"
eval "$(utils.parse.autosetup)"
##########################################
echo.debug "invoking '$(echo "$AWS_EVAL_PREFIX" | sed 's/AWS_\(ACCESS_KEY_ID\|SECRET_ACCESS_KEY\)=[^ ]\+ //g')eksctl ${ARGS[@]}'"
eval "${AWS_EVAL_PREFIX}eksctl ${ARGS[@]}"
}
#####################################################################
${scwryptsmodule}.parse() {
return 0
}
${scwryptsmodule}.parse.locals() {
local ARGS=()
}
${scwryptsmodule}.parse.usage() {
USAGE__args+='
args all remaining arguments are forwarded to eksctl
'
}

View File

@ -0,0 +1,72 @@
#####################################################################
use unittest
testmodule=cloud.aws.eksctl.cli
#####################################################################
beforeall() {
use cloud/aws/eksctl/cli
}
beforeeach() {
unittest.mock eksctl
unittest.mock echo.debug
_ARGS=($(uuidgen) $(uuidgen) $(uuidgen))
_AWS_REGION=$(uuidgen)
_AWS_PROFILE=$(uuidgen)
unittest.mock.env AWS_ACCOUNT --value $(uuidgen)
unittest.mock.env AWS_PROFILE --value ${_AWS_PROFILE}
unittest.mock.env AWS_REGION --value ${_AWS_REGION}
}
aftereach() {
unset _AWS_REGION
unset _AWS_PROFILE
}
#####################################################################
test.forwards-arguments() {
${testmodule} ${_ARGS[@]}
eksctl.assert.callstack \
${_ARGS[@]} \
;
}
test.forwards-profile() {
#
# --profile is an invalid argument for eksctl, so it
# MUST be forwarded as AWS_PROFILE to prevent environment
# bleeding
#
${testmodule} ${_ARGS[@]}
echo.debug.assert.callstackincludes \
AWS_PROFILE=${OVERRIDE_REGION} \
;
}
test.overrides-region() {
local OVERRIDE_REGION=$(uuidgen)
${testmodule} --region ${OVERRIDE_REGION} ${_ARGS[@]}
echo.debug.assert.callstackincludes \
AWS_REGION=${OVERRIDE_REGION} \
;
}
test.overrides-account() {
local OVERRIDE_ACCOUNT=$(uuidgen)
${testmodule} --account ${OVERRIDE_ACCOUNT} ${_ARGS[@]}
echo.debug.assert.callstackincludes \
AWS_ACCOUNT=${OVERRIDE_ACCOUNT} \
;
}

View File

@ -0,0 +1,10 @@
#
# module for eksctl actions
#
# context wrapper for direct use of eksctl
use cloud/aws/eksctl/cli
eval "${scwryptsmodule}() { ${scwryptsmodule}.cli \$@; }"
# argument helper for creating a standard iamserviceaccount
use cloud/aws/eksctl/iamserviceaccount

View File

@ -0,0 +1,20 @@
#####################################################################
use unittest
testmodule=cloud.aws.eksctl
#####################################################################
beforeall() {
use cloud/aws/eksctl
}
#####################################################################
test.provides-eksctl-cli() {
unittest.test.provides ${testmodule}.cli
}
test.provides-eksctl-alias() {
unittest.test.provides ${testmodule}
}

View File

@ -0,0 +1,57 @@
#####################################################################
use cloud/aws/eks/cli
use cloud/aws/eksctl/iamserviceaccount/zshparse
use cloud/aws/zshparse/overrides
DEPENDENCIES+=(kubectl yq)
#####################################################################
${scwryptsmodule}() {
local DESCRIPTION="
determine whether the target iamserviceaccount already
exists on Kubernetes
OK:
exit code 0 : the serviceaccount exists on kubernetes
exit code 100 : the serviceaccount does not exist on kubernetes
ERROR:
exit code 200 : the serviceaccount exists on kubernetes, but does not match the target role
"
local PARSERS=(
cloud.aws.eksctl.iamserviceaccount.zshparse
cloud.aws.zshparse.overrides
)
eval "$(utils.parse.autosetup)"
##########################################
echo.status "checking for existing role-arn"
local CURRENT_ROLE_ARN=$(
cloud.aws.eks.cli kubectl ${AWS_PASSTHROUGH_ARGS[@]} --namespace "${NAMESPACE}" get serviceaccount "${SERVICEACCOUNT}" -o yaml \
| utils.yq -r '.metadata.annotations["eks.amazonaws.com/role-arn"]' \
| grep -v '^null$' \
)
[ "${CURRENT_ROLE_ARN}" ] || {
echo.status "serviceaccount does not exist or has no configured role"
return 100
}
[[ ${CURRENT_ROLE_ARN} =~ "${ROLE_NAME}$" ]] || {
echo.status "\
serviceaccount current role does not match desired role:
CURRENT : ${CURRENT_ROLE_ARN}
DESIRED : arn:aws:iam::${AWS_ACCOUNT}:role/${ROLE_NAME}
"
return 200
}
echo.status "serviceaccount current role matches desired role"
return 0
}

View File

@ -0,0 +1,63 @@
#####################################################################
use unittest
testmodule=cloud.aws.eksctl.iamserviceaccount.check-exists
#####################################################################
beforeall() {
use cloud/aws/eksctl/iamserviceaccount/check-exists
use cloud/aws/eks/cli
}
beforeeach() {
_SERVICEACCOUNT=$(uuidgen)
_NAMEPACE=$(uuidgen)
_ROLE_NAME=$(uuidgen)
_ROLE_ARN="$(uuidgen)/${_ROLE_NAME}"
unittest.mock.env AWS_ACCOUNT --value $(uuidgen)
unittest.mock.env AWS_PROFILE --value $(uuidgen)
unittest.mock.env AWS_REGION --value $(uuidgen)
_ARGS=(
--serviceaccount ${_SERVICEACCOUNT}
--namespace ${_NAMEPACE}
--role-name ${_ROLE_NAME}
)
}
aftereach() {
unset _SERVICEACCOUNT _NAMESPACE _ROLE_NAME _ARGS
}
#####################################################################
test.detects-exists() {
unittest.mock cloud.aws.eks.cli \
--stdout '{"metadata":{"annotations":{"eks.amazonaws.com/role-arn":"'$_ROLE_ARN'"}}}' \
;
${testmodule} ${_ARGS[@]}
[[ $? -eq 0 ]]
}
test.detects-not-exists() {
unittest.mock cloud.aws.eks.cli \
--stdout '{}'
${testmodule} ${_ARGS[@]}
[[ $? -eq 100 ]]
}
test.detects-exists-but-does-not-match() {
unittest.mock cloud.aws.eks.cli \
--stdout '{"metadata":{"annotations":{"eks.amazonaws.com/role-arn":"'$(uuidgen)'"}}}' \
;
${testmodule} ${_ARGS[@]}
[[ $? -eq 200 ]]
}

View File

@ -0,0 +1,93 @@
#####################################################################
use cloud/aws/eksctl/cli
use cloud/aws/eksctl/iamserviceaccount/check-exists
use cloud/aws/eksctl/iamserviceaccount/zshparse
use cloud/aws/zshparse/overrides
use cloud/aws/eks/zshparse/cluster-name
#####################################################################
${scwryptsmodule}() {
local DESCRIPTION="
creates an 'iamserviceaccount' which provides a Kubernetes
serviceaccount with AWS role identity and access control
"
local PARSERS=(
cloud.aws.eksctl.iamserviceaccount.zshparse
cloud.aws.zshparse.overrides
cloud.aws.eks.zshparse.cluster-name
)
eval "$(utils.parse.autosetup)"
##########################################
case ${FORCE} in
( true ) ;;
( false )
cloud.aws.eksctl.iamserviceaccount.check-exists \
--serviceaccount "${SERVICEACCOUNT}" \
--namespace "${NAMESPACE}" \
--role-name "${ROLE_NAME}" \
${AWS_PASSTHROUGH[@]} \
;
case $? in
( 0 ) echo.success "'${NAMESPACE}/${SERVICEACCOUNT}' already configured with '${ROLE_NAME}'"
return 0
;;
( 100 ) # role does not exist yet; continue with rollout
;;
( 200 ) echo.error "'${NAMESPACE}/${SERVICEACCOUNT}' has been configured with a different role than '${ROLE_NAME}'"
echo.reminder "must use --force flag to overwrite"
return 2
;;
esac
;;
esac
echo.status "creating iamserviceaccount" \
&& cloud.aws.eksctl.cli ${AWS_PASSTHROUGH_ARGS[@]} create iamserviceaccount \
--cluster "${CLUSTER_NAME}" \
--namespace "${NAMESPACE}" \
--name "${SERVICEACCOUNT}" \
--role-name "${ROLE_NAME}" \
--override-existing-serviceaccounts \
--approve \
${ARGS[@]} \
&& echo.success "successfully configured '${NAMESPACE}/${SERVICEACCOUNT}' with IAM role '${ROLE_NAME}'" \
|| echo.error "unable to configure '${NAMESPACE}/${SERVICEACCOUNT}' with IAM role '${ROLE_NAME}'\n(check cloudformation dashboard for details)" \
;
}
#####################################################################
${scwryptsmodule}.parse.locals() {
local FORCE=false # whether or not to force a new eksctl deployment
local ARGS=()
}
${scwryptsmodule}.parse() {
local PARSED=0
case $1 in
--force ) PARSED=1; FORCE=true ;;
esac
return ${PARSED}
}
${scwryptsmodule}.parse.usage() {
USAGE__options+="
--force don't check for existing serviceaccount and override any existing configuration
"
USAGE__args+="
args all remaining arguments are forwarded to 'eksctl create iamserviceaccount'
eksctl create iamserviceaccount args:
$(eksctl create iamserviceaccount --help 2>&1 | grep -v -- '--name' | grep -v -- '--namespace' | grep -v -- '--role-name' | sed 's/^/ /')
"
}

View File

@ -0,0 +1,150 @@
#####################################################################
use unittest
testmodule=cloud.aws.eksctl.iamserviceaccount.create
#####################################################################
beforeall() {
use cloud/aws/eksctl/iamserviceaccount/create
}
beforeeach() {
unittest.mock cloud.aws.eksctl.cli
_SERVICEACCOUNT=$(uuidgen)
_NAMESPACE=$(uuidgen)
_ROLE_NAME=$(uuidgen)
_ROLE_ARN="$(uuidgen)/${_ROLE_NAME}"
_CLUSTER_NAME=$(uuidgen)
_AWS_ACCOUNT=$(uuidgen)
_AWS_REGION=$(uuidgen)
unittest.mock.env AWS_ACCOUNT --value ${_AWS_ACCOUNT}
unittest.mock.env AWS_PROFILE --value $(uuidgen)
unittest.mock.env AWS_REGION --value ${_AWS_REGION}
_IAMSERVICEACCOUNT_ARGS=(
--serviceaccount ${_SERVICEACCOUNT}
--namespace ${_NAMESPACE}
--role-name ${_ROLE_NAME}
)
_EXTRA_ARGS=($(uuidgen) $(uuidgen) $(uuidgen))
_ARGS=(
--cluster-name ${_CLUSTER_NAME}
${_IAMSERVICEACCOUNT_ARGS[@]}
--
${_EXTRA_ARGS[@]}
)
_EXPECTED_AWS_PASSTHROUGH=(
--account ${_AWS_ACCOUNT}
--region ${_AWS_REGION}
)
}
aftereach() {
unset \
_SERVICEACCOUNT _NAMESPACE _ROLE_NAME \
_CLUSTER_NAME \
_AWS_ACCOUNT _AWS_REGION \
_ARGS _EXPECTED_AWS_PASSTHROUGH_ARGS \
;
}
#####################################################################
test.performs-check-exists() {
unittest.mock cloud.aws.eksctl.iamserviceaccount.check-exists \
--exit-code 0 \
;
${testmodule} ${_ARGS[@]}
cloud.aws.eksctl.iamserviceaccount.check-exists.assert.callstack \
${_IAMSERVICEACCOUNT_ARGS[@]} \
${_EXPECTED_AWS_PASSTHROUGH[@]} \
;
}
test.ignores-check-exist-on-force() {
unittest.mock cloud.aws.eksctl.iamserviceaccount.check-exists \
--exit-code 0 \
;
${testmodule} ${_ARGS[@]} --force
cloud.aws.eksctl.iamserviceaccount.check-exists.assert.not.called
}
test.does-not-create-if-exists() {
unittest.mock cloud.aws.eksctl.iamserviceaccount.check-exists \
--exit-code 0 \
;
${testmodule} ${_ARGS[@]}
cloud.aws.eksctl.cli.assert.not.called
}
test.creates-role() {
unittest.mock cloud.aws.eksctl.iamserviceaccount.check-exists \
--exit-code 100 \
;
${testmodule} ${_ARGS[@]}
cloud.aws.eksctl.cli.assert.callstack \
create iamserviceaccount \
--cluster ${_CLUSTER_NAME} \
--namespace ${_NAMESPACE} \
--name ${_SERVICEACCOUNT} \
--role-name ${_ROLE_NAME} \
--override-existing-serviceaccounts \
--approve \
${_EXTRA_ARGS[@]} \
;
}
test.creates-role-on-force() {
unittest.mock cloud.aws.eksctl.iamserviceaccount.check-exists \
--exit-code 0 \
;
${testmodule} ${_ARGS[@]} --force
cloud.aws.eksctl.cli.assert.callstack \
create iamserviceaccount \
--cluster ${_CLUSTER_NAME} \
--namespace ${_NAMESPACE} \
--name ${_SERVICEACCOUNT} \
--role-name ${_ROLE_NAME} \
--override-existing-serviceaccounts \
--approve \
${_EXTRA_ARGS[@]} \
;
}
test.does-not-create-if-mismatched-role() {
unittest.mock cloud.aws.eksctl.iamserviceaccount.check-exists \
--exit-code 200 \
;
${testmodule} ${_ARGS[@]}
cloud.aws.eksctl.cli.assert.not.called
}
test.returns-correct-error-if-mismatched-role() {
unittest.mock cloud.aws.eksctl.iamserviceaccount.check-exists \
--exit-code 200 \
;
${testmodule} ${_ARGS[@]}
[[ $? -eq 2 ]]
}

View File

@ -0,0 +1,9 @@
#
# build 'iamserviceaccount' to enable IAM identity / access control
#
# create the iamserviceaccount
use cloud/aws/eksctl/iamserviceaccount/create
# check whether the iamserviceaccount exists in kubernetes
use cloud/aws/eksctl/iamserviceaccount/check-exists

View File

@ -0,0 +1,20 @@
#####################################################################
use unittest
testmodule=cloud.aws.eksctl.iamserviceaccount
#####################################################################
beforeall() {
use cloud/aws/eksctl/iamserviceaccount
}
#####################################################################
test.provides-create() {
unittest.test.provides ${testmodule}.create
}
test.provides-check-exists() {
unittest.test.provides ${testmodule}.check-exists
}

View File

@ -0,0 +1,35 @@
#####################################################################
${scwryptsmodule}.locals() {
local SERVICEACCOUNT
local NAMESPACE
local ROLE_NAME
}
${scwryptsmodule}() {
local PARSED=0
case $1 in
( --serviceaccount ) PARSED=2; SERVICEACCOUNT=$2 ;;
( --namespace ) PARSED=2; NAMESPACE=$2 ;;
( --role-name ) PARSED=2; ROLE_NAME=$2 ;;
esac
return ${PARSED}
}
${scwryptsmodule}.usage() {
USAGE__options+="
--serviceaccount (required) target k8s:ServiceAccount
--namespace (required) target k8s:Namespace
--role-name (required) name of the IAM role to assign
"
}
${scwryptsmodule}.validate() {
[ "${SERVICEACCOUNT}" ] || echo.error "--serviceaccount is required"
[ "${NAMESPACE}" ] || echo.error "--namespace is required"
[ "${ROLE_NAME}" ] || echo.error "--role-name is required"
}
#####################################################################

View File

@ -1,4 +1,4 @@
#!/bin/zsh
#!/usr/bin/env zsh
use cloud/aws/rds
use db/postgres
#####################################################################

View File

@ -1,4 +1,4 @@
#!/bin/zsh
#!/usr/bin/env zsh
use cloud/aws/rds
use db/postgres
#####################################################################

View File

@ -1,4 +1,4 @@
#!/bin/zsh
#!/usr/bin/env zsh
use cloud/aws/rds
use db/postgres
#####################################################################

View File

@ -1,13 +1,7 @@
#####################################################################
DEPENDENCIES+=(
docker
)
REQUIRED_ENV+=(
AWS_ACCOUNT
AWS_REGION
)
DEPENDENCIES+=(docker)
REQUIRED_ENV+=(AWS_ACCOUNT AWS_REGION)
use cloud/aws/cli
@ -15,13 +9,13 @@ use cloud/aws/cli
RDS__SELECT_DATABASE() {
local DATABASES=$(_RDS__GET_AVAILABLE_DATABASES)
[ ! $DATABASES ] && FAIL 1 'no databases available'
[ ! $DATABASES ] && utils.fail 1 'no databases available'
local ID=$(\
echo $DATABASES | jq -r '.instance + " @ " + .cluster' \
| FZF 'select a database (instance@cluster)' \
| utils.fzf 'select a database (instance@cluster)' \
)
[ ! $ID ] && ABORT
[ ! $ID ] && user.abort
local INSTANCE=$(echo $ID | sed 's/ @ .*$//')
local CLUSTER=$(echo $ID | sed 's/^.* @ //')
@ -49,24 +43,24 @@ RDS__GET_DATABASE_CREDENTIALS() {
while [[ $# -gt 0 ]]
do
case $1 in
--print-password ) PRINT_PASSWORD=1 ;;
* )
WARNING "unrecognized argument $1"
( --print-password ) PRINT_PASSWORD=1 ;;
( * )
echo.warning "unrecognized argument $1"
ERRORS+=1
;;
esac
shift 1
done
CHECK_ERRORS
utils.check-errors --fail
##########################################
local DATABASE=$(RDS__SELECT_DATABASE)
[ ! $DATABASE ] && ABORT
[ ! $DATABASE ] && user.abort
DB_HOST="$(echo $DATABASE | jq -r '.host')"
[ ! $DB_HOST ] && { ERROR 'unable to find host'; return 2; }
[ ! $DB_HOST ] && { echo.error 'unable to find host'; return 2; }
DB_PORT="$(echo $DATABASE | jq -r '.port')"
[ ! $DB_PORT ] && DB_PORT=5432
@ -76,17 +70,17 @@ RDS__GET_DATABASE_CREDENTIALS() {
local AUTH_METHOD=$(\
echo "iam\nsecretsmanager\nuser-input" \
| FZF 'select an authentication method' \
| utils.fzf 'select an authentication method' \
)
[ ! $AUTH_METHOD ] && ABORT
[ ! $AUTH_METHOD ] && user.abort
case $AUTH_METHOD in
iam ) _RDS_AUTH__iam ;;
secretsmanager ) _RDS_AUTH__secretsmanager ;;
user-input ) _RDS_AUTH__userinput ;;
( iam ) _RDS_AUTH__iam ;;
( secretsmanager ) _RDS_AUTH__secretsmanager ;;
( user-input ) _RDS_AUTH__userinput ;;
esac
[[ $PRINT_PASSWORD -eq 1 ]] && DEBUG "password : $DB_PASS"
[[ $PRINT_PASSWORD -eq 1 ]] && echo.debug "password : $DB_PASS"
return 0
}
@ -125,7 +119,7 @@ _RDS__GET_SECRETSMANAGER_CREDENTIALS() {
local ID=$(\
AWS secretsmanager list-secrets \
| jq -r '.[] | .[] | .Name' \
| FZF 'select a secret' \
| utils.fzf 'select a secret' \
)
[ ! $ID ] && return 1

View File

@ -1,33 +1,30 @@
#!/bin/zsh
#!/usr/bin/env zsh
DEPENDENCIES+=(cli53)
REQUIRED_ENV+=(AWS_PROFILE)
REQUIRED_ENV+=(AWS_PROFILE AWS_ACCOUNT)
#####################################################################
utils.cli53() {
AWS_ACCOUNT=${AWS_ACCOUNT} \
cli53 --profile ${AWS_PROFILE} $@;
}
MAIN() {
local BACKUP_PATH="$SCWRYPTS_OUTPUT_PATH/$ENV_NAME/aws-dns-backup/$(date '+%Y-%m-%d')"
mkdir -p $BACKUP_PATH >/dev/null 2>&1
local BACKUP_BASE_PATH="${SCWRYPTS_DATA_PATH}/route53-backup/${SCWRYPTS_ENV}"
local DOMAIN
local JOBS=()
for DOMAIN in $(ROUTE53_GET_DOMAINS)
for DOMAIN in $(utils.cli53 list | awk '{print $2;}' | sed '1d; s/\.$//')
do
( STATUS "creating '$BACKUP_PATH/$DOMAIN.txt'" \
&& cli53 export --profile $AWS_PROFILE $DOMAIN > "$BACKUP_PATH/$DOMAIN.txt" \
&& SUCCESS "backed up '$DOMAIN'" \
|| ERROR "failed to back up '$DOMAIN'" \
(
utils.cli53 export ${DOMAIN} > "${BACKUP_BASE_PATH}/${DOMAIN}/$(date '+%Y-%m-%d_%H%M').cli53.txt" \
&& echo.success "backed up '${DOMAIN}'" \
|| echo.error "failed to back up '${DOMAIN}'" \
) &
JOBS+=$!
done
local P
for P in ${JOBS[@]}; do wait $P >/dev/null 2>&1; done
}
for P in ${JOBS[@]}; do wait ${P} &>/dev/null; done
#####################################################################
ROUTE53_GET_DOMAINS() {
cli53 list --profile $AWS_PROFILE \
| awk '{print $2;}' \
| sed '1d; s/\.$//'\
;
echo.reminder "successful backups can be found in '${BACKUP_BASE_PATH}'"
}

View File

@ -0,0 +1,79 @@
${scwryptsmodule}.locals() {
local ACCOUNT # parsed/configured AWS_ACCOUNT (use this instead of the env var!)
local REGION # parsed/configured AWS_REGION (use this instead of the env var!)
local AWS_PASSTHROUGH=() # used to forward parsed overrides to cloud.aws.cli calls (e.g. 'cloud.aws.cli ${AWS_PASSTHROUGH[@]} your command')
local AWS=() # used to forward parsed overrides to cloud.aws.cli calls (e.g. '$AWS your command')
# should only be used by cloud/aws/cli
local AWS_EVAL_PREFIX
local AWS_CONTEXT_ARGS=()
}
${scwryptsmodule}() {
local PARSED=0
case $1 in
( --account ) PARSED+=2; ACCOUNT=$2 ;;
( --region ) PARSED+=2; REGION=$2 ;;
esac
return $PARSED
}
${scwryptsmodule}.usage() {
[[ "$USAGE__usage" =~ ' \[...options...\]' ]] || USAGE__usage+=' [...options...]'
USAGE__options+="\n
--account overrides required AWS_ACCOUNT scwrypts env value
--region overrides required AWS_REGION scwrypts env value
"
}
${scwryptsmodule}.validate() {
AWS_CONTEXT_ARGS=(--output json)
[ $ACCOUNT ] || { utils.environment.check AWS_ACCOUNT &>/dev/null && ACCOUNT=$AWS_ACCOUNT; }
[ $ACCOUNT ] \
&& AWS_EVAL_PREFIX+="AWS_ACCOUNT=$ACCOUNT " \
&& AWS_PASSTHROUGH+=(--account $ACCOUNT) \
|| echo.error "missing either --account or AWS_ACCOUNT" \
;
[ $REGION ] || { utils.environment.check AWS_REGION &>/dev/null && REGION=$AWS_REGION; }
[ $REGION ] \
&& AWS_EVAL_PREFIX+="AWS_REGION=$REGION AWS_DEFAULT_REGION=$REGION " \
&& AWS_CONTEXT_ARGS+=(--region $REGION) \
&& AWS_PASSTHROUGH+=(--region $REGION) \
|| echo.error "missing either --region or AWS_REGION" \
;
utils.environment.check AWS_PROFILE &>/dev/null
[ $AWS_PROFILE ] \
&& AWS_EVAL_PREFIX+="AWS_PROFILE=$AWS_PROFILE " \
&& AWS_CONTEXT_ARGS+=(--profile $AWS_PROFILE) \
;
AWS=(cloud.aws.cli ${AWS_PASSTHROUGH[@]})
[ ! $CI ] && {
# non-CI must use PROFILE authentication
[ $AWS_PROFILE ] || echo.error "missing either --profile or AWS_PROFILE";
[[ $AWS_PROFILE =~ ^default$ ]] \
&& echo.warning "it is HIGHLY recommended to NOT use the 'default' profile for aws operations\nconsider using '$USER.$SCWRYPTS_ENV' instead"
}
[ $CI ] && {
# CI can use 'profile' or envvar 'access key' authentication
[ $AWS_PROFILE ] && return 0 # 'profile' preferred
[ $AWS_ACCESS_KEY_ID ] && [ $AWS_SECRET_ACCESS_KEY ] \
&& AWS_EVAL_PREFIX+="AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID " \
&& AWS_EVAL_PREFIX+="AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY " \
&& return 0
echo.error "running in CI, but missing both profile and access-key configuration\n(one AWS authentication method *must* be used)"
}
}

View File

@ -0,0 +1,6 @@
#
# argument parsers for common AWS-CLI arguments
#
# load/override AWS_* variables
use cloud/aws/zshparse/overrides

View File

@ -1,7 +0,0 @@
#!/bin/zsh
use cloud/media-sync
#####################################################################
MAIN() {
MEDIA_SYNC__PULL $@
}

View File

@ -1,7 +0,0 @@
#!/bin/zsh
use cloud/media-sync
#####################################################################
MAIN() {
MEDIA_SYNC__PUSH $@
}

9
zsh/config.global.zsh Normal file
View File

@ -0,0 +1,9 @@
scwrypts.config() {
[ $1 ] || return 1
case $1 in
( python.versions ) echo "3.13\n3.12\n3.11\n3.10" ;;
( nodejs.version ) echo "22.0.0" ;;
( * ) return 1 ;;
esac
}

BIN
zsh/config.user.zsh Normal file

Binary file not shown.

183
zsh/config.zsh Normal file
View File

@ -0,0 +1,183 @@
#####################################################################
### preflight config validation #####################################
#####################################################################
[[ ${__SCWRYPT} -eq 1 ]] && return 0 # avoid config reload if already active
# Apparently MacOS puts ALL of the homebrew stuff inside of a top level git repository
# with bizarre git ignores; so:
# - USE the git root if it's a manual install...
# - UNLESS that git root is just the $(brew --prefix)
__SCWRYPTS_ROOT="$(cd -- "${0:a:h}"; git rev-parse --show-toplevel 2>/dev/null | grep -v "^$(brew --prefix 2>/dev/null)$")"
[ ${__SCWRYPTS_ROOT} ] && [ -d "${__SCWRYPTS_ROOT}" ] \
|| __SCWRYPTS_ROOT="$(echo "${0:a:h}" | sed -n 's|\(share/scwrypts\).*$|\1|p')"
[ ${__SCWRYPTS_ROOT} ] && [ -d "${__SCWRYPTS_ROOT}" ] || {
echo "cannot determine scwrypts root path for current installation; aborting"
exit 1
}
[ -f "${__SCWRYPTS_ROOT}/MANAGED_BY" ] \
&& readonly SCWRYPTS_INSTALLATION_TYPE=$(cat "${__SCWRYPTS_ROOT}/MANAGED_BY") \
|| readonly SCWRYPTS_INSTALLATION_TYPE=manual \
;
#####################################################################
#
# this is like a manual "use" invocation, but the import driver relies
# on this file, so here we go ahead and do it by hand once
#
# equivalent to "use utils"
# (which is unnecessary since utils are ALWAYS loaded with import.driver.zsh)
#
# normally more logic is necessary for "use", but utils.module.zsh is a
# special module which supports direct-sourcing from any zsh environment
#
source "${__SCWRYPTS_ROOT}/zsh/utils/utils.module.zsh"
SCWRYPTS_LIBRARY_LOADED__scwrypts__utils=true
#####################################################################
### scwrypts global configuration ###################################
#####################################################################
readonly SCWRYPTS_CONFIG_PATH="${XDG_CONFIG_HOME:-${HOME}/.config}/scwrypts"
readonly SCWRYPTS_ENV_PATH="${SCWRYPTS_CONFIG_PATH}/environments"
readonly SCWRYPTS_DATA_PATH="${XDG_DATA_HOME:-${HOME}/.local/share}/scwrypts"
readonly SCWRYPTS_STATE_PATH="${XDG_STATE_HOME:-${HOME}/.local/state}/scwrypts"
readonly SCWRYPTS_LOG_PATH="${SCWRYPTS_STATE_PATH}/logs"
[ -d /tmp ] \
&& readonly SCWRYPTS_TEMP_PATH="/tmp/scwrypts/${SCWRYPTS_RUNTIME_ID}" \
|| readonly SCWRYPTS_TEMP_PATH="${XDG_RUNTIME_DIR:-/run/user/${UID}}/scwrypts/${SCWRYPTS_RUNTIME_ID}" \
;
mkdir -p \
"${SCWRYPTS_ENV_PATH}" \
"${SCWRYPTS_LOG_PATH}" \
"${SCWRYPTS_TEMP_PATH}" \
;
DEFAULT_CONFIG="${__SCWRYPTS_ROOT}/zsh/config.user.zsh"
source "${DEFAULT_CONFIG}"
USER_CONFIG_OVERRIDES="${SCWRYPTS_CONFIG_PATH}/config.zsh"
[ ! -f "${USER_CONFIG_OVERRIDES}" ] && {
mkdir -p $(dirname "${USER_CONFIG_OVERRIDES}")
cp "${DEFAULT_CONFIG}" "${USER_CONFIG_OVERRIDES}"
}
source "${USER_CONFIG_OVERRIDES}"
source "${__SCWRYPTS_ROOT}/zsh/config.global.zsh"
#####################################################################
### load groups and plugins #########################################
#####################################################################
SCWRYPTS_GROUPS=()
command -v echo.warning &>/dev/null || WARNING() { echo "echo.warning : $@" >&2; }
command -v echo.error &>/dev/null || ERROR() { echo "echo.error : $@" >&2; return 1; }
command -v utils.fail &>/dev/null || FAIL() { echo.error "${@:2}"; exit $1; }
__SCWRYPTS_GROUP_LOADERS=(
"${__SCWRYPTS_ROOT}/scwrypts.scwrypts.zsh"
)
[ "${GITHUB_WORKSPACE}" ] && [ ! "${SCWRYPTS_GITHUB_NO_AUTOLOAD}" ] && {
SCWRYPTS_GROUP_DIRS+=("${GITHUB_WORKSPACE}")
}
for __SCWRYPTS_GROUP_DIR in ${SCWRYPTS_GROUP_DIRS[@]}
do
[ -d "${__SCWRYPTS_GROUP_DIR}" ] || continue
for __SCWRYPTS_GROUP_LOADER in $(find "${__SCWRYPTS_GROUP_DIR}" -type f -name \*scwrypts.zsh)
do
__SCWRYPTS_GROUP_LOADERS+=("${__SCWRYPTS_GROUP_LOADER}")
done
done
scwrypts.config.group() {
local GROUP_NAME="$1"
local CONFIG_KEY="$2"
[ "$GROUP_NAME" ] && [ "$CONFIG_KEY" ] \
|| return 1
echo ${(P)$(echo SCWRYPTS_GROUP_CONFIGURATION__${GROUP_NAME}__${CONFIG_KEY})}
}
for __SCWRYPTS_GROUP_LOADER in ${__SCWRYPTS_GROUP_LOADERS}
do
__SCWRYPTS_GROUP_LOADER_REALPATH="$(readlink -f -- "${__SCWRYPTS_GROUP_LOADER}")"
[ -f "${__SCWRYPTS_GROUP_LOADER_REALPATH}" ] || {
echo.warning "error loading group '${__SCWRYPTS_GROUP_LOADER}': cannot read file"
continue
}
__SCWRYPTS_GROUP_NAME="$(\
basename -- "${__SCWRYPTS_GROUP_LOADER_REALPATH}" \
| sed -n 's/^\([a-z][a-z0-9_]*[a-z0-9]\).scwrypts.zsh$/\1/p' \
)"
[ "$__SCWRYPTS_GROUP_NAME" ] || {
echo.warning "unable to load group '${__SCWRYPTS_GROUP_LOADER_REALPATH}': invalid group name" >&2
continue
}
[[ $(scwrypts.config.group "$__SCWRYPTS_GROUP_NAME" loaded) =~ true ]] && {
echo.warning "unable to load group '${__SCWRYPTS_GROUP_NAME}': duplicate name"
continue
}
scwryptsgroup="SCWRYPTS_GROUP_CONFIGURATION__${__SCWRYPTS_GROUP_NAME}"
scwryptsgrouproot="$(dirname -- "${__SCWRYPTS_GROUP_LOADER_REALPATH}")"
: \
&& readonly ${scwryptsgroup}__root="${scwryptsgrouproot}" \
&& source "${__SCWRYPTS_GROUP_LOADER_REALPATH}" \
&& SCWRYPTS_GROUPS+=(${__SCWRYPTS_GROUP_NAME}) \
&& readonly ${scwryptsgroup}__loaded=true \
|| echo.warning "error encountered when loading group '${__SCWRYPTS_GROUP_NAME}'" \
;
[[ ! ${__SCWRYPTS_GROUP_NAME} =~ ^scwrypts$ ]] && [ ! "$(scwrypts.config.group ${__SCWRYPTS_GROUP_NAME} zshlibrary)" ] && {
case $(scwrypts.config.group ${__SCWRYPTS_GROUP_NAME} type) in
( zsh )
[ -d "${scwryptsgrouproot}/lib" ] \
&& readonly ${scwryptsgroup}__zshlibrary="${scwryptsgrouproot}/lib" \
|| readonly ${scwryptsgroup}__zshlibrary="${scwryptsgrouproot}" \
;
;;
( '' )
[ -d "${scwryptsgrouproot}/zsh/lib" ] \
&& readonly ${scwryptsgroup}__zshlibrary="${scwryptsgrouproot}/zsh/lib" \
|| readonly ${scwryptsgroup}__zshlibrary="${scwryptsgrouproot}/zsh" \
;
;;
esac
}
done
[[ ${SCWRYPTS_GROUPS[1]} =~ ^scwrypts$ ]] \
|| utils.fail 69 "encountered error when loading essential group 'scwrypts'; aborting"
#####################################################################
### cleanup #########################################################
#####################################################################
unset __SCWRYPTS_ROOT # you should now use '$(scwrypts.config.group scwrypts root)'
unset \
__SCWRYPTS_GROUP_LOADER __SCWRYPTS_GROUP_LOADERS __SCWRYPTS_GROUP_LOADER_REALPATH \
__SCWRYPTS_GROUP_DIR SCWRYPTS_GROUP_DIRS \
__SCWRYPTS_GROUP_NAME \
scwryptsgroup scwryptsgrouproot \
;
__SCWRYPT=1 # arbitrary; indicates currently inside a scwrypt

View File

@ -1,4 +1,4 @@
#!/bin/zsh
#!/usr/bin/env zsh
use db/postgres
#####################################################################

View File

@ -1,4 +1,4 @@
#!/bin/zsh
#!/usr/bin/env zsh
use db/postgres
#####################################################################

View File

@ -1,4 +1,4 @@
#!/bin/zsh
#!/usr/bin/env zsh
use db/postgres
#####################################################################

View File

@ -25,7 +25,7 @@ PG_DUMP() {
STATUS "
echo.status "
making backup of : $DB_USER@$DB_HOST:$DB_PORT/$DB_NAME
(compressed) : '$OUTPUT_FILE.dump'
@ -34,21 +34,21 @@ PG_DUMP() {
"
: \
&& STATUS "creating compressed backup..." \
&& echo.status "creating compressed backup..." \
&& eval PGPASSWORD=$(printf '%q ' "$DB_PASS") psql ${PSQL_ARGS[@]} \
--format custom \
--file "$OUTPUT_FILE.dump" \
--verbose \
&& SUCCESS "completed compressed backup" \
&& STATUS "creating raw backup..." \
&& echo.success "completed compressed backup" \
&& echo.status "creating raw backup..." \
&& pg_restore -f "$OUTPUT_FILE.raw.sql" "$OUTPUT_FILE.dump" \
&& SUCCESS "completed raw backup" \
&& STATUS "creating single-transaction raw backup..." \
&& echo.success "completed raw backup" \
&& echo.status "creating single-transaction raw backup..." \
&& { echo "BEGIN;\n"; cat "$OUTPUT_FILE.raw.sql"; echo "\nEND;" } > "$OUTPUT_FILE.sql" \
&& SUCCESS "completed single-transaction raw backup" \
|| { ERROR "error creating backup for '$DB_HOST/$DB_NAME' (see above)"; return 1; }
&& echo.success "completed single-transaction raw backup" \
|| { echo.error "error creating backup for '$DB_HOST/$DB_NAME' (see above)"; return 1; }
SUCCESS "
echo.success "
completed backup : $DB_USER@$DB_HOST:$DB_PORT/$DB_NAME
(compressed) : '$OUTPUT_FILE.dump'
@ -64,11 +64,11 @@ PG_RESTORE() {
local FILE
POSTGRES__SET_LOGIN_ARGS $@
local INPUT_FILE=$(find "$DATA_DIR"/backup.* -type f | FZF 'select database file to restore')
local INPUT_FILE=$(find "$DATA_DIR"/backup.* -type f | utils.fzf 'select database file to restore')
[ $INPUT_FILE ] && [ -f "$INPUT_FILE" ] || {
ERROR 'no file selected or missing backup file; aborting'
REMINDER "
echo.error 'no file selected or missing backup file; aborting'
echo.reminder "
backups must be *.sql or *.dump files starting with the prefix 'backup.'
in the following directory:
@ -80,7 +80,7 @@ PG_RESTORE() {
local RAW=1
[[ $INPUT_FILE =~ \\.dump$ ]] && RAW=0
STATUS "
echo.status "
loading backup for : $DB_USER@$DB_HOST:$DB_PORT/$DB_NAME
file : '$INPUT_FILE'
@ -88,13 +88,13 @@ PG_RESTORE() {
local EXIT_CODE
[[ $RAW -eq 1 ]] && {
REMINDER "
echo.reminder "
loading a backup from a raw sql dump may result in data loss
make sure your database is ready to accept the database file!
"
yN 'continue?' || ABORT
yN 'continue?' || user.abort
PSQL < "$INPUT_FILE"
EXIT_CODE=$?
@ -110,8 +110,8 @@ PG_RESTORE() {
}
[[ $EXIT_CODE -eq 0 ]] \
&& SUCCESS "finished restoring backup for '$DB_HOST/$DB_NAME'" \
|| ERROR "error restoring backup for '$DB_HOST/$DB_NAME' (see above)" \
&& echo.success "finished restoring backup for '$DB_HOST/$DB_NAME'" \
|| echo.error "error restoring backup for '$DB_HOST/$DB_NAME' (see above)" \
;
return $EXIT_CODE
@ -120,14 +120,14 @@ PG_RESTORE() {
#####################################################################
POSTGRES__LOGIN_INTERACTIVE() {
DEPENDENCIES=(pgcli) CHECK_ENVIRONMENT --optional \
utils.dependencies.check pgcli --optional \
&& COMMAND=pgcli || COMMAND=psql
[[ $COMMAND =~ psql ]] && WARNING "using 'psql' instead"
[[ $COMMAND =~ psql ]] && echo.warning "using 'psql' instead"
POSTGRES__SET_LOGIN_ARGS $@
STATUS "
echo.status "
performing login : $DB_USER@$DB_HOST:$DB_PORT/$DB_NAME
working directory : $DATA_DIR
"
@ -146,25 +146,25 @@ POSTGRES__SET_LOGIN_ARGS() {
while [[ $# -gt 0 ]]
do
case $1 in
-h | --host ) DB_HOST="$2"; shift 1 ;;
-p | --port ) DB_PORT="$2"; shift 1 ;;
-d | --name ) DB_NAME="$2"; shift 1 ;;
-U | --user ) DB_USER="$2"; shift 1 ;;
-P | --pass ) DB_PASS="$2"; shift 1 ;;
( -h | --host ) DB_HOST="$2"; shift 1 ;;
( -p | --port ) DB_PORT="$2"; shift 1 ;;
( -d | --name ) DB_NAME="$2"; shift 1 ;;
( -U | --user ) DB_USER="$2"; shift 1 ;;
( -P | --pass ) DB_PASS="$2"; shift 1 ;;
--file ) PSQL_FILE="$2"; shift 1 ;;
( --file ) PSQL_FILE="$2"; shift 1 ;;
--data-dir-prefix ) DATA_DIR_PREFIX="$2"; shift 1 ;;
( --data-dir-prefix ) DATA_DIR_PREFIX="$2"; shift 1 ;;
* ) PSQL_ARGS+=($1) ;;
( * ) PSQL_ARGS+=($1) ;;
esac
shift 1
done
[ $PSQL_FILE ] && [ ! -f "$PSQL_FILE" ] \
&& ERROR "no such file available:\n'$PSQL_FILE'"
&& echo.error "no such file available:\n'$PSQL_FILE'"
CHECK_ERRORS
utils.check-errors --fail
##########################################

View File

@ -1,9 +1,9 @@
#!/bin/zsh
#!/usr/bin/env zsh
use db/postgres
#####################################################################
MAIN() {
WARNING " \nthis function is in a beta state\n "
echo.warning " \nthis function is in a beta state\n "
local _PASS _ARGS=()
POSTGRES__SET_LOGIN_ARGS $@
@ -15,27 +15,27 @@ MAIN() {
cd $SQL_DIR
[[ $(ls "*.sql" 2>&1 | wc -l) -eq 0 ]] && {
ERROR "you haven't made any SQL commands yet"
REMINDER "add '.sql' files here: '$SQL_DIR/'"
echo.error "you haven't made any SQL commands yet"
echo.reminder "add '.sql' files here: '$SQL_DIR/'"
return 1
}
[ ! $INPUT_FILE ] && INPUT_FILE=$(FZF 'select a sql file to run')
[ ! $INPUT_FILE ] && ABORT
[ ! $INPUT_FILE ] && INPUT_FILE=$(utils.fzf 'select a sql file to run')
[ ! $INPUT_FILE ] && user.abort
[ ! -f "$INPUT_FILE" ] && FAIL 2 "no such sql file '$SQL_DIR/$INPUT_FILE'"
[ ! -f "$INPUT_FILE" ] && utils.fail 2 "no such sql file '$SQL_DIR/$INPUT_FILE'"
STATUS "loading '$INPUT_FILE' preview..."
echo.status "loading '$INPUT_FILE' preview..."
LESS "$INPUT_FILE"
STATUS "login : $_USER@$_HOST:$_PORT/$_NAME"
STATUS "command : '$INPUT_FILE'"
echo.status "login : $_USER@$_HOST:$_PORT/$_NAME"
echo.status "command : '$INPUT_FILE'"
yN 'run this command?' || ABORT
yN 'run this command?' || user.abort
STATUS "running '$INPUT_FILE'"
echo.status "running '$INPUT_FILE'"
PSQL < $INPUT_FILE \
&& SUCCESS "finished running '$INPUT_FILE'" \
|| FAIL 3 "something went wrong running '$INPUT_FILE' (see above)"
&& echo.success "finished running '$INPUT_FILE'" \
|| utils.fail 3 "something went wrong running '$INPUT_FILE' (see above)"
}

View File

@ -1,13 +1,29 @@
#!/bin/zsh
#!/usr/bin/env zsh
DEPENDENCIES+=(docker)
#####################################################################
MAIN() {
WARNING 'this will prune all docker resources from the current machine'
WARNING 'pruned resources are PERMANENTLY DELETED'
yN 'continue?' || return 1
local RESOURCES=(
container
image
volume
system
)
SUCCESS "CONTAINER : $(docker container prune -f 2>/dev/null | tail -n 1)"
SUCCESS "IMAGE : $(docker image prune -f 2>/dev/null | tail -n 1)"
SUCCESS "VOLUME : $(docker volume prune -f 2>/dev/null | tail -n 1)"
echo.warning "
this will prune the following docker resources from the
current machine:
(${RESOURCES[@]})
pruned resources are PERMANENTLY DELETED
"
utils.yN 'continue?' || utils.abort
echo.success "$(
for RESOURCE in ${RESOURCES[@]}
do
echo "${RESOURCE}^:^$(docker ${RESOURCE} prune -f 2>/dev/null | tail -n 1)"
done | column -ts '^'
)"
}

View File

@ -1,6 +1,6 @@
#!/bin/zsh
#!/usr/bin/env zsh
#####################################################################
MAIN() {
SUCCESS 'Hello, World!'
echo.success 'Hello, World!'
}

View File

@ -1,9 +1,13 @@
#!/bin/zsh
use helm
use scwrypts
#!/usr/bin/env zsh
#####################################################################
MAIN() {
unset USAGE
HELM__TEMPLATE__GET $@
}
use helm
#####################################################################
helm.zshparse.usage
helm.get-template.parse.usage
#####################################################################
MAIN() { helm.get-template $@; }

View File

@ -0,0 +1,135 @@
#####################################################################
use scwrypts/get-realpath
use helm/zshparse
DEPENDENCIES+=(helm)
#####################################################################
${scwryptsmodule}() {
local PARSERS=(helm.zshparse)
eval "$(utils.parse.autosetup)"
##########################################
local HELM_LINT_ARGS=(${HELM_ARGS[@]})
[[ ${USE_CHART_ROOT} =~ false ]] \
&& HELM_ARGS+=(--show-only "$(echo "${TEMPLATE_FILENAME}" | sed "s|^${CHART_ROOT}/||")")
local TEMPLATE_OUTPUT DEBUG_OUTPUT
utils.io.capture TEMPLATE_OUTPUT DEBUG_OUTPUT \
helm template "${CHART_ROOT}" ${HELM_ARGS[@]} --debug \
;
local EXIT_CODE
[ "${TEMPLATE_OUTPUT}" ] && EXIT_CODE=0 || EXIT_CODE=1
case ${OUTPUT_MODE} in
( raw )
[[ ${EXIT_CODE} -eq 0 ]] \
&& echo "${TEMPLATE_OUTPUT}" | grep -v '^# Source:.*$' \
|| echo "${DEBUG_OUTPUT}" >&2 \
;
;;
( color )
[[ ${EXIT_CODE} -eq 0 ]] \
&& echo "${TEMPLATE_OUTPUT}" | bat --language yaml --color always \
|| echo "${DEBUG_OUTPUT}" >&2 \
;
;;
( debug )
[[ ${EXIT_CODE} -eq 0 ]] \
&& KUBEVAL_RAW=$(
echo "${TEMPLATE_OUTPUT}" \
| kubeval --schema-location https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master \
) \
|| KUBEVAL_RAW='no template output; kubeval skipped'
echo "
${TEMPLATE_OUTPUT}
---
debug: |\n$(echo ${DEBUG_OUTPUT} | sed 's/^/ /g')
kubeval: |\n$(echo ${KUBEVAL_RAW} | sed 's/^/ /g')
lint: |\n$(helm lint "${CHART_ROOT}" ${HELM_LINT_ARGS[@]} 2>&1 | sed 's/^/ /g')
" | sed 's/^ \+//; 1d; $d'
;;
esac
return ${EXIT_CODE}
}
#####################################################################
${scwryptsmodule}.parse() {
local PARSED=0
case $1 in
( --colorize ) PARSED=1; OUTPUT_MODE=color ;;
( --raw ) PARSED=1; OUTPUT_MODE=raw ;;
( --debug ) PARSED=1; OUTPUT_MODE=debug ;;
( --update ) PARSED=1; UPDATE_DEPENDENCIES=true ;;
esac
return ${PARSED}
}
${scwryptsmodule}.parse.locals() {
local OUTPUT_MODE=default
local UPDATE_DEPENDENCIES=false
}
${scwryptsmodule}.parse.usage() {
USAGE__description="
Smart helm-template generator which auto-detects the chart
and sample values for testing and developing helm charts.
"
local DEFAULT
utils.dependencies.check bat &>/dev/null \
&& DEFAULT=color || DEFAULT=raw
USAGE__options+="
--colorize $([[ ${DEFAULT} =~ color ]] && printf '(default) ')use 'bat' to colorize output
--raw $([[ ${DEFAULT} =~ raw ]] && printf '(default) ')remove scwrypts-added fluff and only output helm template details
--debug debug template with kubeval and helm-lint
--update update dependencies before generating template output
"
}
${scwryptsmodule}.parse.validate() {
case ${OUTPUT_MODE} in
( default )
utils.dependencies.check bat &>/dev/null \
&& OUTPUT_MODE=color \
|| OUTPUT_MODE=raw \
;
;;
( color )
utils.dependencies.check bat || ((ERRORS+=1))
;;
( debug )
utils.dependencies.check kubeval \
|| echo.error 'kubeval is required for --debug'
;;
esac
case ${UPDATE_DEPENDENCIES} in
( false ) ;;
( true )
use helm/update-dependencies
helm.update-dependencies --template-filename "${TEMPLATE_FILENAME}" &>/dev/null \
|| echo.error 'failed to update dependencies'
;;
esac
}

15
zsh/helm/helm.module.zsh Normal file
View File

@ -0,0 +1,15 @@
#
# helm template testing and generation helpers
#
# ensures default values are injected from local Chart dependencies
use helm/update-dependencies
# template generation
use helm/get-template
# shared argument parser
use helm/zshparse

View File

@ -1,9 +1,13 @@
#!/bin/zsh
use helm
use scwrypts
#!/usr/bin/env zsh
#####################################################################
MAIN() {
unset USAGE
HELM__DEPENDENCY__UPDATE $@
}
use helm
#####################################################################
helm.zshparse.usage
helm.update-dependencies.parse.usage
#####################################################################
MAIN() { helm.update-dependencies $@; }

View File

@ -0,0 +1,31 @@
#####################################################################
use helm/zshparse
DEPENDENCIES+=(helm)
#####################################################################
${scwryptsmodule}() {
local PARSERS=(helm.zshparse)
eval "$(utils.parse.autosetup)"
##########################################
echo.status "updating helm dependencies for '${CHART_ROOT}'" \
helm dependency update "${CHART_ROOT}" \
&& echo.success "helm chart dependencies updated" \
|| echo.error "unable to update helm chart dependencies (see above)" \
|| return 1
}
#####################################################################
${scwryptsmodule}.parse() { return 0; }
${scwryptsmodule}.parse.usage() {
USAGE__description='
Auto-detect chart and build dependencies for any file within a helm chart.
'
}

View File

@ -0,0 +1,119 @@
#####################################################################
DEPENDENCIES+=(yq)
REQUIRED_ENV+=()
#####################################################################
${scwryptsmodule}() {
local USAGE="
Smart helm-detection / validator which determines the helm
chart root and other details given a particular filename.
You must set TEMPLATE_FILENAME, and this function will verify
the template is part of a helm chart and set up the following
variables:
- CHART_ROOT : the fully qualified path to the directory
containing Chart.yaml
- CHART_NAME : Chart.yaml > .name
- USE_CHART_ROOT (true/false)
true : operations should use/output the entire chart
false : operations should use/output a single template
- HELM_ARGS : an array of arguments which apply to any 'helm'
command
"
[ "${TEMPLATE_FILENAME}" ] && [ -f "${TEMPLATE_FILENAME}" ] \
|| echo.error 'must provide a template filename' \
|| return 1
##########################################
CHART_ROOT="$(helm.validate.get-chart-root)"
[ ${CHART_ROOT} ] && [ -d "${CHART_ROOT}" ] \
|| echo.error 'unable to determine helm root; is this a helm template file?' \
|| return 1
##########################################
CHART_NAME=$(utils.yq -r .name "${CHART_ROOT}/Chart.yaml")
##########################################
USE_CHART_ROOT=false
case "${TEMPLATE_FILENAME}" in
( *values.*.yaml | *tests/*.yaml )
HELM_ARGS+=(--values ${TEMPLATE_FILENAME})
USE_CHART_ROOT=true
;;
( *.tpl )
USE_CHART_ROOT=true
;;
esac
[[ $(dirname -- "${TEMPLATE_FILENAME}") =~ ^${CHART_ROOT}$ ]] \
&& USE_CHART_ROOT=true
##########################################
HELM_ARGS=($(helm.validate.get-default-values-args) ${HELM_ARGS[@]})
##########################################
return 0
}
${scwryptsmodule}.get-chart-root() {
local SEARCH_DIR=$(dirname -- "${TEMPLATE_FILENAME}")
while [[ ! ${SEARCH_DIR} =~ ^/$ ]]
do
[ -f "${SEARCH_DIR}/Chart.yaml" ] \
&& echo "${SEARCH_DIR}" \
&& return 0 \
;
SEARCH_DIR="$(dirname -- "${SEARCH_DIR}")"
done
return 1
}
${scwryptsmodule}.get-default-values-args() {
local F
local VALUES_FILES_ORDER=(
values.yaml # the default values of the chart
tests/default.yaml # a template test which provides any required values not included in the default values
)
local LOCAL_DEPENDENCY_CHART LOCAL_CHART_ROOT
for LOCAL_DEPENDENCY_CHART in $(\
cat "${CHART_ROOT}/Chart.yaml" \
| utils.yq -r '.dependencies[] | .repository' \
| grep '^file://' \
| sed 's|file://||' \
)
do
[[ "${LOCAL_DEPENDENCY_CHART}" =~ ^[/~] ]] \
&& LOCAL_CHART_ROOT="${LOCAL_DEPENDENCY_CHART}" \
|| LOCAL_CHART_ROOT=$(readlink -f -- "${CHART_ROOT}/${LOCAL_DEPENDENCY_CHART}") \
;
for F in ${VALUES_FILES_ORDER[@]}
do
[ -f "${LOCAL_CHART_ROOT}/${F}" ] \
&& echo --values "${LOCAL_CHART_ROOT}/${F}"
done
done
local HELM_VALUES_ARGS=()
for F in ${VALUES_FILES_ORDER[@]}
do
[ -f "${CHART_ROOT}/${F}" ] \
&& echo --values "${CHART_ROOT}/${F}"
done
}

View File

@ -0,0 +1,54 @@
#####################################################################
use helm/validate
use scwrypts/get-realpath
#####################################################################
${scwryptsmodule}() {
local PARSED=0
case $1 in
( -t | --template-filename )
PARSED=2
TEMPLATE_FILENAME="$(scwrypts.get-realpath "$2")"
;;
esac
return ${PARSED}
}
${scwryptsmodule}.locals() {
local TEMPLATE_FILENAME
local ARGS=()
# configured by helm.validate
local CHART_NAME
local CHART_ROOT
local HELM_ARGS=()
}
${scwryptsmodule}.usage() {
USAGE__options+='
-t, --template-filename path to a template/*.yaml file of a helm chart
'
USAGE__args+='
\$@ additional args are forwarded to helm
'
}
${scwryptsmodule}.validate() {
helm.validate || return 1
HELM_ARGS+=(${ARGS[@]})
echo.debug "
template filename : ${TEMPLATE_FILENAME}
chart name : ${CHART_NAME}
chart root : ${CHART_ROOT}
helm args : ${HELM_ARGS[@]}
"
}
#####################################################################

340
zsh/import.driver.zsh Normal file
View File

@ -0,0 +1,340 @@
command -v use use.is-loaded use.get-library-root &>/dev/null && return 0
###################################################################
# #
# usage: use [OPTIONS ...] zsh/module/path #
# #
###################################################################
# #
# OPTIONS: #
# #
# -g, --group lookup library root from friendly group #
# name (requires configuration) #
# (default: scwrypts) #
# #
# -r, --library-root fully qualified path to a library root #
# #
# --check-environment check environment immediately rather than #
# wait for downstream call to #
# utils.check-environment #
# #
# #
# ZSHIMPORT_USE_CACHE (true|false; default: true) #
# setting this to false will always require direct, source files #
# #
# Allows for import-style library loading in zsh. No matter what #
# scwrypt is run, this function (and required helpers) are *also* #
# loaded, ensuring that 'use' is always available in scwrypts #
# context. #
# #
# #
# Friendly group-names can be configured by setting the variable #
# 'SCWRYPTS_GROUP_CONFIGRUATION__<group-name>__root' to the fully #
# qualified path to the root directory of the modules library. #
# #
# #
###################################################################
# I have no idea why, but CircleCI obliterates this env var in particular
# every time. Just going to force it like this for now
[ $CIRCLECI ] && export ZSHIMPORT_USE_CACHE=false
[ ${ZSHIMPORT_USE_CACHE} ] || export ZSHIMPORT_USE_CACHE=true
[[ ${ZSHIMPORT_USE_CACHE} =~ true ]] && {
command -v jo jq sha1sum &>/dev/null || {
echo.warning "missing utilities prevents import cache"
export ZSHIMPORT_USE_CACHE=false
}
}
[ ${ZSHIMPORT_CACHE_DIR} ] || export ZSHIMPORT_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/zshimport"
source "${0:a:h}/config.zsh"
use() {
local SCWRYPTS_LIBRARY SCWRYPTS_LIBRARY_ROOT SCWRYPTS_LIBRARY_GROUP
local DEFER_ENVIRONMENT_CHECK=true
local ONLY_OUTPUT_METADATA=false
local ONLY_GENERATE_CACHE=false
while [[ $# -gt 0 ]]
do
case $1 in
( -g | --group )
[ "${SCWRYPTS_LIBRARY_ROOT}" ] && echo.error 'specify only one of {(-g), (-r)}'
SCWRYPTS_LIBRARY_GROUP=$2
shift 1
;;
( -r | --library-root )
[ "${SCWRYPTS_LIBRARY_GROUP}" ] && echo.error 'specify only one of {(-g), (-r)}'
SCWRYPTS_LIBRARY_ROOT=$2
shift 1
;;
( -c | --check-environment )
DEFER_ENVIRONMENT_CHECK=false
;;
( --meta )
ONLY_OUTPUT_METADATA=true
;;
( --generate-cache )
ONLY_GENERATE_CACHE=true
;;
( * )
[ ! "${SCWRYPTS_LIBRARY}" ] \
&& SCWRYPTS_LIBRARY=$1 \
|| echo.error 'too many arguments; expected exactly 1 argument' \
;;
esac
shift 1
done
[ ! "${SCWRYPTS_LIBRARY}" ] && echo.error 'no library specified for import'
: \
&& [ ! "${SCWRYPTS_LIBRARY_GROUP}" ] \
&& [ ! "${SCWRYPTS_LIBRARY_ROOT}" ] \
&& SCWRYPTS_LIBRARY_GROUP=scwrypts
# bail ASAP, check errors later
use.is-loaded && [[ ${ONLY_OUTPUT_METADATA} =~ false ]] && [[ ${ONLY_GENERATE_CACHE} =~ false ]] && return 0
[ ! "${SCWRYPTS_LIBRARY_ROOT}" ] && SCWRYPTS_LIBRARY_ROOT="$(use.get-scwrypts-library-root)"
[ ! "${SCWRYPTS_LIBRARY_ROOT}" ] && echo.error "unable to determine library root from group name '${SCWRYPTS_LIBRARY_GROUP}'"
#####################################################################
local LIBRARY_FILE LIBRARY_FILE_TEMP CACHE_FILE
[ ! "${LIBRARY_FILE}" ] && {
LIBRARY_FILE_TEMP="${SCWRYPTS_LIBRARY_ROOT}/${SCWRYPTS_LIBRARY}.module.zsh"
[ -f "${LIBRARY_FILE_TEMP}" ] && {
LIBRARY_FILE="${LIBRARY_FILE_TEMP}"
CACHE_FILE="${SCWRYPTS_LIBRARY}.module.zsh"
}
}
[ ! "${LIBRARY_FILE}" ] && { # "group" library reference
LIBRARY_FILE_TEMP="${SCWRYPTS_LIBRARY_ROOT}/${SCWRYPTS_LIBRARY}/$(basename -- "${SCWRYPTS_LIBRARY}").module.zsh"
[ -f "${LIBRARY_FILE_TEMP}" ] && {
LIBRARY_FILE="${LIBRARY_FILE_TEMP}"
CACHE_FILE="${SCWRYPTS_LIBRARY}/$(basename -- "${SCWRYPTS_LIBRARY}").module.zsh"
}
}
[ "${LIBRARY_FILE}" ] \
|| echo.error "no such library '${SCWRYPTS_LIBRARY_GROUP}/${SCWRYPTS_LIBRARY}'"
#####################################################################
local LIBRARY_HASH LIBRARY_CACHE_DIR LIBRARY_CACHE_FILE
#####################################################################
utils.check-errors || {
((IMPORT_ERRORS+=1))
return 1
}
#####################################################################
local SCWRYPTS_MODULE_BEFORE=${scwryptsmodule}
[[ ${SCWRYPTS_LIBRARY_GROUP} =~ ^scwrypts$ ]] \
&& export scwryptsmodule="$(echo "${SCWRYPTS_LIBRARY}" | sed 's|/|.|g')" \
|| export scwryptsmodule="${SCWRYPTS_LIBRARY_GROUP}.$(echo "${SCWRYPTS_LIBRARY}" | sed 's|/|.|g')" \
;
[[ ${ONLY_OUTPUT_METADATA} =~ true ]] && {
use.get-metadata
return 0
}
case "${ZSHIMPORT_USE_CACHE}" in
( false ) ;;
( true )
LIBRARY_HASH="$(use.compute-scwrypts-library-hash)"
LIBRARY_CACHE_DIR="${ZSHIMPORT_CACHE_DIR}/${SCWRYPTS_LIBRARY_GROUP}-${LIBRARY_HASH}"
LIBRARY_CACHE_FILE="${LIBRARY_CACHE_DIR}/${CACHE_FILE}"
[ "${LIBRARY_HASH}" ] && [ "${LIBRARY_CACHE_DIR}" ] && [ "${LIBRARY_CACHE_FILE}" ] \
|| echo.error "error when computing library hash for ${SCWRYPTS_LIBRARY_GROUP}/${SCWRYPTS_LIBRARY}"
use.generate-cache \
|| echo.error "error generating cache for ${SCWRYPTS_LIBRARY_GROUP}/${SCWRYPTS_LIBRARY}" \
|| return 1
;;
esac
[[ ${ONLY_GENERATE_CACHE} =~ true ]] && {
cat "${LIBRARY_CACHE_FILE}" | grep .
return $?
}
case ${ZSHIMPORT_USE_CACHE} in
( true )
source "${LIBRARY_CACHE_FILE}" || {
((IMPORT_ERRORS+=1))
echo.error "import error for '${SCWRYPTS_LIBRARY_GROUP}/${SCWRYPTS_LIBRARY}'"
echo.debug "cache : '${LIBRARY_CACHE_FILE}'"
return 1
}
;;
( false )
source "${LIBRARY_FILE}" || {
((IMPORT_ERRORS+=1))
echo.error "import error for '${SCWRYPTS_LIBRARY_GROUP}/${SCWRYPTS_LIBRARY}'"
export scwryptsmodule=${SCWRYPTS_MODULE_BEFORE}
return 1
}
[[ ${DEFER_ENVIRONMENT_CHECK} =~ false ]] && {
utils.check-environment || {
((IMPORT_ERRORS+=1))
echo.error "import error for '${SCWRYPTS_LIBRARY_GROUP}/${SCWRYPTS_LIBRARY}'"
return 1
}
}
use.is-loaded --set
[[ ${SCWRYPTS_MODULE_BEFORE} ]] \
&& export scwryptsmodule=${SCWRYPTS_MODULE_BEFORE} \
|| unset scwryptsmodule \
;
;;
esac
return 0
}
use.get-scwrypts-library-root() {
local VARIABLE_NAME="SCWRYPTS_LIBRARY_ROOT__${SCWRYPTS_LIBRARY_GROUP}"
echo "${(P)VARIABLE_NAME}" | grep . && return 0
##########################################
local ROOT
ROOT="$(scwrypts.config.group "${SCWRYPTS_LIBRARY_GROUP}" zshlibrary)"
[ "${ROOT}" ] && eval ${VARIABLE_NAME}="${ROOT}" && echo "${ROOT}" && return 0
##########################################
local GROUP_ROOT="$(scwrypts.config.group "${SCWRYPTS_LIBRARY_GROUP}" root)"
local GROUP_TYPE="$(scwrypts.config.group "${SCWRYPTS_LIBRARY_GROUP}" type)"
[[ ${GROUP_TYPE} =~ zsh ]] \
&& ROOT="${GROUP_ROOT}/lib" \
|| ROOT="${GROUP_ROOT}/zsh/lib" \
;
[ -d "${ROOT}" ] || ROOT="$(dirname -- "${ROOT}")"
[ "${ROOT}" ] && [ -d "${ROOT}" ] \
|| echo.error "unable to determine library root" \
|| return 1
eval ${VARIABLE_NAME}="${ROOT}"
echo "${ROOT}"
}
use.compute-scwrypts-library-hash() {
LC_ALL=POSIX find "${SCWRYPTS_LIBRARY_ROOT}" -type f -name \*.module.zsh -print0 \
| sort -z \
| xargs -0 sha1sum \
| sha1sum \
| awk '{print $1;}' \
;
}
use.is-loaded() {
local VARIABLE_NAME="SCWRYPTS_LIBRARY_LOADED__${SCWRYPTS_LIBRARY_GROUP}__$(echo ${SCWRYPTS_LIBRARY} | sed 's|[/-]|_|g')"
[[ $1 =~ ^--set$ ]] && eval ${VARIABLE_NAME}=true
[[ ${(P)VARIABLE_NAME} =~ true ]]
}
use.get-metadata() {
jo \
LIBRARY_FILE="${LIBRARY_FILE}" \
SCWRYPTS_LIBRARY_GROUP="${SCWRYPTS_LIBRARY_GROUP}" \
SCWRYPTS_LIBRARY="${SCWRYPTS_LIBRARY}" \
scwryptsmodule="${scwryptsmodule}" \
;
}
#####################################################################
use.generate-cache() {
[ "${LIBRARY_CACHE_FILE}" ] || return 1
[ -f "${LIBRARY_CACHE_FILE}" ] && return 0
##########################################
mkdir -p -- "$(dirname -- "${LIBRARY_CACHE_FILE}")"
local IMPORTS=":${LIBRARY_FILE}:"
use.generate-cache.create-import $(use.get-metadata) > "${LIBRARY_CACHE_FILE}"
local METADATA SUBFILE SUBMODULE
while $(grep -q '^use\s' "${LIBRARY_CACHE_FILE}")
do
NEXT_IMPORT=$(grep '^use\s' ${LIBRARY_CACHE_FILE} | head -n1)
METADATA="$(eval "${NEXT_IMPORT} --meta")"
[ "${METADATA}" ] \
|| echo.error "error getting metadata for '${NEXT_IMPORT}'" \
|| return 1
SUBFILE="$(echo "${METADATA}" | jq -r .LIBRARY_FILE)"
[[ "${IMPORTS}" =~ ":${SUBFILE}:" ]] && {
grep -v "^${NEXT_IMPORT}$" "${LIBRARY_CACHE_FILE}" > "${LIBRARY_CACHE_FILE}.tmp"
mv "${LIBRARY_CACHE_FILE}.tmp" "${LIBRARY_CACHE_FILE}"
continue
}
IMPORTS+="${SUBFILE}:"
SUBMODULE="$(echo "${METADATA}" | jq -r .scwryptsmodule)"
use.generate-cache.create-import "${METADATA}" > "${LIBRARY_CACHE_FILE}.subfile"
{
sed -n '0,/^use\s/p' ${LIBRARY_CACHE_FILE} | sed '$d'
echo '() {'
cat "${LIBRARY_CACHE_FILE}.subfile"
echo '}'
sed -n '/^use\s/,$p' ${LIBRARY_CACHE_FILE} | sed '1d'
} > "${LIBRARY_CACHE_FILE}.tmp"
mv "${LIBRARY_CACHE_FILE}.tmp" "${LIBRARY_CACHE_FILE}"
rm "${LIBRARY_CACHE_FILE}.subfile"
done
}
use.generate-cache.create-import() {
local METADATA="$1"
local FILENAME="$(echo "${METADATA}" | jq -r .LIBRARY_FILE)"
local SCWRYPTSMODULE="$(echo "${METADATA}" | jq -r .scwryptsmodule)"
local GROUP="$(echo "${METADATA}" | jq -r .SCWRYPTS_LIBRARY_GROUP)"
local LIBRARY="$(echo "${METADATA}" | jq -r .SCWRYPTS_LIBRARY | sed 's|[/-]|_|g')"
local IS_LOADED_VARIABLE="SCWRYPTS_LIBRARY_LOADED__${GROUP}__${LIBRARY}"
echo "[[ \$${IS_LOADED_VARIABLE} =~ true ]] && return 0"
echo "export scwryptsmodule=${SCWRYPTSMODULE}"
sed "/^use\\s/aexport scwryptsmodule=${SCWRYPTSMODULE}" "${FILENAME}"
echo "${IS_LOADED_VARIABLE}=true"
echo "unset scwryptsmodule"
}

View File

@ -1,23 +0,0 @@
#####################################################################
DEPENDENCIES+=(
aws
)
REQUIRED_ENV+=()
#####################################################################
AWS() {
local ARGS=()
ARGS+=(--output json)
[ ! $CI ] && {
REQUIRED_ENV=(AWS_REGION AWS_ACCOUNT AWS_PROFILE) CHECK_ENVIRONMENT || return 1
ARGS+=(--profile $AWS_PROFILE)
ARGS+=(--region $AWS_REGION)
}
aws ${ARGS[@]} $@
}

Some files were not shown because too many files have changed in this diff Show More