Compare commits

..

1 Commits

Author SHA1 Message Date
b7a86e9841 - new workflows to test and publish aur builds 2024-04-14 01:42:46 -06:00
282 changed files with 10616 additions and 12033 deletions

View File

@ -22,14 +22,10 @@ executors:
- image: node:18
resource_class: medium
zsh:
docker:
- image: alpine:3
resource_class: small
commands:
archlinux-run:
description: execute steps in the archlinux container as the CI user
description: execute command steps in the archlinux container from the CI user
parameters:
_name:
type: string
@ -48,7 +44,7 @@ commands:
custom:
archlinux:
prepare:
- &archlinux-prepare
- &archlinux_prepare
run:
name: prepare archlinux dependencies
command: |
@ -56,26 +52,19 @@ custom:
useradd -m ci
echo "ci ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
temp-downgrade-fakeroot:
- &archlinux-temp-downgrade-fakeroot
run:
name: downgrade fakeroot to v1.34 (v1.35 and v1.36 are confirmed to break)
command: |
pacman -U --noconfirm https://archive.archlinux.org/packages/f/fakeroot/fakeroot-1.34-1-x86_64.pkg.tar.zst
clone-aur:
- &archlinux-clone-aur
- &archlinux_clone_aur
archlinux-run:
_name: clone aur/scwrypts
command: git clone https://aur.archlinux.org/scwrypts.git aur
clone-scwrypts:
- &archlinux-clone-scwrypts
- &archlinux_clone_scwrypts
run:
name: clone wrynegade/scwrypts
working_directory: /home/ci
command: |
GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git clone -b "$(echo $CIRCLE_BRANCH | grep . || echo $CIRCLE_TAG)" "$CIRCLE_REPOSITORY_URL" scwrypts
GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git clone -b "$CIRCLE_BRANCH" "$CIRCLE_REPOSITORY_URL" scwrypts
chown -R ci:ci ./scwrypts
@ -94,10 +83,9 @@ jobs:
aur-test:
executor: archlinux
steps:
- *archlinux-prepare
- *archlinux-temp-downgrade-fakeroot
- *archlinux-clone-aur
- *archlinux-clone-scwrypts
- *archlinux_prepare
- *archlinux_clone_aur
- *archlinux_clone_scwrypts
- archlinux-run:
_name: test aur build on current source
working_directory: /home/ci/aur
@ -114,22 +102,21 @@ jobs:
&& echo "source=(scwrypts.tar.gz)" >> PKGBUILD
&& echo "sha256sums=(SKIP)" >> PKGBUILD
&& makepkg --noconfirm -si
&& echo validating scwrypts version
&& scwrypts --version | grep "^scwrypts v$PKGVER$"
;
aur-publish:
executor: archlinux
steps:
- *archlinux-prepare
- *archlinux-temp-downgrade-fakeroot
- *archlinux-clone-aur
- *archlinux_prepare
- *archlinux_clone_aur
- archlinux-run:
_name: update PKGBUILD and .SRCINFO
working_directory: /home/ci/aur
command: >-
:
&& NEW_VERSION=$(echo $CIRCLE_TAG | sed 's/^v//')
&& NEW_VERSION=4.2.0
&& sed "s/pkgver=.*/pkgver=$NEW_VERSION/; s/^pkgrel=.*/pkgrel=1/; /sha256sums/d" PKGBUILD -i
&& makepkg -g >> PKGBUILD
&& makepkg --printsrcinfo > .SRCINFO
@ -153,7 +140,7 @@ jobs:
&& eval $(ssh-agent)
&& echo -e $SSH_KEY_PRIVATE__AUR | ssh-add -
&& git remote add upstream ssh://aur@aur.archlinux.org/scwrypts.git
&& GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git push upstream
&& echo 'DO THE GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git push upstream'
;
nodejs-test:
@ -247,66 +234,23 @@ 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:
jobs:
- aur-test:
&dev-filters
&dev_filters
filters:
branches:
ignore: /^main$/
- python-test: *dev-filters
- nodejs-test: *dev-filters
- zsh-test: *dev-filters
- python-test: *dev_filters
- nodejs-test: *dev_filters
publish:
jobs:
- require-full-semver:
filters:
&only-run-on-full-semver-tag-filters
tags:
only: /^v\d+\.\d+\.\d+.*$/
branches:
@ -314,39 +258,20 @@ workflows:
- aur-test:
&only-publish-for-full-semver
filters: *only-run-on-full-semver-tag-filters
requires:
- require-full-semver
requires: [require-full-semver]
- aur-publish:
#
# there's a crazy-low-chance race-condition between this job and the GH Action '../.github/workflows/automatic-release.yaml'
# - automatic-release creates the release artifact, but takes no more than 15-30 seconds (current avg:16s max:26s)
# - this publish step requires the release artifact, but waits for all language-repository publishes to complete first (a few minutes at least)
#
# if something goes wrong, this step can be safely rerun after fixing the release artifact :)
#
filters: *only-run-on-full-semver-tag-filters
context: [aur-yage]
requires:
- aur-test
- python-publish
- nodejs-publish
- zsh-test
- python-test: *only-publish-for-full-semver
- python-publish:
filters: *only-run-on-full-semver-tag-filters
context: [pypi-yage]
requires:
- python-test
- zsh-test
- nodejs-test: *only-publish-for-full-semver
- nodejs-publish:
filters: *only-run-on-full-semver-tag-filters
context: [npm-wrynegade]
requires:
- nodejs-test
- zsh-test
- zsh-test: *only-publish-for-full-semver

View File

@ -1,89 +0,0 @@
#!/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'"

View File

@ -12,9 +12,13 @@ export DISCORD__DEFAULT_AVATAR_URL=
export DISCORD__DEFAULT_CHANNEL_ID=
export DISCORD__DEFAULT_USERNAME=
export DISCORD__DEFAULT_WEBHOOK=
export I3__BORDER_PIXEL_SIZE=
export I3__DMENU_FONT_SIZE=
export I3__GLOBAL_FONT_SIZE=
export I3__MODEL_CONFIG=
export LINEAR__API_TOKEN=
export MEDIA_SYNC__S3_BUCKET=
export MEDIA_SYNC__TARGETS=
export MEDIA_SYNC__S3_BUCKET
export MEDIA_SYNC__TARGETS
export REDIS_AUTH=
export REDIS_HOST=
export REDIS_PORT=

View File

@ -15,6 +15,11 @@ DISCORD__DEFAULT_CHANNEL_ID |
DISCORD__DEFAULT_USERNAME |
DISCORD__DEFAULT_WEBHOOK |
I3__BORDER_PIXEL_SIZE | custom i3 configuration settings
I3__DMENU_FONT_SIZE |
I3__GLOBAL_FONT_SIZE |
I3__MODEL_CONFIG |
LINEAR__API_TOKEN | linear.app project management configuration
MEDIA_SYNC__S3_BUCKET | s3 bucket name and filesystem targets for media backups

View File

