diff --git a/.vscode/settings.json b/.vscode/settings.json index c5d5fdae..82d38ab9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ "rust-analyzer.cargo.features": "all", "rust-analyzer.rustfmt.overrideCommand": [ "rustfmt", - "+nightly-2025-01-15", + "+nightly-2025-10-23", "--edition", "2024", "--" diff --git a/Cargo.lock b/Cargo.lock index be70db85..28e10a79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -433,6 +433,12 @@ dependencies = [ "serde", ] +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + [[package]] name = "bumpalo" version = "3.19.0" @@ -3607,6 +3613,7 @@ dependencies = [ name = "stackablectl" version = "1.2.0" dependencies = [ + "built", "clap", "clap_complete", "clap_complete_nushell", diff --git a/Cargo.nix b/Cargo.nix index c85e0d0e..90d2c8e2 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -1542,6 +1542,22 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "std" ]; }; + "built" = rec { + crateName = "built"; + version = "0.8.0"; + edition = "2021"; + sha256 = "0r5f08lpjsr6j5ajkbmd0ymfmajpq8ddbfvi8ji8rx48y88qzbgl"; + authors = [ + "Lukas Lueg " + ]; + features = { + "cargo-lock" = [ "dep:cargo-lock" ]; + "chrono" = [ "dep:chrono" ]; + "dependency-tree" = [ "cargo-lock/dependency-tree" ]; + "git2" = [ "dep:git2" ]; + "semver" = [ "dep:semver" ]; + }; + }; "bumpalo" = rec { crateName = "bumpalo"; version = "3.19.0"; @@ -12126,6 +12142,12 @@ rec { packageId = "urlencoding"; } ]; + buildDependencies = [ + { + name = "built"; + packageId = "built"; + } + ]; }; "strsim" = rec { diff --git a/Cargo.toml b/Cargo.toml index 93e88231..fba4c5cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ axum = { version = "0.7", features = ["http2"] } axum-extra = { version = "0.9", features = ["typed-header"] } bcrypt = "0.15" bindgen = "0.70.1" +built = "0.8.0" cc = "1.0.106" clap = { version = "4.5", features = ["derive", "env"] } clap_complete = "4.5" diff --git a/docs/modules/stackablectl/partials/commands/help.adoc b/docs/modules/stackablectl/partials/commands/help.adoc index 6a390f6f..c3441a55 100644 --- a/docs/modules/stackablectl/partials/commands/help.adoc +++ b/docs/modules/stackablectl/partials/commands/help.adoc @@ -14,5 +14,6 @@ Commands: completions Generate shell completions for this tool cache Interact with locally cached files experimental-debug EXPERIMENTAL: Launch a debug container for a Pod + version Retrieve version data of the stackablectl installation help Print this message or the help of the given subcommand(s) ---- diff --git a/docs/modules/stackablectl/partials/commands/index.adoc b/docs/modules/stackablectl/partials/commands/index.adoc index b2f8ca3e..a1a305c2 100644 --- a/docs/modules/stackablectl/partials/commands/index.adoc +++ b/docs/modules/stackablectl/partials/commands/index.adoc @@ -14,6 +14,7 @@ Commands: completions Generate shell completions for this tool cache Interact with locally cached files experimental-debug EXPERIMENTAL: Launch a debug container for a Pod + version Retrieve version data of the stackablectl installation help Print this message or the help of the given subcommand(s) Options: diff --git a/docs/modules/stackablectl/partials/commands/version.adoc b/docs/modules/stackablectl/partials/commands/version.adoc new file mode 100644 index 00000000..06610aae --- /dev/null +++ b/docs/modules/stackablectl/partials/commands/version.adoc @@ -0,0 +1,95 @@ +// Autogenerated by cargo xtask gen-docs. DO NOT CHANGE MANUALLY! +[source,console] +---- +Retrieve version data of the stackablectl installation + +Usage: stackablectl version [OPTIONS] + +Commands: + check Check if there is a new version available + help Print this message or the help of the given subcommand(s) + +Options: + -l, --log-level + Log level this application uses + + --no-cache + Do not cache the remote (default) demo, stack and release files + + Cached files are saved at '$XDG_CACHE_HOME/stackablectl', which is usually + '$HOME/.cache/stackablectl' when not explicitly set. + + -h, --help + Print help (see a summary with '-h') + + -V, --version + Print version + +File options: + -d, --demo-file + Provide one or more additional (custom) demo file(s) + + Demos are loaded in the following order: Remote (default) demo file, custom + demo files provided via the 'STACKABLE_DEMO_FILES' environment variable, and + lastly demo files provided via the '-d/--demo-file' argument(s). If there are + demos with the same name, the last demo definition will be used. + + Use "stackablectl [OPTIONS] -d path/to/demos1.yaml -d path/to/demos2.yaml" + to provide multiple additional demo files. + + -s, --stack-file + Provide one or more additional (custom) stack file(s) + + Stacks are loaded in the following order: Remote (default) stack file, custom + stack files provided via the 'STACKABLE_STACK_FILES' environment variable, and + lastly demo files provided via the '-s/--stack-file' argument(s). If there are + stacks with the same name, the last stack definition will be used. + + Use "stackablectl [OPTIONS] -s path/to/stacks1.yaml -s path/to/stacks2.yaml" + to provide multiple additional stack files. + + -r, --release-file + Provide one or more additional (custom) release file(s) + + Releases are loaded in the following order: Remote (default) release file, + custom release files provided via the 'STACKABLE_RELEASE_FILES' environment + variable, and lastly release files provided via the '-r/--release-file' + argument(s). If there are releases with the same name, the last release + definition will be used. + + Use "stackablectl [OPTIONS] -r path/to/releases1.yaml -r path/to/releases2.yaml" + to provide multiple additional release files. + +Helm repository options: + --helm-repo-stable + Provide a custom Helm stable repository URL + + [default: https://repo.stackable.tech/repository/helm-stable/] + + --helm-repo-test + Provide a custom Helm test repository URL + + [default: https://repo.stackable.tech/repository/helm-test/] + + --helm-repo-dev + Provide a custom Helm dev repository URL + + [default: https://repo.stackable.tech/repository/helm-dev/] + + --chart-source + Source the charts from either a OCI registry or from index.yaml-based repositories. + + Possible values: + - oci: OCI registry + - repo: index.yaml-based repositories: resolution (dev, test, stable) is based on the version and thus will be operator-specific + + [default: oci] + +Operator specific configurations: + --listener-class-preset + Choose the ListenerClass preset (`none`, `ephemeral-nodes` or `stable-nodes`). + + This maps to the listener-operator Helm Chart preset value, see [the listener-operator documentation](https://docs.stackable.tech/home/nightly/listener-operator/listenerclass/#presets) for details. + + [possible values: none, stable-nodes, ephemeral-nodes] +---- diff --git a/extra/completions/_stackablectl b/extra/completions/_stackablectl index bba6e21d..342cc466 100644 --- a/extra/completions/_stackablectl +++ b/extra/completions/_stackablectl @@ -1307,6 +1307,88 @@ repo\:"index.yaml-based repositories\: resolution (dev, test, stable) is based o '*::cmd -- The command to run in the debug container:_default' \ && ret=0 ;; +(version) +_arguments "${_arguments_options[@]}" : \ +'-l+[Log level this application uses]:LOG_LEVEL:_default' \ +'--log-level=[Log level this application uses]:LOG_LEVEL:_default' \ +'*-d+[Provide one or more additional (custom) demo file(s)]:DEMO_FILE:_files' \ +'*--demo-file=[Provide one or more additional (custom) demo file(s)]:DEMO_FILE:_files' \ +'*-s+[Provide one or more additional (custom) stack file(s)]:STACK_FILE:_files' \ +'*--stack-file=[Provide one or more additional (custom) stack file(s)]:STACK_FILE:_files' \ +'*-r+[Provide one or more additional (custom) release file(s)]:RELEASE_FILE:_files' \ +'*--release-file=[Provide one or more additional (custom) release file(s)]:RELEASE_FILE:_files' \ +'--helm-repo-stable=[Provide a custom Helm stable repository URL]:URL:_urls' \ +'--helm-repo-test=[Provide a custom Helm test repository URL]:URL:_urls' \ +'--helm-repo-dev=[Provide a custom Helm dev repository URL]:URL:_urls' \ +'--chart-source=[Source the charts from either a OCI registry or from index.yaml-based repositories]:CHART_SOURCE:((oci\:"OCI registry" +repo\:"index.yaml-based repositories\: resolution (dev, test, stable) is based on the version and thus will be operator-specific"))' \ +'--listener-class-preset=[Choose the ListenerClass preset (\`none\`, \`ephemeral-nodes\` or \`stable-nodes\`)]:LISTENER_CLASS_PRESET:(none stable-nodes ephemeral-nodes)' \ +'--no-cache[Do not cache the remote (default) demo, stack and release files]' \ +'-h[Print help (see more with '\''--help'\'')]' \ +'--help[Print help (see more with '\''--help'\'')]' \ +'-V[Print version]' \ +'--version[Print version]' \ +":: :_stackablectl__version_commands" \ +"*::: :->version" \ +&& ret=0 + + case $state in + (version) + words=($line[1] "${words[@]}") + (( CURRENT += 1 )) + curcontext="${curcontext%:*:*}:stackablectl-version-command-$line[1]:" + case $line[1] in + (check) +_arguments "${_arguments_options[@]}" : \ +'-l+[Log level this application uses]:LOG_LEVEL:_default' \ +'--log-level=[Log level this application uses]:LOG_LEVEL:_default' \ +'*-d+[Provide one or more additional (custom) demo file(s)]:DEMO_FILE:_files' \ +'*--demo-file=[Provide one or more additional (custom) demo file(s)]:DEMO_FILE:_files' \ +'*-s+[Provide one or more additional (custom) stack file(s)]:STACK_FILE:_files' \ +'*--stack-file=[Provide one or more additional (custom) stack file(s)]:STACK_FILE:_files' \ +'*-r+[Provide one or more additional (custom) release file(s)]:RELEASE_FILE:_files' \ +'*--release-file=[Provide one or more additional (custom) release file(s)]:RELEASE_FILE:_files' \ +'--helm-repo-stable=[Provide a custom Helm stable repository URL]:URL:_urls' \ +'--helm-repo-test=[Provide a custom Helm test repository URL]:URL:_urls' \ +'--helm-repo-dev=[Provide a custom Helm dev repository URL]:URL:_urls' \ +'--chart-source=[Source the charts from either a OCI registry or from index.yaml-based repositories]:CHART_SOURCE:((oci\:"OCI registry" +repo\:"index.yaml-based repositories\: resolution (dev, test, stable) is based on the version and thus will be operator-specific"))' \ +'--listener-class-preset=[Choose the ListenerClass preset (\`none\`, \`ephemeral-nodes\` or \`stable-nodes\`)]:LISTENER_CLASS_PRESET:(none stable-nodes ephemeral-nodes)' \ +'--no-cache[Do not cache the remote (default) demo, stack and release files]' \ +'-h[Print help (see more with '\''--help'\'')]' \ +'--help[Print help (see more with '\''--help'\'')]' \ +'-V[Print version]' \ +'--version[Print version]' \ +&& ret=0 +;; +(help) +_arguments "${_arguments_options[@]}" : \ +":: :_stackablectl__version__help_commands" \ +"*::: :->help" \ +&& ret=0 + + case $state in + (help) + words=($line[1] "${words[@]}") + (( CURRENT += 1 )) + curcontext="${curcontext%:*:*}:stackablectl-version-help-command-$line[1]:" + case $line[1] in + (check) +_arguments "${_arguments_options[@]}" : \ +&& ret=0 +;; +(help) +_arguments "${_arguments_options[@]}" : \ +&& ret=0 +;; + esac + ;; +esac +;; + esac + ;; +esac +;; (help) _arguments "${_arguments_options[@]}" : \ ":: :_stackablectl__help_commands" \ @@ -1535,6 +1617,26 @@ esac _arguments "${_arguments_options[@]}" : \ && ret=0 ;; +(version) +_arguments "${_arguments_options[@]}" : \ +":: :_stackablectl__help__version_commands" \ +"*::: :->version" \ +&& ret=0 + + case $state in + (version) + words=($line[1] "${words[@]}") + (( CURRENT += 1 )) + curcontext="${curcontext%:*:*}:stackablectl-help-version-command-$line[1]:" + case $line[1] in + (check) +_arguments "${_arguments_options[@]}" : \ +&& ret=0 +;; + esac + ;; +esac +;; (help) _arguments "${_arguments_options[@]}" : \ && ret=0 @@ -1559,6 +1661,7 @@ _stackablectl_commands() { 'completions:Generate shell completions for this tool' \ 'cache:Interact with locally cached files' \ 'experimental-debug:EXPERIMENTAL\: Launch a debug container for a Pod' \ +'version:Retrieve version data of the stackablectl installation' \ 'help:Print this message or the help of the given subcommand(s)' \ ) _describe -t commands 'stackablectl commands' commands "$@" @@ -1756,6 +1859,7 @@ _stackablectl__help_commands() { 'completions:Generate shell completions for this tool' \ 'cache:Interact with locally cached files' \ 'experimental-debug:EXPERIMENTAL\: Launch a debug container for a Pod' \ +'version:Retrieve version data of the stackablectl installation' \ 'help:Print this message or the help of the given subcommand(s)' \ ) _describe -t commands 'stackablectl help commands' commands "$@" @@ -1962,6 +2066,18 @@ _stackablectl__help__stacklet__list_commands() { local commands; commands=() _describe -t commands 'stackablectl help stacklet list commands' commands "$@" } +(( $+functions[_stackablectl__help__version_commands] )) || +_stackablectl__help__version_commands() { + local commands; commands=( +'check:Check if there is a new version available' \ + ) + _describe -t commands 'stackablectl help version commands' commands "$@" +} +(( $+functions[_stackablectl__help__version__check_commands] )) || +_stackablectl__help__version__check_commands() { + local commands; commands=() + _describe -t commands 'stackablectl help version check commands' commands "$@" +} (( $+functions[_stackablectl__operator_commands] )) || _stackablectl__operator_commands() { local commands; commands=( @@ -2218,6 +2334,37 @@ _stackablectl__stacklet__list_commands() { local commands; commands=() _describe -t commands 'stackablectl stacklet list commands' commands "$@" } +(( $+functions[_stackablectl__version_commands] )) || +_stackablectl__version_commands() { + local commands; commands=( +'check:Check if there is a new version available' \ +'help:Print this message or the help of the given subcommand(s)' \ + ) + _describe -t commands 'stackablectl version commands' commands "$@" +} +(( $+functions[_stackablectl__version__check_commands] )) || +_stackablectl__version__check_commands() { + local commands; commands=() + _describe -t commands 'stackablectl version check commands' commands "$@" +} +(( $+functions[_stackablectl__version__help_commands] )) || +_stackablectl__version__help_commands() { + local commands; commands=( +'check:Check if there is a new version available' \ +'help:Print this message or the help of the given subcommand(s)' \ + ) + _describe -t commands 'stackablectl version help commands' commands "$@" +} +(( $+functions[_stackablectl__version__help__check_commands] )) || +_stackablectl__version__help__check_commands() { + local commands; commands=() + _describe -t commands 'stackablectl version help check commands' commands "$@" +} +(( $+functions[_stackablectl__version__help__help_commands] )) || +_stackablectl__version__help__help_commands() { + local commands; commands=() + _describe -t commands 'stackablectl version help help commands' commands "$@" +} if [ "$funcstack[1]" = "_stackablectl" ]; then _stackablectl "$@" diff --git a/extra/completions/stackablectl.bash b/extra/completions/stackablectl.bash index 3348a0ca..140bd2f6 100644 --- a/extra/completions/stackablectl.bash +++ b/extra/completions/stackablectl.bash @@ -43,6 +43,9 @@ _stackablectl() { stackablectl,stacklet) cmd="stackablectl__stacklet" ;; + stackablectl,version) + cmd="stackablectl__version" + ;; stackablectl__cache,clean) cmd="stackablectl__cache__clean" ;; @@ -148,6 +151,9 @@ _stackablectl() { stackablectl__help,stacklet) cmd="stackablectl__help__stacklet" ;; + stackablectl__help,version) + cmd="stackablectl__help__version" + ;; stackablectl__help__cache,clean) cmd="stackablectl__help__cache__clean" ;; @@ -223,6 +229,9 @@ _stackablectl() { stackablectl__help__stacklet,list) cmd="stackablectl__help__stacklet__list" ;; + stackablectl__help__version,check) + cmd="stackablectl__help__version__check" + ;; stackablectl__operator,describe) cmd="stackablectl__operator__describe" ;; @@ -337,6 +346,18 @@ _stackablectl() { stackablectl__stacklet__help,list) cmd="stackablectl__stacklet__help__list" ;; + stackablectl__version,check) + cmd="stackablectl__version__check" + ;; + stackablectl__version,help) + cmd="stackablectl__version__help" + ;; + stackablectl__version__help,check) + cmd="stackablectl__version__help__check" + ;; + stackablectl__version__help,help) + cmd="stackablectl__version__help__help" + ;; *) ;; esac @@ -344,7 +365,7 @@ _stackablectl() { case "${cmd}" in stackablectl) - opts="-l -d -s -r -h -V --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --chart-source --listener-class-preset --help --version operator release stack stacklet demo completions cache experimental-debug help" + opts="-l -d -s -r -h -V --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --chart-source --listener-class-preset --help --version operator release stack stacklet demo completions cache experimental-debug version help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -2648,7 +2669,7 @@ _stackablectl() { return 0 ;; stackablectl__help) - opts="operator release stack stacklet demo completions cache experimental-debug help" + opts="operator release stack stacklet demo completions cache experimental-debug version help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -3137,6 +3158,34 @@ _stackablectl() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; + stackablectl__help__version) + opts="check" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + stackablectl__help__version__check) + opts="" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; stackablectl__operator) opts="-l -d -s -r -h -V --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --chart-source --listener-class-preset --help --version list describe install uninstall installed help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then @@ -6243,6 +6292,312 @@ _stackablectl() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; + stackablectl__version) + opts="-l -d -s -r -h -V --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --chart-source --listener-class-preset --help --version check help" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + --log-level) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -l) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --demo-file) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; + -d) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; + --stack-file) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; + -s) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; + --release-file) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; + -r) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; + --helm-repo-stable) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --helm-repo-test) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --helm-repo-dev) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --chart-source) + COMPREPLY=($(compgen -W "oci repo" -- "${cur}")) + return 0 + ;; + --listener-class-preset) + COMPREPLY=($(compgen -W "none stable-nodes ephemeral-nodes" -- "${cur}")) + return 0 + ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + stackablectl__version__check) + opts="-l -d -s -r -h -V --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --chart-source --listener-class-preset --help --version" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + --log-level) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -l) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --demo-file) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; + -d) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; + --stack-file) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; + -s) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; + --release-file) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; + -r) + local oldifs + if [ -n "${IFS+x}" ]; then + oldifs="$IFS" + fi + IFS=$'\n' + COMPREPLY=($(compgen -f "${cur}")) + if [ -n "${oldifs+x}" ]; then + IFS="$oldifs" + fi + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi + return 0 + ;; + --helm-repo-stable) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --helm-repo-test) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --helm-repo-dev) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --chart-source) + COMPREPLY=($(compgen -W "oci repo" -- "${cur}")) + return 0 + ;; + --listener-class-preset) + COMPREPLY=($(compgen -W "none stable-nodes ephemeral-nodes" -- "${cur}")) + return 0 + ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + stackablectl__version__help) + opts="check help" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + stackablectl__version__help__check) + opts="" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + stackablectl__version__help__help) + opts="" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; esac } diff --git a/extra/completions/stackablectl.elv b/extra/completions/stackablectl.elv index 07e64733..04175a39 100644 --- a/extra/completions/stackablectl.elv +++ b/extra/completions/stackablectl.elv @@ -44,6 +44,7 @@ set edit:completion:arg-completer[stackablectl] = {|@words| cand completions 'Generate shell completions for this tool' cand cache 'Interact with locally cached files' cand experimental-debug 'EXPERIMENTAL: Launch a debug container for a Pod' + cand version 'Retrieve version data of the stackablectl installation' cand help 'Print this message or the help of the given subcommand(s)' } &'stackablectl;operator'= { @@ -946,6 +947,56 @@ set edit:completion:arg-completer[stackablectl] = {|@words| cand -V 'Print version' cand --version 'Print version' } + &'stackablectl;version'= { + cand -l 'Log level this application uses' + cand --log-level 'Log level this application uses' + cand -d 'Provide one or more additional (custom) demo file(s)' + cand --demo-file 'Provide one or more additional (custom) demo file(s)' + cand -s 'Provide one or more additional (custom) stack file(s)' + cand --stack-file 'Provide one or more additional (custom) stack file(s)' + cand -r 'Provide one or more additional (custom) release file(s)' + cand --release-file 'Provide one or more additional (custom) release file(s)' + cand --helm-repo-stable 'Provide a custom Helm stable repository URL' + cand --helm-repo-test 'Provide a custom Helm test repository URL' + cand --helm-repo-dev 'Provide a custom Helm dev repository URL' + cand --chart-source 'Source the charts from either a OCI registry or from index.yaml-based repositories' + cand --listener-class-preset 'Choose the ListenerClass preset (`none`, `ephemeral-nodes` or `stable-nodes`)' + cand --no-cache 'Do not cache the remote (default) demo, stack and release files' + cand -h 'Print help (see more with ''--help'')' + cand --help 'Print help (see more with ''--help'')' + cand -V 'Print version' + cand --version 'Print version' + cand check 'Check if there is a new version available' + cand help 'Print this message or the help of the given subcommand(s)' + } + &'stackablectl;version;check'= { + cand -l 'Log level this application uses' + cand --log-level 'Log level this application uses' + cand -d 'Provide one or more additional (custom) demo file(s)' + cand --demo-file 'Provide one or more additional (custom) demo file(s)' + cand -s 'Provide one or more additional (custom) stack file(s)' + cand --stack-file 'Provide one or more additional (custom) stack file(s)' + cand -r 'Provide one or more additional (custom) release file(s)' + cand --release-file 'Provide one or more additional (custom) release file(s)' + cand --helm-repo-stable 'Provide a custom Helm stable repository URL' + cand --helm-repo-test 'Provide a custom Helm test repository URL' + cand --helm-repo-dev 'Provide a custom Helm dev repository URL' + cand --chart-source 'Source the charts from either a OCI registry or from index.yaml-based repositories' + cand --listener-class-preset 'Choose the ListenerClass preset (`none`, `ephemeral-nodes` or `stable-nodes`)' + cand --no-cache 'Do not cache the remote (default) demo, stack and release files' + cand -h 'Print help (see more with ''--help'')' + cand --help 'Print help (see more with ''--help'')' + cand -V 'Print version' + cand --version 'Print version' + } + &'stackablectl;version;help'= { + cand check 'Check if there is a new version available' + cand help 'Print this message or the help of the given subcommand(s)' + } + &'stackablectl;version;help;check'= { + } + &'stackablectl;version;help;help'= { + } &'stackablectl;help'= { cand operator 'Interact with single operator instead of the full platform' cand release 'Interact with all operators of the platform which are released together' @@ -955,6 +1006,7 @@ set edit:completion:arg-completer[stackablectl] = {|@words| cand completions 'Generate shell completions for this tool' cand cache 'Interact with locally cached files' cand experimental-debug 'EXPERIMENTAL: Launch a debug container for a Pod' + cand version 'Retrieve version data of the stackablectl installation' cand help 'Print this message or the help of the given subcommand(s)' } &'stackablectl;help;operator'= { @@ -1048,6 +1100,11 @@ set edit:completion:arg-completer[stackablectl] = {|@words| } &'stackablectl;help;experimental-debug'= { } + &'stackablectl;help;version'= { + cand check 'Check if there is a new version available' + } + &'stackablectl;help;version;check'= { + } &'stackablectl;help;help'= { } ] diff --git a/extra/completions/stackablectl.fish b/extra/completions/stackablectl.fish index 935d79c0..6c090ca3 100644 --- a/extra/completions/stackablectl.fish +++ b/extra/completions/stackablectl.fish @@ -47,6 +47,7 @@ complete -c stackablectl -n "__fish_stackablectl_needs_command" -f -a "demo" -d complete -c stackablectl -n "__fish_stackablectl_needs_command" -f -a "completions" -d 'Generate shell completions for this tool' complete -c stackablectl -n "__fish_stackablectl_needs_command" -f -a "cache" -d 'Interact with locally cached files' complete -c stackablectl -n "__fish_stackablectl_needs_command" -f -a "experimental-debug" -d 'EXPERIMENTAL: Launch a debug container for a Pod' +complete -c stackablectl -n "__fish_stackablectl_needs_command" -f -a "version" -d 'Retrieve version data of the stackablectl installation' complete -c stackablectl -n "__fish_stackablectl_needs_command" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and not __fish_seen_subcommand_from list describe install uninstall installed help" -s l -l log-level -d 'Log level this application uses' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and not __fish_seen_subcommand_from list describe install uninstall installed help" -s d -l demo-file -d 'Provide one or more additional (custom) demo file(s)' -r -F @@ -702,15 +703,50 @@ ephemeral-nodes\t''" complete -c stackablectl -n "__fish_stackablectl_using_subcommand experimental-debug" -l no-cache -d 'Do not cache the remote (default) demo, stack and release files' complete -c stackablectl -n "__fish_stackablectl_using_subcommand experimental-debug" -s h -l help -d 'Print help (see more with \'--help\')' complete -c stackablectl -n "__fish_stackablectl_using_subcommand experimental-debug" -s V -l version -d 'Print version' -complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug help" -f -a "operator" -d 'Interact with single operator instead of the full platform' -complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug help" -f -a "release" -d 'Interact with all operators of the platform which are released together' -complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug help" -f -a "stack" -d 'Interact with stacks, which are ready-to-use product combinations' -complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug help" -f -a "stacklet" -d 'Interact with deployed stacklets, which are bundles of resources and containers required to run the product' -complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug help" -f -a "demo" -d 'Interact with demos, which are end-to-end usage demonstrations of the Stackable data platform' -complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug help" -f -a "completions" -d 'Generate shell completions for this tool' -complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug help" -f -a "cache" -d 'Interact with locally cached files' -complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug help" -f -a "experimental-debug" -d 'EXPERIMENTAL: Launch a debug container for a Pod' -complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -s l -l log-level -d 'Log level this application uses' -r +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -s d -l demo-file -d 'Provide one or more additional (custom) demo file(s)' -r -F +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -s s -l stack-file -d 'Provide one or more additional (custom) stack file(s)' -r -F +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -s r -l release-file -d 'Provide one or more additional (custom) release file(s)' -r -F +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -l helm-repo-stable -d 'Provide a custom Helm stable repository URL' -r -f +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -l helm-repo-test -d 'Provide a custom Helm test repository URL' -r -f +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -l helm-repo-dev -d 'Provide a custom Helm dev repository URL' -r -f +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -l chart-source -d 'Source the charts from either a OCI registry or from index.yaml-based repositories' -r -f -a "oci\t'OCI registry' +repo\t'index.yaml-based repositories: resolution (dev, test, stable) is based on the version and thus will be operator-specific'" +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -l listener-class-preset -d 'Choose the ListenerClass preset (`none`, `ephemeral-nodes` or `stable-nodes`)' -r -f -a "none\t'' +stable-nodes\t'' +ephemeral-nodes\t''" +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -l no-cache -d 'Do not cache the remote (default) demo, stack and release files' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -s h -l help -d 'Print help (see more with \'--help\')' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -s V -l version -d 'Print version' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -f -a "check" -d 'Check if there is a new version available' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and not __fish_seen_subcommand_from check help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from check" -s l -l log-level -d 'Log level this application uses' -r +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from check" -s d -l demo-file -d 'Provide one or more additional (custom) demo file(s)' -r -F +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from check" -s s -l stack-file -d 'Provide one or more additional (custom) stack file(s)' -r -F +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from check" -s r -l release-file -d 'Provide one or more additional (custom) release file(s)' -r -F +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from check" -l helm-repo-stable -d 'Provide a custom Helm stable repository URL' -r -f +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from check" -l helm-repo-test -d 'Provide a custom Helm test repository URL' -r -f +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from check" -l helm-repo-dev -d 'Provide a custom Helm dev repository URL' -r -f +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from check" -l chart-source -d 'Source the charts from either a OCI registry or from index.yaml-based repositories' -r -f -a "oci\t'OCI registry' +repo\t'index.yaml-based repositories: resolution (dev, test, stable) is based on the version and thus will be operator-specific'" +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from check" -l listener-class-preset -d 'Choose the ListenerClass preset (`none`, `ephemeral-nodes` or `stable-nodes`)' -r -f -a "none\t'' +stable-nodes\t'' +ephemeral-nodes\t''" +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from check" -l no-cache -d 'Do not cache the remote (default) demo, stack and release files' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from check" -s h -l help -d 'Print help (see more with \'--help\')' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from check" -s V -l version -d 'Print version' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from help" -f -a "check" -d 'Check if there is a new version available' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand version; and __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug version help" -f -a "operator" -d 'Interact with single operator instead of the full platform' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug version help" -f -a "release" -d 'Interact with all operators of the platform which are released together' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug version help" -f -a "stack" -d 'Interact with stacks, which are ready-to-use product combinations' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug version help" -f -a "stacklet" -d 'Interact with deployed stacklets, which are bundles of resources and containers required to run the product' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug version help" -f -a "demo" -d 'Interact with demos, which are end-to-end usage demonstrations of the Stackable data platform' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug version help" -f -a "completions" -d 'Generate shell completions for this tool' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug version help" -f -a "cache" -d 'Interact with locally cached files' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug version help" -f -a "experimental-debug" -d 'EXPERIMENTAL: Launch a debug container for a Pod' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug version help" -f -a "version" -d 'Retrieve version data of the stackablectl installation' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and not __fish_seen_subcommand_from operator release stack stacklet demo completions cache experimental-debug version help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and __fish_seen_subcommand_from operator" -f -a "list" -d 'List available operators' complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and __fish_seen_subcommand_from operator" -f -a "describe" -d 'Print out detailed operator information' complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and __fish_seen_subcommand_from operator" -f -a "install" -d 'Install one or more operators' @@ -736,3 +772,4 @@ complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and __fi complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and __fish_seen_subcommand_from completions" -f -a "zsh" -d 'Generate shell completions for ZSH' complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and __fish_seen_subcommand_from cache" -f -a "list" -d 'List cached files' complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and __fish_seen_subcommand_from cache" -f -a "clean" -d 'Clean cached files' +complete -c stackablectl -n "__fish_stackablectl_using_subcommand help; and __fish_seen_subcommand_from version" -f -a "check" -d 'Check if there is a new version available' diff --git a/extra/completions/stackablectl.nu b/extra/completions/stackablectl.nu index 3d72d82d..3bf0c67a 100644 --- a/extra/completions/stackablectl.nu +++ b/extra/completions/stackablectl.nu @@ -1117,6 +1117,66 @@ module completions { --version(-V) # Print version ] + def "nu-complete stackablectl version chart_source" [] { + [ "oci" "repo" ] + } + + def "nu-complete stackablectl version listener_class_preset" [] { + [ "none" "stable-nodes" "ephemeral-nodes" ] + } + + # Retrieve version data of the stackablectl installation + export extern "stackablectl version" [ + --log-level(-l): string # Log level this application uses + --no-cache # Do not cache the remote (default) demo, stack and release files + --demo-file(-d): path # Provide one or more additional (custom) demo file(s) + --stack-file(-s): path # Provide one or more additional (custom) stack file(s) + --release-file(-r): path # Provide one or more additional (custom) release file(s) + --helm-repo-stable: string # Provide a custom Helm stable repository URL + --helm-repo-test: string # Provide a custom Helm test repository URL + --helm-repo-dev: string # Provide a custom Helm dev repository URL + --chart-source: string@"nu-complete stackablectl version chart_source" # Source the charts from either a OCI registry or from index.yaml-based repositories + --listener-class-preset: string@"nu-complete stackablectl version listener_class_preset" # Choose the ListenerClass preset (`none`, `ephemeral-nodes` or `stable-nodes`) + --help(-h) # Print help (see more with '--help') + --version(-V) # Print version + ] + + def "nu-complete stackablectl version check chart_source" [] { + [ "oci" "repo" ] + } + + def "nu-complete stackablectl version check listener_class_preset" [] { + [ "none" "stable-nodes" "ephemeral-nodes" ] + } + + # Check if there is a new version available + export extern "stackablectl version check" [ + --log-level(-l): string # Log level this application uses + --no-cache # Do not cache the remote (default) demo, stack and release files + --demo-file(-d): path # Provide one or more additional (custom) demo file(s) + --stack-file(-s): path # Provide one or more additional (custom) stack file(s) + --release-file(-r): path # Provide one or more additional (custom) release file(s) + --helm-repo-stable: string # Provide a custom Helm stable repository URL + --helm-repo-test: string # Provide a custom Helm test repository URL + --helm-repo-dev: string # Provide a custom Helm dev repository URL + --chart-source: string@"nu-complete stackablectl version check chart_source" # Source the charts from either a OCI registry or from index.yaml-based repositories + --listener-class-preset: string@"nu-complete stackablectl version check listener_class_preset" # Choose the ListenerClass preset (`none`, `ephemeral-nodes` or `stable-nodes`) + --help(-h) # Print help (see more with '--help') + --version(-V) # Print version + ] + + # Print this message or the help of the given subcommand(s) + export extern "stackablectl version help" [ + ] + + # Check if there is a new version available + export extern "stackablectl version help check" [ + ] + + # Print this message or the help of the given subcommand(s) + export extern "stackablectl version help help" [ + ] + # Print this message or the help of the given subcommand(s) export extern "stackablectl help" [ ] @@ -1253,6 +1313,14 @@ module completions { export extern "stackablectl help experimental-debug" [ ] + # Retrieve version data of the stackablectl installation + export extern "stackablectl help version" [ + ] + + # Check if there is a new version available + export extern "stackablectl help version check" [ + ] + # Print this message or the help of the given subcommand(s) export extern "stackablectl help help" [ ] diff --git a/extra/man/stackablectl.1 b/extra/man/stackablectl.1 index 643a4340..9178039a 100644 --- a/extra/man/stackablectl.1 +++ b/extra/man/stackablectl.1 @@ -117,6 +117,9 @@ Interact with locally cached files stackablectl\-experimental\-debug(1) EXPERIMENTAL: Launch a debug container for a Pod .TP +stackablectl\-version(1) +Retrieve version data of the stackablectl installation +.TP stackablectl\-help(1) Print this message or the help of the given subcommand(s) .SH VERSION diff --git a/rust/stackable-cockpit/src/common/list.rs b/rust/stackable-cockpit/src/common/list.rs index 66b721e4..c2a486c3 100644 --- a/rust/stackable-cockpit/src/common/list.rs +++ b/rust/stackable-cockpit/src/common/list.rs @@ -49,7 +49,7 @@ where for file in files { let specs = transfer_client - .get(file, &Yaml::::new()) + .get(file, &Yaml::::default()) .await .context(FileTransferSnafu)?; diff --git a/rust/stackable-cockpit/src/platform/manifests.rs b/rust/stackable-cockpit/src/platform/manifests.rs index 4d983317..15fcf306 100644 --- a/rust/stackable-cockpit/src/platform/manifests.rs +++ b/rust/stackable-cockpit/src/platform/manifests.rs @@ -98,7 +98,10 @@ pub trait InstallManifestsExt { })?; let helm_chart: helm::Chart = transfer_client - .get(&helm_file, &Template::new(¶meters).then(Yaml::new())) + .get( + &helm_file, + &Template::new(¶meters).then(Yaml::default()), + ) .await .context(FileTransferSnafu)?; diff --git a/rust/stackable-cockpit/src/utils/path.rs b/rust/stackable-cockpit/src/utils/path.rs index f71493e5..d0d6020a 100644 --- a/rust/stackable-cockpit/src/utils/path.rs +++ b/rust/stackable-cockpit/src/utils/path.rs @@ -25,18 +25,6 @@ impl> IntoPathOrUrl for T { } } -impl IntoPathOrUrl for PathOrUrl { - fn into_path_or_url(self) -> Result { - Ok(self) - } -} - -impl IntoPathOrUrl for &PathOrUrl { - fn into_path_or_url(self) -> Result { - Ok(self.to_owned()) - } -} - pub trait IntoPathsOrUrls: Sized { fn into_paths_or_urls(self) -> Result, PathOrUrlParseError>; } @@ -54,18 +42,6 @@ impl> IntoPathsOrUrls for Vec { } } -impl IntoPathsOrUrls for PathOrUrl { - fn into_paths_or_urls(self) -> Result, PathOrUrlParseError> { - Ok(vec![self]) - } -} - -impl IntoPathsOrUrls for &PathOrUrl { - fn into_paths_or_urls(self) -> Result, PathOrUrlParseError> { - Ok(vec![self.to_owned()]) - } -} - pub trait ParsePathsOrUrls { fn parse_paths_or_urls(self) -> Result, PathOrUrlParseError>; } diff --git a/rust/stackable-cockpit/src/xfer/mod.rs b/rust/stackable-cockpit/src/xfer/mod.rs index 3792305b..0aaf0027 100644 --- a/rust/stackable-cockpit/src/xfer/mod.rs +++ b/rust/stackable-cockpit/src/xfer/mod.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{path::PathBuf, time::SystemTime}; use snafu::{ResultExt, Snafu}; use tokio::fs; @@ -10,7 +10,7 @@ pub mod processor; use crate::{ utils::path::PathOrUrl, xfer::{ - cache::{Cache, Settings, Status}, + cache::{Cache, DeleteFilter, Settings, Status}, processor::{Processor, ProcessorError}, }, }; @@ -31,9 +31,15 @@ pub enum Error { #[snafu(display("failed to store file in cache"))] CacheStore { source: cache::Error }, - #[snafu(display("failed to retrieve file from cache"))] + #[snafu(display("failed to retrieve file(s) from cache"))] CacheRetrieve { source: cache::Error }, + #[snafu(display("failed to purge cached files"))] + CachePurge { source: cache::Error }, + + #[snafu(display("failed to initialize http client"))] + InitializeClient { source: reqwest::Error }, + #[snafu(display("failed to build http request"))] BuildRequest { source: reqwest::Error }, @@ -57,14 +63,14 @@ impl Client { .try_into_cache() .await .context(CacheSettingsSnafu)?; - let client = reqwest::Client::new(); - Ok(Self { client, cache }) - } + // Some servers require this (eg: GitHub API) + let client = reqwest::Client::builder() + .user_agent("stackable-cockpit") + .build() + .context(InitializeClientSnafu)?; - pub fn new_with(cache: Cache) -> Self { - let client = reqwest::Client::new(); - Self { client, cache } + Ok(Self { client, cache }) } /// Retrieves data from `path_or_url` which can either be a [`PathBuf`] @@ -84,6 +90,30 @@ impl Client { } } + /// Lists all currently cached files. + /// + /// This function does not make any requests to remote resources. + pub async fn list_cached_files(&self) -> Result> { + self.cache.list().await.context(CacheRetrieveSnafu) + } + + /// Purges currently cached files selected by the [`DeleteFilter`]. + /// + /// This function does not make any requests to remote resources. + pub async fn purge_cached_files(&self, delete_filter: DeleteFilter) -> Result<()> { + self.cache + .purge(delete_filter) + .await + .context(CachePurgeSnafu) + } + + /// Auto-purges the underlying cache. + /// + /// This function does not make any requests to remote resources. + pub async fn auto_purge(&self) -> Result<()> { + self.cache.auto_purge().await.context(CachePurgeSnafu) + } + async fn get_from_local_file(&self, path: &PathBuf) -> Result { fs::read_to_string(path) .await diff --git a/rust/stackable-cockpit/src/xfer/processor.rs b/rust/stackable-cockpit/src/xfer/processor.rs index c2f705ba..0631fbc6 100644 --- a/rust/stackable-cockpit/src/xfer/processor.rs +++ b/rust/stackable-cockpit/src/xfer/processor.rs @@ -12,6 +12,9 @@ pub enum ProcessorError { #[snafu(display("failed to deserialize YAML content"))] DeserializeYaml { source: serde_yaml::Error }, + #[snafu(display("failed to deserialize JSON content"))] + DeserializeJson { source: serde_json::Error }, + #[snafu(display("failed to render templated content"))] RenderTemplate { source: tera::Error }, } @@ -81,9 +84,24 @@ impl Default for Yaml { } } -impl Yaml { - pub fn new() -> Self { - Self::default() +#[derive(Debug)] +pub struct Json(PhantomData); + +impl Processor for Json +where + T: DeserializeOwned, +{ + type Input = String; + type Output = T; + + fn process(&self, input: Self::Input) -> Result { + serde_json::from_str(&input).context(DeserializeJsonSnafu) + } +} + +impl Default for Json { + fn default() -> Self { + Self(PhantomData) } } diff --git a/rust/stackablectl/Cargo.toml b/rust/stackablectl/Cargo.toml index d13f34bb..5dea04c2 100644 --- a/rust/stackablectl/Cargo.toml +++ b/rust/stackablectl/Cargo.toml @@ -38,3 +38,6 @@ indicatif.workspace = true termion.workspace = true urlencoding.workspace = true libc.workspace = true + +[build-dependencies] +built.workspace = true diff --git a/rust/stackablectl/README.md b/rust/stackablectl/README.md index 406f586d..cf48ffe4 100644 --- a/rust/stackablectl/README.md +++ b/rust/stackablectl/README.md @@ -23,6 +23,7 @@ Commands: completions Generate shell completions for this tool cache Interact with locally cached files experimental-debug EXPERIMENTAL: Launch a debug container for a Pod + version Retrieve version data of the stackablectl installation help Print this message or the help of the given subcommand(s) Options: diff --git a/rust/stackablectl/build.rs b/rust/stackablectl/build.rs new file mode 100644 index 00000000..d764d79c --- /dev/null +++ b/rust/stackablectl/build.rs @@ -0,0 +1,3 @@ +fn main() { + built::write_built_file().expect("failed to acquire build-time information"); +} diff --git a/rust/stackablectl/src/cli/mod.rs b/rust/stackablectl/src/cli/mod.rs index bad3bdd8..e4404633 100644 --- a/rust/stackablectl/src/cli/mod.rs +++ b/rust/stackablectl/src/cli/mod.rs @@ -1,4 +1,4 @@ -use std::env; +use std::{env, sync::Arc}; use clap::{Parser, Subcommand, ValueEnum}; use directories::ProjectDirs; @@ -12,19 +12,21 @@ use stackable_cockpit::{ utils::path::{ IntoPathOrUrl, IntoPathsOrUrls, ParsePathsOrUrls, PathOrUrl, PathOrUrlParseError, }, - xfer::cache::Settings, + xfer::{self, cache::Settings}, }; use tracing::{Level, instrument}; +use tracing_indicatif::indicatif_eprintln; use crate::{ args::{CommonFileArgs, CommonOperatorConfigsArgs, CommonRepoArgs}, - cmds::{cache, completions, debug, demo, operator, release, stack, stacklet}, + cmds::{cache, completions, debug, demo, operator, release, stack, stacklet, version}, constants::{ DEMOS_REPOSITORY_DEMOS_SUBPATH, DEMOS_REPOSITORY_STACKS_SUBPATH, DEMOS_REPOSITORY_URL_BASE, ENV_KEY_DEMO_FILES, ENV_KEY_RELEASE_FILES, ENV_KEY_STACK_FILES, REMOTE_RELEASE_FILE, USER_DIR_APPLICATION_NAME, USER_DIR_ORGANIZATION_NAME, USER_DIR_QUALIFIER, }, output::{ErrorContext, Output, ResultContext}, + release_check, }; #[derive(Debug, Snafu)] @@ -41,20 +43,39 @@ pub enum Error { #[snafu(display("failed to execute stacklet (sub)command"))] Stacklet { source: stacklet::CmdError }, - #[snafu(display("demo command error"))] + #[snafu(display("failed to execute demo (sub)command"))] Demo { source: demo::CmdError }, - #[snafu(display("completions command error"))] + #[snafu(display("failed to execute completions (sub)command"))] Completions { source: completions::CmdError }, - #[snafu(display("cache command error"))] + #[snafu(display("failed to execute cache (sub)command"))] Cache { source: cache::CmdError }, - #[snafu(display("debug command error"))] + #[snafu(display("failed to execute debug (sub)command"))] Debug { source: debug::CmdError }, - #[snafu(display("helm error"))] - Helm { source: helm::Error }, + #[snafu(display("failed to execute version (sub)command"))] + Version { source: version::CmdError }, + + #[snafu(display("failed to add Helm repositories"))] + AddHelmRepos { source: helm::Error }, + + #[snafu(display("failed to retrieve cache settings"))] + RetrieveCacheSettings { source: CacheSettingsError }, + + #[snafu(display("failed to auto-purge cache"))] + AutoPurgeCache { source: xfer::Error }, + + #[snafu(display("failed to initialize transfer client"))] + InitializeTransferClient { source: xfer::Error }, +} + +#[derive(Debug, Snafu)] +#[snafu(module)] +pub enum CacheSettingsError { + #[snafu(display("unable to resolve user directories"))] + UserDir, } #[derive(Debug, Parser)] @@ -85,7 +106,7 @@ Cached files are saved at '$XDG_CACHE_HOME/stackablectl', which is usually pub operator_configs: CommonOperatorConfigsArgs, #[command(subcommand)] - pub subcommand: Commands, + pub subcommand: Command, } impl Cli { @@ -170,50 +191,103 @@ impl Cli { } #[instrument(skip_all)] - pub async fn run(&self) -> Result { + pub async fn run(self) -> Result { // FIXME (Techassi): There might be a better way to handle this with // the match later in this function. // Add Helm repos only when required match &self.subcommand { - Commands::Completions(_) => (), - Commands::Cache(_) => (), - _ => self.add_helm_repos().context(HelmSnafu)?, + Command::Completions(_) => (), + Command::Cache(_) => (), + _ => self.add_helm_repos().context(AddHelmReposSnafu)?, } - let cache = self - .cache_settings() - .unwrap() - .try_into_cache() + let cache_settings = self.cache_settings().context(RetrieveCacheSettingsSnafu)?; + let transfer_client = xfer::Client::new(cache_settings) .await - .unwrap(); + .context(InitializeTransferClientSnafu)?; + + // Only run the cache auto-purge when the user executes ANY command other than the cache + // commands. + if !matches!(self.subcommand, Command::Cache(_)) { + transfer_client + .auto_purge() + .await + .context(AutoPurgeCacheSnafu)?; + } - // TODO (Techassi): Do we still want to auto purge when running cache commands? - cache.auto_purge().await.unwrap(); + // Make transfer client sharable across multiple futures/threads + let transfer_client = Arc::new(transfer_client); determine_and_store_listener_class_preset( self.operator_configs.listener_class_preset.as_ref(), ) .await; - match &self.subcommand { - Commands::Operator(args) => args.run(self).await.context(OperatorSnafu), - Commands::Release(args) => args.run(self, cache).await.context(ReleaseSnafu), - Commands::Stack(args) => args.run(self, cache).await.context(StackSnafu), - Commands::Stacklet(args) => args.run(self).await.context(StackletSnafu), - Commands::Demo(args) => args.run(self, cache).await.context(DemoSnafu), - Commands::Completions(args) => args.run().context(CompletionsSnafu), - Commands::Cache(args) => args.run(self, cache).await.context(CacheSnafu), - Commands::ExperimentalDebug(args) => args.run(self).await.context(DebugSnafu), + // Only run the version check in the background if the user runs ANY other command than + // the version command. Also only report if the current version is outdated. + let check_version_in_background = !matches!(self.subcommand, Command::Version(_)); + let release_check_future = release_check::version_notice_output( + transfer_client.clone(), + check_version_in_background, + true, + ); + let release_check_future = + tokio::time::timeout(std::time::Duration::from_secs(2), release_check_future); + + #[rustfmt::skip] + let command_future = async move { + match self.subcommand { + Command::Operator(ref args) => args.run(&self).await.context(OperatorSnafu), + Command::Release(ref args) => args.run(&self, transfer_client).await.context(ReleaseSnafu), + Command::Stack(ref args) => args.run(&self, transfer_client).await.context(StackSnafu), + Command::Stacklet(ref args) => args.run().await.context(StackletSnafu), + Command::Demo(ref args) => args.run(&self, transfer_client).await.context(DemoSnafu), + Command::Completions(ref args) => args.run().context(CompletionsSnafu), + Command::Cache(ref args) => args.run(transfer_client).await.context(CacheSnafu), + Command::ExperimentalDebug(ref args) => args.run().await.context(DebugSnafu), + Command::Version(ref args) => args.run(transfer_client).await.context(VersionSnafu), + } + }; + + // Run the version check and the actual command in parallel and not sequentially. This is + // done to not abort/stall execution when the version check couldn't be performed (because + // of network issues for example). We also optimistically run the version check and don't + // hard-error below when the check failed. + let (release_check_result, command_result) = + tokio::join!(release_check_future, command_future); + + // NOTE (@Techassi): This is freaking ugly (I'm sorry) but there seems to be no other better + // way to achieve what we want without reworking the entire output handling/rendering + // mechanism. + // FIXME (@Techassi): This currently messes up any structured output. This is also not + // trivially solved as explained above. + match command_result { + Ok(command_output) => { + let output = if let Ok(Ok(Some(release_check_output))) = release_check_result { + format!("{command_output}\n\n{release_check_output}") + } else { + command_output + }; + + Ok(output) + } + Err(err) => { + if let Ok(Ok(Some(release_check_output))) = release_check_result { + indicatif_eprintln!("{release_check_output}\n"); + } + + Err(err) + } } } // Output utility functions - pub fn result(&self) -> Output { + pub fn result() -> Output { Output::new(ResultContext::default(), true).expect("Failed to create output renderer") } - pub fn error(&self) -> Output { + pub fn error() -> Output { Output::new(ErrorContext::default(), true).expect("Failed to create output renderer") } @@ -223,7 +297,7 @@ impl Cli { } #[derive(Debug, Subcommand)] -pub enum Commands { +pub enum Command { /// Interact with single operator instead of the full platform #[command(alias("op"))] Operator(operator::OperatorArgs), @@ -264,6 +338,9 @@ CRDs." This container will have access to the same data volumes as the primary container.")] ExperimentalDebug(debug::DebugArgs), + + /// Retrieve version data of the stackablectl installation + Version(version::VersionArguments), } #[derive(Clone, Debug, Default, ValueEnum)] @@ -282,13 +359,6 @@ pub enum OutputType { Yaml, } -#[derive(Debug, Snafu)] -#[snafu(module)] -pub enum CacheSettingsError { - #[snafu(display("unable to resolve user directories"))] - UserDir, -} - /// Returns a list of paths or urls based on the default (remote) file and /// files provided via the env variable. fn get_files(default_file: &str, env_key: &str) -> Result, PathOrUrlParseError> { diff --git a/rust/stackablectl/src/cmds/cache.rs b/rust/stackablectl/src/cmds/cache.rs index 37dc77ee..62fc1bb3 100644 --- a/rust/stackablectl/src/cmds/cache.rs +++ b/rust/stackablectl/src/cmds/cache.rs @@ -1,9 +1,9 @@ -use std::time::Duration; +use std::{sync::Arc, time::Duration}; use clap::{Args, Subcommand}; use comfy_table::{ColumnConstraint, Table, Width, presets::UTF8_FULL}; use snafu::{ResultExt, Snafu}; -use stackable_cockpit::xfer::cache::{self, Cache, DeleteFilter}; +use stackable_cockpit::xfer::{self, cache::DeleteFilter}; use tracing::{info, instrument}; use crate::cli::Cli; @@ -35,26 +35,29 @@ pub struct CacheCleanArgs { #[derive(Debug, Snafu)] pub enum CmdError { #[snafu(display("failed to list cached files"))] - ListCachedFiles { source: cache::Error }, + ListCachedFiles { source: xfer::Error }, #[snafu(display("failed to purge cached files"))] - PurgeCachedFiles { source: cache::Error }, + PurgeCachedFiles { source: xfer::Error }, } impl CacheArgs { - pub async fn run(&self, cli: &Cli, cache: Cache) -> Result { + pub async fn run(&self, transfer_client: Arc) -> Result { match &self.subcommand { - CacheCommands::List => list_cmd(cache, cli).await, - CacheCommands::Clean(args) => clean_cmd(args, cache).await, + CacheCommands::List => list_cmd(transfer_client).await, + CacheCommands::Clean(args) => clean_cmd(args, transfer_client).await, } } } #[instrument(skip_all)] -async fn list_cmd(cache: Cache, cli: &Cli) -> Result { +async fn list_cmd(transfer_client: Arc) -> Result { info!("Listing cached files"); - let files = cache.list().await.context(ListCachedFilesSnafu)?; + let files = transfer_client + .list_cached_files() + .await + .context(ListCachedFilesSnafu)?; if files.is_empty() { return Ok("No cached files".into()); @@ -77,7 +80,7 @@ async fn list_cmd(cache: Cache, cli: &Cli) -> Result { table.add_row(vec![file_path, format!("{modified} seconds ago")]); } - let mut result = cli.result(); + let mut result = Cli::result(); result .with_command_hint("stackablectl cache clean", "to clean all cached files") @@ -87,7 +90,10 @@ async fn list_cmd(cache: Cache, cli: &Cli) -> Result { } #[instrument(skip_all)] -async fn clean_cmd(args: &CacheCleanArgs, cache: Cache) -> Result { +async fn clean_cmd( + args: &CacheCleanArgs, + transfer_client: Arc, +) -> Result { info!("Cleaning cached files"); let delete_filter = if args.only_remove_old_files { @@ -96,8 +102,8 @@ async fn clean_cmd(args: &CacheCleanArgs, cache: Cache) -> Result Result { + pub async fn run(&self) -> Result { let kube = kube::Client::try_default() .await .context(KubeClientCreateSnafu)?; diff --git a/rust/stackablectl/src/cmds/demo.rs b/rust/stackablectl/src/cmds/demo.rs index c8c003e8..513a33f9 100644 --- a/rust/stackablectl/src/cmds/demo.rs +++ b/rust/stackablectl/src/cmds/demo.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use clap::{Args, Subcommand}; use comfy_table::{ ContentArrangement, Row, Table, @@ -16,7 +18,7 @@ use stackable_cockpit::{ k8s::{self, Client}, path::PathOrUrlParseError, }, - xfer::{self, cache::Cache}, + xfer, }; use stackable_operator::kvp::{LabelError, Labels}; use tracing::{Span, debug, info, instrument}; @@ -161,11 +163,13 @@ pub enum CmdError { impl DemoArgs { #[instrument(skip_all, fields(with_cache = !cli.no_cache))] - pub async fn run(&self, cli: &Cli, cache: Cache) -> Result { + pub async fn run( + &self, + cli: &Cli, + transfer_client: Arc, + ) -> Result { debug!("Handle demo args"); - let transfer_client = xfer::Client::new_with(cache); - let release_files = cli.get_release_files().context(PathOrUrlParseSnafu)?; let release_list = release::ReleaseList::build(&release_files, &transfer_client) .await @@ -201,8 +205,8 @@ impl DemoArgs { .context(BuildListSnafu)?; match &self.subcommand { - DemoCommands::List(args) => list_cmd(args, cli, list).await, - DemoCommands::Describe(args) => describe_cmd(args, cli, list).await, + DemoCommands::List(args) => list_cmd(args, list).await, + DemoCommands::Describe(args) => describe_cmd(args, list).await, DemoCommands::Install(args) => { install_cmd(args, cli, list, &transfer_client, &release_branch).await } @@ -212,7 +216,7 @@ impl DemoArgs { /// Print out a list of demos, either as a table (plain), JSON or YAML #[instrument(skip_all, fields(indicatif.pb_show = true))] -async fn list_cmd(args: &DemoListArgs, cli: &Cli, list: demo::List) -> Result { +async fn list_cmd(args: &DemoListArgs, list: demo::List) -> Result { info!("Listing demos"); Span::current().pb_set_message("Fetching demo information"); @@ -239,7 +243,7 @@ async fn list_cmd(args: &DemoListArgs, cli: &Cli, list: demo::List) -> Result Result Result { +async fn describe_cmd(args: &DemoDescribeArgs, list: demo::List) -> Result { info!(demo_name = %args.demo_name, "Describing demo"); Span::current().pb_set_message("Fetching demo information"); @@ -299,7 +299,7 @@ async fn describe_cmd( // TODO (Techassi): Add parameter output - let mut result = cli.result(); + let mut result = Cli::result(); result .with_command_hint( @@ -337,7 +337,7 @@ async fn install_cmd( Span::current().pb_set_message("Installing demo"); // Init result output and progress output - let mut output = cli.result(); + let mut output = Cli::result(); let demo = list.get(&args.demo_name).ok_or(CmdError::NoSuchDemo { name: args.demo_name.clone(), diff --git a/rust/stackablectl/src/cmds/mod.rs b/rust/stackablectl/src/cmds/mod.rs index 039e1fd8..e6c82df5 100644 --- a/rust/stackablectl/src/cmds/mod.rs +++ b/rust/stackablectl/src/cmds/mod.rs @@ -6,3 +6,4 @@ pub mod operator; pub mod release; pub mod stack; pub mod stacklet; +pub mod version; diff --git a/rust/stackablectl/src/cmds/operator.rs b/rust/stackablectl/src/cmds/operator.rs index 2c01fbea..71326388 100644 --- a/rust/stackablectl/src/cmds/operator.rs +++ b/rust/stackablectl/src/cmds/operator.rs @@ -183,8 +183,8 @@ impl OperatorArgs { OperatorCommands::List(args) => list_cmd(args, cli).await, OperatorCommands::Describe(args) => describe_cmd(args, cli).await, OperatorCommands::Install(args) => install_cmd(args, cli).await, - OperatorCommands::Uninstall(args) => uninstall_cmd(args, cli), - OperatorCommands::Installed(args) => installed_cmd(args, cli), + OperatorCommands::Uninstall(args) => uninstall_cmd(args), + OperatorCommands::Installed(args) => installed_cmd(args), } } } @@ -227,7 +227,7 @@ async fn list_cmd(args: &OperatorListArgs, cli: &Cli) -> Result Result Result Result Result { +fn uninstall_cmd(args: &OperatorUninstallArgs) -> Result { info!("Uninstalling operator(s)"); Span::current().pb_set_message("Uninstalling operator(s)"); @@ -371,7 +371,7 @@ fn uninstall_cmd(args: &OperatorUninstallArgs, cli: &Cli) -> Result Result Result { +fn installed_cmd(args: &OperatorInstalledArgs) -> Result { info!("Listing installed operators"); Span::current().pb_set_message("Fetching operator information"); @@ -443,7 +443,7 @@ fn installed_cmd(args: &OperatorInstalledArgs, cli: &Cli) -> Result Result { + pub async fn run( + &self, + cli: &Cli, + transfer_client: Arc, + ) -> Result { debug!("Handle release args"); - let transfer_client = xfer::Client::new_with(cache); let files = cli.get_release_files().context(PathOrUrlParseSnafu)?; let release_list = release::ReleaseList::build(&files, &transfer_client) .await @@ -180,10 +185,10 @@ impl ReleaseArgs { } match &self.subcommand { - ReleaseCommands::List(args) => list_cmd(args, cli, release_list).await, - ReleaseCommands::Describe(args) => describe_cmd(args, cli, release_list).await, + ReleaseCommands::List(args) => list_cmd(args, release_list).await, + ReleaseCommands::Describe(args) => describe_cmd(args, release_list).await, ReleaseCommands::Install(args) => install_cmd(args, cli, release_list).await, - ReleaseCommands::Uninstall(args) => uninstall_cmd(args, cli, release_list).await, + ReleaseCommands::Uninstall(args) => uninstall_cmd(args, release_list).await, ReleaseCommands::Upgrade(args) => { upgrade_cmd(args, cli, release_list, &transfer_client).await } @@ -191,10 +196,9 @@ impl ReleaseArgs { } } -#[instrument(skip(cli, release_list), fields(indicatif.pb_show = true))] +#[instrument(skip(release_list), fields(indicatif.pb_show = true))] async fn list_cmd( args: &ReleaseListArgs, - cli: &Cli, release_list: release::ReleaseList, ) -> Result { info!("Listing releases"); @@ -226,7 +230,7 @@ async fn list_cmd( ]); } - let mut result = cli.result(); + let mut result = Cli::result(); result .with_command_hint( @@ -246,10 +250,9 @@ async fn list_cmd( } } -#[instrument(skip(cli, release_list), fields(indicatif.pb_show = true))] +#[instrument(skip(release_list), fields(indicatif.pb_show = true))] async fn describe_cmd( args: &ReleaseDescribeArgs, - cli: &Cli, release_list: release::ReleaseList, ) -> Result { info!(release = %args.release, "Describing release"); @@ -289,7 +292,7 @@ async fn describe_cmd( product_table.to_string().as_str(), ]); - let mut result = cli.result(); + let mut result = Cli::result(); result .with_command_hint( @@ -324,7 +327,7 @@ async fn install_cmd( match release_list.get(&args.release) { Some(release) => { - let mut output = cli.result(); + let mut output = Cli::result(); // Install local cluster if needed args.local_cluster @@ -381,7 +384,7 @@ async fn upgrade_cmd( match release_list.get(&args.release) { Some(release) => { - let mut output = cli.result(); + let mut output = Cli::result(); let client = Client::new().await.context(KubeClientCreateSnafu)?; // Get all currently installed operators to only upgrade those @@ -455,10 +458,9 @@ async fn upgrade_cmd( } } -#[instrument(skip(cli, release_list), fields(indicatif.pb_show = true))] +#[instrument(skip(release_list), fields(indicatif.pb_show = true))] async fn uninstall_cmd( args: &ReleaseUninstallArgs, - cli: &Cli, release_list: release::ReleaseList, ) -> Result { Span::current().pb_set_message("Uninstalling release"); @@ -469,7 +471,7 @@ async fn uninstall_cmd( .uninstall(&Vec::new(), &Vec::new(), &args.operator_namespace) .context(ReleaseUninstallSnafu)?; - let mut result = cli.result(); + let mut result = Cli::result(); result .with_command_hint("stackablectl release list", "list available releases") diff --git a/rust/stackablectl/src/cmds/stack.rs b/rust/stackablectl/src/cmds/stack.rs index 65cf3a26..3912f713 100644 --- a/rust/stackablectl/src/cmds/stack.rs +++ b/rust/stackablectl/src/cmds/stack.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use clap::{Args, Subcommand}; use comfy_table::{ ContentArrangement, Table, @@ -16,7 +18,7 @@ use stackable_cockpit::{ k8s::{self, Client}, path::PathOrUrlParseError, }, - xfer::{self, cache::Cache}, + xfer, }; use stackable_operator::kvp::{LabelError, Labels}; use tracing::{Span, debug, info, instrument}; @@ -151,11 +153,13 @@ pub enum CmdError { } impl StackArgs { - pub async fn run(&self, cli: &Cli, cache: Cache) -> Result { + pub async fn run( + &self, + cli: &Cli, + transfer_client: Arc, + ) -> Result { debug!("Handle stack args"); - let transfer_client = xfer::Client::new_with(cache); - let release_files = cli.get_release_files().context(PathOrUrlParseSnafu)?; let release_list = release::ReleaseList::build(&release_files, &transfer_client) .await @@ -188,8 +192,8 @@ impl StackArgs { .context(BuildListSnafu)?; match &self.subcommand { - StackCommands::List(args) => list_cmd(args, cli, stack_list), - StackCommands::Describe(args) => describe_cmd(args, cli, stack_list), + StackCommands::List(args) => list_cmd(args, stack_list), + StackCommands::Describe(args) => describe_cmd(args, stack_list), StackCommands::Install(args) => { install_cmd(args, cli, stack_list, &transfer_client).await } @@ -198,11 +202,7 @@ impl StackArgs { } #[instrument(skip_all, fields(indicatif.pb_show = true))] -fn list_cmd( - args: &StackListArgs, - cli: &Cli, - stack_list: stack::StackList, -) -> Result { +fn list_cmd(args: &StackListArgs, stack_list: stack::StackList) -> Result { info!("Listing stacks"); Span::current().pb_set_message("Fetching stack information"); @@ -228,7 +228,7 @@ fn list_cmd( ]); } - let mut result = cli.result(); + let mut result = Cli::result(); result .with_command_hint( @@ -251,7 +251,6 @@ fn list_cmd( #[instrument(skip_all, fields(indicatif.pb_show = true))] fn describe_cmd( args: &StackDescribeArgs, - cli: &Cli, stack_list: stack::StackList, ) -> Result { info!(stack_name = %args.stack_name, "Describing stack"); @@ -292,7 +291,7 @@ fn describe_cmd( .add_row(vec!["LABELS", stack.labels.join(", ").as_str()]) .add_row(vec!["PARAMETERS", parameter_table.to_string().as_str()]); - let mut result = cli.result(); + let mut result = Cli::result(); result .with_command_hint( @@ -331,7 +330,7 @@ async fn install_cmd( match stack_list.get(&args.stack_name) { Some(stack_spec) => { - let mut output = cli.result(); + let mut output = Cli::result(); // Install local cluster if needed args.local_cluster diff --git a/rust/stackablectl/src/cmds/stacklet.rs b/rust/stackablectl/src/cmds/stacklet.rs index 62c8aff2..475ba4d5 100644 --- a/rust/stackablectl/src/cmds/stacklet.rs +++ b/rust/stackablectl/src/cmds/stacklet.rs @@ -82,16 +82,16 @@ pub enum CmdError { } impl StackletArgs { - pub async fn run(&self, cli: &Cli) -> Result { + pub async fn run(&self) -> Result { match &self.subcommand { - StackletCommands::List(args) => list_cmd(args, cli).await, + StackletCommands::List(args) => list_cmd(args).await, StackletCommands::Credentials(args) => credentials_cmd(args).await, } } } #[instrument(skip_all, fields(indicatif.pb_show = true))] -async fn list_cmd(args: &StackletListArgs, cli: &Cli) -> Result { +async fn list_cmd(args: &StackletListArgs) -> Result { info!("Listing installed stacklets"); Span::current().pb_set_message("Fetching stacklet information"); @@ -105,7 +105,7 @@ async fn list_cmd(args: &StackletListArgs, cli: &Cli) -> Result Result) -> Result { + match &self.subcommand { + VersionCommand::Check => check_cmd(client).await, + } + } +} + +#[instrument(skip_all)] +async fn check_cmd(client: Arc) -> Result { + let output = release_check::version_notice_output(client, true, false) + .await + .context(RetrieveLatestReleaseSnafu)? + .unwrap_or_default(); + + let mut result = Cli::result(); + result.with_output(output); + + Ok(result.render()) +} diff --git a/rust/stackablectl/src/lib.rs b/rust/stackablectl/src/lib.rs index f49b1be7..0eb18d76 100644 --- a/rust/stackablectl/src/lib.rs +++ b/rust/stackablectl/src/lib.rs @@ -3,4 +3,15 @@ pub mod cli; pub mod cmds; pub mod constants; pub mod output; +pub mod release_check; pub mod utils; + +pub mod built_info { + use std::{str::FromStr, sync::LazyLock}; + + include!(concat!(env!("OUT_DIR"), "/built.rs")); + + pub static PKG_SEMVER: LazyLock = LazyLock::new(|| { + semver::Version::from_str(PKG_VERSION).expect("must be a valid semantic version") + }); +} diff --git a/rust/stackablectl/src/main.rs b/rust/stackablectl/src/main.rs index d6f96b24..db4a576a 100644 --- a/rust/stackablectl/src/main.rs +++ b/rust/stackablectl/src/main.rs @@ -19,7 +19,7 @@ use tracing_subscriber::{ #[tokio::main] async fn main() -> Result<(), Error> { // Parse the CLI args and commands - let app = Cli::parse(); + let cli = Cli::parse(); // Construct the tracing subscriber let format = fmt::format() @@ -35,7 +35,7 @@ async fn main() -> Result<(), Error> { ) .with_progress_style(PROGRESS_SPINNER_STYLE.clone()); - if let Some(level) = app.log_level { + if let Some(level) = cli.log_level { tracing_subscriber::registry() .with( fmt::layer() @@ -62,10 +62,10 @@ async fn main() -> Result<(), Error> { } } - match app.run().await { + match cli.run().await { Ok(result) => indicatif_println!("{result}"), Err(err) => { - let mut output = app.error(); + let mut output = Cli::error(); output.with_error_report(err); indicatif_eprintln!("{error}", error = output.render()); diff --git a/rust/stackablectl/src/output/templates/result.tpl b/rust/stackablectl/src/output/templates/result.tpl index c4d57e06..19434ad6 100644 --- a/rust/stackablectl/src/output/templates/result.tpl +++ b/rust/stackablectl/src/output/templates/result.tpl @@ -2,19 +2,17 @@ {% for pre_hint in pre_hints -%} {{ pre_hint }} {% endfor %} -{% endif -%} -{%- if output | length != 0 %} -{{ output }} -{%- endif %} +{% endif -%} +{%- if output | length != 0 %}{{ output }}{% endif -%} +{% if command_hints | length != 0 %} -{% if command_hints | length != 0 -%} {% for command_hint in command_hints -%} {{ command_hint }} {% endfor -%} {% endif -%} +{% if post_hints | length != 0 %} -{% if post_hints | length != 0 -%} {% for post_hint in post_hints -%} {{ post_hint }} {% endfor -%} diff --git a/rust/stackablectl/src/release_check.rs b/rust/stackablectl/src/release_check.rs new file mode 100644 index 00000000..36ab59e5 --- /dev/null +++ b/rust/stackablectl/src/release_check.rs @@ -0,0 +1,86 @@ +use std::{str::FromStr, sync::Arc}; + +use semver::Version; +use serde::Deserialize; +use snafu::{OptionExt, ResultExt, Snafu}; +use stackable_cockpit::{utils::path::PathOrUrl, xfer}; + +use crate::built_info::PKG_SEMVER; + +const URL: &str = "https://api.github.com/repos/stackabletech/stackable-cockpit/releases"; +const PREFIX: &str = "stackablectl-"; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("failed to retrieve list of releases"))] + RetrieveReleases { source: xfer::Error }, + + #[snafu(display("failed to find latest release"))] + FindLatestRelease, + + #[snafu(display("failed to parse {input} as semantic version"))] + ParseVersion { + source: semver::Error, + input: String, + }, +} + +#[derive(Debug, Deserialize)] +pub struct Release { + pub name: String, + pub prerelease: bool, + pub draft: bool, +} + +async fn fetch_latest_release_version_from_github( + client: Arc, +) -> Result { + let url = PathOrUrl::from_str(URL).expect("constant URL must be a valid URL"); + + let releases: Vec = client + .get(&url, &xfer::processor::Json::default()) + .await + .context(RetrieveReleasesSnafu)?; + + // We assume the list of releases is ordered (newest to oldest). + // If this ever changes, sorting needs to be done here instead. + let latest_release = releases + .into_iter() + // Filter out any draft and prerelease releases. + .filter(|release| !release.draft && !release.prerelease) + // Find a release starting with 'stackablectl-'. + .find(|release| release.name.starts_with(PREFIX)) + .context(FindLatestReleaseSnafu)?; + + let version = latest_release.name.trim_start_matches(PREFIX).to_owned(); + let version = Version::from_str(&version).context(ParseVersionSnafu { input: version })?; + + Ok(version) +} + +pub async fn version_notice_output( + client: Arc, + run_check: bool, + only_output_outdated: bool, +) -> Result, Error> { + if !run_check { + return Ok(None); + } + + let latest_version = fetch_latest_release_version_from_github(client).await?; + let current_version = &*PKG_SEMVER; + + let output = if &latest_version > current_version { + Some(format!( + "The current stackablectl version ({current_version}) is out-of-date. The latest version available is {latest_version}" + )) + } else if !only_output_outdated { + Some(format!( + "The current stackablectl version ({current_version}) is up-to-date" + )) + } else { + None + }; + + Ok(output) +}