diff --git a/plugins/scw/README.md b/plugins/scw/README.md index d2312c2e5..006c2651d 100644 --- a/plugins/scw/README.md +++ b/plugins/scw/README.md @@ -1,7 +1,46 @@ -## Scaleway CLI autocomplete plugin +# SCW -[scw](https://github.com/scaleway/scaleway-cli): Manage Bare Metal servers from Command Line (as easily as with Docker) +This plugin adds functions to easily manage your Scaleway profiles with the +`scw` command. -- Adds autocomplete options for all `scw` commands. +## Prerequisites -Maintainer : Manfred Touron ([@moul](https://github.com/moul)) +Scaleway CLI (scw) should be installed. You can install it from +https://github.com/scaleway/scaleway-cli. + +Copy and paste the code into your Oh My Zsh configuration file (e.g., .zshrc): + +```bash +plugins=(... scw) +``` + +## Functions + +| Commands | Description | +| :---------------: |:-------------------------------------------------------- | +| scw_upgrade | Update your Scaleway CLI version if needed. | +| sgp | Displays the current Scaleway profile. | +| ssp | Sets the Scaleway profile. If no profile name is provided, fallback to the curent active profile set in your configuration file. | +| scw_profiles | Displays a list of available Scaleway profiles. | +| scw_config_path | Returns the path to the Scaleway CLI configuration file (config.yaml). | + +In addition to setting the `SCW_PROFILE` environment variable, `ssp` also sets +the following variables: `SCW_DEFAULT_ORGANIZATION_ID`, +`SCW_DEFAULT_PROJECT_ID`, `SCW_DEFAULT_REGION`, `SCW_DEFAULT_ZONE`, +`SCW_API_URL`. +Additionnally, if `SCW_EXPORT_TOKENS` is set to "true", `SCW_ACCESS_KEY` and +`SCW_SECRET_KEY` are also exported. + +## Customizations + +| Env variables | Description | +| :--------------------------: |:--------------------------------------------- | +| SHOW_SCW_PROMPT | Controls whether to display the Scaleway profile information in the shell prompt. Set this variable to false if you don't want to show the profile information. | +| ZSH_THEME_SCW_PROFILE_PREFIX | sets the prompt prefix. Defaults to `` | + +## Scaleway CLI Autocompletion + +If Scaleway CLI autocompletion is not already loaded, the code automatically +loads the autocompletion script for the scw command. This enables autocompletion +for all Scaleway CLI commands. diff --git a/plugins/scw/_scw b/plugins/scw/_scw deleted file mode 100644 index 0eb125c65..000000000 --- a/plugins/scw/_scw +++ /dev/null @@ -1,333 +0,0 @@ -#compdef scw -# -# zsh completion for scw (https://www.scaleway.com) -# -# Inspired by https://github.com/felixr/docker-zsh-completion - -__scw_get_servers() { - local expl - declare -a servers - servers=(${(f)"$(_call_program commands scw _completion servers-names)"}) - _describe -t servers "servers" servers -} - -__scw_stoppedservers() { - __scw_get_servers -} - -__scw_runningservers() { - __scw_get_servers -} - -__scw_servers () { - __scw_get_servers -} - -__scw_images () { - local expl - declare -a images - images=(${(f)"$(_call_program commands scw _completion images-names)"}) - _describe -t images "images" images -} - -__scw_images_and_snapshots () { - __scw_images - __scw_snapshots -} - -__scw_snapshots () { - local expl - declare -a snapshots - snapshots=(${(f)"$(_call_program commands scw _completion --prefix snapshots-names)"}) - _describe -t snapshots "snapshots" snapshots -} - -__scw_bootscripts () { - local expl - declare -a bootscripts - bootscripts=(${(f)"$(_call_program commands scw _completion bootscripts-names)"}) - _describe -t bootscripts "bootscripts" bootscripts -} - -__scw_tags() { - __scw_images -} - -__scw_repositories_with_tags() { - __scw_images -} - -__scw_search() { - # declare -a scwsearch - local cache_policy - zstyle -s ":completion:${curcontext}:" cache-policy cache_policy - if [[ -z "$cache_policy" ]]; then - zstyle ":completion:${curcontext}:" cache-policy __scw_caching_policy - fi - - local searchterm cachename - searchterm="${words[$CURRENT]%/}" - cachename=_scw-search-$searchterm - - local expl - local -a result - if ( [[ ${(P)+cachename} -eq 0 ]] || _cache_invalid ${cachename#_} ) \ - && ! _retrieve_cache ${cachename#_}; then - _message "Searching for ${searchterm}..." - result=(${${${(f)"$(_call_program commands scw search ${searchterm})"}%% *}[2,-1]}) - _store_cache ${cachename#_} result - fi - _wanted scwsearch expl 'available images' compadd -a result -} - -__scw_caching_policy() -{ - oldp=( "$1"(Nmh+1) ) # 1 hour - (( $#oldp )) -} - - -__scw_repositories () { - __scw_images -} - -__scw_commands () { - # local -a _scw_subcommands - local cache_policy - - zstyle -s ":completion:${curcontext}:" cache-policy cache_policy - if [[ -z "$cache_policy" ]]; then - zstyle ":completion:${curcontext}:" cache-policy __scw_caching_policy - fi - - if ( [[ ${+_scw_subcommands} -eq 0 ]] || _cache_invalid scw_subcommands) \ - && ! _retrieve_cache scw_subcommands; - then - local -a lines - lines=(${(f)"$(_call_program commands scw 2>&1)"}) - _scw_subcommands=(${${${lines[$((${lines[(i)Commands:]} + 1)),${lines[(I) *]}]}## #}/ ##/:}) - _scw_subcommands=($_scw_subcommands 'help:Show help for a command') - _store_cache scw_subcommands _scw_subcommands - fi - _describe -t scw-commands "scw command" _scw_subcommands -} - -__scw_subcommand () { - local -a _command_args - case "$words[1]" in - (attach) - _arguments \ - '--no-stdin[Do not attach stdin]' \ - ':servers:__scw_runningservers' - ;; - (commit) - _arguments \ - {-v,--volume=0}'[Volume slot]:volume: ' \ - ':server:__scw_servers' \ - ':repository:__scw_repositories_with_tags' - ;; - (cp) - _arguments \ - ':server:->server' \ - ':hostpath:_files' - case $state in - (server) - if compset -P '*:'; then - _files - else - __scw_servers -qS ":" - fi - ;; - esac - ;; - (exec) - local state ret - _arguments \ - {-T,--timeout=0}'[Set timeout values to seconds]' \ - {-w,--wait}'[Wait for SSH to be ready]' \ - ':servers:__scw_runningservers' \ - '*::command:->anycommand' && ret=0 - - case $state in - (anycommand) - shift 1 words - (( CURRENT-- )) - _normal - ;; - esac - - return ret - ;; - (history) - _arguments \ - '--no-trunc[Do not truncate output]' \ - {-q,--quiet}'[Only show numeric IDs]' \ - '*:images:__scw_images' - ;; - (images) - _arguments \ - {-a,--all}'[Show all images]' \ - '--no-trunc[Do not truncate output]' \ - {-q,--quiet}'[Only show numeric IDs]' \ - ':repository:__scw_repositories' - ;; - (info) - ;; - (inspect) - _arguments \ - {-f,--format=-}'[Format the output using the given go template]:template: ' \ - '*:servers:__scw_servers' - ;; - (kill) - _arguments \ - '*:servers:__scw_runningservers' - ;; - (login) - _arguments \ - {-o,--organization=-}'[Organization]:organization: ' \ - {-t,--token=-}'[Token]:token: ' \ - ':server: ' - ;; - (logout) - _arguments \ - ':server: ' - ;; - (logs) - _arguments \ - '*:servers:__scw_servers' - ;; - (port) - _arguments \ - '1:servers:__scw_runningservers' \ - '2:port:_ports' - ;; - (start) - _arguments \ - {-T,--timeout=0}'[Set timeout values to seconds]' \ - {-w,--wait}'[Wait for SSH to be ready]' \ - '*:servers:__scw_stoppedservers' - ;; - (rm) - _arguments \ - '*:servers:__scw_stoppedservers' - ;; - (rmi) - _arguments \ - '*:images:__scw_images' - ;; - (restart) - _arguments \ - '*:servers:__scw_runningservers' - ;; - (stop) - _arguments \ - {-t,--terminate}'[Stop and trash a server with its volumes]' \ - {-w,--wait}'[Synchronous stop. Wait for server to be stopped]' \ - '*:servers:__scw_runningservers' - ;; - (top) - _arguments \ - '1:servers:__scw_runningservers' \ - '(-)*:: :->ps-arguments' - case $state in - (ps-arguments) - _ps - ;; - esac - ;; - (ps) - _arguments \ - {-a,--all}'[Show all servers. Only running servers are shown by default]' \ - {-l,--latest}'[Show only the latest created server]' \ - '-n[Show n last created servers, include non-running one]:n:(1 5 10 25 50)' \ - '--no-trunc[Do not truncate output]' \ - {-q,--quiet}'[Only show numeric IDs]' - ;; - (tag) - _arguments \ - {-f,--force}'[force]'\ - ':image:__scw_images'\ - ':repository:__scw_repositories_with_tags' - ;; - (create|run) - _arguments \ - {-a,--attach}'[Attach to stdin, stdout or stderr]' \ - '*'{-e,--environment=-}'[Set environment variables]:environment variable: ' \ - '--name=-[Server name]:name: ' \ - '--bootscript=-[Assign a bootscript]:bootscript:__scw_bootscripts ' \ - '*-v[Bind mount a volume]:volume: '\ - '(-):images:__scw_images_and_snapshots' \ - '(-):command: _command_names -e' \ - '*::arguments: _normal' - - case $state in - (link) - if compset -P '*:'; then - _wanted alias expl 'Alias' compadd -E "" - else - __scw_runningservers -qS ":" - fi - ;; - esac - ;; - (rename) - _arguments \ - ':old name:__scw_servers' \ - ':new name: ' - ;; - (search) - _arguments \ - '--no-trunc[Do not truncate output]' \ - ':term: ' - ;; - (wait) - _arguments '*:servers:__scw_runningservers' - ;; - (help) - _arguments ':subcommand:__scw_commands' - ;; - (*) - _message 'Unknown sub command' - esac - -} - -_scw () { - # Support for subservices, which allows for `compdef _scw scw-shell=_scw_servers`. - # Based on /usr/share/zsh/functions/Completion/Unix/_git without support for `ret`. - if [[ $service != scw ]]; then - _call_function - _$service - return - fi - - local curcontext="$curcontext" state line - typeset -A opt_args - - _arguments -C \ - '-H[tcp://host:port to bind/connect to]:socket: ' \ - '(-): :->command' \ - '(-)*:: :->option-or-argument' - - if (( CURRENT == 1 )); then - - fi - case $state in - (command) - __scw_commands - ;; - (option-or-argument) - curcontext=${curcontext%:*:*}:scw-$words[1]: - __scw_subcommand - ;; - esac -} - -_scw "$@" - -# Local Variables: -# mode: Shell-Script -# sh-indentation: 4 -# indent-tabs-mode: nil -# sh-basic-offset: 4 -# End: -# vim: ft=zsh sw=4 ts=4 et diff --git a/plugins/scw/scw.plugin.zsh b/plugins/scw/scw.plugin.zsh new file mode 100644 index 000000000..b5a2163f5 --- /dev/null +++ b/plugins/scw/scw.plugin.zsh @@ -0,0 +1,172 @@ +if ! command -v scw &> /dev/null; then + echo "[oh-my-zsh] scw command not found, please install it from https://github.com/scaleway/scaleway-cli" + return +fi + +function sgp() { + echo "$SCW_PROFILE" +} + +# SCW profile selection +function ssp() { + if [[ -z "$1" ]]; then + unset SCW_PROFILE \ + SCW_DEFAULT_ORGANIZATION_ID \ + SCW_DEFAULT_PROJECT_ID \ + SCW_DEFAULT_REGION \ + SCW_DEFAULT_ZONE \ + SCW_API_URL \ + SCW_ACCESS_KEY \ + SCW_SECRET_KEY + echo SCW profile cleared. + return + fi + + local -a available_profiles + available_profiles=($(scw_profiles)) + if [[ -z "${available_profiles[(r)$1]}" ]]; then + echo "${fg[red]}Profile '$1' not found in '$(scw_config_path)'" >&2 + echo "Available profiles: ${(j:, :)available_profiles:-no profiles found}${reset_color}" >&2 + return 1 + fi + + export SCW_PROFILE="$1" + unset SCW_DEFAULT_ORGANIZATION_ID \ + SCW_DEFAULT_PROJECT_ID \ + SCW_DEFAULT_REGION \ + SCW_DEFAULT_ZONE \ + SCW_API_URL \ + SCW_ACCESS_KEY \ + SCW_SECRET_KEY + + function scw_export() { + local -r scw_var="$1" + local -r scw_key="$2" + + local -r scw_val="$(scw config get $scw_key)" + if [[ -n "$scw_val" ]] && [[ "$scw_val" != "-" ]]; then + eval "export ${scw_var}=$scw_val" + fi + } + scw_export "SCW_DEFAULT_ORGANIZATION_ID" "default-organization-id" + scw_export "SCW_DEFAULT_PROJECT_ID" "default-project-id" + scw_export "SCW_DEFAULT_REGION" "default-region" + scw_export "SCW_DEFAULT_ZONE" "default-zone" + scw_export "SCW_API_URL" "api-url" + if [[ "$SCW_EXPORT_TOKENS" = "true" ]]; then + scw_export "SCW_ACCESS_KEY" "access-key" + scw_export "SCW_SECRET_KEY" "secret-key" + fi +} + +function scw_profiles() { + scw autocomplete complete zsh 5 -- scw config profile activate 2> /dev/null +} + +function scw_config_path() { + if [[ -v SCW_CONFIG_PATH ]]; then + echo "$SCW_CONFIG_PATH" + return + fi + + for f in "$XDG_CONFIG_HOME/scw/config.yaml" \ + "$HOME/.config/scw/config.yaml" \ + "$USERPROFILE/.config/scw/config.yaml"; do + if [[ -f "$f" ]]; then + echo "$f" + return + fi + done +} + +function scw_upgrade() { + if ! command -v curl &> /dev/null; then + echo "[oh-my-zsh] scw upgrade requires curl, please install it" + return + fi + + local -r scw_path="$(which scw)" + if ! [[ "$scw_path" =~ "^$HOME/.*" ]]; then + echo "[oh-my-zsh] scw not installed in your HOME, upgrade cannot be performed" + return + fi + local scw_version="" + while read -r line; do + if [[ "$line" =~ "^Version +([0-9.]+)\$" ]]; then + scw_version="${match[1]}" + break + fi + done <<< "$(scw version)" + if [[ -z "$scw_version" ]]; then + echo "[oh-my-zsh] cannot determine current scw version" + return + fi + + local -r shasums="$(curl --location --silent "https://github.com/scaleway/scaleway-cli/releases/latest/download/SHA256SUMS")" + if [[ -z "$shasums" ]]; then + echo "[oh-my-zsh] cannot download the list of binaries for the last release" + return + fi + + local -r kernel_name="${$(uname --kernel-name):l}" + local -r arch="${$(uname --machine)//x86_64/amd64}" + local binary_sha256="" + local binary_name="" + while read -r line; do + if [[ "$line" =~ "^([0-9a-f]+) (scaleway-cli_[0-9.]+_${kernel_name}_${arch})\$" ]]; then + binary_sha256="${match[1]}" + binary_name="${match[2]}" + break + fi + done <<< "$shasums" + if [[ -z "$binary_sha256" ]] || [[ -z "$binary_name" ]]; then + echo "[oh-my-zsh] cannot find a scw binary for your computer" + return + fi + if [[ "$binary_name" =~ "^scaleway-cli_${scw_version}_.*\$" ]]; then + echo "[oh-my-zsh] current scw version is already the latest (v${scw_version})" + return + fi + + local -r binary_tmp="$(mktemp)" + echo "[oh-my-zsh] downloading ${binary_name}..." + curl --location --progress-bar "https://github.com/scaleway/scaleway-cli/releases/latest/download/${binary_name}" -o "$binary_tmp" + if [[ $? -ne 0 ]]; then + echo "[oh-my-zsh] cannot download the latest ${binary_name} release binary" + rm -f "$binary_tmp" + return + fi + if [[ "${$(sha256sum "$binary_tmp")%% *}" != "$binary_sha256" ]]; then + echo "[oh-my-zsh] downloaded ${binary_name} binary has a wrong sha256sum" + rm -f "$binary_tmp" + return + fi + # Install new scw command and preserve original file permissions + \cp --no-preserve=all --force "$binary_tmp" "$scw_path" + echo "[oh-my-zsh] scw successfully updated" + rm -f "$binary_tmp" +} + +function _scw_profiles() { + reply=($(scw_profiles)) +} +compctl -K _scw_profiles ssp + +function scw_prompt_info() { + local _scw_to_show + + if [[ -n "$SCW_PROFILE" ]]; then + _scw_to_show+="${ZSH_THEME_SCW_PROFILE_PREFIX=""}" + fi + + echo "$_scw_to_show" +} + +if [[ "$SHOW_SCW_PROMPT" != false && "$RPROMPT" != *'$(scw_prompt_info)'* ]]; then + RPROMPT='$(scw_prompt_info)'"$RPROMPT" +fi + +# Load scw autocompletion if autocompletion not already loaded +if ! command -v _scw &> /dev/null; then + eval "$(scw autocomplete script shell=zsh)" +fi