@ -1,66 +0,0 @@
---
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,76 +1,59 @@
# *Scwrypts*
# *Scwrypts* (Wryn + Scripts)
Scwrypts is a CLI and API for safely running scripts in the terminal, CI, and other automated environments.
Scwrypts is a friendly CLI / API for quickly running *sandboxed scripts* in the terminal.
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!
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.
## Major Version Upgrade Notice
Please refer to [Version 4 to Version 5 Upgrade Path](./docs/upgrade/v4-to-v5.md) when upgrading from scwrypts v4 to scwrypts v5!
Please refer to [Version 3 to Version 4 Upgrade Path](./docs/upgrade/v3-to-v4.md) when upgrading from scwrypts v3 to scwrypts v4!
## Installation
## 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.
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)
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.
```bash
# AUR
yay -Syu scwrypts
## 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`).
# homebrew
brew install wrynegade/scwrypts
```console
% cd <path-to-cloned-repo>
% echo "source $(pwd)/scwrypts.plugin.zsh >> $HOME/.zshrc"
```
### Manual Installation
Check out [Meta Scwrypts](./zsh/scwrypts) to quickly set up environments and adjust configuration.
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`:
### No Install / API Usage
Alternatively, the `scwrypts` API can be used directly:
```zsh
source /path/to/cloned-repo/scwrypts.plugin.zsh
./scwrypts [--env environment-name] (...script-name-patterns...) [-- ...passthrough arguments... ]
```
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 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.
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"
```
Given no script patterns, Scwrypts becomes an interactive CLI, prompting the user to select a command.
#### 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
```
After determining which script to run, if no environment has been specified, Scwrypts prompts the user to choose one.
## Usage in CI and Automated Environments
Set environment variable `CI=true` to run scwrypts in an automated environment.
### Using in CI/CD or Automated Workflows
Set environment variable `CI=true` (and use the no install method) to run in an automated pipeline.
There are a few notable changes to this runtime:
- **The Scwrypts sandbox environment will not load.** All variables will be read directly from the current context.
- **The Scwrypts sandbox environment will not load.** All variables will be read from context.
- User yes/no prompts will **always be YES**
- Other user input will default to an empty string
- 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
- 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)
- In GitHub actions, `*.scwrypts.zsh` groups are detected automatically from the `$GITHUB_WORKSPACE`; set `SCWRYPTS_GITHUB_NO_AUTOLOAD=true` to disable
## Contributing

View File

@ -1,6 +1,6 @@
___________________________
___________________________
< ~thanks for your support~ >
---------------------------
---------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\

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
#!/usr/bin/env zsh
#!/bin/zsh
#####################################################################
DEPENDENCIES+=(dep-function-a dep-function-b)
REQUIRED_ENV+=()
@ -69,11 +69,11 @@ CHECK_ENVIRONMENT
- echo "do some stuff here"
- # ... etc ...
- echo.success "completed the stuff"
- SUCCESS "completed the stuff"
+ MAIN() {
+ echo "do some stuff here"
+ # ... etc ...
+ echo.success "completed the stuff
+ 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() {
echo.success "look at me I'm so cool I already wrote this in a main function"
SUCCESS "look at me I'm so cool I already wrote this in a main function"
}
-
- #####################################################################
@ -115,20 +115,20 @@ 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
#!/usr/bin/env zsh
#!/bin/zsh
- #####################################################################
DEPENDENCIES+=(dep-function-a dep-function-b)
- REQUIRED_ENV+=()
use do/awesome/stuff --group my-custom-library
-
-
- CHECK_ENVIRONMENT
#####################################################################
MAIN() {
echo "do some stuff here"
# ... etc ...
echo.success "completed the stuff
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
#!/usr/bin/env zsh
#!/bin/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
#!/usr/bin/env zsh
#!/bin/zsh
DEPENDENCIES+=(dep-function-a dep-function-b)
use do/awesome/stuff --group my-custom-library

View File

@ -1,136 +0,0 @@
# 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

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

View File

@ -1,3 +1,10 @@
# CI Helper
# Kubernetes `kubectl` Helper Plugin
Disabled by default, this is used in CI contexts to try and identify missing requirements for the current workflow.
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`.

View File

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

View File

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

View File

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

View File

@ -1,10 +0,0 @@
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

@ -1,18 +0,0 @@
#####################################################################
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

@ -1,56 +0,0 @@
#####################################################################
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

@ -1,17 +0,0 @@
#
# 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

@ -1,23 +0,0 @@
${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

@ -1,77 +0,0 @@
#####################################################################
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

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

View File

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

View File

@ -1,18 +1,19 @@
[[ $SCWRYPTS_KUBECTL_DRIVER_READY -eq 1 ]] && return 0
unalias k h f >/dev/null 2>&1
unalias k h >/dev/null 2>&1
k() { _SCWRYPTS_KUBECTL_DRIVER kubectl $@; }
h() { _SCWRYPTS_KUBECTL_DRIVER helm $@; }
f() { _SCWRYPTS_KUBECTL_DRIVER flux $@; }
_SCWRYPTS_KUBECTL_DRIVER() {
[ ! $SCWRYPTS_ENV ] && {
echo.error "must set SCWRYPTS_ENV in order to use '$(echo $CLI | head -c1)'"
ERROR "must set SCWRYPTS_ENV in order to use '$(echo $CLI | head -c1)'"
return 1
}
which kube.redis >/dev/null 2>&1 \
|| eval "$(scwrypts -n --name meta/get-static-redis-definition --type zsh --group kube)"
which REDIS >/dev/null 2>&1 \
|| eval "$(scwrypts -n --name meta/get-static-redis-definition --type zsh --group kubectl)"
local CLI="$1"; shift 1
@ -38,16 +39,16 @@ _SCWRYPTS_KUBECTL_DRIVER() {
description: -
"
local USAGE__usage=$(echo $CLI | head -c1)
local USAGE__usage=$(echo $CLI | head -c1)
local USAGE__args="$(
{
echo "$(utils.colors.print green '[0-9]')^if the first argument is a number 0-9, uses or creates a subsession (default 0)"
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 " ^ "
for C in ${CUSTOM_COMMANDS[@]}
do
echo "$(utils.colors.print green ${C})^$(SCWRYPTS_KUBECTL_CUSTOM_COMMAND_DESCRIPTION__$C 2>/dev/null)"
done
echo "\\033[0;32m$C\\033[0m^$(SCWRYPTS_KUBECTL_CUSTOM_COMMAND_DESCRIPTION__$C 2>/dev/null)"
done
} | column -ts '^'
)"
@ -61,18 +62,18 @@ _SCWRYPTS_KUBECTL_DRIVER() {
utility. These functions leverage redis and scwrypts environments to
allow quick selection of contexts and namespaces usable across all
active shell instances.
The scwrypts group 'kubectl' has simple selection executables for
kubecontext and namespace, but also provides the library to enable
enriched, use-case-sensitive setup of kubernetes context.
All actions are scoped to the current SCWRYPTS_ENV
currently : $(utils.colors.print yellow ${SCWRYPTS_ENV})
currently : \\033[0;33m$SCWRYPTS_ENV\\033[0m
"
##########################################
local USER_ARGS=()
local CUSTOM_COMMAND=0
@ -134,9 +135,9 @@ _SCWRYPTS_KUBECTL_DRIVER() {
while [[ $# -gt 0 ]]; do USER_ARGS+=($1); shift 1; done
utils.check-errors || return 1
CHECK_ERRORS --no-fail || return 1
[[ $HELP -eq 1 ]] && { utils.io.usage; return 0; }
[[ $HELP -eq 1 ]] && { USAGE; return 0; }
#####################################################################
@ -154,12 +155,12 @@ _SCWRYPTS_KUBECTL_DRIVER() {
[ $CONTEXT ] && [[ $CLI =~ ^flux$ ]] && CLI_ARGS+=(--context $CONTEXT)
[[ $STRICT -eq 1 ]] && {
[ $CONTEXT ] || echo.error "missing kubectl 'context'"
[ $NAMESPACE ] || echo.error "missing kubectl 'namespace'"
[ $CONTEXT ] || ERROR "missing kubectl 'context'"
[ $NAMESPACE ] || ERROR "missing kubectl 'namespace'"
utils.check-errors --no-fail --no-usage || {
echo.error "with 'strict' settings enabled, context and namespace must be set!"
echo.reminder "
CHECK_ERRORS --no-fail --no-usage || {
ERROR "with 'strict' settings enabled, context and namespace must be set!"
REMINDER "
these values can be set directly with
$(echo $CLI | head -c1) meta set (namespace|context)
"
@ -170,16 +171,16 @@ _SCWRYPTS_KUBECTL_DRIVER() {
[ $NAMESPACE ] && CLI_ARGS+=(--namespace $NAMESPACE)
[[ $VERBOSE -eq 1 ]] && {
echo.reminder "
REMINDER "
context '$CONTEXT'
namespace '$NAMESPACE'
environment '$SCWRYPTS_ENV'
subsession '$SUBSESSION'
"
echo.status "running $CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}"
STATUS "running $CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}"
} || {
[[ $(_SCWRYPTS_KUBECTL_SETTINGS get context) =~ ^show$ ]] && {
echo.reminder "$SCWRYPTS_ENV.$SUBSESSION : $CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}"
REMINDER "$SCWRYPTS_ENV.$SUBSESSION : $CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}"
}
}
$CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}
@ -190,7 +191,7 @@ _SCWRYPTS_KUBECTL_DRIVER() {
_SCWRYPTS_KUBECTL_SETTINGS() {
# (get setting-name) or (set setting-name setting-value)
kube.redis h$1 ${SCWRYPTS_ENV}:kubectl:settings ${@:2} | grep .
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 $?
;;
( * ) echo.error "cannot set '$2'" ;;
* ) 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 ;;
( * ) echo.error "cannot get '$2'" ;;
* ) 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 ;;
( * ) echo.error "target session must be a number [0-9]" ;;
[0-9] ) USER_ARGS+=($1 $2) ;;
-h | --help ) HELP=1 ;;
* ) 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) ;;
( * ) echo.error "no meta command '$1'"
* ) 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=$(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")
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")
echo "
environment : $SCWRYPTS_ENV
context : $CONTEXT
@ -92,53 +92,51 @@ SCWRYPTS_KUBECTL_CUSTOM_COMMAND__meta() {
CLI settings
command context : $(_SCWRYPTS_KUBECTL_SETTINGS get context)
strict mode : $([[ $STRICT -eq 1 ]] && utils.colors.print green on || utils.colors.print bright-red off)
strict mode : $([[ $STRICT -eq 1 ]] && echo "on" || echo "\\033[1;31moff\\033[0m")
" | sed 's/^ \+//' >&2
return 0
}
kube.redis get --prefix current:$2
REDIS get --prefix current:$2
;;
( set )
set )
scwrypts -n --name set-$2 --type zsh --group kubectl -- $3 --subsession $SUBSESSION >/dev/null \
&& SUCCESS "$2 set"
;;
copy )
: \
&& scwrypts -n --name set-$2 --type zsh --group kube -- $3 --subsession $SUBSESSION >/dev/null \
&& k $SUBSESSION meta get $2 \
&& 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" \
;
;;
( 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" \
;
clear )
scwrypts -n --name set-context --type zsh --group kubectl -- --subsession $SUBSESSION reset >/dev/null \
&& SUCCESS "subsession $SUBSESSION reset to default"
;;
( clear )
scwrypts -n --name set-context --type zsh --group kube -- --subsession $SUBSESSION reset >/dev/null \
&& echo.success "subsession $SUBSESSION reset to default"
;;
( show )
show )
_SCWRYPTS_KUBECTL_SETTINGS set context show >/dev/null \
&& echo.success "now showing full command context"
&& SUCCESS "now showing full command context"
;;
( hide )
hide )
_SCWRYPTS_KUBECTL_SETTINGS set context hide >/dev/null \
&& echo.success "now hiding command context"
&& SUCCESS "now hiding command context"
;;
( loose )
loose )
_SCWRYPTS_KUBECTL_SETTINGS set strict 0 >/dev/null \
&& echo.warning "now running in 'loose' mode"
&& WARNING "now running in 'loose' mode"
;;
( strict )
strict )
_SCWRYPTS_KUBECTL_SETTINGS set strict 1 >/dev/null \
&& echo.success "now running in 'strict' mode"
&& SUCCESS "now running in 'strict' mode"
;;
esac
}

View File

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

View File

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

View File

@ -0,0 +1,11 @@
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

@ -0,0 +1,158 @@
#####################################################################
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

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

View File

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

View File

@ -1,5 +1,5 @@
#!/usr/bin/env zsh
use kubectl --group kube
#!/bin/zsh
use kubectl --group kubectl
#####################################################################
MAIN() {
@ -12,7 +12,7 @@ MAIN() {
options:
--context override context
--namespace override namespace
--subsession kube.redis subsession (default 0)
--subsession 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 ) utils.io.usage; return 0 ;;
-h | --help ) USAGE; return 0 ;;
* )
[ $SERVICE ] && echo.error "unexpected argument '$2'"
[ $SERVICE ] && ERROR "unexpected argument '$2'"
SERVICE=$1
;;
esac
shift 1
done
utils.check-errors --fail
CHECK_ERRORS
kube.kubectl.serve
KUBECTL__SERVE
}

