Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
72e831da33 | |||
a03885e8db | |||
6cc10e3f4f | |||
4a1208942d | |||
91780024f0 | |||
3ca4fe0c65 | |||
e6dfff255c | |||
15942bb08d | |||
6f42c9cb16 | |||
570fc6a435 | |||
768350e6ab | |||
531aa52146 | |||
f8ccce9285 | |||
6fc17bcfe5 | |||
2034325ac9 | |||
ab567f6950 | |||
e199e9bf91 | |||
4c161aba49 | |||
3ea2e0cd8f | |||
e0cbf58b3c | |||
09c214f939 | |||
e2c6007a65 | |||
620d07f1a8 | |||
4baacd9c32 | |||
6c546ebb6f | |||
9783119a7d | |||
a94d6bc197 | |||
76a746a53e | |||
7617c938b1 | |||
a1256bb0af | |||
73e26a2ecb | |||
20b7cc32eb | |||
22dd6f8112 | |||
710d42e248 | |||
244c188deb | |||
5c882597da | |||
4047aad29e | |||
6333a2f6b8 | |||
a740a66870 | |||
96992e9344 | |||
e8bb889789 | |||
89e899d49d | |||
f7eec633ef | |||
db0d0092db | |||
f30eb7fb9e | |||
eaefc99774 | |||
2dcf94199b |
.config
.config
.gitattributes.github/workflows
.template.envREADME.mdaction.yamlplugins
ci
kubectl
py
.gitignore
runscwryptsscwrypts.plugin.zshdata
__init__.py
convert
directus
discord
hello-world.pyhello_world.pylib
linear
redis
requirements.txtscwrypts
twilio
zsh
README.mdcommon.zsh
aws
cloud
aws
media-sync
db
docker
hello-worldhelm
lib
cloud
config.group.zshconfig.user.zshconfig.zshdb
helm
import.driver.zshmedia
misc
office
redis
scwrypts
system
config
desktop
packages
vim
utils
media
office
redis
scwrypts
system
config
i3
packages
vim
utils
zx
14
.config
14
.config
@ -1,14 +0,0 @@
|
|||||||
#
|
|
||||||
# scwrypts config
|
|
||||||
#
|
|
||||||
|
|
||||||
# resource paths
|
|
||||||
SCWRYPTS_CONFIG_PATH="$HOME/.config/scwrypts"
|
|
||||||
SCWRYPTS_ENV_PATH="$SCWRYPTS_CONFIG_PATH/env"
|
|
||||||
SCWRYPTS_LOG_PATH="$SCWRYPTS_CONFIG_PATH/logs"
|
|
||||||
|
|
||||||
SCWRYPTS_OUTPUT_PATH="$HOME/SCWRYPTS"
|
|
||||||
|
|
||||||
# ZLE hotkeys
|
|
||||||
SCWRYPTS_SHORTCUT='' # CTRL + W
|
|
||||||
SCWRYPTS_ENV_SHORTCUT='' # CTRL + /
|
|
29
.config/env.template
Normal file
29
.config/env.template
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
export AWS_ACCOUNT=
|
||||||
|
export AWS_PROFILE=
|
||||||
|
export AWS_REGION=
|
||||||
|
export AWS__EFS__LOCAL_MOUNT_POINT=
|
||||||
|
export DIRECTUS__API_TOKEN=
|
||||||
|
export DIRECTUS__BASE_URL=
|
||||||
|
export DISCORD__BOT_TOKEN=
|
||||||
|
export DISCORD__CONTENT_FOOTER=
|
||||||
|
export DISCORD__CONTENT_HEADER=
|
||||||
|
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 REDIS_AUTH=
|
||||||
|
export REDIS_HOST=
|
||||||
|
export REDIS_PORT=
|
||||||
|
export TWILIO__ACCOUNT_SID=
|
||||||
|
export TWILIO__API_KEY=
|
||||||
|
export TWILIO__API_SECRET=
|
||||||
|
export TWILIO__DEFAULT_PHONE_FROM=
|
||||||
|
export TWILIO__DEFAULT_PHONE_TO=
|
36
.config/env.template.descriptions
Normal file
36
.config/env.template.descriptions
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
AWS_ACCOUNT | standard AWS environment variables used by awscli and other tools
|
||||||
|
AWS_PROFILE |
|
||||||
|
AWS_REGION |
|
||||||
|
|
||||||
|
AWS__EFS__LOCAL_MOUNT_POINT | fully-qualified path to mount the EFS drive
|
||||||
|
|
||||||
|
DIRECTUS__API_TOKEN | details for a directus instance
|
||||||
|
DIRECTUS__BASE_URL |
|
||||||
|
|
||||||
|
DISCORD__BOT_TOKEN | details for discord bot
|
||||||
|
DISCORD__CONTENT_HEADER |
|
||||||
|
DISCORD__CONTENT_FOOTER |
|
||||||
|
DISCORD__DEFAULT_AVATAR_URL |
|
||||||
|
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
|
||||||
|
MEDIA_SYNC__TARGETS |
|
||||||
|
|
||||||
|
REDIS_AUTH | redis connection credentials
|
||||||
|
REDIS_HOST |
|
||||||
|
REDIS_PORT |
|
||||||
|
|
||||||
|
TWILIO__ACCOUNT_SID | twilio account / credentials
|
||||||
|
TWILIO__API_KEY |
|
||||||
|
TWILIO__API_SECRET |
|
||||||
|
TWILIO__DEFAULT_PHONE_FROM |
|
||||||
|
TWILIO__DEFAULT_PHONE_TO |
|
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.zsh diff
|
||||||
|
.config diff
|
16
.github/workflows/update-semver.yaml
vendored
Normal file
16
.github/workflows/update-semver.yaml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: Update Semver
|
||||||
|
|
||||||
|
on: # yamllint disable-line rule:truthy
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- '**'
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-semver:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: rickstaa/action-update-semver@v1
|
@ -1,10 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
export AWS__EFS__LOCAL_MOUNT_POINT=
|
|
||||||
export AWS__S3__MEDIA_BUCKET=
|
|
||||||
export AWS__S3__MEDIA_TARGETS=
|
|
||||||
export REDIS_AUTH=
|
|
||||||
export REDIS_HOST=
|
|
||||||
export REDIS_PORT=
|
|
||||||
export _AWS_ACCOUNT=
|
|
||||||
export _AWS_PROFILE=
|
|
||||||
export _AWS_REGION=
|
|
16
README.md
16
README.md
@ -28,12 +28,9 @@ Check out [Meta Scwrypts](./zsh/scwrypts) to quickly set up environments and adj
|
|||||||
### No Install / API Usage
|
### No Install / API Usage
|
||||||
Alternatively, the `scwrypts` API can be used directly:
|
Alternatively, the `scwrypts` API can be used directly:
|
||||||
```zsh
|
```zsh
|
||||||
./scwrypts (environment-name) (...script-patterns)
|
./scwrypts [--env environment-name] (...script-name-patterns...) [-- ...passthrough arguments... ]
|
||||||
```
|
```
|
||||||
|
|
||||||
If not already set with `$SCWRYPTS_ENV`, Scwrypts will try to load `$1` as an environment.
|
|
||||||
If no environment with the name `$1` is found, `$1` is assumed to be a script pattern.
|
|
||||||
|
|
||||||
Given one or more script patterns, Scwrypts will filter the commands by pattern conjunction.
|
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 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.
|
If multiple commands match, the user will be prompted to select from the filtered list.
|
||||||
@ -44,6 +41,17 @@ Given no script patterns, Scwrypts becomes an interactive CLI, prompting the use
|
|||||||
After determining which script to run, if no environment has been specified, Scwrypts prompts the user to choose one.
|
After determining which script to run, if no environment has been specified, Scwrypts prompts the user to choose one.
|
||||||
|
|
||||||
|
|
||||||
|
### 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 from context.
|
||||||
|
- User yes/no prompts will **always be YES**
|
||||||
|
- Other user input will default to an empty string
|
||||||
|
- Logs will not be captured
|
||||||
|
- Setting the environment variable `SCWRYPTS_GROUP_LOADER__[a-z_]\+` will source the file indicated in the variable (this allows custom groups without needing to modify the `config.zsh` directly)
|
||||||
|
- In GitHub actions, `*.scwrypts.zsh` groups are detected automatically from the `$GITHUB_WORKSPACE`; set `SCWRYPTS_GITHUB_NO_AUTOLOAD=true` to disable
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Before contributing an issue, idea, or pull request, check out the [super-brief contributing guide](./docs/CONTRIBUTING.md)
|
Before contributing an issue, idea, or pull request, check out the [super-brief contributing guide](./docs/CONTRIBUTING.md)
|
||||||
|
68
action.yaml
Normal file
68
action.yaml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
--- # allow running scwrypts in Github Actions
|
||||||
|
name: scwrypts
|
||||||
|
author: yage
|
||||||
|
description: check required dependencies and run a scwrypt
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
scwrypt:
|
||||||
|
description: "args / identifiers for scwrypts CLI (e.g. '--name <scwrypt-name> --group <group-name> --type <type-name>')"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
args:
|
||||||
|
description: "arguments to pass to the scwrypt-to-be-run"
|
||||||
|
required: false
|
||||||
|
|
||||||
|
version:
|
||||||
|
description: "scwrypts version; defaults to latest (minimum v3.7.0)"
|
||||||
|
required: false
|
||||||
|
|
||||||
|
scwrypts-env:
|
||||||
|
description: "override value for SCWRYPTS_ENV"
|
||||||
|
required: false
|
||||||
|
default: "ci.github-actions"
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: wrynegade/scwrypts
|
||||||
|
path: ./wrynegade/scwrypts
|
||||||
|
ref: ${{ inputs.version }}
|
||||||
|
|
||||||
|
- name: check dependencies
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
SCWRYPTS_PLUGIN_ENABLED__ci: 1
|
||||||
|
run: |
|
||||||
|
[ $CI_SCWRYPTS_READY ] && [[ $CI_SCWRYPTS_READY -eq 1 ]] && echo 'setup completed previously' && exit 0
|
||||||
|
|
||||||
|
echo "updating package dependencies"
|
||||||
|
{
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install --yes zsh fzf ripgrep
|
||||||
|
|
||||||
|
for D in $($GITHUB_WORKSPACE/wrynegade/scwrypts/scwrypts -n --name check-all-dependencies --group ci --type zsh)
|
||||||
|
do
|
||||||
|
echo "--- installing $D ---"
|
||||||
|
( sudo apt-get install --yes $D; exit 0; )
|
||||||
|
done
|
||||||
|
} > $HOME/.scwrypts.apt-get.log 2>&1
|
||||||
|
|
||||||
|
echo "updating virtual dependencies"
|
||||||
|
$GITHUB_WORKSPACE/wrynegade/scwrypts/scwrypts -n \
|
||||||
|
--name scwrypts/virtualenv/update-all \
|
||||||
|
--group scwrypts \
|
||||||
|
--type zsh \
|
||||||
|
> $HOME/.scwrypts.virtualenv.log 2>&1
|
||||||
|
|
||||||
|
echo "CI_SCWRYPTS_READY=1" >> $GITHUB_ENV
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
- name: run scwrypt
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
SCWRYPTS_ENV: ${{ inputs.scwrypts-env }}
|
||||||
|
run: $GITHUB_WORKSPACE/wrynegade/scwrypts/scwrypts ${{inputs.scwrypt}} -- ${{inputs.args}} || exit 1
|
1
plugins/ci/.config/env.template
Normal file
1
plugins/ci/.config/env.template
Normal file
@ -0,0 +1 @@
|
|||||||
|
#!/bin/zsh
|
0
plugins/ci/.config/env.template.descriptions
Normal file
0
plugins/ci/.config/env.template.descriptions
Normal file
10
plugins/ci/README.md
Normal file
10
plugins/ci/README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Kubernetes `kubectl` Helper Plugin
|
||||||
|
|
||||||
|
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`.
|
40
plugins/ci/check-all-dependencies
Executable file
40
plugins/ci/check-all-dependencies
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
#####################################################################
|
||||||
|
DEPENDENCIES+=()
|
||||||
|
REQUIRED_ENV+=()
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
MAIN() {
|
||||||
|
cd "$SCWRYPTS_ROOT__scwrypts/"
|
||||||
|
|
||||||
|
DEPENDENCIES+=()
|
||||||
|
for group in ${SCWRYPTS_GROUPS[@]}
|
||||||
|
do
|
||||||
|
[[ $group =~ ^ci$ ]] && continue
|
||||||
|
|
||||||
|
GROUP_HOME="$(eval 'echo $SCWRYPTS_ROOT__'$group)"
|
||||||
|
[ $GROUP_HOME ] && [ -d "$GROUP_HOME" ] || continue
|
||||||
|
|
||||||
|
STATUS "checking dependencies for $group"
|
||||||
|
DEPENDENCIES+=($(
|
||||||
|
for file in $(
|
||||||
|
{
|
||||||
|
cd "$GROUP_HOME"
|
||||||
|
rg -l '^DEPENDENCIES\+=\($'
|
||||||
|
rg -l '^DEPENDENCIES\+=\([^)]\+\)'
|
||||||
|
} | 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
|
||||||
|
|
||||||
|
DEPENDENCIES=(zsh $(echo $DEPENDENCIES | sed 's/ /\n/g' | sort -u | grep '^[-_a-zA-Z]\+$'))
|
||||||
|
|
||||||
|
STATUS "discovered dependencies: ($DEPENDENCIES)"
|
||||||
|
echo $DEPENDENCIES | sed 's/ /\n/g'
|
||||||
|
CHECK_ENVIRONMENT && SUCCESS "all dependencies satisfied"
|
||||||
|
}
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
MAIN $@
|
5
plugins/ci/ci.scwrypts.zsh
Normal file
5
plugins/ci/ci.scwrypts.zsh
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
SCWRYPTS_GROUPS+=(ci)
|
||||||
|
|
||||||
|
export SCWRYPTS_TYPE__ci=zsh
|
||||||
|
export SCWRYPTS_ROOT__ci="$SCWRYPTS_ROOT__scwrypts/plugins/ci"
|
||||||
|
export SCWRYPTS_COLOR__ci='\033[0m'
|
2
plugins/kubectl/.config/env.template
Normal file
2
plugins/kubectl/.config/env.template
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
export SCWRYPTS_KUBECTL_REDIS=
|
1
plugins/kubectl/.config/env.template.descriptions
Normal file
1
plugins/kubectl/.config/env.template.descriptions
Normal file
@ -0,0 +1 @@
|
|||||||
|
SCWRYPTS_KUBECTL_REDIS | (currently only 'managed') 'managed' or 'custom' redis configuration
|
4
plugins/kubectl/.config/static/redis.zsh
Normal file
4
plugins/kubectl/.config/static/redis.zsh
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export SCWRYPTS_KUBECTL_REDIS_HOST__managed=127.0.0.1
|
||||||
|
export SCWRYPTS_KUBECTL_REDIS_PORT__managed=26379
|
||||||
|
export SCWRYPTS_KUBECTL_REDIS_AUTH__managed=
|
||||||
|
export SCWRYPTS_KUBECTL_REDIS_KEY_PREFIX__managed=
|
10
plugins/kubectl/README.md
Normal file
10
plugins/kubectl/README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Kubernetes `kubectl` Helper Plugin
|
||||||
|
|
||||||
|
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`.
|
31
plugins/kubectl/driver/kubectl.completion.zsh
Normal file
31
plugins/kubectl/driver/kubectl.completion.zsh
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#####################################################################
|
||||||
|
command -v compdef >/dev/null 2>&1 || return 0
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
_k() {
|
||||||
|
local C=$(k meta get context)
|
||||||
|
local NS=$(k meta get namespace)
|
||||||
|
|
||||||
|
local KUBEWORDS=(kubectl)
|
||||||
|
[ $C ] && KUBEWORDS+=(--context $C)
|
||||||
|
[ $NS ] && KUBEWORDS+=(--namespace $NS)
|
||||||
|
|
||||||
|
words="$KUBEWORDS ${words[@]:1}"
|
||||||
|
_kubectl
|
||||||
|
}
|
||||||
|
|
||||||
|
compdef _k k
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
_h() {
|
||||||
|
local C=$(k meta get context)
|
||||||
|
local NS=$(k meta get namespace)
|
||||||
|
|
||||||
|
local KUBEWORDS=(kubectl)
|
||||||
|
[ $C ] && KUBEWORDS+=(--context $C)
|
||||||
|
[ $NS ] && KUBEWORDS+=(--namespace $NS)
|
||||||
|
|
||||||
|
words="$KUBEWORDS ${words[@]:1}"
|
||||||
|
_helm
|
||||||
|
}
|
||||||
|
compdef _h h
|
195
plugins/kubectl/driver/kubectl.driver.zsh
Normal file
195
plugins/kubectl/driver/kubectl.driver.zsh
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
[[ $SCWRYPTS_KUBECTL_DRIVER_READY -eq 1 ]] && return 0
|
||||||
|
|
||||||
|
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 ] && {
|
||||||
|
ERROR "must set SCWRYPTS_ENV in order to use '$(echo $CLI | head -c1)'"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
which REDIS >/dev/null 2>&1 \
|
||||||
|
|| eval "$(scwrypts -n --name meta/get-static-redis-definition --type zsh --group kubectl)"
|
||||||
|
|
||||||
|
local CLI="$1"; shift 1
|
||||||
|
|
||||||
|
local SCWRYPTS_GROUP CUSTOM_COMMANDS=(meta)
|
||||||
|
for SCWRYPTS_GROUP in ${SCWRYPTS_GROUPS[@]}
|
||||||
|
do
|
||||||
|
CUSTOM_COMMANDS+=($(eval echo '$SCWRYPTS_KUBECTL_CUSTOM_COMMANDS__'$SCWRYPTS_GROUP))
|
||||||
|
done
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
local USAGE="
|
||||||
|
usage: - [...args...] [...options...] -- [...$CLI options...]
|
||||||
|
|
||||||
|
args: -
|
||||||
|
|
||||||
|
options: -
|
||||||
|
--subsession [0-9] use indicated subsession (use for script clarity instead of positional arg)
|
||||||
|
|
||||||
|
-h, --help display this help dialogue
|
||||||
|
-v, --verbose output debugging information
|
||||||
|
|
||||||
|
description: -
|
||||||
|
"
|
||||||
|
|
||||||
|
local USAGE__usage=$(echo $CLI | head -c1)
|
||||||
|
|
||||||
|
local USAGE__args="$(
|
||||||
|
{
|
||||||
|
echo "\\033[0;32m[0-9]\\033[0m^if the first argument is a number 0-9, uses or creates a subsession (default 0)"
|
||||||
|
echo " ^ "
|
||||||
|
for C in ${CUSTOM_COMMANDS[@]}
|
||||||
|
do
|
||||||
|
echo "\\033[0;32m$C\\033[0m^$(SCWRYPTS_KUBECTL_CUSTOM_COMMAND_DESCRIPTION__$C 2>/dev/null)"
|
||||||
|
done
|
||||||
|
} | column -ts '^'
|
||||||
|
)"
|
||||||
|
|
||||||
|
local USAGE__options="
|
||||||
|
-n, --namespace set the namespace
|
||||||
|
-k, --context set the context
|
||||||
|
"
|
||||||
|
|
||||||
|
local USAGE__description="
|
||||||
|
Provides 'k' (kubectl), 'h' (helm), and 'f' (flux) shorthands to the respective
|
||||||
|
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 : \\033[0;33m$SCWRYPTS_ENV\\033[0m
|
||||||
|
|
||||||
|
"
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
local USER_ARGS=()
|
||||||
|
|
||||||
|
local CUSTOM_COMMAND=0
|
||||||
|
local VERBOSE=0
|
||||||
|
local HELP=0
|
||||||
|
local ERRORS=0
|
||||||
|
|
||||||
|
local COMMAND_SWITCH_CASE="@($(echo $CUSTOM_COMMANDS | sed 's/ /|/g'))"
|
||||||
|
|
||||||
|
[ ! $SUBSESSION ] && local SUBSESSION=0
|
||||||
|
[[ $1 =~ ^[0-9]$ ]] && SUBSESSION=$1 && shift 1
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]
|
||||||
|
do
|
||||||
|
case $1 in
|
||||||
|
meta )
|
||||||
|
CUSTOM_COMMAND=$1
|
||||||
|
SCWRYPTS_KUBECTL_CUSTOM_COMMAND_PARSE__$1 ${@:2}
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
|
||||||
|
-v | --verbose ) VERBOSE=1 ;;
|
||||||
|
-h | --help ) HELP=1 ;;
|
||||||
|
|
||||||
|
--subsession ) SUBSESSION=$2; shift 1 ;;
|
||||||
|
|
||||||
|
-n | --namespace )
|
||||||
|
_SCWRYPTS_KUBECTL_DRIVER kubectl meta set namespace $2
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
-k | --context | --kube-context )
|
||||||
|
_SCWRYPTS_KUBECTL_DRIVER kubectl meta set context $2
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
-- ) shift 1; break ;;
|
||||||
|
|
||||||
|
* )
|
||||||
|
[ ! $CUSTOM_COMMAND ] && {
|
||||||
|
for C in ${CUSTOM_COMMANDS[@]}
|
||||||
|
do
|
||||||
|
[[ $1 =~ ^$C$ ]] && {
|
||||||
|
SCWRYPTS_KUBECTL_CUSTOM_COMMAND_PARSE__$1 ${@:2}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
done
|
||||||
|
}
|
||||||
|
USER_ARGS+=($1)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift 1
|
||||||
|
done
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do USER_ARGS+=($1); shift 1; done
|
||||||
|
|
||||||
|
|
||||||
|
CHECK_ERRORS --no-fail || return 1
|
||||||
|
|
||||||
|
[[ $HELP -eq 1 ]] && { USAGE; return 0; }
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
local STRICT=$(_SCWRYPTS_KUBECTL_SETTINGS get strict || echo 1)
|
||||||
|
|
||||||
|
case $CUSTOM_COMMAND in
|
||||||
|
0 )
|
||||||
|
local CLI_ARGS=()
|
||||||
|
|
||||||
|
local CONTEXT=$(k meta get context)
|
||||||
|
local NAMESPACE=$(k meta get namespace)
|
||||||
|
|
||||||
|
[ $CONTEXT ] && [[ $CLI =~ ^helm$ ]] && CLI_ARGS+=(--kube-context $CONTEXT)
|
||||||
|
[ $CONTEXT ] && [[ $CLI =~ ^kubectl$ ]] && CLI_ARGS+=(--context $CONTEXT)
|
||||||
|
[ $CONTEXT ] && [[ $CLI =~ ^flux$ ]] && CLI_ARGS+=(--context $CONTEXT)
|
||||||
|
|
||||||
|
[[ $STRICT -eq 1 ]] && {
|
||||||
|
[ $CONTEXT ] || ERROR "missing kubectl 'context'"
|
||||||
|
[ $NAMESPACE ] || ERROR "missing kubectl 'namespace'"
|
||||||
|
|
||||||
|
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)
|
||||||
|
"
|
||||||
|
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ $NAMESPACE ] && CLI_ARGS+=(--namespace $NAMESPACE)
|
||||||
|
[[ $VERBOSE -eq 1 ]] && {
|
||||||
|
INFO "
|
||||||
|
context '$CONTEXT'
|
||||||
|
namespace '$NAMESPACE'
|
||||||
|
environment '$SCWRYPTS_ENV'
|
||||||
|
subsession '$SUBSESSION'
|
||||||
|
"
|
||||||
|
STATUS "running $CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}"
|
||||||
|
} || {
|
||||||
|
[[ $(_SCWRYPTS_KUBECTL_SETTINGS get context) =~ ^show$ ]] && {
|
||||||
|
INFO "$SCWRYPTS_ENV.$SUBSESSION : $CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$CLI ${CLI_ARGS[@]} ${USER_ARGS[@]}
|
||||||
|
;;
|
||||||
|
* ) SCWRYPTS_KUBECTL_CUSTOM_COMMAND__$CUSTOM_COMMAND ${USER_ARGS[@]} ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_SCWRYPTS_KUBECTL_SETTINGS() {
|
||||||
|
# (get setting-name) or (set setting-name setting-value)
|
||||||
|
REDIS h$1 ${SCWRYPTS_ENV}:kubectl:settings ${@:2} | grep .
|
||||||
|
}
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
source ${0:a:h}/kubectl.completion.zsh
|
||||||
|
source ${0:a:h}/meta.zsh
|
147
plugins/kubectl/driver/meta.zsh
Normal file
147
plugins/kubectl/driver/meta.zsh
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
SCWRYPTS_KUBECTL_CUSTOM_COMMAND_PARSE__meta() {
|
||||||
|
USAGE__usage+=" meta"
|
||||||
|
USAGE__args="
|
||||||
|
- get output value of meta variable
|
||||||
|
- set interactively configure value of meta variable
|
||||||
|
- clear clear current subsession variables
|
||||||
|
|
||||||
|
(settings args)
|
||||||
|
- show output context for every command
|
||||||
|
- hide (default) hide output context for every command
|
||||||
|
|
||||||
|
- strict (default) require context *and* namespace to be set
|
||||||
|
- loose do not require context and namespace to be set
|
||||||
|
"
|
||||||
|
USAGE__options=''
|
||||||
|
USAGE__description=$(SCWRYPTS_KUBECTL_CUSTOM_COMMAND_DESCRIPTION__meta)
|
||||||
|
|
||||||
|
META_SUBARGS="
|
||||||
|
- namespace
|
||||||
|
- context
|
||||||
|
"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]
|
||||||
|
do
|
||||||
|
case $1 in
|
||||||
|
-h | --help ) HELP=1 ;;
|
||||||
|
|
||||||
|
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 ;;
|
||||||
|
'' )
|
||||||
|
: \
|
||||||
|
&& SCWRYPTS_KUBECTL_CUSTOM_COMMAND__meta set context \
|
||||||
|
&& SCWRYPTS_KUBECTL_CUSTOM_COMMAND__meta set namespace \
|
||||||
|
;
|
||||||
|
return $?
|
||||||
|
;;
|
||||||
|
|
||||||
|
* ) ERROR "cannot set '$2'" ;;
|
||||||
|
esac
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
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) ;;
|
||||||
|
|
||||||
|
-h | --help ) HELP=1 ;;
|
||||||
|
|
||||||
|
* ) ERROR "cannot get '$2'" ;;
|
||||||
|
esac
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
copy )
|
||||||
|
USAGE__usage+=" copy"
|
||||||
|
USAGE__args+="copy [0-9]"
|
||||||
|
USAGE__description="copy current subsession ($SUBSESSION) to target subsession id"
|
||||||
|
case $2 in
|
||||||
|
[0-9] ) USER_ARGS+=($1 $2) ;;
|
||||||
|
-h | --help ) HELP=1 ;;
|
||||||
|
* ) ERROR "target session must be a number [0-9]" ;;
|
||||||
|
esac
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
clear | show | hide | strict | loose ) USER_ARGS+=($1) ;;
|
||||||
|
|
||||||
|
* ) ERROR "no meta command '$1'"
|
||||||
|
esac
|
||||||
|
shift 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
SCWRYPTS_KUBECTL_CUSTOM_COMMAND__meta() {
|
||||||
|
case $1 in
|
||||||
|
get )
|
||||||
|
[[ $2 =~ ^all$ ]] && {
|
||||||
|
local CONTEXT=$(REDIS get --prefix current:context | grep . || echo "\\033[1;31mnone set\\033[0m")
|
||||||
|
local NAMESPACE=$(REDIS get --prefix current:namespace | grep . || echo "\\033[1;31mnone set\\033[0m")
|
||||||
|
echo "
|
||||||
|
environment : $SCWRYPTS_ENV
|
||||||
|
context : $CONTEXT
|
||||||
|
namespace : $NAMESPACE
|
||||||
|
|
||||||
|
CLI settings
|
||||||
|
command context : $(_SCWRYPTS_KUBECTL_SETTINGS get context)
|
||||||
|
strict mode : $([[ $STRICT -eq 1 ]] && echo "on" || echo "\\033[1;31moff\\033[0m")
|
||||||
|
" | sed 's/^ \+//' >&2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
REDIS get --prefix current:$2
|
||||||
|
;;
|
||||||
|
|
||||||
|
set )
|
||||||
|
scwrypts -n --name set-$2 --type zsh --group kubectl -- $3 --subsession $SUBSESSION >/dev/null \
|
||||||
|
&& SUCCESS "$2 set"
|
||||||
|
;;
|
||||||
|
|
||||||
|
copy )
|
||||||
|
: \
|
||||||
|
&& 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" \
|
||||||
|
;
|
||||||
|
;;
|
||||||
|
|
||||||
|
clear )
|
||||||
|
scwrypts -n --name set-context --type zsh --group kubectl -- --subsession $SUBSESSION reset >/dev/null \
|
||||||
|
&& SUCCESS "subsession $SUBSESSION reset to default"
|
||||||
|
;;
|
||||||
|
|
||||||
|
show )
|
||||||
|
_SCWRYPTS_KUBECTL_SETTINGS set context show >/dev/null \
|
||||||
|
&& SUCCESS "now showing full command context"
|
||||||
|
;;
|
||||||
|
|
||||||
|
hide )
|
||||||
|
_SCWRYPTS_KUBECTL_SETTINGS set context hide >/dev/null \
|
||||||
|
&& SUCCESS "now hiding command context"
|
||||||
|
;;
|
||||||
|
|
||||||
|
loose )
|
||||||
|
_SCWRYPTS_KUBECTL_SETTINGS set strict 0 >/dev/null \
|
||||||
|
&& WARNING "now running in 'loose' mode"
|
||||||
|
;;
|
||||||
|
|
||||||
|
strict )
|
||||||
|
_SCWRYPTS_KUBECTL_SETTINGS set strict 1 >/dev/null \
|
||||||
|
&& SUCCESS "now running in 'strict' mode"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
SCWRYPTS_KUBECTL_CUSTOM_COMMAND_DESCRIPTION__meta() {
|
||||||
|
[ $CLI ] || CLI='kubectl'
|
||||||
|
echo "operations for $CLI session variables and other CLI settings"
|
||||||
|
}
|
16
plugins/kubectl/get-context
Executable file
16
plugins/kubectl/get-context
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
#####################################################################
|
||||||
|
DEPENDENCIES+=()
|
||||||
|
REQUIRED_ENV+=()
|
||||||
|
|
||||||
|
use kubectl --group kubectl
|
||||||
|
|
||||||
|
CHECK_ENVIRONMENT
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
MAIN() {
|
||||||
|
KUBECTL__GET_CONTEXT
|
||||||
|
}
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
MAIN $@
|
16
plugins/kubectl/get-namespace
Executable file
16
plugins/kubectl/get-namespace
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
#####################################################################
|
||||||
|
DEPENDENCIES+=()
|
||||||
|
REQUIRED_ENV+=()
|
||||||
|
|
||||||
|
use kubectl --group kubectl
|
||||||
|
|
||||||
|
CHECK_ENVIRONMENT
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
MAIN() {
|
||||||
|
KUBECTL__GET_NAMESPACE
|
||||||
|
}
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
MAIN $@
|
11
plugins/kubectl/kubectl.scwrypts.zsh
Normal file
11
plugins/kubectl/kubectl.scwrypts.zsh
Normal 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"
|
158
plugins/kubectl/lib/kubectl.module.zsh
Normal file
158
plugins/kubectl/lib/kubectl.module.zsh
Normal 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
|
||||||
|
|
||||||
|
INFO "attempting to serve ${NAMESPACE}/${SERVICE_NAME}:${SERVICE_PORT}"
|
||||||
|
[ $SERVICE_PASSWORD ] && INFO "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|/.*$||')
|
||||||
|
}
|
97
plugins/kubectl/lib/redis.module.zsh
Normal file
97
plugins/kubectl/lib/redis.module.zsh
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#####################################################################
|
||||||
|
|
||||||
|
DEPENDENCIES+=(
|
||||||
|
redis-cli
|
||||||
|
docker
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO; allow custom redis configuration
|
||||||
|
export SCWRYPTS_KUBECTL_REDIS=managed
|
||||||
|
|
||||||
|
REQUIRED_ENV+=(
|
||||||
|
SCWRYPTS_KUBECTL_REDIS
|
||||||
|
)
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
REDIS() {
|
||||||
|
[ ! $USAGE ] && local USAGE="
|
||||||
|
usage: [...options...]
|
||||||
|
|
||||||
|
options:
|
||||||
|
--subsession [0-9] use a particular subsession
|
||||||
|
|
||||||
|
-p, --prefix apply dynamic prefix to the next command line argument
|
||||||
|
|
||||||
|
--get-prefix output key prefix for current session+subsession
|
||||||
|
--get-static-definition output the static ZSH function definition for REDIS
|
||||||
|
|
||||||
|
additional arguments and options are passed through to 'redis-cli'
|
||||||
|
"
|
||||||
|
|
||||||
|
local REDIS_ARGS=() USER_ARGS=()
|
||||||
|
|
||||||
|
[ $SUBSESSION ] || local SUBSESSION=0
|
||||||
|
|
||||||
|
local REDIS_PREFIX=$(eval echo '$SCWRYPTS_KUBECTL_REDIS_KEY_PREFIX__'$SCWRYPTS_KUBECTL_REDIS)
|
||||||
|
[ $REDIS_PREFIX ] && REDIS_PREFIX+=':'
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]
|
||||||
|
do
|
||||||
|
case $1 in
|
||||||
|
-p | --prefix ) USER_ARGS+=("${REDIS_PREFIX}${SCWRYPTS_ENV}:${SUBSESSION}:$2"); shift 1 ;;
|
||||||
|
|
||||||
|
--subsession ) SUBSESSION=$2; shift 1 ;;
|
||||||
|
|
||||||
|
--get-prefix ) echo $REDIS_PREFIX; return 0 ;;
|
||||||
|
--get-static-definition ) ECHO_STATIC_DEFINITION=1 ;;
|
||||||
|
|
||||||
|
* ) USER_ARGS+=($1) ;;
|
||||||
|
esac
|
||||||
|
shift 1
|
||||||
|
done
|
||||||
|
|
||||||
|
local REDIS_HOST=$(eval echo '$SCWRYPTS_KUBECTL_REDIS_HOST__'$SCWRYPTS_KUBECTL_REDIS)
|
||||||
|
local REDIS_PORT=$(eval echo '$SCWRYPTS_KUBECTL_REDIS_PORT__'$SCWRYPTS_KUBECTL_REDIS)
|
||||||
|
local REDIS_AUTH=$(eval echo '$SCWRYPTS_KUBECTL_REDIS_AUTH__'$SCWRYPTS_KUBECTL_REDIS)
|
||||||
|
|
||||||
|
[ $REDIS_HOST ] && REDIS_ARGS+=(-h $REDIS_HOST)
|
||||||
|
[ $REDIS_PORT ] && REDIS_ARGS+=(-p $REDIS_PORT)
|
||||||
|
[ $REDIS_AUTH ] && REDIS_ARGS+=(-a $REDIS_AUTH)
|
||||||
|
|
||||||
|
REDIS_ARGS+=(--raw)
|
||||||
|
|
||||||
|
[[ $ECHO_STATIC_DEFINITION -eq 1 ]] && {
|
||||||
|
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) ;; \
|
||||||
|
esac; \
|
||||||
|
shift 1; \
|
||||||
|
done; \
|
||||||
|
redis-cli ${REDIS_ARGS[@]} \${USER_ARGS[@]}; \
|
||||||
|
}" | sed 's/\s\+/ /g'
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
redis-cli ${REDIS_ARGS[@]} ${USER_ARGS[@]}
|
||||||
|
}
|
||||||
|
|
||||||
|
REDIS ping | grep -qi pong || {
|
||||||
|
RPID=$(docker ps -a | grep scwrypts-kubectl-redis | awk '{print $1;}')
|
||||||
|
[ $RPID ] && STATUS 'refreshing redis instance' && docker rm -f $RPID
|
||||||
|
unset RPID
|
||||||
|
|
||||||
|
docker run \
|
||||||
|
--detach \
|
||||||
|
--name scwrypts-kubectl-redis \
|
||||||
|
--publish $SCWRYPTS_KUBECTL_REDIS_PORT__managed:6379 \
|
||||||
|
redis >/dev/null 2>&1
|
||||||
|
|
||||||
|
STATUS 'awaiting redis connection'
|
||||||
|
until REDIS ping 2>/dev/null | grep -qi pong; do sleep 0.5; done
|
||||||
|
}
|
11
plugins/kubectl/meta/get-static-redis-definition
Executable file
11
plugins/kubectl/meta/get-static-redis-definition
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
#####################################################################
|
||||||
|
DEPENDENCIES+=()
|
||||||
|
REQUIRED_ENV+=()
|
||||||
|
|
||||||
|
use redis --group kubectl
|
||||||
|
|
||||||
|
CHECK_ENVIRONMENT
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
echo $(REDIS --get-static-definition)
|
58
plugins/kubectl/serve
Executable file
58
plugins/kubectl/serve
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
#####################################################################
|
||||||
|
DEPENDENCIES+=()
|
||||||
|
REQUIRED_ENV+=()
|
||||||
|
|
||||||
|
use kubectl --group kubectl
|
||||||
|
|
||||||
|
CHECK_ENVIRONMENT
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
MAIN() {
|
||||||
|
local USAGE="
|
||||||
|
usage: [service] [...options...]
|
||||||
|
|
||||||
|
args:
|
||||||
|
service (optional) name of the service to forward locally
|
||||||
|
|
||||||
|
options:
|
||||||
|
--context override context
|
||||||
|
--namespace override namespace
|
||||||
|
--subsession REDIS subsession (default 0)
|
||||||
|
|
||||||
|
to show a required password on screen, use both:
|
||||||
|
--password-secret Secret resource
|
||||||
|
--password-key key within Secret's 'data'
|
||||||
|
|
||||||
|
-h, --help show this dialogue and exit
|
||||||
|
"
|
||||||
|
local CONTEXT NAMESPACE SERVICE
|
||||||
|
local SUBSESSION=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]
|
||||||
|
do
|
||||||
|
case $1 in
|
||||||
|
--context ) CONTEXT=$2; shift 1 ;;
|
||||||
|
--namespace ) NAMESPACE=$2; shift 1 ;;
|
||||||
|
--subsession ) SUBSESSION=$2; shift 1 ;;
|
||||||
|
|
||||||
|
--password-secret ) PASSWORD_SECRET=$2; shift 1 ;;
|
||||||
|
--password-key ) PASSWORD_KEY=$2; shift 1 ;;
|
||||||
|
|
||||||
|
-h | --help ) USAGE; return 0 ;;
|
||||||
|
|
||||||
|
* )
|
||||||
|
[ $SERVICE ] && ERROR "unexpected argument '$2'"
|
||||||
|
SERVICE=$1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift 1
|
||||||
|
done
|
||||||
|
|
||||||
|
CHECK_ERRORS
|
||||||
|
|
||||||
|
KUBECTL__SERVE
|
||||||
|
}
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
MAIN $@
|
50
plugins/kubectl/set-context
Executable file
50
plugins/kubectl/set-context
Executable file
@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
#####################################################################
|
||||||
|
DEPENDENCIES+=()
|
||||||
|
REQUIRED_ENV+=()
|
||||||
|
|
||||||
|
use kubectl --group kubectl
|
||||||
|
|
||||||
|
CHECK_ENVIRONMENT
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
MAIN() {
|
||||||
|
local USAGE="
|
||||||
|
usage: [context] [...options...]
|
||||||
|
|
||||||
|
args:
|
||||||
|
context (optional) the full name of the kubeconfig context to set
|
||||||
|
|
||||||
|
options:
|
||||||
|
--subsession REDIS subsession (default 0)
|
||||||
|
|
||||||
|
-h, --help show this dialogue and exit
|
||||||
|
"
|
||||||
|
local CONTEXT
|
||||||
|
local SUBSESSION=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]
|
||||||
|
do
|
||||||
|
case $1 in
|
||||||
|
--subsession ) SUBSESSION=$2; shift 1 ;;
|
||||||
|
|
||||||
|
-h | --help ) USAGE; return 0 ;;
|
||||||
|
|
||||||
|
* )
|
||||||
|
[ $CONTEXT ] && ERROR "unexpected argument '$2'"
|
||||||
|
CONTEXT=$1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift 1
|
||||||
|
done
|
||||||
|
|
||||||
|
[ $CONTEXT ] || CONTEXT=$(KUBECTL__SELECT_CONTEXT)
|
||||||
|
[ $CONTEXT ] || ERROR 'must provide or select a valid kube context'
|
||||||
|
|
||||||
|
CHECK_ERRORS
|
||||||
|
|
||||||
|
KUBECTL__SET_CONTEXT $CONTEXT
|
||||||
|
}
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
MAIN $@
|
50
plugins/kubectl/set-namespace
Executable file
50
plugins/kubectl/set-namespace
Executable file
@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
#####################################################################
|
||||||
|
DEPENDENCIES+=()
|
||||||
|
REQUIRED_ENV+=()
|
||||||
|
|
||||||
|
use kubectl --group kubectl
|
||||||
|
|
||||||
|
CHECK_ENVIRONMENT
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
MAIN() {
|
||||||
|
local USAGE="
|
||||||
|
usage: [namespace] [...options...]
|
||||||
|
|
||||||
|
args:
|
||||||
|
namespace (optional) the full name of the namespace context to set
|
||||||
|
|
||||||
|
options:
|
||||||
|
--subsession REDIS subsession (default 0)
|
||||||
|
|
||||||
|
-h, --help show this dialogue and exit
|
||||||
|
"
|
||||||
|
local NAMESPACE
|
||||||
|
local SUBSESSION=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]
|
||||||
|
do
|
||||||
|
case $1 in
|
||||||
|
--subsession ) SUBSESSION=$2; shift 1 ;;
|
||||||
|
|
||||||
|
-h | --help ) USAGE; return 0 ;;
|
||||||
|
|
||||||
|
* )
|
||||||
|
[ $NAMESPACE ] && ERROR "unexpected argument '$2'"
|
||||||
|
NAMESPACE=$1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift 1
|
||||||
|
done
|
||||||
|
|
||||||
|
[ $NAMESPACE ] || NAMESPACE=$(KUBECTL__SELECT_NAMESPACE)
|
||||||
|
[ $NAMESPACE ] || ERROR 'must provide or select a valid namespace'
|
||||||
|
|
||||||
|
CHECK_ERRORS
|
||||||
|
|
||||||
|
KUBECTL__SET_NAMESPACE $NAMESPACE
|
||||||
|
}
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
MAIN $@
|
1
py/.gitignore
vendored
1
py/.gitignore
vendored
@ -1,4 +1,3 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*.so
|
*.so
|
||||||
.env/
|
|
||||||
|
0
py/data/__init__.py
Normal file
0
py/data/__init__.py
Normal file
0
py/data/convert/__init__.py
Normal file
0
py/data/convert/__init__.py
Normal file
24
py/data/convert/csv-to-json.py
Executable file
24
py/data/convert/csv-to-json.py
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from py.lib.data.converter import convert
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
def main(_args, stream):
|
||||||
|
return convert(
|
||||||
|
input_stream = stream.input,
|
||||||
|
input_type = 'csv',
|
||||||
|
output_stream = stream.output,
|
||||||
|
output_type = 'json',
|
||||||
|
)
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
execute(main,
|
||||||
|
description = 'convert csv into json',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
24
py/data/convert/csv-to-yaml.py
Executable file
24
py/data/convert/csv-to-yaml.py
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from py.lib.data.converter import convert
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
def main(_args, stream):
|
||||||
|
return convert(
|
||||||
|
input_stream = stream.input,
|
||||||
|
input_type = 'csv',
|
||||||
|
output_stream = stream.output,
|
||||||
|
output_type = 'yaml',
|
||||||
|
)
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
execute(main,
|
||||||
|
description = 'convert csv into yaml',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
24
py/data/convert/json-to-csv.py
Executable file
24
py/data/convert/json-to-csv.py
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from py.lib.data.converter import convert
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
def main(_args, stream):
|
||||||
|
return convert(
|
||||||
|
input_stream = stream.input,
|
||||||
|
input_type = 'json',
|
||||||
|
output_stream = stream.output,
|
||||||
|
output_type = 'csv',
|
||||||
|
)
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
execute(main,
|
||||||
|
description = 'convert json into csv',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
24
py/data/convert/json-to-yaml.py
Executable file
24
py/data/convert/json-to-yaml.py
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from py.lib.data.converter import convert
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
def main(_args, stream):
|
||||||
|
return convert(
|
||||||
|
input_stream = stream.input,
|
||||||
|
input_type = 'json',
|
||||||
|
output_stream = stream.output,
|
||||||
|
output_type = 'yaml',
|
||||||
|
)
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
execute(main,
|
||||||
|
description = 'convert json into yaml',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
24
py/data/convert/yaml-to-csv.py
Executable file
24
py/data/convert/yaml-to-csv.py
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from py.lib.data.converter import convert
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
def main(_args, stream):
|
||||||
|
return convert(
|
||||||
|
input_stream = stream.input,
|
||||||
|
input_type = 'yaml',
|
||||||
|
output_stream = stream.output,
|
||||||
|
output_type = 'csv',
|
||||||
|
)
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
execute(main,
|
||||||
|
description = 'convert yaml into csv',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
24
py/data/convert/yaml-to-json.py
Executable file
24
py/data/convert/yaml-to-json.py
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from py.lib.data.converter import convert
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
def main(_args, stream):
|
||||||
|
return convert(
|
||||||
|
input_stream = stream.input,
|
||||||
|
input_type = 'yaml',
|
||||||
|
output_stream = stream.output,
|
||||||
|
output_type = 'json',
|
||||||
|
)
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
execute(main,
|
||||||
|
description = 'convert yaml into json',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
0
py/directus/__init__.py
Normal file
0
py/directus/__init__.py
Normal file
145
py/directus/get-items.py
Executable file
145
py/directus/get-items.py
Executable file
@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from json import dumps
|
||||||
|
|
||||||
|
from py.lib.fzf import fzf, fzf_tail
|
||||||
|
from py.lib.http import directus
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
def main(args, stream):
|
||||||
|
if {None} == { args.collection, args.filters, args.fields }:
|
||||||
|
args.interactive = True
|
||||||
|
|
||||||
|
if args.interactive:
|
||||||
|
args.generate_filters_prompt = True
|
||||||
|
args.generate_fields_prompt = True
|
||||||
|
|
||||||
|
collection = _get_or_select_collection(args)
|
||||||
|
filters = _get_or_select_filters(args, collection)
|
||||||
|
fields = _get_or_select_fields(args, collection)
|
||||||
|
|
||||||
|
query = '&'.join([
|
||||||
|
param for param in [
|
||||||
|
fields,
|
||||||
|
filters,
|
||||||
|
]
|
||||||
|
if param
|
||||||
|
])
|
||||||
|
|
||||||
|
endpoint = f'items/{collection}?{query}'
|
||||||
|
|
||||||
|
response = directus.request('GET', endpoint)
|
||||||
|
|
||||||
|
stream.writeline(dumps({
|
||||||
|
**response.json(),
|
||||||
|
'scwrypts_metadata': {
|
||||||
|
'endpoint': endpoint,
|
||||||
|
'repeat_with': f'scwrypts -n py/directus/get-items -- -c {collection} -f \'{query}\'',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
def _get_or_select_collection(args):
|
||||||
|
collection = args.collection
|
||||||
|
|
||||||
|
if collection is None:
|
||||||
|
collection = fzf(
|
||||||
|
prompt = 'select a collection',
|
||||||
|
choices = directus.get_collections(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if not collection:
|
||||||
|
raise ValueError('collection required for query')
|
||||||
|
|
||||||
|
return collection
|
||||||
|
|
||||||
|
def _get_or_select_filters(args, collection):
|
||||||
|
filters = args.filters or ''
|
||||||
|
|
||||||
|
if filters == '' and args.generate_filters_prompt:
|
||||||
|
filters = '&'.join([
|
||||||
|
f'filter[{filter}][' + (
|
||||||
|
operator := fzf(
|
||||||
|
prompt = f'select operator for {filter}',
|
||||||
|
choices = directus.FILTER_OPERATORS,
|
||||||
|
)
|
||||||
|
) + ']=' + fzf_tail(prompt = f'filter[{filter}][{operator}]')
|
||||||
|
|
||||||
|
for filter in fzf(
|
||||||
|
prompt = 'select filter(s) [C^c to skip]',
|
||||||
|
fzf_options = '--multi',
|
||||||
|
force_list = True,
|
||||||
|
choices = directus.get_fields(collection),
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
return filters
|
||||||
|
|
||||||
|
def _get_or_select_fields(args, collection):
|
||||||
|
fields = args.fields or ''
|
||||||
|
|
||||||
|
if fields == '' and args.generate_fields_prompt:
|
||||||
|
fields = ','.join(fzf(
|
||||||
|
prompt = 'select return field(s) [C^c to get all]',
|
||||||
|
fzf_options = '--multi',
|
||||||
|
choices = directus.get_fields(collection),
|
||||||
|
force_list = True,
|
||||||
|
))
|
||||||
|
|
||||||
|
if fields:
|
||||||
|
fields = f'fields[]={fields}'
|
||||||
|
|
||||||
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
execute(main,
|
||||||
|
description = 'interactive CLI to get data from directus',
|
||||||
|
parse_args = [
|
||||||
|
( ['-c', '--collection'], {
|
||||||
|
"dest" : 'collection',
|
||||||
|
"default" : None,
|
||||||
|
"help" : 'the name of the collection',
|
||||||
|
"required" : False,
|
||||||
|
}),
|
||||||
|
( ['-f', '--filters'], {
|
||||||
|
"dest" : 'filters',
|
||||||
|
"default" : None,
|
||||||
|
"help" : 'as a URL-suffix, filters for the query',
|
||||||
|
"required" : False,
|
||||||
|
}),
|
||||||
|
( ['-d', '--fields'], {
|
||||||
|
"dest" : 'fields',
|
||||||
|
"default" : None,
|
||||||
|
"help" : 'comma-separated list of fields to include',
|
||||||
|
"required" : False,
|
||||||
|
}),
|
||||||
|
( ['-p', '--interactive-prompt'], {
|
||||||
|
"action" : 'store_true',
|
||||||
|
"dest" : 'interactive',
|
||||||
|
"default" : False,
|
||||||
|
"help" : 'interactively generate filter prompts; implied if no flags are provided',
|
||||||
|
"required" : False,
|
||||||
|
}),
|
||||||
|
( ['--prompt-filters'], {
|
||||||
|
"action" : 'store_true',
|
||||||
|
"dest" : 'generate_filters_prompt',
|
||||||
|
"default" : False,
|
||||||
|
"help" : '(superceded by -p) only generate filters interactively',
|
||||||
|
"required" : False,
|
||||||
|
}),
|
||||||
|
( ['--prompt-fields'], {
|
||||||
|
"action" : 'store_true',
|
||||||
|
"dest" : 'generate_fields_prompt',
|
||||||
|
"default" : False,
|
||||||
|
"help" : '(superceded by -p) only generate filters interactively',
|
||||||
|
"required" : False,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
)
|
0
py/discord/__init__.py
Normal file
0
py/discord/__init__.py
Normal file
61
py/discord/post-message.py
Executable file
61
py/discord/post-message.py
Executable file
@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from json import dumps
|
||||||
|
from sys import stderr
|
||||||
|
|
||||||
|
from py.lib.http import discord
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
def main(args, stream):
|
||||||
|
if args.content is None:
|
||||||
|
print(f'reading input from {stream.input.name}', file=stderr)
|
||||||
|
args.content = ''.join(stream.readlines()).strip()
|
||||||
|
|
||||||
|
if len(args.content) == 0:
|
||||||
|
args.content = 'PING'
|
||||||
|
|
||||||
|
response = discord.send_message(**vars(args))
|
||||||
|
|
||||||
|
stream.writeline(dumps({
|
||||||
|
**(response.json() if response.text != '' else {'message': 'OK'}),
|
||||||
|
'scwrypts_metadata': {},
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
execute(main,
|
||||||
|
description = 'post a message to the indicated discord channel',
|
||||||
|
parse_args = [
|
||||||
|
( ['-b', '--body'], {
|
||||||
|
'dest' : 'content',
|
||||||
|
'help' : 'message body',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
( ['-c', '--channel-id'], {
|
||||||
|
'dest' : 'channel_id',
|
||||||
|
'help' : 'override default target channel id',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
( ['-w', '--webhook'], {
|
||||||
|
'dest' : 'webhook',
|
||||||
|
'help' : 'override default target webhook (takes precedence over -c)',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
( ['--avatar-url'], {
|
||||||
|
'dest' : 'avatar_url',
|
||||||
|
'help' : 'override default avatar_url',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
( ['--username'], {
|
||||||
|
'dest' : 'username',
|
||||||
|
'help' : 'override default username',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
)
|
27
py/hello-world.py
Executable file
27
py/hello-world.py
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def main(args, stream):
|
||||||
|
stream.writeline(args.message)
|
||||||
|
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
execute(main,
|
||||||
|
description = 'a simple "Hello, World!" program',
|
||||||
|
parse_args = [
|
||||||
|
( ['-m', '--message'], {
|
||||||
|
'dest' : 'message',
|
||||||
|
'default' : 'HELLO WORLD',
|
||||||
|
'help' : 'message to print',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
)
|
@ -1,7 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print('HELLO WORLD')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
6
py/lib/__init__.py
Normal file
6
py/lib/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import py.lib.data
|
||||||
|
import py.lib.fzf
|
||||||
|
import py.lib.http
|
||||||
|
import py.lib.redis
|
||||||
|
import py.lib.scwrypts
|
||||||
|
import py.lib.twilio
|
1
py/lib/data/__init__.py
Normal file
1
py/lib/data/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
import py.lib.data.converter
|
73
py/lib/data/converter.py
Normal file
73
py/lib/data/converter.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import csv
|
||||||
|
import json
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
def convert(input_stream, input_type, output_stream, output_type):
|
||||||
|
if input_type == output_type:
|
||||||
|
raise ValueError('input type and output type are the same')
|
||||||
|
|
||||||
|
data = convert_input(input_stream, input_type)
|
||||||
|
write_output(output_stream, output_type, data)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_input(stream, input_type):
|
||||||
|
supported_input_types = {'csv', 'json', 'yaml'}
|
||||||
|
|
||||||
|
if input_type not in supported_input_types:
|
||||||
|
raise ValueError(f'input_type "{input_type}" not supported; must be one of {supported_input_types}')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'csv': _read_csv,
|
||||||
|
'json': _read_json,
|
||||||
|
'yaml': _read_yaml,
|
||||||
|
}[input_type](stream)
|
||||||
|
|
||||||
|
|
||||||
|
def write_output(stream, output_type, data):
|
||||||
|
supported_output_types = {'csv', 'json', 'yaml'}
|
||||||
|
|
||||||
|
if output_type not in supported_output_types:
|
||||||
|
raise ValueError(f'output_type "{output_type}" not supported; must be one of {supported_output_types}')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'csv': _write_csv,
|
||||||
|
'json': _write_json,
|
||||||
|
'yaml': _write_yaml,
|
||||||
|
}[output_type](stream, data)
|
||||||
|
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
def _read_csv(stream):
|
||||||
|
return [dict(line) for line in csv.DictReader(stream)]
|
||||||
|
|
||||||
|
def _write_csv(stream, data):
|
||||||
|
writer = csv.DictWriter(stream, fieldnames=list({
|
||||||
|
key
|
||||||
|
for dictionary in data
|
||||||
|
for key in dictionary.keys()
|
||||||
|
}))
|
||||||
|
|
||||||
|
writer.writeheader()
|
||||||
|
|
||||||
|
for value in data:
|
||||||
|
writer.writerow(value)
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
def _read_json(stream):
|
||||||
|
data = json.loads(stream.read())
|
||||||
|
return data if isinstance(data, list) else [data]
|
||||||
|
|
||||||
|
def _write_json(stream, data):
|
||||||
|
stream.write(json.dumps(data))
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
def _read_yaml(stream):
|
||||||
|
data = yaml.safe_load(stream)
|
||||||
|
return data if isinstance(data, list) else [data]
|
||||||
|
|
||||||
|
def _write_yaml(stream, data):
|
||||||
|
yaml.dump(data, stream, default_flow_style=False)
|
1
py/lib/fzf/__init__.py
Normal file
1
py/lib/fzf/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from py.lib.fzf.client import fzf, fzf_tail, fzf_head
|
61
py/lib/fzf/client.py
Normal file
61
py/lib/fzf/client.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from pyfzf.pyfzf import FzfPrompt
|
||||||
|
|
||||||
|
FZF_PROMPT = None
|
||||||
|
|
||||||
|
|
||||||
|
def fzf( # pylint: disable=too-many-arguments
|
||||||
|
choices=None,
|
||||||
|
prompt=None,
|
||||||
|
fzf_options='',
|
||||||
|
delimiter='\n',
|
||||||
|
return_type=str,
|
||||||
|
force_list=False,
|
||||||
|
):
|
||||||
|
global FZF_PROMPT # pylint: disable=global-statement
|
||||||
|
|
||||||
|
if choices is None:
|
||||||
|
choices = []
|
||||||
|
|
||||||
|
if not isinstance(return_type, type):
|
||||||
|
raise ValueError(f'return_type must be a valid python type; "{return_type}" is not a type')
|
||||||
|
|
||||||
|
if FZF_PROMPT is None:
|
||||||
|
FZF_PROMPT = FzfPrompt()
|
||||||
|
|
||||||
|
options = ' '.join({
|
||||||
|
'-i',
|
||||||
|
'--layout=reverse',
|
||||||
|
'--ansi',
|
||||||
|
'--height=30%',
|
||||||
|
f'--prompt "{prompt} : "' if prompt is not None else '',
|
||||||
|
fzf_options,
|
||||||
|
})
|
||||||
|
|
||||||
|
selections = [
|
||||||
|
return_type(selection)
|
||||||
|
for selection in FZF_PROMPT.prompt(choices, options, delimiter)
|
||||||
|
]
|
||||||
|
|
||||||
|
if not force_list:
|
||||||
|
if len(selections) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(selections) == 1:
|
||||||
|
return selections[0]
|
||||||
|
|
||||||
|
return selections
|
||||||
|
|
||||||
|
|
||||||
|
def fzf_tail(*args, **kwargs):
|
||||||
|
return _fzf_print(*args, **kwargs)[-1]
|
||||||
|
|
||||||
|
def fzf_head(*args, **kwargs):
|
||||||
|
return _fzf_print(*args, **kwargs)[0]
|
||||||
|
|
||||||
|
def _fzf_print(*args, fzf_options='', **kwargs):
|
||||||
|
return fzf(
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
fzf_options = f'--print-query {fzf_options}',
|
||||||
|
force_list = True,
|
||||||
|
)
|
5
py/lib/http/__init__.py
Normal file
5
py/lib/http/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from py.lib.http.client import get_request_client
|
||||||
|
|
||||||
|
import py.lib.http.directus
|
||||||
|
import py.lib.http.discord
|
||||||
|
import py.lib.http.linear
|
20
py/lib/http/client.py
Normal file
20
py/lib/http/client.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from requests import request
|
||||||
|
|
||||||
|
|
||||||
|
def get_request_client(base_url, headers=None):
|
||||||
|
if headers is None:
|
||||||
|
headers = {}
|
||||||
|
|
||||||
|
return lambda method, endpoint, **kwargs: request(
|
||||||
|
method = method,
|
||||||
|
url = f'{base_url}/{endpoint}',
|
||||||
|
headers = {
|
||||||
|
**headers,
|
||||||
|
**kwargs.get('headers', {}),
|
||||||
|
},
|
||||||
|
**{
|
||||||
|
key: value
|
||||||
|
for key, value in kwargs.items()
|
||||||
|
if key != 'headers'
|
||||||
|
},
|
||||||
|
)
|
2
py/lib/http/directus/__init__.py
Normal file
2
py/lib/http/directus/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from py.lib.http.directus.client import *
|
||||||
|
from py.lib.http.directus.constant import *
|
56
py/lib/http/directus/client.py
Normal file
56
py/lib/http/directus/client.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from py.lib.http import get_request_client
|
||||||
|
from py.lib.scwrypts import getenv
|
||||||
|
|
||||||
|
|
||||||
|
REQUEST = None
|
||||||
|
COLLECTIONS = None
|
||||||
|
FIELDS = {}
|
||||||
|
|
||||||
|
|
||||||
|
def request(method, endpoint, **kwargs):
|
||||||
|
global REQUEST # pylint: disable=global-statement
|
||||||
|
|
||||||
|
if REQUEST is None:
|
||||||
|
REQUEST = get_request_client(
|
||||||
|
base_url = getenv("DIRECTUS__BASE_URL"),
|
||||||
|
headers = {
|
||||||
|
'Authorization': f'bearer {getenv("DIRECTUS__API_TOKEN")}',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return REQUEST(method, endpoint, **kwargs)
|
||||||
|
|
||||||
|
def graphql(query, system=False):
|
||||||
|
return request(
|
||||||
|
'POST',
|
||||||
|
'graphql' if system is True else 'graphql/system',
|
||||||
|
json={'query': query},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_collections():
|
||||||
|
global COLLECTIONS # pylint: disable=global-statement
|
||||||
|
|
||||||
|
if COLLECTIONS is None:
|
||||||
|
COLLECTIONS = [
|
||||||
|
item['collection']
|
||||||
|
for item in request(
|
||||||
|
'GET',
|
||||||
|
'collections?limit=-1&fields[]=collection',
|
||||||
|
).json()['data']
|
||||||
|
]
|
||||||
|
|
||||||
|
return COLLECTIONS
|
||||||
|
|
||||||
|
|
||||||
|
def get_fields(collection):
|
||||||
|
if FIELDS.get(collection) is None:
|
||||||
|
FIELDS[collection] = [
|
||||||
|
item['field']
|
||||||
|
for item in request(
|
||||||
|
'GET',
|
||||||
|
f'fields/{collection}?limit=-1&fields[]=field',
|
||||||
|
).json()['data']
|
||||||
|
]
|
||||||
|
|
||||||
|
return FIELDS[collection]
|
25
py/lib/http/directus/constant.py
Normal file
25
py/lib/http/directus/constant.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
FILTER_OPERATORS = {
|
||||||
|
'_eq',
|
||||||
|
'_neq',
|
||||||
|
'_lt',
|
||||||
|
'_lte',
|
||||||
|
'_gt',
|
||||||
|
'_gte',
|
||||||
|
'_in',
|
||||||
|
'_nin',
|
||||||
|
'_null',
|
||||||
|
'_nnull',
|
||||||
|
'_contains',
|
||||||
|
'_ncontains',
|
||||||
|
'_starts_with',
|
||||||
|
'_ends_with',
|
||||||
|
'_nends_with',
|
||||||
|
'_between',
|
||||||
|
'_nbetween',
|
||||||
|
'_empty',
|
||||||
|
'_nempty',
|
||||||
|
'_intersects',
|
||||||
|
'_nintersects',
|
||||||
|
'_intersects_bbox',
|
||||||
|
'_nintersects_bbox',
|
||||||
|
}
|
2
py/lib/http/discord/__init__.py
Normal file
2
py/lib/http/discord/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from py.lib.http.discord.client import *
|
||||||
|
from py.lib.http.discord.send_message import *
|
20
py/lib/http/discord/client.py
Normal file
20
py/lib/http/discord/client.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from py.lib.http import get_request_client
|
||||||
|
from py.lib.scwrypts import getenv
|
||||||
|
|
||||||
|
REQUEST = None
|
||||||
|
|
||||||
|
def request(method, endpoint, **kwargs):
|
||||||
|
global REQUEST # pylint: disable=global-statement
|
||||||
|
|
||||||
|
if REQUEST is None:
|
||||||
|
headers = {}
|
||||||
|
|
||||||
|
if (token := getenv("DISCORD__BOT_TOKEN", required = False)) is not None:
|
||||||
|
headers['Authorization'] = f'Bot {token}'
|
||||||
|
|
||||||
|
REQUEST = get_request_client(
|
||||||
|
base_url = 'https://discord.com/api',
|
||||||
|
headers = headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
return REQUEST(method, endpoint, **kwargs)
|
48
py/lib/http/discord/send_message.py
Normal file
48
py/lib/http/discord/send_message.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from py.lib.scwrypts import getenv
|
||||||
|
from py.lib.http.discord import request
|
||||||
|
|
||||||
|
def send_message(content, channel_id=None, webhook=None, username=None, avatar_url=None, **kwargs):
|
||||||
|
if username is None:
|
||||||
|
username = getenv('DISCORD__DEFAULT_USERNAME', required=False)
|
||||||
|
|
||||||
|
if avatar_url is None:
|
||||||
|
avatar_url = getenv('DISCORD__DEFAULT_AVATAR_URL', required=False)
|
||||||
|
|
||||||
|
endpoint = None
|
||||||
|
|
||||||
|
if webhook is not None:
|
||||||
|
endpoint = f'webhooks/{webhook}'
|
||||||
|
|
||||||
|
elif channel_id is not None:
|
||||||
|
endpoint = f'channels/{channel_id}/messages'
|
||||||
|
|
||||||
|
elif (webhook := getenv('DISCORD__DEFAULT_WEBHOOK', required=False)) is not None:
|
||||||
|
endpoint = f'webhooks/{webhook}'
|
||||||
|
|
||||||
|
elif (channel_id := getenv('DISCORD__DEFAULT_CHANNEL_ID', required=False)) is not None:
|
||||||
|
endpoint = f'channels/{channel_id}/messages'
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError('must provide target channel_id or webhook')
|
||||||
|
|
||||||
|
if (header := getenv('DISCORD__CONTENT_HEADER', required=False)) is not None:
|
||||||
|
content = f'{header}{content}'
|
||||||
|
|
||||||
|
if (footer := getenv('DISCORD__CONTENT_FOOTER', required=False)) is not None:
|
||||||
|
content = f'{content}{footer}'
|
||||||
|
|
||||||
|
|
||||||
|
return request(
|
||||||
|
method = 'POST',
|
||||||
|
endpoint = endpoint,
|
||||||
|
json = {
|
||||||
|
key: value
|
||||||
|
for key, value in {
|
||||||
|
'content': content,
|
||||||
|
'username': username,
|
||||||
|
'avatar_url': avatar_url,
|
||||||
|
**kwargs,
|
||||||
|
}.items()
|
||||||
|
if value is not None
|
||||||
|
},
|
||||||
|
)
|
1
py/lib/http/linear/__init__.py
Normal file
1
py/lib/http/linear/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from py.lib.http.linear.client import *
|
20
py/lib/http/linear/client.py
Normal file
20
py/lib/http/linear/client.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from py.lib.http import get_request_client
|
||||||
|
from py.lib.scwrypts import getenv
|
||||||
|
|
||||||
|
REQUEST = None
|
||||||
|
|
||||||
|
def request(method, endpoint, **kwargs):
|
||||||
|
global REQUEST # pylint: disable=global-statement
|
||||||
|
|
||||||
|
if REQUEST is None:
|
||||||
|
REQUEST = get_request_client(
|
||||||
|
base_url = 'https://api.linear.app',
|
||||||
|
headers = {
|
||||||
|
'Authorization': f'bearer {getenv("LINEAR__API_TOKEN")}',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return REQUEST(method, endpoint, **kwargs)
|
||||||
|
|
||||||
|
def graphql(query):
|
||||||
|
return request('POST', 'graphql', json={'query': query})
|
1
py/lib/redis/__init__.py
Normal file
1
py/lib/redis/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from py.lib.redis.client import get_client
|
19
py/lib/redis/client.py
Normal file
19
py/lib/redis/client.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from redis import StrictRedis
|
||||||
|
|
||||||
|
from py.lib.scwrypts import getenv
|
||||||
|
|
||||||
|
CLIENT = None
|
||||||
|
|
||||||
|
def get_client():
|
||||||
|
global CLIENT # pylint: disable=global-statement
|
||||||
|
|
||||||
|
if CLIENT is None:
|
||||||
|
print('getting redis client')
|
||||||
|
CLIENT = StrictRedis(
|
||||||
|
host = getenv('REDIS_HOST'),
|
||||||
|
port = getenv('REDIS_PORT'),
|
||||||
|
password = getenv('REDIS_AUTH', required=False),
|
||||||
|
decode_responses = True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return CLIENT
|
6
py/lib/scwrypts/__init__.py
Normal file
6
py/lib/scwrypts/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from py.lib.scwrypts.execute import execute
|
||||||
|
from py.lib.scwrypts.getenv import getenv
|
||||||
|
from py.lib.scwrypts.interactive import interactive
|
||||||
|
from py.lib.scwrypts.run import run
|
||||||
|
|
||||||
|
import py.lib.scwrypts.io
|
16
py/lib/scwrypts/exceptions.py
Normal file
16
py/lib/scwrypts/exceptions.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from argparse import ArgumentError
|
||||||
|
|
||||||
|
|
||||||
|
class MissingVariableError(EnvironmentError):
|
||||||
|
def init(self, name):
|
||||||
|
super().__init__(f'Missing required environment variable "{name}"')
|
||||||
|
|
||||||
|
|
||||||
|
class ImportedExecutableError(ImportError):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__('executable only; must run through scwrypts')
|
||||||
|
|
||||||
|
|
||||||
|
class MissingFlagAndEnvironmentVariableError(EnvironmentError, ArgumentError):
|
||||||
|
def __init__(self, flags, env_var):
|
||||||
|
super().__init__(f'must provide at least one of : {{ flags: {flags} OR {env_var} }}')
|
23
py/lib/scwrypts/execute.py
Normal file
23
py/lib/scwrypts/execute.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||||
|
|
||||||
|
from py.lib.scwrypts.io import get_combined_stream, add_io_arguments
|
||||||
|
|
||||||
|
|
||||||
|
def execute(main, description=None, parse_args=None, toggle_input=True, toggle_output=True):
|
||||||
|
if parse_args is None:
|
||||||
|
parse_args = []
|
||||||
|
|
||||||
|
parser = ArgumentParser(
|
||||||
|
description = description,
|
||||||
|
formatter_class = ArgumentDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
|
||||||
|
add_io_arguments(parser, toggle_input, toggle_output)
|
||||||
|
|
||||||
|
for a in parse_args:
|
||||||
|
parser.add_argument(*a[0], **a[1])
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
with get_combined_stream(args.input_file, args.output_file) as stream:
|
||||||
|
return main(args, stream)
|
15
py/lib/scwrypts/getenv.py
Normal file
15
py/lib/scwrypts/getenv.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from os import getenv as os_getenv
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import MissingVariableError
|
||||||
|
|
||||||
|
|
||||||
|
def getenv(name, required=True):
|
||||||
|
value = os_getenv(name, None)
|
||||||
|
|
||||||
|
if required and not value:
|
||||||
|
raise MissingVariableError(name)
|
||||||
|
|
||||||
|
if value == '':
|
||||||
|
value = None
|
||||||
|
|
||||||
|
return value
|
22
py/lib/scwrypts/interactive.py
Normal file
22
py/lib/scwrypts/interactive.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from bpython import embed
|
||||||
|
|
||||||
|
|
||||||
|
def interactive(variable_descriptions):
|
||||||
|
def outer(function):
|
||||||
|
|
||||||
|
def inner(*args, **kwargs):
|
||||||
|
|
||||||
|
print('\npreparing interactive environment...\n')
|
||||||
|
|
||||||
|
local_vars = function(*args, **kwargs)
|
||||||
|
|
||||||
|
print('\n\n'.join([
|
||||||
|
f'>>> {x}' for x in variable_descriptions
|
||||||
|
]))
|
||||||
|
print('\nenvironment ready; user, GO! :)\n')
|
||||||
|
|
||||||
|
embed(local_vars)
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
return outer
|
86
py/lib/scwrypts/io.py
Normal file
86
py/lib/scwrypts/io.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
from contextlib import contextmanager
|
||||||
|
from pathlib import Path
|
||||||
|
from sys import stdin, stdout, stderr
|
||||||
|
|
||||||
|
from py.lib.scwrypts.getenv import getenv
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def get_stream(filename=None, mode='r', encoding='utf-8', verbose=False, **kwargs):
|
||||||
|
allowed_modes = {'r', 'w', 'w+'}
|
||||||
|
|
||||||
|
if mode not in allowed_modes:
|
||||||
|
raise ValueError(f'mode "{mode}" not supported modes (must be one of {allowed_modes})')
|
||||||
|
|
||||||
|
is_read = mode == 'r'
|
||||||
|
|
||||||
|
if filename is not None:
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(f'opening file {filename} for {"read" if is_read else "write"}', file=stderr)
|
||||||
|
|
||||||
|
if filename[0] not in {'/', '~'}:
|
||||||
|
filename = Path(f'{getenv("EXECUTION_DIR")}/{filename}').resolve()
|
||||||
|
with open(filename, mode=mode, encoding=encoding, **kwargs) as stream:
|
||||||
|
yield stream
|
||||||
|
|
||||||
|
else:
|
||||||
|
if verbose:
|
||||||
|
print('using stdin for read' if is_read else 'using stdout for write', file=stderr)
|
||||||
|
|
||||||
|
yield stdin if is_read else stdout
|
||||||
|
|
||||||
|
if not is_read:
|
||||||
|
stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def add_io_arguments(parser, toggle_input=True, toggle_output=True):
|
||||||
|
if toggle_input:
|
||||||
|
parser.add_argument(
|
||||||
|
'-i', '--input-file',
|
||||||
|
dest = 'input_file',
|
||||||
|
default = None,
|
||||||
|
help = 'path to input file; omit for stdin',
|
||||||
|
required = False,
|
||||||
|
)
|
||||||
|
|
||||||
|
if toggle_output:
|
||||||
|
parser.add_argument(
|
||||||
|
'-o', '--output-file',
|
||||||
|
dest = 'output_file',
|
||||||
|
default = None,
|
||||||
|
help = 'path to output file; omit for stdout',
|
||||||
|
required = False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def get_combined_stream(input_file=None, output_file=None):
|
||||||
|
with get_stream(input_file, 'r') as input_stream, get_stream(output_file, 'w+') as output_stream:
|
||||||
|
yield CombinedStream(input_stream, output_stream)
|
||||||
|
|
||||||
|
|
||||||
|
class CombinedStream:
|
||||||
|
def __init__(self, input_stream, output_stream):
|
||||||
|
self.input = input_stream
|
||||||
|
self.output = output_stream
|
||||||
|
|
||||||
|
def read(self, *args, **kwargs):
|
||||||
|
return self.input.read(*args, **kwargs)
|
||||||
|
|
||||||
|
def readline(self, *args, **kwargs):
|
||||||
|
return self.input.readline(*args, **kwargs)
|
||||||
|
|
||||||
|
def readlines(self, *args, **kwargs):
|
||||||
|
return self.input.readlines(*args, **kwargs)
|
||||||
|
|
||||||
|
def write(self, *args, **kwargs):
|
||||||
|
return self.output.write(*args, **kwargs)
|
||||||
|
|
||||||
|
def writeline(self, line):
|
||||||
|
x = self.output.write(f'{line}\n')
|
||||||
|
self.output.flush()
|
||||||
|
return x
|
||||||
|
|
||||||
|
def writelines(self, *args, **kwargs):
|
||||||
|
return self.output.writelines(*args, **kwargs)
|
22
py/lib/scwrypts/run.py
Normal file
22
py/lib/scwrypts/run.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from os import getenv
|
||||||
|
from pathlib import Path
|
||||||
|
from subprocess import run as subprocess_run
|
||||||
|
|
||||||
|
|
||||||
|
def run(scwrypt_name, *args):
|
||||||
|
DEPTH = int(getenv('SUBSCWRYPT', '0'))
|
||||||
|
DEPTH += 1
|
||||||
|
|
||||||
|
SCWRYPTS_EXE = Path(__file__).parents[3] / 'scwrypts'
|
||||||
|
ARGS = ' '.join([str(x) for x in args])
|
||||||
|
print(f'SUBSCWRYPT={DEPTH} {SCWRYPTS_EXE} {scwrypt_name} -- {ARGS}')
|
||||||
|
|
||||||
|
print(f'\n {"--"*DEPTH} ({DEPTH}) BEGIN SUBSCWRYPT : {Path(scwrypt_name).name}')
|
||||||
|
subprocess_run(
|
||||||
|
f'SUBSCWRYPT={DEPTH} {SCWRYPTS_EXE} {scwrypt_name} -- {ARGS}',
|
||||||
|
shell=True,
|
||||||
|
executable='/bin/zsh',
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f' {"--"*DEPTH} ({DEPTH}) END SUBSCWRYPT : {Path(scwrypt_name).name}\n')
|
2
py/lib/twilio/__init__.py
Normal file
2
py/lib/twilio/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from py.lib.twilio.client import get_client
|
||||||
|
from py.lib.twilio.send_sms import send_sms
|
18
py/lib/twilio/client.py
Normal file
18
py/lib/twilio/client.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from twilio.rest import Client
|
||||||
|
|
||||||
|
from py.lib.scwrypts import getenv
|
||||||
|
|
||||||
|
CLIENT = None
|
||||||
|
|
||||||
|
def get_client():
|
||||||
|
global CLIENT # pylint: disable=global-statement
|
||||||
|
|
||||||
|
if CLIENT is None:
|
||||||
|
print('loading client')
|
||||||
|
CLIENT = Client(
|
||||||
|
username = getenv('TWILIO__API_KEY'),
|
||||||
|
password = getenv('TWILIO__API_SECRET'),
|
||||||
|
account_sid = getenv('TWILIO__ACCOUNT_SID'),
|
||||||
|
)
|
||||||
|
|
||||||
|
return CLIENT
|
57
py/lib/twilio/send_sms.py
Normal file
57
py/lib/twilio/send_sms.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
from json import dumps
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from py.lib.twilio.client import get_client
|
||||||
|
|
||||||
|
|
||||||
|
def send_sms(to, from_, body, max_char_count=300, stream=None):
|
||||||
|
'''
|
||||||
|
abstraction for twilio.client.messages.create which will break
|
||||||
|
messages into multi-part SMS rather than throwing an error or
|
||||||
|
requiring the use of MMS data
|
||||||
|
|
||||||
|
@param to messages.create parameter
|
||||||
|
@param from_ messages.create parameter
|
||||||
|
@param body messages.create parameter
|
||||||
|
@param max_char_count 1 ≤ N ≤ 1500 (default 300)
|
||||||
|
@param stream used to report success/failure (optional)
|
||||||
|
|
||||||
|
@return a list of twilio MessageInstance objects
|
||||||
|
'''
|
||||||
|
client = get_client()
|
||||||
|
messages = []
|
||||||
|
|
||||||
|
max_char_count = max(1, min(max_char_count, 1500))
|
||||||
|
|
||||||
|
total_sms_parts = 1 + len(body) // max_char_count
|
||||||
|
contains_multiple_parts = total_sms_parts > 1
|
||||||
|
|
||||||
|
for i in range(0, len(body), max_char_count):
|
||||||
|
msg_body = body[i:i+max_char_count]
|
||||||
|
current_part = 1 + i // max_char_count
|
||||||
|
|
||||||
|
if contains_multiple_parts:
|
||||||
|
msg_body = f'{current_part}/{total_sms_parts}\n{msg_body}'
|
||||||
|
|
||||||
|
message = client.messages.create(
|
||||||
|
to = to,
|
||||||
|
from_ = from_,
|
||||||
|
body = msg_body,
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.append(message)
|
||||||
|
|
||||||
|
if stream is not None:
|
||||||
|
stream.writeline(
|
||||||
|
dumps({
|
||||||
|
'sid': message.sid,
|
||||||
|
'to': to,
|
||||||
|
'from': from_,
|
||||||
|
'body': msg_body,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
if contains_multiple_parts:
|
||||||
|
sleep(2 if max_char_count <= 500 else 5)
|
||||||
|
|
||||||
|
return messages
|
0
py/linear/__init__.py
Normal file
0
py/linear/__init__.py
Normal file
45
py/linear/comment.py
Executable file
45
py/linear/comment.py
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from py.lib.http.linear import graphql
|
||||||
|
from py.lib.scwrypts import execute
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def get_query(args):
|
||||||
|
body = f'"""from wrobot:\n```\n{args.message}\n```\n"""'
|
||||||
|
return f'''
|
||||||
|
mutation CommentCreate {{
|
||||||
|
commentCreate(
|
||||||
|
input: {{
|
||||||
|
issueId: "{args.issue_id}"
|
||||||
|
body: {body}
|
||||||
|
}}
|
||||||
|
) {{ success }}
|
||||||
|
}}'''
|
||||||
|
|
||||||
|
def main(args, stream):
|
||||||
|
response = graphql(get_query(args))
|
||||||
|
stream.writeline(response)
|
||||||
|
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
execute(main,
|
||||||
|
description = 'comment on an inssue in linear.app',
|
||||||
|
parse_args = [
|
||||||
|
( ['-d', '--issue-id'], {
|
||||||
|
'dest' : 'issue_id',
|
||||||
|
'help' : 'issue short-code (e.g. CLOUD-319)',
|
||||||
|
'required' : True,
|
||||||
|
}),
|
||||||
|
( ['-m', '--message'], {
|
||||||
|
'dest' : 'message',
|
||||||
|
'help' : 'comment to post to the target issue',
|
||||||
|
'required' : True,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
)
|
@ -1,15 +0,0 @@
|
|||||||
from redis import StrictRedis
|
|
||||||
|
|
||||||
from py.scwrypts import getenv
|
|
||||||
|
|
||||||
|
|
||||||
class RedisClient(StrictRedis):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__(
|
|
||||||
host = getenv('REDIS_HOST'),
|
|
||||||
port = getenv('REDIS_PORT'),
|
|
||||||
password = getenv('REDIS_AUTH'),
|
|
||||||
decode_responses = True,
|
|
||||||
)
|
|
||||||
|
|
||||||
Client = RedisClient()
|
|
@ -1,20 +1,26 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from os import getenv
|
from py.lib.redis import get_client
|
||||||
|
from py.lib.scwrypts import execute, interactive, getenv
|
||||||
|
|
||||||
from py.redis.client import Client
|
from py.lib.scwrypts.exceptions import ImportedExecutableError
|
||||||
from py.scwrypts import interactive
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
|
||||||
@interactive
|
@interactive([
|
||||||
def main():
|
f'r = StrictRedis(\'{getenv("REDIS_HOST")}:{getenv("REDIS_PORT")}\')',
|
||||||
r = Client
|
])
|
||||||
|
def main(_args, _stream):
|
||||||
print('''
|
# pylint: disable=possibly-unused-variable
|
||||||
r = StrictRedis("{getenv("REDIS_HOST")}")
|
r = get_client()
|
||||||
''')
|
|
||||||
|
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
#####################################################################
|
||||||
main()
|
execute(main,
|
||||||
|
description = 'establishes a redis client in an interactive python shell',
|
||||||
|
parse_args = [],
|
||||||
|
)
|
||||||
|
@ -1,2 +1,5 @@
|
|||||||
redis
|
|
||||||
bpython
|
bpython
|
||||||
|
pyfzf
|
||||||
|
pyyaml
|
||||||
|
redis
|
||||||
|
twilio
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
from py.scwrypts.getenv import getenv
|
|
||||||
from py.scwrypts.interactive import interactive
|
|
@ -1,3 +0,0 @@
|
|||||||
class MissingVariableError(Exception):
|
|
||||||
def init(self, name):
|
|
||||||
super().__init__(f'Missing required environment variable "{name}"')
|
|
@ -1,23 +0,0 @@
|
|||||||
from os import getenv as os_getenv
|
|
||||||
from pathlib import Path
|
|
||||||
from subprocess import run
|
|
||||||
|
|
||||||
from py.scwrypts.exceptions import MissingVariableError
|
|
||||||
|
|
||||||
|
|
||||||
def getenv(name, required=True):
|
|
||||||
value = os_getenv(name, None)
|
|
||||||
|
|
||||||
if value == None:
|
|
||||||
ZSH_COMMAND = Path(__file__).parents[2] / 'zsh/scwrypts/environment/stage-variables'
|
|
||||||
|
|
||||||
run(
|
|
||||||
f'{ZSH_COMMAND} {name}',
|
|
||||||
shell=True,
|
|
||||||
executable='/bin/zsh',
|
|
||||||
)
|
|
||||||
|
|
||||||
if required:
|
|
||||||
raise MissingVariableError(name)
|
|
||||||
|
|
||||||
return value
|
|
@ -1,9 +0,0 @@
|
|||||||
from bpython import embed
|
|
||||||
|
|
||||||
|
|
||||||
def interactive(function):
|
|
||||||
def main(*args, **kwargs):
|
|
||||||
local_vars = function(*args, **kwargs)
|
|
||||||
embed(local_vars)
|
|
||||||
|
|
||||||
return main
|
|
0
py/twilio/__init__.py
Normal file
0
py/twilio/__init__.py
Normal file
65
py/twilio/send-sms.py
Executable file
65
py/twilio/send-sms.py
Executable file
@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from sys import stderr
|
||||||
|
|
||||||
|
from py.lib.scwrypts import execute, getenv
|
||||||
|
from py.lib.twilio import send_sms
|
||||||
|
|
||||||
|
from py.lib.scwrypts.exceptions import ImportedExecutableError, MissingFlagAndEnvironmentVariableError
|
||||||
|
|
||||||
|
if __name__ != '__main__':
|
||||||
|
raise ImportedExecutableError()
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
def main(args, stream):
|
||||||
|
if args.body is None:
|
||||||
|
print(f'reading input from {stream.input.name}', file=stderr)
|
||||||
|
args.body = ''.join(stream.readlines()).strip()
|
||||||
|
|
||||||
|
if len(args.body) == 0:
|
||||||
|
args.body = 'PING'
|
||||||
|
|
||||||
|
if args.from_ is None:
|
||||||
|
raise MissingFlagAndEnvironmentVariableError(['-f', '--from'], 'TWILIO__DEFAULT_PHONE_FROM')
|
||||||
|
|
||||||
|
if args.to is None:
|
||||||
|
raise MissingFlagAndEnvironmentVariableError(['-t', '--to'], 'TWILIO__DEFAULT_PHONE_TO')
|
||||||
|
|
||||||
|
send_sms(
|
||||||
|
to = args.to,
|
||||||
|
from_ = args.from_,
|
||||||
|
body = args.body,
|
||||||
|
max_char_count = args.max_char_count,
|
||||||
|
stream = stream,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
execute(main,
|
||||||
|
description = 'send a simple SMS through twilio',
|
||||||
|
parse_args = [
|
||||||
|
( ['-t', '--to'], {
|
||||||
|
'dest' : 'to',
|
||||||
|
'help' : 'phone number of the receipient',
|
||||||
|
'required' : False,
|
||||||
|
'default' : getenv('TWILIO__DEFAULT_PHONE_TO', required=False),
|
||||||
|
}),
|
||||||
|
( ['-f', '--from'], {
|
||||||
|
'dest' : 'from_',
|
||||||
|
'help' : 'phone number of the receipient',
|
||||||
|
'required' : False,
|
||||||
|
'default' : getenv('TWILIO__DEFAULT_PHONE_FROM', required=False),
|
||||||
|
}),
|
||||||
|
( ['-b', '--body'], {
|
||||||
|
'dest' : 'body',
|
||||||
|
'help' : 'message body',
|
||||||
|
'required' : False,
|
||||||
|
}),
|
||||||
|
( ['--max-char-count'], {
|
||||||
|
'dest' : 'max_char_count',
|
||||||
|
'help' : 'separate message into parts by character count (1 < N <= 1500)',
|
||||||
|
'required' : False,
|
||||||
|
'default' : 300,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
)
|
350
run
Executable file
350
run
Executable file
@ -0,0 +1,350 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
export EXECUTION_DIR=$(pwd)
|
||||||
|
source "${0:a:h}/zsh/lib/import.driver.zsh" || exit 42
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
__RUN() {
|
||||||
|
local USAGE='
|
||||||
|
usage: scwrypts [OPTIONS ...] SCRIPT -- [SCRIPT OPTIONS ...]
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
-g, --group <group-name> only use scripts from the indicated group
|
||||||
|
-t, --type <type-name> only use scripts of the indicated type
|
||||||
|
-m, --name <scwrypt-name> only run the script if there is an exact match
|
||||||
|
(requires type and group)
|
||||||
|
|
||||||
|
-y, --yes auto-accept all [yn] prompts through current scwrypt
|
||||||
|
-e, --env <env-name> set environment; overwrites SCWRYPTS_ENV
|
||||||
|
-n, --no-log skip logging and run in quiet mode
|
||||||
|
|
||||||
|
--update update scwrypts library to latest version
|
||||||
|
--list-envs print out environment list and exit
|
||||||
|
|
||||||
|
-v, --version print out scwrypts version and exit
|
||||||
|
-l, --list print out command list and exit
|
||||||
|
-h, --help display this message and exit
|
||||||
|
'
|
||||||
|
cd "$SCWRYPTS_ROOT"
|
||||||
|
|
||||||
|
local ENV_NAME="$SCWRYPTS_ENV"
|
||||||
|
local SEARCH_PATTERNS=()
|
||||||
|
|
||||||
|
local VARSPLIT SEARCH_GROUP SEARCH_TYPE SEARCH_NAME
|
||||||
|
|
||||||
|
local ERROR=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]
|
||||||
|
do
|
||||||
|
case $1 in
|
||||||
|
-t | --type )
|
||||||
|
[ ! $2 ] && ERROR "missing value for argument $1" && break
|
||||||
|
SEARCH_TYPE=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-g | --group )
|
||||||
|
[ ! $2 ] && ERROR "missing value for argument $1" && break
|
||||||
|
SEARCH_GROUP=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-m | --name )
|
||||||
|
[ ! $2 ] && ERROR "missing value for argument $1" && break
|
||||||
|
SEARCH_NAME=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
|
||||||
|
-[a-z][a-z]* )
|
||||||
|
VARSPLIT=$(echo "$1 " | sed 's/^\(-.\)\(.*\) /\1 -\2/')
|
||||||
|
set -- $(echo " $VARSPLIT ") ${@:2}
|
||||||
|
;;
|
||||||
|
|
||||||
|
-h | --help )
|
||||||
|
USAGE
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
-n | --no-log )
|
||||||
|
[ ! $SUBSCWRYPT ] && SUBSCWRYPT=0
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
-y | --yes )
|
||||||
|
export __SCWRYPTS_YES=1
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
-e | --env )
|
||||||
|
[ ! $2 ] && ERROR "missing value for argument $1" && break
|
||||||
|
[ ! $SUBSCWRYPTS ] \
|
||||||
|
&& [ $ENV_NAME ] \
|
||||||
|
&& WARNING 'overwriting session environment' \
|
||||||
|
;
|
||||||
|
|
||||||
|
ENV_NAME="$2"
|
||||||
|
STATUS "using CLI environment '$ENV_NAME'"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-l | --list )
|
||||||
|
SCWRYPTS__GET_AVAILABLE_SCWRYPTS
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--list-envs )
|
||||||
|
SCWRYPTS__GET_ENV_NAMES
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-v | --version )
|
||||||
|
echo scwrypts $(cd "$SCWRYPTS__ROOT__scwrypts"; git describe --tags)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--update )
|
||||||
|
cd "$SCWRYPTS__ROOT__scwrypts"
|
||||||
|
git fetch --quiet origin main
|
||||||
|
git fetch --quiet origin main --tags
|
||||||
|
local SYNC_STATUS=$?
|
||||||
|
|
||||||
|
git diff --exit-code origin/main -- . >&2
|
||||||
|
local DIFF_STATUS=$?
|
||||||
|
|
||||||
|
[[ $SYNC_STATUS -eq 0 ]] && [[ $DIFF_STATUS -eq 0 ]] && {
|
||||||
|
SUCCESS 'already up-to-date with origin/main'
|
||||||
|
} || {
|
||||||
|
git rebase --autostash origin/main \
|
||||||
|
&& SUCCESS 'up-to-date with origin/main' \
|
||||||
|
|| {
|
||||||
|
git rebase --abort
|
||||||
|
ERROR 'unable to update scwrypts; please try manual upgrade'
|
||||||
|
REMINDER "installation in '$(pwd)'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-- )
|
||||||
|
shift 1
|
||||||
|
break # pass arguments after '--' to the scwrypt
|
||||||
|
;;
|
||||||
|
--* )
|
||||||
|
ERROR "unrecognized argument '$1'"
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
SEARCH_PATTERNS+=($1)
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[ $SEARCH_NAME ] && {
|
||||||
|
[ ! $SEARCH_TYPE ] && ERROR '--name requires --type argument'
|
||||||
|
[ ! $SEARCH_GROUP ] && ERROR '--name requires --group argument'
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_ERRORS
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
local SCWRYPTS_AVAILABLE
|
||||||
|
local POTENTIAL_ERROR="no such scwrypt exists:"
|
||||||
|
|
||||||
|
SCWRYPTS_AVAILABLE=$(SCWRYPTS__GET_AVAILABLE_SCWRYPTS)
|
||||||
|
|
||||||
|
[ $SEARCH_NAME ] && {
|
||||||
|
POTENTIAL_ERROR+="\n NAME : '$SEARCH_NAME'"
|
||||||
|
POTENTIAL_ERROR+="\n TYPE : '$SEARCH_TYPE'"
|
||||||
|
POTENTIAL_ERROR+="\n GROUP : '$SEARCH_GROUP'"
|
||||||
|
SCWRYPTS_AVAILABLE=$({
|
||||||
|
echo $SCWRYPTS_AVAILABLE | head -n1
|
||||||
|
echo $SCWRYPTS_AVAILABLE | sed -e 's/\x1b\[[0-9;]*m//g' | grep "^$SEARCH_NAME *$SEARCH_TYPE *$SEARCH_GROUP\$"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
[ ! $SEARCH_NAME ] && {
|
||||||
|
[ $SEARCH_TYPE ] && {
|
||||||
|
POTENTIAL_ERROR+="\n TYPE : '$SEARCH_TYPE'"
|
||||||
|
SCWRYPTS_AVAILABLE=$(\
|
||||||
|
{
|
||||||
|
echo $SCWRYPTS_AVAILABLE | head -n1
|
||||||
|
echo $SCWRYPTS_AVAILABLE | grep ' [^/]*'$SEARCH_TYPE'[^/]* '
|
||||||
|
} \
|
||||||
|
| awk '{$2=""; print $0;}' \
|
||||||
|
| sed 's/ \+$/'$(printf $__COLOR_RESET)'/; s/ \+/^/g' \
|
||||||
|
| column -ts '^'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
[ $SEARCH_GROUP ] && {
|
||||||
|
POTENTIAL_ERROR+="\n GROUP : '$SEARCH_GROUP'"
|
||||||
|
SCWRYPTS_AVAILABLE=$(
|
||||||
|
{
|
||||||
|
echo $SCWRYPTS_AVAILABLE | head -n1
|
||||||
|
echo $SCWRYPTS_AVAILABLE | grep "$SEARCH_GROUP"'[^/]*$'
|
||||||
|
} \
|
||||||
|
| awk '{$NF=""; print $0;}' \
|
||||||
|
| sed 's/ \+$/'$(printf $__COLOR_RESET)'/; s/ \+/^/g' \
|
||||||
|
| column -ts '^'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ ${#SEARCH_PATTERNS[@]} -gt 0 ]] && {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ $(echo $SCWRYPTS_AVAILABLE | wc -l) -lt 2 ]] && ERROR "$POTENTIAL_ERROR"
|
||||||
|
|
||||||
|
CHECK_ERRORS
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
local NAME="$SEARCH_NAME"
|
||||||
|
local TYPE="$SEARCH_TYPE"
|
||||||
|
local GROUP="$SEARCH_GROUP"
|
||||||
|
|
||||||
|
[[ $(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 ] || exit 2
|
||||||
|
|
||||||
|
SCWRYPTS__SEPARATE_SCWRYPT_SELECTION $SCWRYPT_SELECTION
|
||||||
|
|
||||||
|
export SCWRYPT_NAME=$NAME
|
||||||
|
export SCWRYPT_TYPE=$TYPE
|
||||||
|
export SCWRYPT_GROUP=$GROUP
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
local ENV_REQUIRED=$(__CHECK_ENV_REQUIRED && echo 1 || echo 0)
|
||||||
|
local REQUIRED_ENVIRONMENT_REGEX=$(eval echo '$SCWRYPTS_REQUIRED_ENVIRONMENT_REGEX__'$SCWRYPT_GROUP)
|
||||||
|
|
||||||
|
[ $REQUIRED_ENVIRONMENT_REGEX ] && {
|
||||||
|
[[ $ENV_NAME =~ $REQUIRED_ENVIRONMENT_REGEX ]] \
|
||||||
|
|| FAIL 5 "group '$SCWRYPT_GROUP' requires current environment to match '$REQUIRED_ENVIRONMENT_REGEX' (currently $ENV_NAME)"
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ $ENV_REQUIRED -eq 1 ]] && {
|
||||||
|
[ ! $ENV_NAME ] && ENV_NAME=$(SCWRYPTS__SELECT_ENV)
|
||||||
|
|
||||||
|
for GROUP in ${SCWRYPTS_GROUPS[@]}
|
||||||
|
do
|
||||||
|
local ENV_FILE=$(SCWRYPTS__GET_ENV_FILE "$ENV_NAME" "$GROUP")
|
||||||
|
source "$ENV_FILE" || FAIL 5 "missing or invalid environment '$GROUP/$ENV_NAME'"
|
||||||
|
|
||||||
|
for f in $(eval 'echo $SCWRYPTS_STATIC_CONFIG__'$GROUP)
|
||||||
|
do
|
||||||
|
source "$f" || FAIL 5 "invalid static config '$f'"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
export ENV_NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
[ $REQUIRED_ENVIRONMENT_REGEX ] && {
|
||||||
|
[[ $ENV_NAME =~ $REQUIRED_ENVIRONMENT_REGEX ]] \
|
||||||
|
|| FAIL 5 "group '$SCWRYPT_GROUP' requires current environment to match '$REQUIRED_ENVIRONMENT_REGEX' (currently $ENV_NAME)"
|
||||||
|
}
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
[ ! $SUBSCWRYPT ] \
|
||||||
|
&& [[ $ENV_NAME =~ prod ]] \
|
||||||
|
&& { __VALIDATE_UPSTREAM_TIMELINE || ABORT; }
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
local RUN_STRING=$(SCWRYPTS__GET_RUNSTRING $SCWRYPT_NAME $SCWRYPT_TYPE $SCWRYPT_GROUP)
|
||||||
|
[ ! $RUN_STRING ] && exit 3
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
|
||||||
|
local LOGFILE=$(__GET_LOGFILE)
|
||||||
|
|
||||||
|
local HEADER=$(
|
||||||
|
[ $SUBSCWRYPT ] && return 0
|
||||||
|
echo '====================================================================='
|
||||||
|
echo "script : $SCWRYPT_GROUP $SCWRYPT_TYPE $SCWRYPT_NAME"
|
||||||
|
echo "run at : $(date)"
|
||||||
|
echo "config : $ENV_NAME"
|
||||||
|
[ ! $LOGFILE ] && echo '\033[1;33m------------------------------------------\033[0m'
|
||||||
|
)
|
||||||
|
|
||||||
|
[ ! $LOGFILE ] && {
|
||||||
|
[ $HEADER ] && echo $HEADER
|
||||||
|
[ $SUBSCWRYPT ] && {
|
||||||
|
eval "$RUN_STRING $(printf "%q " "$@")"
|
||||||
|
exit $?
|
||||||
|
} || {
|
||||||
|
eval "$RUN_STRING $(printf "%q " "$@")" </dev/tty >/dev/tty 2>&1
|
||||||
|
exit $?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
[ $HEADER ] && echo $HEADER
|
||||||
|
echo '\033[1;33m--- BEGIN OUTPUT -------------------------\033[0m'
|
||||||
|
(eval "$RUN_STRING $(printf "%q " "$@")")
|
||||||
|
EXIT_CODE=$?
|
||||||
|
echo '\033[1;33m--- END OUTPUT ---------------------------\033[0m'
|
||||||
|
|
||||||
|
[[ $EXIT_CODE -eq 0 ]] && EXIT_COLOR='32m' || EXIT_COLOR='31m'
|
||||||
|
|
||||||
|
echo "terminated with\\033[1;$EXIT_COLOR code $EXIT_CODE\\033[0m"
|
||||||
|
} 2>&1 | tee --append "$LOGFILE"
|
||||||
|
|
||||||
|
exit $(\
|
||||||
|
sed -n 's/^terminated with.*code \([0-9]*\).*$/\1/p' $LOGFILE \
|
||||||
|
| tail -n1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
__CHECK_ENV_REQUIRED() {
|
||||||
|
[ $CI ] && return 1
|
||||||
|
|
||||||
|
echo $SCWRYPT_NAME | grep -q 'scwrypts/logs/' && return 1
|
||||||
|
echo $SCWRYPT_NAME | grep -q 'scwrypts/environment/' && return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
__VALIDATE_UPSTREAM_TIMELINE() {
|
||||||
|
STATUS "on '$ENV_NAME'; checking diff against origin/main"
|
||||||
|
|
||||||
|
git fetch --quiet origin main
|
||||||
|
local SYNC_STATUS=$?
|
||||||
|
|
||||||
|
git diff --exit-code origin/main -- . >&2
|
||||||
|
local DIFF_STATUS=$?
|
||||||
|
|
||||||
|
[[ $SYNC_STATUS -eq 0 ]] && [[ $DIFF_STATUS -eq 0 ]] && {
|
||||||
|
SUCCESS 'up-to-date with origin/main'
|
||||||
|
} || {
|
||||||
|
WARNING
|
||||||
|
[[ $SYNC_STATUS -ne 0 ]] && WARNING 'unable to synchronize with origin/main'
|
||||||
|
[[ $DIFF_STATUS -ne 0 ]] && WARNING 'your branch differs from origin/main (diff listed above)'
|
||||||
|
WARNING
|
||||||
|
|
||||||
|
yN 'continue?' || return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__GET_LOGFILE() {
|
||||||
|
[ $SUBSCWRYPT ] \
|
||||||
|
|| [[ $SCWRYPT_NAME =~ scwrypts/logs ]] \
|
||||||
|
|| [[ $SCWRYPT_NAME =~ interactive ]] \
|
||||||
|
&& return 0
|
||||||
|
|
||||||
|
echo "$SCWRYPTS_LOG_PATH/$(echo $GROUP/$TYPE/$NAME | sed 's/^\.\///; s/\//\%/g').log"
|
||||||
|
}
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
__RUN $@
|
151
scwrypts
151
scwrypts
@ -1,151 +1,2 @@
|
|||||||
#!/bin/zsh
|
#!/bin/zsh
|
||||||
SCWRYPTS_ROOT="${0:a:h}"
|
source "${0:a:h}/run" $@
|
||||||
|
|
||||||
source "$SCWRYPTS_ROOT/zsh/common.zsh"
|
|
||||||
|
|
||||||
__CHECK_DEPENDENCIES \
|
|
||||||
fzf \
|
|
||||||
;
|
|
||||||
|
|
||||||
__CHECK_IMPORTS
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
__RUN() {
|
|
||||||
cd "$SCWRYPTS_ROOT"
|
|
||||||
|
|
||||||
##########################################
|
|
||||||
### parse arguments ######################
|
|
||||||
##########################################
|
|
||||||
|
|
||||||
local ENV_NAME
|
|
||||||
[ $SCWRYPTS_ENV ] && ENV_NAME="$SCWRYPTS_ENV" || {
|
|
||||||
[ $1 ] && [ -f $(__GET_ENV_FILE $1) ] && {
|
|
||||||
ENV_NAME="$1"
|
|
||||||
shift 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
local SCRIPT
|
|
||||||
local SCRIPTS=$(__GET_AVAILABLE_SCRIPTS)
|
|
||||||
|
|
||||||
[ $1 ] && {
|
|
||||||
for PATTERN in $*
|
|
||||||
do
|
|
||||||
shift 1
|
|
||||||
[[ $PATTERN =~ ^--$ ]] && break
|
|
||||||
SCRIPT=$(echo $SCRIPTS | grep $PATTERN)
|
|
||||||
done
|
|
||||||
|
|
||||||
[ ! $SCRIPT ] && __FAIL 2 "no script found by name '$@'"
|
|
||||||
|
|
||||||
[[ $(echo $SCRIPT | wc -l) -gt 1 ]] && {
|
|
||||||
__STATUS "more than one script matched '$@'"
|
|
||||||
SCRIPT=$(echo $SCRIPT | __FZF 'select a script')
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} || SCRIPT=$(echo $SCRIPTS | __FZF 'select a script')
|
|
||||||
|
|
||||||
[ ! $SCRIPT ] && exit 2
|
|
||||||
|
|
||||||
##########################################
|
|
||||||
### check type and min dependencies ######
|
|
||||||
##########################################
|
|
||||||
|
|
||||||
local ENV_REQUIRED=1
|
|
||||||
local RUN_STRING="./$SCRIPT"
|
|
||||||
local TYPE=$(echo $SCRIPT | sed 's/\/.*$//')
|
|
||||||
|
|
||||||
local VIRTUALENV="$SCWRYPTS_ROOT/$TYPE/.env/bin/activate"
|
|
||||||
[ -f $VIRTUALENV ] && source $VIRTUALENV
|
|
||||||
|
|
||||||
case $TYPE in
|
|
||||||
py ) __CHECK_DEPENDENCY python || exit 3
|
|
||||||
|
|
||||||
python --version | grep -q '3.[91]' || {
|
|
||||||
__WARNING 'only tested on python>=3.9'
|
|
||||||
__WARNING 'compatibility may vary'
|
|
||||||
}
|
|
||||||
|
|
||||||
RUN_STRING="python -m $(echo $SCRIPT | sed 's/\//./g; s/\.py$//; s/\.\.//')"
|
|
||||||
;;
|
|
||||||
|
|
||||||
zsh ) __CHECK_DEPENDENCY zsh || exit 3
|
|
||||||
echo $SCRIPT | grep -q 'scwrypts' && ENV_REQUIRED=0
|
|
||||||
|
|
||||||
RUN_STRING="./$SCRIPT"
|
|
||||||
;;
|
|
||||||
|
|
||||||
zx ) __CHECK_DEPENDENCY zx || exit 3
|
|
||||||
|
|
||||||
RUN_STRING="FORCE_COLOR=3 ./$SCRIPT.mjs"
|
|
||||||
;;
|
|
||||||
|
|
||||||
* ) __FAIL 4 "unsupported script type '$SCRIPT_TYPE'" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
##########################################
|
|
||||||
### load scwrypts env and virtualenv #####
|
|
||||||
##########################################
|
|
||||||
|
|
||||||
[[ $ENV_REQUIRED -eq 1 ]] && {
|
|
||||||
[ ! $ENV_NAME ] && ENV_NAME=$(__SELECT_ENV)
|
|
||||||
[ ! $ENV_NAME ] && __ABORT
|
|
||||||
|
|
||||||
local ENV_FILE=$(__GET_ENV_FILE $ENV_NAME)
|
|
||||||
|
|
||||||
[ -f "$ENV_FILE" ] \
|
|
||||||
&& source "$ENV_FILE" \
|
|
||||||
&& export ENV_NAME \
|
|
||||||
|| __FAIL 5 "missing or invalid environment '$ENV_NAME'"
|
|
||||||
|
|
||||||
[[ $ENV_NAME =~ prod ]] && {
|
|
||||||
__STATUS "on '$ENV_NAME'; checking diff against origin/main"
|
|
||||||
|
|
||||||
git fetch --quiet origin main \
|
|
||||||
&& git diff --exit-code origin/main -- . >&2 \
|
|
||||||
&& __SUCCESS 'up-to-date with main!' \
|
|
||||||
|| {
|
|
||||||
__WARNING
|
|
||||||
__WARNING 'your branch differs from origin/main'
|
|
||||||
__WARNING 'in '$ENV_NAME', being out-of-sync with main may have BAD CONSEQUENCES'
|
|
||||||
__WARNING
|
|
||||||
__yN 'continue?' || __ABORT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
##########################################
|
|
||||||
### run the scwrypt ######################
|
|
||||||
##########################################
|
|
||||||
|
|
||||||
local HEADER=$(
|
|
||||||
echo '====================================================================='
|
|
||||||
echo "script : $SCRIPT"
|
|
||||||
echo "run at : $(date)"
|
|
||||||
echo "config : $ENV_NAME"
|
|
||||||
echo '------------------------------------------'
|
|
||||||
)
|
|
||||||
|
|
||||||
echo $SCRIPT | grep -q 'interactive' && {
|
|
||||||
echo $HEADER
|
|
||||||
eval $RUN_STRING $@ </dev/tty >/dev/tty 2>&1; exit $?
|
|
||||||
}
|
|
||||||
|
|
||||||
local LOGFILE="$SCWRYPTS_LOG_PATH/$(echo $SCRIPT | sed 's/^\.\///; s/\//\%/g').log"
|
|
||||||
[[ $SCRIPT =~ scwrypts/logs ]] && LOGFILE=/dev/null
|
|
||||||
{
|
|
||||||
echo $HEADER
|
|
||||||
echo '--- BEGIN OUTPUT--------------------------'
|
|
||||||
eval $RUN_STRING $@; local EXIT_CODE="$?"
|
|
||||||
echo '--- END OUTPUT ---------------------------'
|
|
||||||
|
|
||||||
local C
|
|
||||||
[[ $EXIT_CODE -eq 0 ]] && C='32m' || C='31m';
|
|
||||||
|
|
||||||
echo "terminated with\\033[1;$C code $EXIT_CODE\\033[0m"
|
|
||||||
} 2>&1 | tee --append "$LOGFILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
__RUN $@
|
|
||||||
|
@ -1,39 +1,42 @@
|
|||||||
source ${0:a:h}/zsh/common.zsh
|
NO_EXPORT_CONFIG=1 source "${0:a:h}/zsh/lib/import.driver.zsh" || return 42
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
[ ! $SCWRYPTS_SHORTCUT ] && return 0
|
SCWRYPTS__ZSH_PLUGIN() {
|
||||||
|
local SCWRYPT_SELECTION=$(SCWRYPTS__GET_AVAILABLE_SCWRYPTS | FZF 'select a script' --header-lines 1)
|
||||||
__SCWRYPTS() {
|
local NAME
|
||||||
|
local TYPE
|
||||||
local SCRIPT=$(__GET_AVAILABLE_SCRIPTS | __FZF 'select a script')
|
local GROUP
|
||||||
zle clear-command-line
|
zle clear-command-line
|
||||||
[ ! $SCRIPT ] && { zle accept-line; return 0; }
|
[ ! $SCWRYPT_SELECTION ] && { zle accept-line; return 0; }
|
||||||
|
|
||||||
|
SCWRYPTS__SEPARATE_SCWRYPT_SELECTION $SCWRYPT_SELECTION
|
||||||
|
|
||||||
which scwrypts >/dev/null 2>&1\
|
which scwrypts >/dev/null 2>&1\
|
||||||
&& RBUFFER="scwrypts" || RBUFFER="$SCWRYPTS_ROOT/scwrypts"
|
&& RBUFFER="scwrypts" || RBUFFER="$SCWRYPTS_ROOT/scwrypts"
|
||||||
|
|
||||||
RBUFFER+=" $SCRIPT"
|
RBUFFER+=" --name $NAME --group $GROUP --type $TYPE"
|
||||||
zle accept-line
|
zle accept-line
|
||||||
}
|
}
|
||||||
zle -N scwrypts __SCWRYPTS
|
|
||||||
|
zle -N scwrypts SCWRYPTS__ZSH_PLUGIN
|
||||||
bindkey $SCWRYPTS_SHORTCUT scwrypts
|
bindkey $SCWRYPTS_SHORTCUT scwrypts
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
[ ! $SCWRYPTS_ENV_SHORTCUT ] && return 0
|
SCWRYPTS__ZSH_PLUGIN_ENV() {
|
||||||
|
|
||||||
__SCWRYPTS_ENV() {
|
|
||||||
local RESET='reset'
|
local RESET='reset'
|
||||||
local SELECTED=$(\
|
local SELECTED=$(\
|
||||||
{ [ $SCWRYPTS_ENV ] && echo $RESET; __GET_ENV_NAMES; } \
|
{ [ $SCWRYPTS_ENV ] && echo $RESET; SCWRYPTS__GET_ENV_NAMES; } \
|
||||||
| __FZF 'select an environment' \
|
| FZF 'select an environment' \
|
||||||
)
|
)
|
||||||
|
|
||||||
zle clear-command-line
|
zle clear-command-line
|
||||||
[ $SELECTED ] && {
|
[ $SELECTED ] && {
|
||||||
[[ $SELECTED =~ ^$RESET$ ]] \
|
[[ $SELECTED =~ ^$RESET$ ]] \
|
||||||
&& RBUFFER='unset SCWRYPTS_ENV' \
|
&& RBUFFER='unset SCWRYPTS_ENV' \
|
||||||
|| RBUFFER="export SCWRYPTS_ENV=$SELECTED'"
|
|| RBUFFER="export SCWRYPTS_ENV=$SELECTED"
|
||||||
}
|
}
|
||||||
zle accept-line
|
zle accept-line
|
||||||
}
|
}
|
||||||
zle -N scwrypts-setenv __SCWRYPTS_ENV
|
|
||||||
|
zle -N scwrypts-setenv SCWRYPTS__ZSH_PLUGIN_ENV
|
||||||
bindkey $SCWRYPTS_ENV_SHORTCUT scwrypts-setenv
|
bindkey $SCWRYPTS_ENV_SHORTCUT scwrypts-setenv
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
[](https://github.com/junegunn/fzf)
|
[](https://github.com/junegunn/fzf)
|
||||||
[](https://github.com/mikefarah/yq)
|
[](https://github.com/mikefarah/yq)
|
||||||
[](https://github.com/stedolan/jq)
|
[](https://github.com/stedolan/jq)
|
||||||
|
[](https://github.com/dbcli/pgcli)
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Since they emulate direct user interaction, shell scripts are often the straightforward choice for task automation.
|
Since they emulate direct user interaction, shell scripts are often the straightforward choice for task automation.
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
source ${0:a:h}/../common.zsh
|
|
||||||
|
|
||||||
__CHECK_DEPENDENCIES \
|
|
||||||
aws \
|
|
||||||
jq \
|
|
||||||
;
|
|
||||||
|
|
||||||
__CHECK_ENV_VARS \
|
|
||||||
_AWS_ACCOUNT \
|
|
||||||
_AWS_PROFILE \
|
|
||||||
_AWS_REGION \
|
|
||||||
;
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
_AWS() { aws --profile $_AWS_PROFILE --region $_AWS_REGION --output json $@; }
|
|
@ -1,10 +0,0 @@
|
|||||||
source ${0:a:h}/../common.zsh
|
|
||||||
|
|
||||||
__CHECK_DEPENDENCIES \
|
|
||||||
docker \
|
|
||||||
;
|
|
||||||
|
|
||||||
__CHECK_ENV_VARS \
|
|
||||||
;
|
|
||||||
|
|
||||||
#####################################################################
|
|
@ -1,13 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
source ${0:a:h}/common.zsh
|
|
||||||
__CHECK_IMPORTS
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
__STATUS "performing AWS ECR docker login"
|
|
||||||
|
|
||||||
_AWS ecr get-login-password | docker login \
|
|
||||||
--username AWS \
|
|
||||||
--password-stdin \
|
|
||||||
"$_AWS_ACCOUNT.dkr.ecr.$_AWS_REGION.amazonaws.com" \
|
|
||||||
&& __SUCCESS "logged in to 'AWS:$_AWS_ACCOUNT:$_AWS_REGION'" \
|
|
||||||
|| __FAIL 1 "unable to login to '$_AWS_ACCOUNT' in '$_AWS_REGION'"
|
|
@ -1,10 +0,0 @@
|
|||||||
source ${0:a:h}/../common.zsh
|
|
||||||
|
|
||||||
__CHECK_DEPENDENCIES \
|
|
||||||
;
|
|
||||||
|
|
||||||
__CHECK_ENV_VARS \
|
|
||||||
AWS__EFS__LOCAL_MOUNT_POINT \
|
|
||||||
;
|
|
||||||
|
|
||||||
#####################################################################
|
|
@ -1,64 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
source ${0:a:h}/common.zsh
|
|
||||||
__CHECK_IMPORTS
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
_EFS_CONNECT() {
|
|
||||||
__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 FS_ID=$(\
|
|
||||||
_AWS efs describe-file-systems \
|
|
||||||
| jq -r '.[] | .[] | .FileSystemId' \
|
|
||||||
| __FZF 'select a filesystem to mount' \
|
|
||||||
)
|
|
||||||
[ ! $FS_ID ] && __ABORT
|
|
||||||
|
|
||||||
local MOUNT_POINT="$AWS__EFS__LOCAL_MOUNT_POINT/$FS_ID"
|
|
||||||
[ -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 \
|
|
||||||
)
|
|
||||||
|
|
||||||
__SUCCESS 'ready to mount!'
|
|
||||||
__REMINDER 'your device must be connected to the appropriate VPN'
|
|
||||||
|
|
||||||
__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" \
|
|
||||||
&& __SUCCESS "mounted at '$MOUNT_POINT'" \
|
|
||||||
|| {
|
|
||||||
sudo rmdir $MOUNT_POINT >/dev/null 2>&1
|
|
||||||
__FAIL 2 "unable to mount '$FS_ID'"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
_EFS_CONNECT
|
|
@ -1,33 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
source ${0:a:h}/common.zsh
|
|
||||||
__CHECK_IMPORTS
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
_EFS_DISCONNECT() {
|
|
||||||
[ ! -d "$AWS__EFS__LOCAL_MOUNT_POINT" ] && {
|
|
||||||
__STATUS 'no efs currently mounted'
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
local MOUNTED=$(ls "$AWS__EFS__LOCAL_MOUNT_POINT")
|
|
||||||
[ ! $MOUNTED ] && {
|
|
||||||
__STATUS 'no efs currently mounted'
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
__GETSUDO || exit 1
|
|
||||||
|
|
||||||
|
|
||||||
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'"
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
_EFS_DISCONNECT
|
|
@ -1,10 +0,0 @@
|
|||||||
source ${0:a:h}/../common.zsh
|
|
||||||
|
|
||||||
__CHECK_DEPENDENCIES \
|
|
||||||
cli53 \
|
|
||||||
;
|
|
||||||
|
|
||||||
__CHECK_ENV_VARS \
|
|
||||||
;
|
|
||||||
|
|
||||||
#####################################################################
|
|
@ -1,9 +0,0 @@
|
|||||||
source ${0:a:h}/../common.zsh
|
|
||||||
|
|
||||||
__CHECK_DEPENDENCIES \
|
|
||||||
;
|
|
||||||
|
|
||||||
__CHECK_ENV_VARS \
|
|
||||||
;
|
|
||||||
|
|
||||||
#####################################################################
|
|
@ -1,34 +0,0 @@
|
|||||||
source ${0:a:h}/../common.zsh
|
|
||||||
|
|
||||||
__CHECK_DEPENDENCIES \
|
|
||||||
;
|
|
||||||
|
|
||||||
__CHECK_ENV_VARS \
|
|
||||||
AWS__S3__MEDIA_TARGETS \
|
|
||||||
AWS__S3__MEDIA_BUCKET \
|
|
||||||
;
|
|
||||||
|
|
||||||
AWS__S3__MEDIA_TARGETS=($(echo $AWS__S3__MEDIA_TARGETS | sed 's/,/\n/g'))
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
__SYNC_MEDIA() {
|
|
||||||
local ACTION="$1"
|
|
||||||
local REMOTE_TARGET="s3://$AWS__S3__MEDIA_BUCKET/$2"
|
|
||||||
local LOCAL_TARGET="$HOME/$2"
|
|
||||||
|
|
||||||
local A B
|
|
||||||
case $ACTION in
|
|
||||||
push ) A="$LOCAL_TARGET"; B="$REMOTE_TARGET" ;;
|
|
||||||
pull ) A="$REMOTE_TARGET"; B="$LOCAL_TARGET" ;;
|
|
||||||
|
|
||||||
* ) __ERROR "unknown action '$1'"; return 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
local FLAGS=(${@:3})
|
|
||||||
|
|
||||||
__STATUS "${ACTION}ing $2"
|
|
||||||
_AWS s3 sync $REMOTE_TARGET $LOCAL_TARGET $FLAGS \
|
|
||||||
&& __SUCCESS "$2 up-to-date" \
|
|
||||||
|| { __ERROR "unable to sync $2 (see above)"; return 1; }
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
source ${0:a:h}/common.zsh
|
|
||||||
__CHECK_IMPORTS
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
__PULL_ALL_MEDIA() {
|
|
||||||
local FLAGS=($@)
|
|
||||||
local FAILED_COUNT=0
|
|
||||||
|
|
||||||
__STATUS 'starting media download from s3'
|
|
||||||
|
|
||||||
local TARGET
|
|
||||||
for TARGET in $AWS__S3__MEDIA_TARGETS
|
|
||||||
do
|
|
||||||
__SYNC_MEDIA pull $TARGET $FLAGS || ((FAILED_COUNT+=1))
|
|
||||||
done
|
|
||||||
|
|
||||||
[[ $FAILED_COUNT -eq 0 ]] \
|
|
||||||
&& __SUCCESS 'local media files now up-to-date' \
|
|
||||||
|| __FAIL $FAILED_COUNT 'unable to download one or more targets' \
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
__PULL_ALL_MEDIA $@
|
|
@ -1,26 +0,0 @@
|
|||||||
#!/bin/zsh
|
|
||||||
source ${0:a:h}/common.zsh
|
|
||||||
__CHECK_IMPORTS
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
__PUSH_ALL_MEDIA() {
|
|
||||||
local FLAGS=($@)
|
|
||||||
local FAILED_COUNT=0
|
|
||||||
|
|
||||||
__STATUS 'starting media upload to s3'
|
|
||||||
|
|
||||||
local TARGET
|
|
||||||
for TARGET in $AWS__S3__MEDIA_TARGETS
|
|
||||||
do
|
|
||||||
__SYNC_MEDIA push $TARGET $FLAGS || ((FAILED_COUNT+=1))
|
|
||||||
done
|
|
||||||
|
|
||||||
[[ $FAILED_COUNT -eq 0 ]] \
|
|
||||||
&& __SUCCESS 's3 media files now up-to-date' \
|
|
||||||
|| __FAIL $FAILED_COUNT 'unable to upload one or more targets' \
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
__PUSH_ALL_MEDIA $@
|
|
10
zsh/cloud/aws/ecr/login
Executable file
10
zsh/cloud/aws/ecr/login
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
DEPENDENCIES+=()
|
||||||
|
REQUIRED_ENV+=()
|
||||||
|
|
||||||
|
use cloud/aws/ecr
|
||||||
|
|
||||||
|
CHECK_ENVIRONMENT
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
ECR_LOGIN $@
|
69
zsh/cloud/aws/efs/mount
Executable file
69
zsh/cloud/aws/efs/mount
Executable file
@ -0,0 +1,69 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
DEPENDENCIES+=(jq)
|
||||||
|
REQUIRED_ENV+=(AWS__EFS__LOCAL_MOUNT_POINT)
|
||||||
|
|
||||||
|
use cloud/aws/cli
|
||||||
|
|
||||||
|
CHECK_ENVIRONMENT
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
EFS_CONNECT() {
|
||||||
|
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 FS_ID=$(\
|
||||||
|
AWS efs describe-file-systems \
|
||||||
|
| jq -r '.[] | .[] | .FileSystemId' \
|
||||||
|
| FZF 'select a filesystem to mount' \
|
||||||
|
)
|
||||||
|
[ ! $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 \
|
||||||
|
)
|
||||||
|
|
||||||
|
SUCCESS 'ready to mount!'
|
||||||
|
REMINDER 'for private file-systems, you must be connected to the appropriate VPN'
|
||||||
|
|
||||||
|
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" \
|
||||||
|
&& SUCCESS "mounted at '$MOUNT_POINT'" \
|
||||||
|
|| {
|
||||||
|
sudo rmdir $MOUNT_POINT >/dev/null 2>&1
|
||||||
|
FAIL 2 "unable to mount '$FS_ID'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#####################################################################
|
||||||
|
EFS_CONNECT $@
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user