View File

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

View File

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

View File

@ -1,27 +1,10 @@
from json import loads
from .scwrypts import scwrypts
from os import getenv as os_getenv
from .scwrypts.exceptions import MissingVariableError
ENV = {}
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)
def getenv(name, required=True):
value = os_getenv(name, None)
if required and not value:
raise MissingVariableError(name)

View File

@ -2,22 +2,23 @@ from types import SimpleNamespace
from pytest import fixture
from scwrypts.test import get_generator
from scwrypts.test import generate
from scwrypts.test.character_set import uri
generate = get_generator({
options = {
'str_length_minimum': 8,
'str_length_maximum': 128,
'uuid_output_type': str,
})
}
def get_request_client_sample_data():
return {
'base_url' : generate(str, {'character_set': uri}),
'endpoint' : generate(str, {'character_set': uri}),
'method' : generate(str),
'response' : generate('requests_Response', {'depth': 4}),
'base_url' : generate(str, options | {'character_set': uri}),
'endpoint' : generate(str, options | {'character_set': uri}),
'method' : generate(str, options),
'response' : generate('requests_Response', options | {'depth': 4}),
'payload' : generate(dict, {
**options,
'depth': 1,
'data_types': { str, 'uuid' },
}),
@ -29,11 +30,13 @@ def fixture_sample():
**get_request_client_sample_data(),
headers = generate(dict, {
**options,
'depth': 1,
'data_types': { str, 'uuid' },
}),
payload_headers = generate(dict, {
**options,
'depth': 1,
'data_types': { str, 'uuid' },
}),

View File

@ -2,14 +2,15 @@ from types import SimpleNamespace
from pytest import fixture
from scwrypts.test import generate
from scwrypts.test.character_set import uri
from ..conftest import generate, get_request_client_sample_data
from ..conftest import options, get_request_client_sample_data
@fixture(name='sample')
def fixture_sample():
return SimpleNamespace(
**get_request_client_sample_data(),
api_token = generate(str, {'character_set': uri}),
query = generate(str),
api_token = generate(str, options | {'character_set': uri}),
query = generate(str, options),
)

View File

@ -3,8 +3,9 @@ from types import SimpleNamespace
from pytest import fixture
from scwrypts.test import generate
from scwrypts.test.character_set import uri
from ..conftest import generate, get_request_client_sample_data
from ..conftest import options, get_request_client_sample_data
@fixture(name='sample')
def fixture_sample():
@ -13,12 +14,12 @@ def fixture_sample():
**get_request_client_sample_data(),
'base_url': 'https://discord.com/api',
},
bot_token = generate(str, {'character_set': uri}),
username = generate(str, {'character_set': ascii_letters + digits}),
avatar_url = generate(str, {'character_set': uri}),
webhook = generate(str, {'character_set': uri}),
channel_id = generate(str, {'character_set': uri}),
content_header = generate(str),
content_footer = generate(str),
content = generate(str),
bot_token = generate(str, options | {'character_set': uri}),
username = generate(str, options | {'character_set': ascii_letters + digits}),
avatar_url = generate(str, options | {'character_set': uri}),
webhook = generate(str, options | {'character_set': uri}),
channel_id = generate(str, options | {'character_set': uri}),
content_header = generate(str, options),
content_footer = generate(str, options),
content = generate(str, options),
)

View File

@ -5,7 +5,7 @@ from pytest import fixture
from .client import request
def test_discord_request(sample, _mock_getenv, _response):
def test_discord_request(sample, _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

@ -3,8 +3,9 @@ from types import SimpleNamespace
from pytest import fixture
from scwrypts.test import generate
from scwrypts.test.character_set import uri
from ..conftest import generate, get_request_client_sample_data
from ..conftest import options, get_request_client_sample_data
@fixture(name='sample')
def fixture_sample():
@ -13,6 +14,6 @@ def fixture_sample():
**get_request_client_sample_data(),
'base_url': 'https://api.linear.app',
},
api_token = generate(str, {'character_set': uri}),
query = generate(str),
api_token = generate(str, options | {'character_set': uri}),
query = generate(str, options),
)

View File

@ -13,7 +13,7 @@ class MissingFlagAndEnvironmentVariableError(EnvironmentError, ArgumentError):
class MissingScwryptsExecutableError(EnvironmentError):
def __init__(self):
super().__init__('scwrypts must be installed and available on your PATH')
super().__init__(f'scwrypts must be installed and available on your PATH')
class BadScwryptsLookupError(ValueError):

View File

@ -5,54 +5,44 @@ from subprocess import run
from .exceptions import MissingScwryptsExecutableError, BadScwryptsLookupError, MissingScwryptsGroupOrTypeError
def scwrypts(patterns=None, args=None, executable_args=None, name=None, group=None, _type=None):
def scwrypts(*args, patterns=None, name=None, group=None, _type=None, log_level=None):
'''
top-level scwrypts invoker from python
patterns str / list pattern-based scwrypt lookup
args str / list arguments forwarded to the invoked scwrypt
executable_args str / list arguments for the 'scwrypts' executable
(str above assumes space-delimited values)
- patterns allows for pattern-based scwrypt lookup
- name/group/type allos for precise-match lookup
name str exact scwrypt lookup name (requires group and _type)
group str exact scwrypt lookup group
_type str exact scwrypt lookup type
SCWRYPTS_EXECUTABLE configuration variable which defines the full path to scwrypts executable
*args should be a list of strings and is forwarded to the
invoked scwrypt
see 'scwrypts --help' for more information
'''
if patterns is None and name is None:
raise BadScwryptsLookupError()
if name is not None and (group is None or _type is None):
raise MissingScwryptsGroupOrTypeError(group, _type)
executable = which(getenv('SCWRYPTS_EXECUTABLE', 'scwrypts'))
executable = which('scwrypts')
if executable is None:
raise MissingScwryptsExecutableError()
lookup = _parse(patterns) if name is None else f'--name {name} --group {group} --type {_type}'
if patterns is None and name is None:
raise BadScwryptsLookupError()
pre_args = []
if name is None:
pre_args += patterns
else:
pre_args += ['--name', name, '--group', group, '--type', _type]
if group is None or _type is None:
raise MissingScwryptsGroupOrTypeError(group, _type)
if log_level is not None:
pre_args += ['--log-level', log_level]
depth = getenv('SUBSCWRYPT', '')
if depth != '':
depth = int(depth) + 1
return run(
f'SUBSCWRYPT={depth} {executable} {_parse(executable_args)} {lookup} -- {_parse(args)}',
f'SUBSCWRYPT={depth} {executable} {" ".join(pre_args)} -- {" ".join(args)}',
shell=True,
executable='/bin/zsh',
check=False,
capture_output=True,
text=True,
)
def _parse(string_or_list_args):
if string_or_list_args is None:
return ''
if isinstance(string_or_list_args, list):
return ' '.join(string_or_list_args)
return str(string_or_list_args)

View File

@ -1,184 +0,0 @@
from random import choice
from re import search
from string import ascii_letters, digits
from types import SimpleNamespace
from unittest.mock import patch
from pytest import fixture, raises
from scwrypts.test import get_generator
from .exceptions import MissingScwryptsExecutableError, BadScwryptsLookupError, MissingScwryptsGroupOrTypeError
from .scwrypts import scwrypts
#####################################################################
def test_scwrypts(sample, _scwrypts):
assert validate_scwrypts_output(sample, _scwrypts)
def test_scwrypts_finds_system_executable(sample, _scwrypts, mock_which):
mock_which.assert_called_once_with(sample.env['SCWRYPTS_EXECUTABLE'])
def test_scwrypts_uses_configured_executable_path(_scwrypts, mock_getenv):
mock_getenv.assert_any_call('SCWRYPTS_EXECUTABLE', 'scwrypts')
def test_scwrypts_uses_correct_depth(_scwrypts, mock_getenv):
mock_getenv.assert_any_call('SUBSCWRYPT', '')
def test_scwrypts_runs_subprocess(_scwrypts, mock_run):
mock_run.assert_called_once()
##########################################
def test_scwrypts_omit_optionals(sample, _scwrypts_omit_optionals):
assert validate_scwrypts_output(sample, _scwrypts_omit_optionals)
def test_scwrypts_omit_optionals_finds_system_executable(sample, _scwrypts_omit_optionals, mock_which):
mock_which.assert_called_once_with('scwrypts')
def test_scwrypts_omit_optionals_uses_configured_executable_path(_scwrypts_omit_optionals, mock_getenv):
mock_getenv.assert_any_call('SCWRYPTS_EXECUTABLE', 'scwrypts')
def test_scwrypts_omit_optionals_uses_correct_depth(_scwrypts_omit_optionals, mock_getenv):
mock_getenv.assert_any_call('SUBSCWRYPT', '')
def test_scwrypts_omit_optionals_runs_subprocess(_scwrypts_omit_optionals, mock_run):
mock_run.assert_called_once()
##########################################
def test_invalid_lookup_missing_patterns_and_name(sample):
sample.patterns = None
sample.name = None
with raises(BadScwryptsLookupError):
scwrypts(**get_scwrypts_args(sample))
def test_invalid_name_lookup_missing_group(sample):
sample.group = None
with raises(MissingScwryptsGroupOrTypeError):
scwrypts(**get_scwrypts_args(sample))
def test_invalid_name_lookup_missing_type(sample):
sample._type = None # pylint: disable=protected-access
with raises(MissingScwryptsGroupOrTypeError):
scwrypts(**get_scwrypts_args(sample))
def test_invalid_scwrypts_installation(sample, mock_which):
mock_which.return_value = None
with raises(MissingScwryptsExecutableError):
scwrypts(**get_scwrypts_args(sample))
#####################################################################
generate = get_generator({
'str_length_minimum': 8,
'str_length_maximum': 128,
'character_set': ascii_letters + digits + '/-_'
})
def _generate_str_or_list_arg():
random_arg = generate(list, {'data_types': {str}})
return random_arg if choice([str, list]) == list else ' '.join(random_arg)
@fixture(name='sample')
def fixture_sample():
sample = SimpleNamespace(
patterns = _generate_str_or_list_arg(),
args = _generate_str_or_list_arg(),
executable_args = _generate_str_or_list_arg(),
name = generate(str),
group = generate(str),
_type = generate(str),
executable = generate(str),
env = {
'SCWRYPTS_EXECUTABLE': generate(str),
'SUBSCWRYPT': str(generate(int, {'minimum': 1, 'maximum': 99})),
},
returncode = generate(int),
stdout = generate(str),
stderr = generate(str),
)
return sample
def get_scwrypts_args(sample):
return {
key: getattr(sample, key)
for key in [
'patterns',
'args',
'executable_args',
'name',
'group',
'_type',
]
}
#####################################################################
@fixture(name='mock_which', autouse=True)
def fixture_mock_which(sample):
with patch('scwrypts.scwrypts.scwrypts.which') as mock:
mock.return_value = sample.executable
yield mock
@fixture(name='mock_getenv', autouse=True)
def fixture_mock_getenv(sample):
with patch('scwrypts.scwrypts.scwrypts.getenv') as mock:
mock.side_effect = sample.env.get
yield mock
@fixture(name='mock_run', autouse=True)
def fixture_mock_run(sample):
with patch('scwrypts.scwrypts.scwrypts.run') as mock:
mock.side_effect = lambda *args, **_kwargs: SimpleNamespace(
args = args,
returncode = sample.returncode,
stdout = sample.stdout,
stderr = sample.stderr,
)
yield mock
#####################################################################
@fixture(name='_scwrypts')
def fixture_scwrypts(sample):
return scwrypts(**get_scwrypts_args(sample))
@fixture(name='_scwrypts_omit_optionals')
def fixture_scwrypts_omit_optionals(sample):
sample.args = None
sample.executable_args = None
del sample.env['SCWRYPTS_EXECUTABLE']
del sample.env['SUBSCWRYPT']
return scwrypts(**get_scwrypts_args(sample))
def validate_scwrypts_output(sample, output):
#
# I would love to use 'assert _scwrypts == SimpleNamespace(...expected...)'
# but the output.args is difficult to recreate without copying all the
# processing logic over from the scwrypts function
#
# opting for a bit of a strange equality test here, checking the args
# as closely as possible without copying parsing logic
#
run_args_reduced_to_a_single_string = len(output.args) == 1
run_args_follow_expected_form = search(
fr'^SUBSCWRYPT=.* {sample.executable} .*-- .*$',
output.args[0],
)
return all([
run_args_reduced_to_a_single_string,
run_args_follow_expected_form,
output.returncode == sample.returncode,
output.stdout == sample.stdout,
output.stderr == sample.stderr,
])

View File

@ -5,6 +5,6 @@ __all__ = [
'generate',
]
from .generate import generate, get_generator
from .generate import generate
from .character_set import *

View File

@ -4,11 +4,9 @@ from json import dumps, loads
from random import randint, uniform, choice
from re import sub
from string import printable
from typing import Callable
from typing import Hashable, Callable
from uuid import uuid4
from collections.abc import Hashable
from requests import Response, status_codes
from yaml import safe_dump
@ -47,21 +45,6 @@ DEFAULT_OPTIONS = {
'requests_response_status_code': status_codes.codes[200],
}
def get_generator(default_options=None):
if default_options is None:
default_options = {}
def generator_function(data_type=None, options_overrides=None):
if options_overrides is None:
options_overrides = {}
return generate(
data_type = data_type,
options = default_options | options_overrides,
)
return generator_function
def generate(data_type=None, options=None):
'''
generate random data with the call of a function

472
scwrypts
View File

@ -1,16 +1,16 @@
#!/usr/bin/env zsh
#!/bin/zsh
export EXECUTION_DIR=$(pwd)
export SCWRYPTS_RUNTIME_ID=$(uuidgen)
source "$(dirname -- $(readlink -f -- "$0"))/zsh/import.driver.zsh" || return 42
use scwrypts/environment
use scwrypts/list-available
use scwrypts/get-runstring
source "$(dirname $(readlink -f "$0"))/zsh/lib/import.driver.zsh" || exit 42
#####################################################################
() {
cd "$(scwrypts.config.group scwrypts root)"
GIT_SCWRYPTS() { git -C "$(scwrypts.config.group scwrypts root)" $@; }
cd "$SCWRYPTS_ROOT__scwrypts"
GIT_SCWRYPTS() { git -C "$SCWRYPTS_ROOT__scwrypts" $@; }
local INSTALLATION_TYPE
[ ! -d "$SCWRYPTS_ROOT__scwrypts/.git" ] && [ ! -f "$SCWRYPTS_ROOT__scwrypts/.git" ] \
&& INSTALLATION_TYPE=$(cat "$SCWRYPTS_ROOT__scwrypts/MANAGED_BY" 2>/dev/null) \
|| INSTALLATION_TYPE=manual \
;
local ERRORS=0
local USAGE='
@ -26,26 +26,20 @@ use scwrypts/get-runstring
runtime
-y, --yes auto-accept all [yn] prompts through current scwrypt
-e, --env <env-name> set environment; overwrites SCWRYPTS_ENV
-n shorthand for "--log-level 0"
-v, --log-level <0-4> set incremental scwrypts log level to one of the following:
0 : only command output and critical failures; skips logfile
1 : include success / failure messages
2 : include status update messages
3 : (default) include warning messages
4 : include debug messages
-o, --output <format> specify output format; one of: pretty,json (default: pretty)
-n shorthand for "--log-level 0"
-v, --log-level [0-4] set incremental scwrypts log level to one of the following:
0 : only command output and critical failures; skips logfile
1 : include success / failure messages
2 : include status update messages
3 : (default) include warning messages
4 : include debug messages
alternate commands
-h, --help display this message and exit
-l, --list print out command list and exit
--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.config.group.scwrypts.root and exit
--update update scwrypts library to latest version
--version print out scwrypts version and exit
-h, --help display this message and exit
-l, --list print out command list and exit
--list-envs print out environment list and exit
--update update scwrypts library to latest version
--version print out scwrypts version and exit
patterns:
- a list of glob patterns to loose-match a scwrypt by name
@ -59,78 +53,55 @@ use scwrypts/get-runstring
### 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 )
utils.io.usage
-h | --help )
USAGE
return 0
;;
( -l | --list )
scwrypts.list-available
-l | --list )
SCWRYPTS__GET_AVAILABLE_SCWRYPTS
return 0
;;
( --list-envs )
scwrypts.environment.common.get-env-names
--list-envs )
SCWRYPTS__GET_ENV_NAMES
return 0
;;
( --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.config.group scwrypts root)/VERSION")" ;;
--version )
case $INSTALLATION_TYPE in
manual ) echo "scwrypts $(GIT_SCWRYPTS describe --tags) (via GIT)" ;;
* ) echo scwrypts $(cat "$SCWRYPTS_ROOT__scwrypts/VERSION") ;;
esac
return 0
;;
( --root )
scwrypts.config.group scwrypts root
return 0
;;
( --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 $INSTALLATION_TYPE in
aur )
echo.reminder --force-print "
SCWRYPTS_LOG_LEVEL=3 REMINDER "
This installation is built from the AUR. Update through 'makepkg' or use
your preferred AUR package management tool (e.g. 'yay -Syu scwrypts')
"
;;
homebrew )
echo.reminder --force-print "This installation is managed by homebrew. Update me with 'brew update'"
;;
manual )
GIT_SCWRYPTS fetch --quiet origin main
GIT_SCWRYPTS fetch --quiet origin main --tags
@ -139,22 +110,22 @@ use scwrypts/get-runstring
GIT_SCWRYPTS diff --exit-code origin/main -- . >/dev/null 2>&1
local DIFF_STATUS=$?
[[ ${SYNC_STATUS} -eq 0 ]] && [[ ${DIFF_STATUS} -eq 0 ]] && {
echo.success 'already up-to-date with origin/main'
[[ $SYNC_STATUS -eq 0 ]] && [[ $DIFF_STATUS -eq 0 ]] && {
SUCCESS 'already up-to-date with origin/main'
} || {
GIT_SCWRYPTS rebase --autostash origin/main \
&& echo.success 'up-to-date with origin/main' \
&& SUCCESS 'up-to-date with origin/main' \
&& GIT_SCWRYPTS log -n1 \
|| {
GIT_SCWRYPTS rebase --abort
echo.error 'unable to update scwrypts; please try manual upgrade'
echo.reminder "installation in '$(scwrypts.config.group scwrypts root)'"
ERROR 'unable to update scwrypts; please try manual upgrade'
REMINDER "installation in '$SCWRYPTS_ROOT__scwrypts'"
}
}
;;
* )
echo.reminder --force-print "
SCWRYPTS_LOG_LEVEL=3 REMINDER "
This is a managed installation of scwrypts. Please update through your
system package manager.
"
@ -165,236 +136,213 @@ use scwrypts/get-runstring
### scwrypts filters #####################
( -m | --name )
((SHIFT_COUNT+=1))
[ $2 ] || { echo.error "missing value for argument $1"; break; }
-m | --name )
[ $2 ] || { ERROR "missing value for argument $1"; break; }
SEARCH_NAME=$2
shift 1
;;
( -g | --group )
((SHIFT_COUNT+=1))
[ $2 ] || { echo.error "missing value for argument $1"; break; }
-g | --group )
[ $2 ] || { ERROR "missing value for argument $1"; break; }
SEARCH_GROUP=$2
GROUP=$2
shift 1
;;
( -t | --type )
((SHIFT_COUNT+=1))
[ $2 ] || { echo.error "missing value for argument $1"; break; }
-t | --type )
[ $2 ] || { ERROR "missing value for argument $1"; break; }
SEARCH_TYPE=$2
TYPE=$2
shift 1
;;
### runtime settings #####################
( -y | --yes ) export __SCWRYPTS_YES=1 ;;
-y | --yes ) export __SCWRYPTS_YES=1 ;;
( -n ) SCWRYPTS_LOG_LEVEL=0 ;;
-n | --no-log )
SCWRYPTS_LOG_LEVEL=0
[[ $1 =~ ^--no-log$ ]] && WARNING 'the --no-log flag is deprecated and will be removed in scwrypts v4.2'
;;
( -v | --log-level )
((SHIFT_COUNT+=1))
[[ $2 =~ ^[0-4]$ ]] || echo.error "invalid setting for log-level '$2'"
-v | --log-level )
[[ $2 =~ ^[0-4]$ ]] || ERROR "invalid setting for log-level '$2'"
SCWRYPTS_LOG_LEVEL=$2
shift 1
;;
( -o | --output )
((SHIFT_COUNT+=1))
export SCWRYPTS_OUTPUT_FORMAT=$2
case ${SCWRYPTS_OUTPUT_FORMAT} in
( pretty | json ) ;;
* ) echo.error "unsupported format '${SCWRYPTS_OUTPUT_FORMAT}'" ;;
esac
;;
-e | --env )
[ $2 ] || { ERROR "missing value for argument $1"; break; }
( -e | --env )
((SHIFT_COUNT+=1))
[ $2 ] || { echo.error "missing value for argument $1"; break; }
[ ${ENV_NAME} ] && echo.debug 'overwriting session environment'
[ $ENV_NAME ] && DEBUG 'overwriting session environment'
ENV_NAME="$2"
echo.status "using CLI environment '${ENV_NAME}'"
STATUS "using CLI environment '$ENV_NAME'"
shift 1
;;
##########################################
( -- ) shift 1; break ;; # pass arguments after '--' to the scwrypt
( --* ) echo.error "unrecognized argument '$1'" ;;
( * ) SEARCH_PATTERNS+=($1) ;;
-- ) shift 1; break ;; # pass arguments after '--' to the scwrypt
--* ) ERROR "unrecognized argument '$1'" ;;
* ) SEARCH_PATTERNS+=($1) ;;
esac
[[ ${SHIFT_COUNT} -le $# ]] \
&& shift ${SHIFT_COUNT} \
|| echo.error "missing argument for '$1'" \
|| shift $# \
;
shift 1
done
[ ${SCWRYPTS_OUTPUT_FORMAT} ] || export SCWRYPTS_OUTPUT_FORMAT=pretty
[ ${SEARCH_NAME} ] && {
[ ${SEARCH_TYPE} ] || echo.error '--name requires --type argument'
[ ${SEARCH_GROUP} ] || echo.error '--name requires --group argument'
[ $SEARCH_NAME ] && {
[ $SEARCH_TYPE ] || ERROR '--name requires --type argument'
[ $SEARCH_GROUP ] || ERROR '--name requires --group argument'
}
utils.check-errors --fail
CHECK_ERRORS
#####################################################################
### scwrypts selection / filtering ##################################
#####################################################################
local SCWRYPTS_AVAILABLE=$(scwrypts.list-available)
local SCWRYPTS_AVAILABLE
SCWRYPTS_AVAILABLE=$(SCWRYPTS__GET_AVAILABLE_SCWRYPTS)
##########################################
[ ${SEARCH_NAME} ] && SCWRYPTS_AVAILABLE=$({
echo ${SCWRYPTS_AVAILABLE} | head -n1
echo ${SCWRYPTS_AVAILABLE} | utils.colors.remove | grep "^${SEARCH_NAME} *${SEARCH_TYPE} *${SEARCH_GROUP}\$"
[ $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_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/ \+$/'$(utils.colors.reset)'/; s/ \+/^/g' \
| awk '{$2=""; print $0;}' \
| sed 's/ \+$/'$(printf $__COLOR_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/ \+$/'$(utils.colors.reset)'/; s/ \+/^/g' \
| awk '{$NF=""; print $0;}' \
| sed 's/ \+$/'$(printf $__COLOR_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 ]] && {
utils.fail 1 "$(echo "
[[ $(echo $SCWRYPTS_AVAILABLE | wc -l) -lt 2 ]] && {
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} | utils.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 | FZF "select a script to run" --header-lines 1) \
;
[ ${SCWRYPT_SELECTION} ] || utils.abort
[ $SCWRYPT_SELECTION ] || exit 2
##########################################
() {
set -- $(echo $@ | utils.colors.remove)
export SCWRYPT_NAME=$1
export SCWRYPT_TYPE=$2
export SCWRYPT_GROUP=$3
} ${SCWRYPT_SELECTION}
local NAME TYPE GROUP
SCWRYPTS__SEPARATE_SCWRYPT_SELECTION $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="$(scwrypts.config.group ${SCWRYPT_GROUP} required_environment_regex)"
local REQUIRED_ENVIRONMENT_REGEX=$(eval echo '$SCWRYPTS_REQUIRED_ENVIRONMENT_REGEX__'$SCWRYPT_GROUP)
[ ${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_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_REQUIRED} =~ true ]] && {
[ ! ${ENV_NAME} ] && {
scwrypts.environment.init \
|| echo.error "failed to initialize scwrypts environments (see above)" \
|| return 1 \
;
ENV_NAME=$(scwrypts.environment.select-env)
[ "${ENV_NAME}" ] || user.abort
}
[[ $ENV_REQUIRED =~ true ]] && {
[ ! $ENV_NAME ] && ENV_NAME=$(SCWRYPTS__SELECT_ENV)
for GROUP in ${SCWRYPTS_GROUPS[@]}
do
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 $(find "$(scwrypts.config.group ${GROUP} root)/.config/static" -type f 2>/dev/null)
for f in $(eval 'echo $SCWRYPTS_STATIC_CONFIG__'$GROUP)
do
source "${f}" || utils.fail 5 "invalid static config '${f}'"
source "$f" || FAIL 5 "invalid static config '$f'"
done
done
export 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})"
[ $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)"
}
export SCWRYPTS_ENV=${ENV_NAME}
##########################################
[ ! ${SUBSCWRYPT} ] && export SUBSCWRYPT=0
[ ! $SUBSCWRYPT ] && export SUBSCWRYPT=0
[[ ${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"
[[ $INSTALLATION_TYPE =~ ^manual$ ]] && {
[[ $SUBSCWRYPT -eq 0 ]] && [[ $ENV_NAME =~ prod ]] && [[ $SCWRYPTS_LOG_LEVEL -gt 0 ]] && {
STATUS "on '$ENV_NAME'; 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 ]] && {
echo.reminder "you are running in $(utils.colors.bright-red)production$(utils.colors.bright-magenta) and ${WARNING_MESSAGE}"
[ $WARNING_MESSAGE ] && {
[[ $SCWRYPTS_LOG_LEVEL -lt 3 ]] && {
REMINDER "you are running in ${__BRIGHT_RED}production${__BRIGHT_MAGENTA} and $WARNING_MESSAGE"
} || {
GIT_SCWRYPTS diff --exit-code origin/main -- . >&2
echo.warning "you are trying to run in $(utils.colors.bright-red)production$(echo.warning.color) but ${WARNING_MESSAGE} (relevant diffs and errors above)"
WARNING "you are trying to run in ${__BRIGHT_RED}production${__YELLOW} but $WARNING_MESSAGE (relevant diffs and errors above)"
yN 'continue?' || {
echo.reminder "you can use 'scwrypts --update' to quickly update scwrypts to latest"
user.abort
REMINDER "you can use 'scwrypts --update' to quickly update scwrypts to latest"
ABORT
}
}
}
@ -403,123 +351,75 @@ use scwrypts/get-runstring
##########################################
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 \
&& [[ ${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" \
&& [[ $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" \
|| 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
( 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}
run at : $(date)
config : ${SCWRYPTS_ENV}
log level : ${SCWRYPTS_LOG_LEVEL}
$(utils.colors.print bright-yellow '--- SCWRYPT BEGIN ---------------------------------------------------')
" | sed 's/^\s\+//; 1d'
)
FOOTER="$(utils.colors.print bright-yellow '--- SCWRYPT END ---------------------------------------------------')"
;;
( json )
HEADER=$(echo '{}' | jq -c ".
| .timestamp = \"$(date +%s)\"
| .runtime = \"${SCWRYPTS_RUNTIME_ID}\"
| .scwrypt = \"start of ${SCWRYPT_NAME} ${SCWRYPT_GROUP} ${SCWRYPT_TYPE}\"
| .config = \"${SCWRYPTS_ENV}\"
| .logLevel = \"${SCWRYPTS_LOG_LEVEL}\"
| .subscwrypt = ${SUBSCWRYPT}
")
;;
esac
[[ $SCWRYPTS_LOG_LEVEL -ge 2 ]] && {
HEADER=$(
echo "
=====================================================================
script : $SCWRYPT_GROUP $SCWRYPT_TYPE $SCWRYPT_NAME
run at : $(date)
config : $ENV_NAME
log level : $SCWRYPTS_LOG_LEVEL
\\033[1;33m--- SCWRYPT BEGIN ---------------------------------------------------\\033[0m
" | sed 's/^\s\+//; 1d'
)
FOOTER="\\033[1;33m--- SCWRYPT END ---------------------------------------------------\\033[0m"
}
[[ ${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
[[ $SUBSCWRYPT -eq 0 ]] || {
HEADER="\\033[0;33m--- ($SUBSCWRYPT) BEGIN $SCWRYPT_GROUP $SCWRYPT_TYPE $SCWRYPT_NAME ---"
FOOTER="\\033[0;33m--- ($SUBSCWRYPT) END $SCWRYPT_GROUP $SCWRYPT_TYPE $SCWRYPT_NAME ---"
}
#####################################################################
### run the scwrypt #################################################
#####################################################################
set -o pipefail
{
[[ ${SCWRYPTS_LOG_LEVEL} -ge 2 ]] && __SCWRYPTS_PRINT_EXIT_CODE=true
[ $HEADER ] && echo $HEADER
case $RUN_MODE in
normal )
(eval "$RUN_STRING $(printf "%q " "$@")")
EXIT_CODE=$?
;;
no-logfile )
eval "$RUN_STRING $(printf "%q " "$@")"
EXIT_CODE=$?
;;
interactive )
eval "$RUN_STRING $(printf "%q " "$@")" </dev/tty >/dev/tty 2>&1
EXIT_CODE=$?
;;
esac
[ $FOOTER ] && echo $FOOTER
[[ $EXIT_CODE -eq 0 ]] && EXIT_COLOR='32m' || EXIT_COLOR='31m'
[ ${HEADER} ] && echo ${HEADER} >&2
[[ $SCWRYPTS_LOG_LEVEL -ge 2 ]] && [ ! $SUBSCWRYPT ] \
&& echo "terminated with\\033[1;$EXIT_COLOR code $EXIT_CODE\\033[0m"
(
case ${RUN_MODE} in
( normal )
eval "${RUN_STRING} $(printf "%q " "$@")"
;;
( interactive )
eval "${RUN_STRING} $(printf "%q " "$@")" </dev/tty &>/dev/tty
;;
esac
)
EXIT_CODE=$?
[ ${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}"
return $EXIT_CODE
} 2>&1 | 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

@ -1,223 +1,80 @@
#
# typically you do not need to reload this plugin in a single session;
# if for some reason you do, you can run the following command and
# source this file again
#
# unset __SCWRYPTS_PLUGIN_LOADED
#
[[ $__SCWRYPTS_PLUGIN_LOADED =~ true ]] && return 0
#####################################################################
command -v scwrypts &>/dev/null || {
echo 'scwrypts is required in your PATH in order to use the zsh plugins; skipping' >&2
return 0
}
NO_EXPORT_CONFIG=1 source "${0:a:h}/zsh/lib/import.driver.zsh" || return 42
#####################################################################
: \
&& command -v scwrypts &>/dev/null \
&& eval "$(scwrypts --config)" \
|| {
echo 'scwrypts must be in PATH and properly configured; skipping zsh plugin setup' >&2
SCWRYPTS__ZSH_PLUGIN() {
local SCWRYPT_SELECTION=$(scwrypts --list | FZF 'select a script' --header-lines 1)
local NAME
local TYPE
local GROUP
LBUFFER= RBUFFER=
[ ! $SCWRYPT_SELECTION ] && { zle accept-line; return 0; }
SCWRYPTS__SEPARATE_SCWRYPT_SELECTION $SCWRYPT_SELECTION
which scwrypts >/dev/null 2>&1\
&& RBUFFER="scwrypts" || RBUFFER="$SCWRYPTS_ROOT/scwrypts"
RBUFFER+=" --name $NAME --group $GROUP --type $TYPE"
zle accept-line
}
zle -N scwrypts SCWRYPTS__ZSH_PLUGIN
bindkey $SCWRYPTS_SHORTCUT scwrypts
#####################################################################
SCWRYPTS__ZSH_BUILDER_PLUGIN() {
local SCWRYPT_SELECTION=$(scwrypts --list | FZF 'select a script' --header-lines 1)
echo $SCWRYPT_SELECTION >&2
local NAME
local TYPE
local GROUP
LBUFFER= RBUFFER=
[ ! $SCWRYPT_SELECTION ] && { zle accept-line; return 0; }
SCWRYPTS__SEPARATE_SCWRYPT_SELECTION $SCWRYPT_SELECTION
scwrypts -n --name $NAME --group $GROUP --type $TYPE -- --help >&2 || {
zle accept-line
return 0
}
echo
__SCWRYPTS_PARSE() {
SCWRYPT_SELECTION=$(scwrypts --list | fzf --ansi --prompt 'select a script : ' --header-lines 1)
LBUFFER= RBUFFER=
[ $SCWRYPT_SELECTION ] || return 1
zle reset-prompt
which scwrypts >/dev/null 2>&1\
&& LBUFFER="scwrypts" || LBUFFER="$SCWRYPTS_ROOT/scwrypts"
NAME=$(echo "$SCWRYPT_SELECTION" | awk '{print $1;}')
TYPE=$(echo "$SCWRYPT_SELECTION" | awk '{print $2;}')
GROUP=$(echo "$SCWRYPT_SELECTION" | awk '{print $3;}')
[ $NAME ] && [ $TYPE ] && [ $GROUP ]
LBUFFER+=" --name $NAME --group $GROUP --type $TYPE -- "
}
zle -N scwrypts-builder SCWRYPTS__ZSH_BUILDER_PLUGIN
bindkey $SCWRYPTS_BUILDER_SHORTCUT scwrypts-builder
#####################################################################
[ $SCWRYPTS_SHORTCUT ] && {
SCWRYPTS__ZSH_PLUGIN() {
local SCWRYPT_SELECTION NAME TYPE GROUP
__SCWRYPTS_PARSE || { zle accept-line; return 0; }
SCWRYPTS__ZSH_PLUGIN_ENV() {
local RESET='reset'
local SELECTED=$(\
{ [ $SCWRYPTS_ENV ] && echo $RESET; SCWRYPTS__GET_ENV_NAMES; } \
| FZF 'select an environment' \
)
RBUFFER="scwrypts --name $NAME --type $TYPE --group $GROUP"
zle accept-line
zle clear-command-line
[ $SELECTED ] && {
[[ $SELECTED =~ ^$RESET$ ]] \
&& RBUFFER='unset SCWRYPTS_ENV' \
|| RBUFFER="export SCWRYPTS_ENV=$SELECTED"
}
zle -N scwrypts SCWRYPTS__ZSH_PLUGIN
bindkey $SCWRYPTS_SHORTCUT scwrypts
unset SCWRYPTS_SHORTCUT
zle accept-line
}
#####################################################################
[ $SCWRYPTS_BUILDER_SHORTCUT ] && {
SCWRYPTS__ZSH_BUILDER_PLUGIN() {
local SCWRYPT_SELECTION NAME TYPE GROUP
__SCWRYPTS_PARSE || { echo >&2; zle accept-line; return 0; }
echo $SCWRYPT_SELECTION >&2
scwrypts -n --name $NAME --group $GROUP --type $TYPE -- --help >&2 || {
zle accept-line
return 0
}
echo
zle reset-prompt
LBUFFER="scwrypts --name $NAME --type $TYPE --group $GROUP -- "
}
zle -N scwrypts-builder SCWRYPTS__ZSH_BUILDER_PLUGIN
bindkey $SCWRYPTS_BUILDER_SHORTCUT scwrypts-builder
unset SCWRYPTS_BUILDER_SHORTCUT
}
#####################################################################
[ $SCWRYPTS_ENV_SHORTCUT ] && {
SCWRYPTS__ZSH_PLUGIN_ENV() {
local RESET='reset'
local SELECTED=$(\
{ [ $SCWRYPTS_ENV ] && echo $RESET; scwrypts --list-envs; } \
| fzf --prompt 'select an environment : ' \
)
zle clear-command-line
[ $SELECTED ] && {
[[ $SELECTED =~ ^$RESET$ ]] \
&& RBUFFER='unset SCWRYPTS_ENV' \
|| RBUFFER="export SCWRYPTS_ENV=$SELECTED"
}
zle accept-line
}
zle -N scwrypts-setenv SCWRYPTS__ZSH_PLUGIN_ENV
bindkey $SCWRYPTS_ENV_SHORTCUT scwrypts-setenv
unset SCWRYPTS_ENV_SHORTCUT
}
#####################################################################
# badass(/terrifying?) zsh autocompletion
command -v compdef &>/dev/null && {
_scwrypts() {
echo $words | grep -q "\s--\s" && _arguments && return 0
eval "_arguments $(
{
HELP=$(scwrypts --help 2>&1 | sed -n 's/^\s\+\(-.* .\)/\1/p' | sed 's/[[]/(/g; s/[]]/)/g')
echo $HELP \
| sed 's/^\(\(-[^-\s]\),*\s*\|\)\(\(--[-a-z0-9A-Z\]*\)\s\(<\([^>]*\)>\|\)\|\)\s\+\(.*\)/\2[\7]:\6:->\2/' \
| grep -v '^[[]' \
;
echo $HELP \
| sed 's/^\(\(-[^-\s]\),*\s*\|\)\(\(--[-a-z0-9A-Z\]*\)\s\(<\([^>]*\)>\|\)\|\)\s\+\(.*\)/\4[\7]:\6:->\4/' \
| grep -v '^[[]' \
;
echo ":pattern:->pattern"
echo ":pattern:->pattern"
echo ":pattern:->pattern"
echo ":pattern:->pattern"
echo ":pattern:->pattern"
} | sed 's/::->.*$//g' | sed "s/\\(^\\|$\\)/'/g" | tr '\n' ' '
)"
local _group=''
echo $words | grep -q ' -g [^\s]' \
&& _group=$(echo $words | sed 's/.*-g \([^ ]\+\)\s*.*/\1/')
echo $words | grep -q ' --group .' \
&& _group=$(echo $words | sed 's/.*--group \([^ ]\+\)\s*.*/\1/')
local _type=''
echo $words | grep -q ' -t [^\s]' \
&& _type=$(echo $words | sed 's/.*-t \([^ ]\+\)\s*.*/\1/')
echo $words | grep -q ' --type .' \
&& _type=$(echo $words | sed 's/.*--type \([^ ]\+\)\s*.*/\1/')
local _name=''
echo $words | grep -q ' -m [^\s]' \
&& _name=$(echo $words | sed 's/.*-m \([^ ]\+\)\s*.*/\1/')
echo $words | grep -q ' --name .' \
&& _name=$(echo $words | sed 's/.*--name \([^ ]\+\)\s*.*/\1/')
local _pattern _patterns=()
[ ! $_name ] \
&& _patterns=($(echo "${words[@]:1}" | sed 's/\s\+/\n/g' | grep -v '^-'))
_get_remaining_scwrypts() {
[ $_name ] || local _name='[^ ]\+'
[ $_type ] || local _type='[^ ]\+'
[ $_group ] || local _group='[^ ]\+'
local remaining=$(\
scwrypts --list \
| sed "1d; s,\x1B\[[0-9;]*[a-zA-Z],,g" \
| grep "^$_name\s" \
| grep "\s$_group$" \
| grep "\s$_type\s" \
)
for _pattern in ${_patterns[@]}
do
remaining=$(echo "$remaining" | grep "$_pattern")
done
echo "$remaining"
}
case $state in
( -m | --name )
compadd $(_get_remaining_scwrypts | awk '{print $1;}' | sort -u)
;;
( -t | --type )
compadd $(_get_remaining_scwrypts | awk '{print $2;}' | sort -u)
;;
( -g | --group )
[[ $_name$_type$_group =~ ^$ ]] \
&& compadd $(scwrypts --list-groups) \
|| compadd $(_get_remaining_scwrypts | awk '{print $3;}' | sort -u) \
;;
( -e | --env )
compadd $(scwrypts --list-envs)
;;
( -v | --log-level )
local _help="$(\
scwrypts --help 2>&1 \
| sed -n '/-v, --log-level/,/^$/p' \
| sed -n 's/\s\+\([0-9]\) : \(.*\)/\1 -- \2/p' \
)"
eval "local _descriptions=($(echo "$_help" | sed "s/\\(^\|$\\)/'/g"))"
local _values=($(echo "$_help" | sed 's/ --.*//'))
compadd -d _descriptions -a _values
;;
( -o | --output )
compadd pretty json
;;
( pattern )
[[ $_name =~ ^$ ]] && {
local _remaining_scwrypts="$(_get_remaining_scwrypts)"
# 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; s/-/\n/g;' | sort -u)"
for _pattern in ${_patterns[@]}
do
_remaining_patterns="$(echo "$_remaining_patterns" | grep -v "^$_pattern$")"
done
compadd $(echo $_remaining_patterns)
}
;;
( * ) ;;
esac
}
compdef _scwrypts scwrypts
}
__SCWRYPTS_PLUGIN_LOADED=true
zle -N scwrypts-setenv SCWRYPTS__ZSH_PLUGIN_ENV
bindkey $SCWRYPTS_ENV_SHORTCUT scwrypts-setenv

View File

@ -1,177 +0,0 @@
#
# configuration for a scwrypts "group" or "plugin"
#
# this file defines the configuration for the 'scwrypts' group which
# is required for proper operation, but otherwise loads exactly like
# any other group/plugin
#
# 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
#
#####################################################################
### 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,113 +1,21 @@
# 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 a (commonly dreaded) go-to for automation.
Since they emulate direct user interaction, shell scripts are often the straightforward choice for task automation.
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.
## Basic Utilities
## The Basic Framework
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.
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
}
```
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

View File

@ -1,13 +0,0 @@
#
# 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

@ -1,28 +0,0 @@
#####################################################################
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

@ -1,36 +0,0 @@
#####################################################################
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

@ -1,65 +0,0 @@
#####################################################################
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

@ -1,6 +0,0 @@
#
# 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

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

View File

@ -1,17 +1,7 @@
#!/usr/bin/env zsh
#!/bin/zsh
use cloud/aws/ecr
#####################################################################
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 $@; }
MAIN() {
ECR_LOGIN $@
}

View File

@ -1,30 +0,0 @@
#####################################################################
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

@ -1,47 +0,0 @@
#####################################################################
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,82 +1,64 @@
#!/usr/bin/env zsh
#####################################################################
use cloud/aws/cli
use cloud/aws/zshparse/overrides
DEPENDENCIES+=(jq mount sort sudo)
#!/bin/zsh
DEPENDENCIES+=(jq)
REQUIRED_ENV+=(AWS__EFS__LOCAL_MOUNT_POINT)
#####################################################################
USAGE__description='
interactively mount an AWS EFS volume to the local filesystem
'
use cloud/aws/cli
#####################################################################
MAIN() {
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 \
| jq -r '.[] | .[] | .FileSystemId' \
| utils.fzf 'select a filesystem to mount' \
)
[ ! ${FS_ID} ] && utils.abort
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
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 MOUNT_TARGETS=$($AWS efs describe-mount-targets --file-system-id ${FS_ID})
local ZONE=$(\
echo ${MOUNT_TARGETS} \
| jq -r '.[] | .[] | .AvailabilityZoneName' \
| sort -u | utils.fzf 'select availability zone'\
local FS_ID=$(\
AWS efs describe-file-systems \
| jq -r '.[] | .[] | .FileSystemId' \
| FZF 'select a filesystem to mount' \
)
[ ! "${ZONE}" ] && utils.abort
[ ! $FS_ID ] && 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_TARGETS=$(AWS efs describe-mount-targets --file-system-id $FS_ID)
local ZONE=$(\
echo $MOUNT_TARGETS \
| jq -r '.[] | .[] | .AvailabilityZoneName' \
| sort -u | FZF 'select availability zone'\
)
[ ! $ZONE ] && ABORT
local MOUNT_IP=$(\
echo ${MOUNT_TARGETS} \
| jq -r ".[] | .[] | select (.AvailabilityZoneName == \"${ZONE}\") | .IpAddress" \
| head -n1 \
echo $MOUNT_TARGETS \
| jq -r ".[] | .[] | select (.AvailabilityZoneName == \"$ZONE\") | .IpAddress" \
| head -n1 \
)
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
SUCCESS 'ready to mount!'
REMINDER 'for private file-systems, you must be connected to the appropriate VPN'
sudo mkdir -- "${MOUNT_POINT}" \
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 mount \
-t nfs4 \
-o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport \
"${MOUNT_IP}:/" \
"${MOUNT_POINT}" \
&& echo.success "mounted at '${MOUNT_POINT}'" \
$MOUNT_IP:/ \
"$MOUNT_POINT" \
&& SUCCESS "mounted at '$MOUNT_POINT'" \
|| {
sudo rmdir -- "${MOUNT_POINT}" &>/dev/null
echo.error "unable to mount '${FS_ID}'"
}
sudo rmdir $MOUNT_POINT >/dev/null 2>&1
FAIL 2 "unable to mount '$FS_ID'"
}
}

View File

@ -1,39 +1,32 @@
#!/usr/bin/env zsh
#####################################################################
DEPENDENCIES+=(jq umount sudo)
#!/bin/zsh
DEPENDENCIES+=(jq)
REQUIRED_ENV+=(AWS__EFS__LOCAL_MOUNT_POINT)
use cloud/aws/cli
#####################################################################
USAGE__description='
interactively unmount an AWS EFS volume to the local filesystem
'
MAIN() {
eval "$(utils.parse.autosetup)"
##########################################
[ ! -d "${AWS__EFS__LOCAL_MOUNT_POINT}" ] && {
echo.status 'no efs currently mounted'
return 0
[ ! -d "$AWS__EFS__LOCAL_MOUNT_POINT" ] && {
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
local MOUNTED=$(ls "$AWS__EFS__LOCAL_MOUNT_POINT")
[ ! $MOUNTED ] && {
STATUS 'no efs currently mounted'
exit 0
}
utils.io.getsudo || return 1
GETSUDO || exit 1
local SELECTED=$(echo ${MOUNTED} | utils.fzf 'select a file system to unmount')
[ "${SELECTED}" ] || user.abort
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}'"
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'"
}

View File

@ -1,86 +0,0 @@
#####################################################################
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

@ -1,105 +0,0 @@
#####################################################################
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

@ -1,43 +0,0 @@
#####################################################################
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

@ -1,66 +0,0 @@
#####################################################################
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

@ -1,10 +0,0 @@
#
# 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

@ -1,24 +0,0 @@
#####################################################################
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 @@
#!/usr/bin/env zsh
#!/bin/zsh
use cloud/aws/eks
#####################################################################

View File

@ -1,44 +0,0 @@
${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

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

View File

@ -1,40 +0,0 @@
#####################################################################
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

@ -1,72 +0,0 @@
#####################################################################
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

@ -1,10 +0,0 @@
#
# 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

@ -1,20 +0,0 @@
#####################################################################
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

@ -1,57 +0,0 @@
#####################################################################
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

@ -1,63 +0,0 @@
#####################################################################
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

@ -1,93 +0,0 @@
#####################################################################
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

@ -1,150 +0,0 @@
#####################################################################
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

@ -1,9 +0,0 @@
#
# 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

@ -1,20 +0,0 @@
#####################################################################
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

@ -1,35 +0,0 @@
#####################################################################
${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 @@
#!/usr/bin/env zsh
#!/bin/zsh
use cloud/aws/rds
use db/postgres
#####################################################################

View File

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

View File

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

View File

@ -1,30 +1,33 @@
#!/usr/bin/env zsh
#!/bin/zsh
DEPENDENCIES+=(cli53)
REQUIRED_ENV+=(AWS_PROFILE AWS_ACCOUNT)
REQUIRED_ENV+=(AWS_PROFILE)
#####################################################################
utils.cli53() {
AWS_ACCOUNT=${AWS_ACCOUNT} \
cli53 --profile ${AWS_PROFILE} $@;
}
MAIN() {
local BACKUP_BASE_PATH="${SCWRYPTS_DATA_PATH}/route53-backup/${SCWRYPTS_ENV}"
local BACKUP_PATH="$SCWRYPTS_OUTPUT_PATH/$ENV_NAME/aws-dns-backup/$(date '+%Y-%m-%d')"
mkdir -p $BACKUP_PATH >/dev/null 2>&1
local DOMAIN
local JOBS=()
for DOMAIN in $(utils.cli53 list | awk '{print $2;}' | sed '1d; s/\.$//')
for DOMAIN in $(ROUTE53_GET_DOMAINS)
do
(
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}'" \
( 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'" \
) &
JOBS+=$!
done
local P
for P in ${JOBS[@]}; do wait ${P} &>/dev/null; done
echo.reminder "successful backups can be found in '${BACKUP_BASE_PATH}'"
for P in ${JOBS[@]}; do wait $P >/dev/null 2>&1; done
}
#####################################################################
ROUTE53_GET_DOMAINS() {
cli53 list --profile $AWS_PROFILE \
| awk '{print $2;}' \
| sed '1d; s/\.$//'\
;
}

View File

@ -1,79 +0,0 @@
${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

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

7
zsh/cloud/media-sync/pull Executable file
View File

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

7
zsh/cloud/media-sync/push Executable file
View File

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

View File

@ -1,9 +0,0 @@
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
}

Binary file not shown.

View File

@ -1,183 +0,0 @@
#####################################################################
### 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 @@
#!/usr/bin/env zsh
#!/bin/zsh
use db/postgres
#####################################################################

View File

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

View File

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

View File

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

View File

@ -1,29 +1,13 @@
#!/usr/bin/env zsh
#!/bin/zsh
DEPENDENCIES+=(docker)
#####################################################################
MAIN() {
local RESOURCES=(
container
image
volume
system
)
WARNING 'this will prune all docker resources from the current machine'
WARNING 'pruned resources are PERMANENTLY DELETED'
yN 'continue?' || return 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 '^'
)"
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)"
}

View File

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

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