diff --git a/.github/workflows/pr_cockpit.yml b/.github/workflows/pr_cockpit.yml index 26c1d7b1..1c532a72 100644 --- a/.github/workflows/pr_cockpit.yml +++ b/.github/workflows/pr_cockpit.yml @@ -15,7 +15,7 @@ on: - "go.sum" env: - RUST_VERSION: 1.80.1 + RUST_VERSION: 1.83.0 GO_VERSION: '^1.22.5' CARGO_TERM_COLOR: always CARGO_INCREMENTAL: "0" diff --git a/.github/workflows/pr_general.yml b/.github/workflows/pr_general.yml index e5fd8350..dde22fb7 100644 --- a/.github/workflows/pr_general.yml +++ b/.github/workflows/pr_general.yml @@ -4,7 +4,7 @@ name: Pull Request General on: workflow_call env: - RUST_VERSION: 1.80.1 + RUST_VERSION: 1.83.0 GO_VERSION: '^1.22.5' CARGO_TERM_COLOR: always CARGO_INCREMENTAL: "0" diff --git a/.github/workflows/pr_pre-commit.yml b/.github/workflows/pr_pre-commit.yml index c255b089..d60842da 100644 --- a/.github/workflows/pr_pre-commit.yml +++ b/.github/workflows/pr_pre-commit.yml @@ -6,7 +6,7 @@ on: env: CARGO_TERM_COLOR: always - RUST_TOOLCHAIN_VERSION: "1.80.1" + RUST_TOOLCHAIN_VERSION: "1.83.0" HADOLINT_VERSION: "v1.17.6" NIX_VERSION: "2.25.2" diff --git a/.github/workflows/pr_stackablectl.yml b/.github/workflows/pr_stackablectl.yml index d3e116fd..204b3cb0 100644 --- a/.github/workflows/pr_stackablectl.yml +++ b/.github/workflows/pr_stackablectl.yml @@ -14,7 +14,7 @@ on: - "extra/**" env: - RUST_VERSION: 1.80.1 + RUST_VERSION: 1.83.0 GO_VERSION: '^1.22.5' CARGO_TERM_COLOR: always CARGO_INCREMENTAL: "0" diff --git a/.github/workflows/release_stackablectl.yml b/.github/workflows/release_stackablectl.yml index 33c061f5..51fd5468 100644 --- a/.github/workflows/release_stackablectl.yml +++ b/.github/workflows/release_stackablectl.yml @@ -7,7 +7,7 @@ on: - "stackablectl-[0-9]+.[0-9]+.[0-9]+**" env: - RUST_VERSION: 1.80.1 + RUST_VERSION: 1.83.0 CARGO_TERM_COLOR: always CARGO_INCREMENTAL: "0" CARGO_PROFILE_DEV_DEBUG: "0" diff --git a/Cargo.lock b/Cargo.lock index e35fbd0a..639975e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -3049,6 +3049,7 @@ dependencies = [ "tokio", "tracing", "url", + "urlencoding", "utoipa", "which", ] @@ -3159,6 +3160,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", + "urlencoding", ] [[package]] @@ -3762,6 +3764,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" diff --git a/Cargo.nix b/Cargo.nix index c6aa1843..fdaa24a7 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -8052,7 +8052,7 @@ rec { "stream" = [ "tokio/fs" "dep:tokio-util" "dep:wasm-streams" ]; "zstd" = [ "dep:async-compression" "async-compression?/zstd" "dep:tokio-util" ]; }; - resolvedDefaultFeatures = [ "__rustls" "__rustls-ring" "__tls" "blocking" "rustls-tls" "rustls-tls-webpki-roots" ]; + resolvedDefaultFeatures = [ "__rustls" "__rustls-ring" "__tls" "blocking" "json" "rustls-tls" "rustls-tls-webpki-roots" ]; }; "ring" = rec { crateName = "ring"; @@ -9672,7 +9672,7 @@ rec { name = "reqwest"; packageId = "reqwest"; usesDefaultFeatures = false; - features = [ "rustls-tls" ]; + features = [ "json" "rustls-tls" ]; } { name = "semver"; @@ -9722,6 +9722,10 @@ rec { name = "url"; packageId = "url"; } + { + name = "urlencoding"; + packageId = "urlencoding"; + } { name = "utoipa"; packageId = "utoipa"; @@ -10122,7 +10126,7 @@ rec { name = "reqwest"; packageId = "reqwest"; usesDefaultFeatures = false; - features = [ "rustls-tls" ]; + features = [ "json" "rustls-tls" ]; } { name = "semver"; @@ -10177,6 +10181,10 @@ rec { name = "tracing-subscriber"; packageId = "tracing-subscriber"; } + { + name = "urlencoding"; + packageId = "urlencoding"; + } ]; }; @@ -12197,6 +12205,17 @@ rec { }; resolvedDefaultFeatures = [ "default" "serde" ]; }; + "urlencoding" = rec { + crateName = "urlencoding"; + version = "2.1.3"; + edition = "2021"; + sha256 = "1nj99jp37k47n0hvaz5fvz7z6jd0sb4ppvfy3nphr1zbnyixpy6s"; + authors = [ + "Kornel " + "Bertram Truong " + ]; + + }; "utf-8" = rec { crateName = "utf-8"; version = "0.7.6"; diff --git a/Cargo.toml b/Cargo.toml index b0cef9a1..66b79cdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ phf = "0.11" phf_codegen = "0.11" rand = "0.8" regex = "1.10" -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } rstest = "0.22" semver = { version = "1.0", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } @@ -54,6 +54,7 @@ tower-http = { version = "0.5", features = ["validate-request"] } tracing = "0.1" tracing-subscriber = "0.3" url = "2.5" +urlencoding = "2.1.3" utoipa = { version = "4.2", features = ["indexmap"] } utoipa-swagger-ui = { version = "7.1", features = ["axum"] } uuid = { version = "1.10", features = ["v4"] } diff --git a/extra/completions/_stackablectl b/extra/completions/_stackablectl index 2786659a..5916ae54 100644 --- a/extra/completions/_stackablectl +++ b/extra/completions/_stackablectl @@ -78,6 +78,8 @@ yaml\:"Print output formatted as YAML"))' \ table\:"Print output formatted as a table" json\:"Print output formatted as JSON" yaml\:"Print output formatted as YAML"))' \ +'--chart-source=[]:CHART_SOURCE:((oci\:"OCI registry" +repo\:"Nexus repositories\: resolution (dev, test, stable) is based on the version and thus will be operator-specific"))' \ '-l+[Log level this application uses]:LOG_LEVEL: ' \ '--log-level=[Log level this application uses]:LOG_LEVEL: ' \ '*-d+[Provide one or more additional (custom) demo file(s)]:DEMO_FILE:_files' \ @@ -106,6 +108,8 @@ yaml\:"Print output formatted as YAML"))' \ table\:"Print output formatted as a table" json\:"Print output formatted as JSON" yaml\:"Print output formatted as YAML"))' \ +'--chart-source=[]:CHART_SOURCE:((oci\:"OCI registry" +repo\:"Nexus repositories\: resolution (dev, test, stable) is based on the version and thus will be operator-specific"))' \ '-l+[Log level this application uses]:LOG_LEVEL: ' \ '--log-level=[Log level this application uses]:LOG_LEVEL: ' \ '*-d+[Provide one or more additional (custom) demo file(s)]:DEMO_FILE:_files' \ @@ -136,6 +140,8 @@ minikube\:"Use a minikube cluster"))' \ '--cluster-name=[Name of the local cluster]:CLUSTER_NAME: ' \ '--cluster-nodes=[Number of total nodes in the local cluster]:CLUSTER_NODES: ' \ '--cluster-cp-nodes=[Number of control plane nodes in the local cluster]:CLUSTER_CP_NODES: ' \ +'--chart-source=[]:CHART_SOURCE:((oci\:"OCI registry" +repo\:"Nexus repositories\: resolution (dev, test, stable) is based on the version and thus will be operator-specific"))' \ '-l+[Log level this application uses]:LOG_LEVEL: ' \ '--log-level=[Log level this application uses]:LOG_LEVEL: ' \ '*-d+[Provide one or more additional (custom) demo file(s)]:DEMO_FILE:_files' \ @@ -352,6 +358,8 @@ minikube\:"Use a minikube cluster"))' \ '--cluster-name=[Name of the local cluster]:CLUSTER_NAME: ' \ '--cluster-nodes=[Number of total nodes in the local cluster]:CLUSTER_NODES: ' \ '--cluster-cp-nodes=[Number of control plane nodes in the local cluster]:CLUSTER_CP_NODES: ' \ +'--chart-source=[]:CHART_SOURCE:((oci\:"OCI registry" +repo\:"Nexus repositories\: resolution (dev, test, stable) is based on the version and thus will be operator-specific"))' \ '-l+[Log level this application uses]:LOG_LEVEL: ' \ '--log-level=[Log level this application uses]:LOG_LEVEL: ' \ '*-d+[Provide one or more additional (custom) demo file(s)]:DEMO_FILE:_files' \ @@ -538,6 +546,8 @@ minikube\:"Use a minikube cluster"))' \ '-n+[Namespace where the products (e.g. stacks or demos) are deployed]:PRODUCT_NAMESPACE: ' \ '--product-namespace=[Namespace where the products (e.g. stacks or demos) are deployed]:PRODUCT_NAMESPACE: ' \ '--product-ns=[Namespace where the products (e.g. stacks or demos) are deployed]:PRODUCT_NAMESPACE: ' \ +'--chart-source=[]:CHART_SOURCE:((oci\:"OCI registry" +repo\:"Nexus repositories\: resolution (dev, test, stable) is based on the version and thus will be operator-specific"))' \ '--release=[Target a specific Stackable release]:RELEASE: ' \ '-l+[Log level this application uses]:LOG_LEVEL: ' \ '--log-level=[Log level this application uses]:LOG_LEVEL: ' \ @@ -817,6 +827,8 @@ minikube\:"Use a minikube cluster"))' \ '-n+[Namespace where the products (e.g. stacks or demos) are deployed]:PRODUCT_NAMESPACE: ' \ '--product-namespace=[Namespace where the products (e.g. stacks or demos) are deployed]:PRODUCT_NAMESPACE: ' \ '--product-ns=[Namespace where the products (e.g. stacks or demos) are deployed]:PRODUCT_NAMESPACE: ' \ +'--chart-source=[]:CHART_SOURCE:((oci\:"OCI registry" +repo\:"Nexus repositories\: resolution (dev, test, stable) is based on the version and thus will be operator-specific"))' \ '--release=[Target a specific Stackable release]:RELEASE: ' \ '-l+[Log level this application uses]:LOG_LEVEL: ' \ '--log-level=[Log level this application uses]:LOG_LEVEL: ' \ diff --git a/extra/completions/stackablectl.bash b/extra/completions/stackablectl.bash index b218bb20..c7170de6 100644 --- a/extra/completions/stackablectl.bash +++ b/extra/completions/stackablectl.bash @@ -2059,7 +2059,7 @@ _stackablectl() { return 0 ;; stackablectl__demo__install) - opts="-c -n -l -d -s -r -h -V --skip-release --stack-parameters --parameters --cluster --cluster-name --cluster-nodes --cluster-cp-nodes --operator-ns --operator-namespace --product-ns --product-namespace --release --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --help --version " + opts="-c -n -l -d -s -r -h -V --skip-release --stack-parameters --parameters --cluster --cluster-name --cluster-nodes --cluster-cp-nodes --operator-ns --operator-namespace --product-ns --product-namespace --chart-source --release --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --help --version " if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -2113,6 +2113,10 @@ _stackablectl() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --chart-source) + COMPREPLY=($(compgen -W "oci repo" -- "${cur}")) + return 0 + ;; --release) COMPREPLY=($(compgen -f "${cur}")) return 0 @@ -3115,7 +3119,7 @@ _stackablectl() { return 0 ;; stackablectl__operator__describe) - opts="-o -l -d -s -r -h -V --output --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --help --version " + opts="-o -l -d -s -r -h -V --output --chart-source --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --help --version " if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -3129,6 +3133,10 @@ _stackablectl() { COMPREPLY=($(compgen -W "plain table json yaml" -- "${cur}")) return 0 ;; + --chart-source) + COMPREPLY=($(compgen -W "oci repo" -- "${cur}")) + return 0 + ;; --log-level) COMPREPLY=($(compgen -f "${cur}")) return 0 @@ -3345,7 +3353,7 @@ _stackablectl() { return 0 ;; stackablectl__operator__install) - opts="-c -l -d -s -r -h -V --operator-ns --operator-namespace --cluster --cluster-name --cluster-nodes --cluster-cp-nodes --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --help --version ..." + opts="-c -l -d -s -r -h -V --operator-ns --operator-namespace --cluster --cluster-name --cluster-nodes --cluster-cp-nodes --chart-source --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --help --version ..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -3379,6 +3387,10 @@ _stackablectl() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --chart-source) + COMPREPLY=($(compgen -W "oci repo" -- "${cur}")) + return 0 + ;; --log-level) COMPREPLY=($(compgen -f "${cur}")) return 0 @@ -3637,7 +3649,7 @@ _stackablectl() { return 0 ;; stackablectl__operator__list) - opts="-o -l -d -s -r -h -V --output --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --help --version" + opts="-o -l -d -s -r -h -V --output --chart-source --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -3651,6 +3663,10 @@ _stackablectl() { COMPREPLY=($(compgen -W "plain table json yaml" -- "${cur}")) return 0 ;; + --chart-source) + COMPREPLY=($(compgen -W "oci repo" -- "${cur}")) + return 0 + ;; --log-level) COMPREPLY=($(compgen -f "${cur}")) return 0 @@ -4241,7 +4257,7 @@ _stackablectl() { return 0 ;; stackablectl__release__install) - opts="-i -e -c -l -d -s -r -h -V --include --exclude --operator-ns --operator-namespace --cluster --cluster-name --cluster-nodes --cluster-cp-nodes --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --help --version " + opts="-i -e -c -l -d -s -r -h -V --include --exclude --operator-ns --operator-namespace --cluster --cluster-name --cluster-nodes --cluster-cp-nodes --chart-source --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --help --version " if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -4291,6 +4307,10 @@ _stackablectl() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --chart-source) + COMPREPLY=($(compgen -W "oci repo" -- "${cur}")) + return 0 + ;; --log-level) COMPREPLY=($(compgen -f "${cur}")) return 0 @@ -5007,7 +5027,7 @@ _stackablectl() { return 0 ;; stackablectl__stack__install) - opts="-c -n -l -d -s -r -h -V --skip-release --stack-parameters --parameters --cluster --cluster-name --cluster-nodes --cluster-cp-nodes --operator-ns --operator-namespace --product-ns --product-namespace --release --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --help --version " + opts="-c -n -l -d -s -r -h -V --skip-release --stack-parameters --parameters --cluster --cluster-name --cluster-nodes --cluster-cp-nodes --operator-ns --operator-namespace --product-ns --product-namespace --chart-source --release --log-level --no-cache --demo-file --stack-file --release-file --helm-repo-stable --helm-repo-test --helm-repo-dev --help --version " if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -5061,6 +5081,10 @@ _stackablectl() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --chart-source) + COMPREPLY=($(compgen -W "oci repo" -- "${cur}")) + return 0 + ;; --release) COMPREPLY=($(compgen -f "${cur}")) return 0 diff --git a/extra/completions/stackablectl.elv b/extra/completions/stackablectl.elv index f237a783..1f818b3f 100644 --- a/extra/completions/stackablectl.elv +++ b/extra/completions/stackablectl.elv @@ -71,6 +71,7 @@ set edit:completion:arg-completer[stackablectl] = {|@words| &'stackablectl;operator;list'= { cand -o 'o' cand --output 'output' + cand --chart-source 'chart-source' 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)' @@ -91,6 +92,7 @@ set edit:completion:arg-completer[stackablectl] = {|@words| &'stackablectl;operator;describe'= { cand -o 'o' cand --output 'output' + cand --chart-source 'chart-source' 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)' @@ -116,6 +118,7 @@ set edit:completion:arg-completer[stackablectl] = {|@words| cand --cluster-name 'Name of the local cluster' cand --cluster-nodes 'Number of total nodes in the local cluster' cand --cluster-cp-nodes 'Number of control plane nodes in the local cluster' + cand --chart-source 'chart-source' 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)' @@ -270,6 +273,7 @@ set edit:completion:arg-completer[stackablectl] = {|@words| cand --cluster-name 'Name of the local cluster' cand --cluster-nodes 'Number of total nodes in the local cluster' cand --cluster-cp-nodes 'Number of control plane nodes in the local cluster' + cand --chart-source 'chart-source' 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)' @@ -402,6 +406,7 @@ set edit:completion:arg-completer[stackablectl] = {|@words| cand -n 'Namespace where the products (e.g. stacks or demos) are deployed' cand --product-namespace 'Namespace where the products (e.g. stacks or demos) are deployed' cand --product-ns 'Namespace where the products (e.g. stacks or demos) are deployed' + cand --chart-source 'chart-source' cand --release 'Target a specific Stackable release' cand -l 'Log level this application uses' cand --log-level 'Log level this application uses' @@ -591,6 +596,7 @@ set edit:completion:arg-completer[stackablectl] = {|@words| cand -n 'Namespace where the products (e.g. stacks or demos) are deployed' cand --product-namespace 'Namespace where the products (e.g. stacks or demos) are deployed' cand --product-ns 'Namespace where the products (e.g. stacks or demos) are deployed' + cand --chart-source 'chart-source' cand --release 'Target a specific Stackable release' cand -l 'Log level this application uses' cand --log-level 'Log level this application uses' diff --git a/extra/completions/stackablectl.fish b/extra/completions/stackablectl.fish index 99eb4781..32bf8056 100644 --- a/extra/completions/stackablectl.fish +++ b/extra/completions/stackablectl.fish @@ -60,6 +60,7 @@ complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and not __fish_seen_subcommand_from list describe install uninstall installed help" -f -a "installed" -d 'List installed operators' complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and not __fish_seen_subcommand_from list describe install uninstall installed help" -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 __fish_seen_subcommand_from list" -s o -l output -r -f -a "{plain\t'Print output formatted as plain text',table\t'Print output formatted as a table',json\t'Print output formatted as JSON',yaml\t'Print output formatted as YAML'}" +complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from list" -l chart-source -r -f -a "{oci\t'OCI registry',repo\t'Nexus repositories: resolution (dev, test, stable) is based on the version and thus will be operator-specific'}" complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from list" -s l -l log-level -d 'Log level this application uses' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from list" -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 operator; and __fish_seen_subcommand_from list" -s s -l stack-file -d 'Provide one or more additional (custom) stack file(s)' -r -F @@ -71,6 +72,7 @@ complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help (see more with \'--help\')' complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from list" -s V -l version -d 'Print version' complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from describe" -s o -l output -r -f -a "{plain\t'Print output formatted as plain text',table\t'Print output formatted as a table',json\t'Print output formatted as JSON',yaml\t'Print output formatted as YAML'}" +complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from describe" -l chart-source -r -f -a "{oci\t'OCI registry',repo\t'Nexus repositories: resolution (dev, test, stable) is based on the version and thus will be operator-specific'}" complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from describe" -s l -l log-level -d 'Log level this application uses' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from describe" -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 operator; and __fish_seen_subcommand_from describe" -s s -l stack-file -d 'Provide one or more additional (custom) stack file(s)' -r -F @@ -86,6 +88,7 @@ complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from install" -l cluster-name -d 'Name of the local cluster' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from install" -l cluster-nodes -d 'Number of total nodes in the local cluster' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from install" -l cluster-cp-nodes -d 'Number of control plane nodes in the local cluster' -r +complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from install" -l chart-source -r -f -a "{oci\t'OCI registry',repo\t'Nexus repositories: resolution (dev, test, stable) is based on the version and thus will be operator-specific'}" complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from install" -s l -l log-level -d 'Log level this application uses' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand operator; and __fish_seen_subcommand_from install" -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 operator; and __fish_seen_subcommand_from install" -s s -l stack-file -d 'Provide one or more additional (custom) stack file(s)' -r -F @@ -169,6 +172,7 @@ complete -c stackablectl -n "__fish_stackablectl_using_subcommand release; and _ complete -c stackablectl -n "__fish_stackablectl_using_subcommand release; and __fish_seen_subcommand_from install" -l cluster-name -d 'Name of the local cluster' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand release; and __fish_seen_subcommand_from install" -l cluster-nodes -d 'Number of total nodes in the local cluster' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand release; and __fish_seen_subcommand_from install" -l cluster-cp-nodes -d 'Number of control plane nodes in the local cluster' -r +complete -c stackablectl -n "__fish_stackablectl_using_subcommand release; and __fish_seen_subcommand_from install" -l chart-source -r -f -a "{oci\t'OCI registry',repo\t'Nexus repositories: resolution (dev, test, stable) is based on the version and thus will be operator-specific'}" complete -c stackablectl -n "__fish_stackablectl_using_subcommand release; and __fish_seen_subcommand_from install" -s l -l log-level -d 'Log level this application uses' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand release; and __fish_seen_subcommand_from install" -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 release; and __fish_seen_subcommand_from install" -s s -l stack-file -d 'Provide one or more additional (custom) stack file(s)' -r -F @@ -242,6 +246,7 @@ complete -c stackablectl -n "__fish_stackablectl_using_subcommand stack; and __f complete -c stackablectl -n "__fish_stackablectl_using_subcommand stack; and __fish_seen_subcommand_from install" -l cluster-cp-nodes -d 'Number of control plane nodes in the local cluster' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand stack; and __fish_seen_subcommand_from install" -l operator-namespace -l operator-ns -d 'Namespace where the operators are deployed' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand stack; and __fish_seen_subcommand_from install" -s n -l product-namespace -l product-ns -d 'Namespace where the products (e.g. stacks or demos) are deployed' -r +complete -c stackablectl -n "__fish_stackablectl_using_subcommand stack; and __fish_seen_subcommand_from install" -l chart-source -r -f -a "{oci\t'OCI registry',repo\t'Nexus repositories: resolution (dev, test, stable) is based on the version and thus will be operator-specific'}" complete -c stackablectl -n "__fish_stackablectl_using_subcommand stack; and __fish_seen_subcommand_from install" -l release -d 'Target a specific Stackable release' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand stack; and __fish_seen_subcommand_from install" -s l -l log-level -d 'Log level this application uses' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand stack; and __fish_seen_subcommand_from install" -s d -l demo-file -d 'Provide one or more additional (custom) demo file(s)' -r -F @@ -345,6 +350,7 @@ complete -c stackablectl -n "__fish_stackablectl_using_subcommand demo; and __fi complete -c stackablectl -n "__fish_stackablectl_using_subcommand demo; and __fish_seen_subcommand_from install" -l cluster-cp-nodes -d 'Number of control plane nodes in the local cluster' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand demo; and __fish_seen_subcommand_from install" -l operator-namespace -l operator-ns -d 'Namespace where the operators are deployed' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand demo; and __fish_seen_subcommand_from install" -s n -l product-namespace -l product-ns -d 'Namespace where the products (e.g. stacks or demos) are deployed' -r +complete -c stackablectl -n "__fish_stackablectl_using_subcommand demo; and __fish_seen_subcommand_from install" -l chart-source -r -f -a "{oci\t'OCI registry',repo\t'Nexus repositories: resolution (dev, test, stable) is based on the version and thus will be operator-specific'}" complete -c stackablectl -n "__fish_stackablectl_using_subcommand demo; and __fish_seen_subcommand_from install" -l release -d 'Target a specific Stackable release' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand demo; and __fish_seen_subcommand_from install" -s l -l log-level -d 'Log level this application uses' -r complete -c stackablectl -n "__fish_stackablectl_using_subcommand demo; and __fish_seen_subcommand_from install" -s d -l demo-file -d 'Provide one or more additional (custom) demo file(s)' -r -F diff --git a/extra/completions/stackablectl.nu b/extra/completions/stackablectl.nu index 02e9ef04..695a0bd3 100644 --- a/extra/completions/stackablectl.nu +++ b/extra/completions/stackablectl.nu @@ -32,9 +32,14 @@ module completions { [ "plain" "table" "json" "yaml" ] } + def "nu-complete stackablectl operator list chart_source" [] { + [ "oci" "repo" ] + } + # List available operators export extern "stackablectl operator list" [ --output(-o): string@"nu-complete stackablectl operator list output_type" + --chart-source: string@"nu-complete stackablectl operator list chart_source" --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): string # Provide one or more additional (custom) demo file(s) @@ -51,10 +56,15 @@ module completions { [ "plain" "table" "json" "yaml" ] } + def "nu-complete stackablectl operator describe chart_source" [] { + [ "oci" "repo" ] + } + # Print out detailed operator information export extern "stackablectl operator describe" [ OPERATOR: string # Operator to describe --output(-o): string@"nu-complete stackablectl operator describe output_type" + --chart-source: string@"nu-complete stackablectl operator describe chart_source" --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): string # Provide one or more additional (custom) demo file(s) @@ -71,6 +81,10 @@ module completions { [ "kind" "minikube" ] } + def "nu-complete stackablectl operator install chart_source" [] { + [ "oci" "repo" ] + } + # Install one or more operators export extern "stackablectl operator install" [ ...OPERATORS: string # Operator(s) to install @@ -80,6 +94,7 @@ module completions { --cluster-name: string # Name of the local cluster --cluster-nodes: string # Number of total nodes in the local cluster --cluster-cp-nodes: string # Number of control plane nodes in the local cluster + --chart-source: string@"nu-complete stackablectl operator install chart_source" --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): string # Provide one or more additional (custom) demo file(s) @@ -215,6 +230,10 @@ module completions { [ "kind" "minikube" ] } + def "nu-complete stackablectl release install chart_source" [] { + [ "oci" "repo" ] + } + # Install a specific release export extern "stackablectl release install" [ RELEASE: string # Release to install @@ -226,6 +245,7 @@ module completions { --cluster-name: string # Name of the local cluster --cluster-nodes: string # Number of total nodes in the local cluster --cluster-cp-nodes: string # Number of control plane nodes in the local cluster + --chart-source: string@"nu-complete stackablectl release install chart_source" --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): string # Provide one or more additional (custom) demo file(s) @@ -339,6 +359,10 @@ module completions { [ "kind" "minikube" ] } + def "nu-complete stackablectl stack install chart_source" [] { + [ "oci" "repo" ] + } + # Install a specific stack export extern "stackablectl stack install" [ stack_name: string # Name of the stack to describe @@ -353,6 +377,7 @@ module completions { --operator-ns: string # Namespace where the operators are deployed --product-namespace(-n): string # Namespace where the products (e.g. stacks or demos) are deployed --product-ns: string # Namespace where the products (e.g. stacks or demos) are deployed + --chart-source: string@"nu-complete stackablectl stack install chart_source" --release: string # Target a specific Stackable release --log-level(-l): string # Log level this application uses --no-cache # Do not cache the remote (default) demo, stack and release files @@ -517,6 +542,10 @@ module completions { [ "kind" "minikube" ] } + def "nu-complete stackablectl demo install chart_source" [] { + [ "oci" "repo" ] + } + # Install a specific demo export extern "stackablectl demo install" [ DEMO: string # Demo to install @@ -531,6 +560,7 @@ module completions { --operator-ns: string # Namespace where the operators are deployed --product-namespace(-n): string # Namespace where the products (e.g. stacks or demos) are deployed --product-ns: string # Namespace where the products (e.g. stacks or demos) are deployed + --chart-source: string@"nu-complete stackablectl demo install chart_source" --release: string # Target a specific Stackable release --log-level(-l): string # Log level this application uses --no-cache # Do not cache the remote (default) demo, stack and release files diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a56a283d..0193dee3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.80.1" +channel = "1.83.0" diff --git a/rust/stackable-cockpit/Cargo.toml b/rust/stackable-cockpit/Cargo.toml index 6a83a80d..87380fb8 100644 --- a/rust/stackable-cockpit/Cargo.toml +++ b/rust/stackable-cockpit/Cargo.toml @@ -32,6 +32,7 @@ tera.workspace = true tokio.workspace = true tracing.workspace = true url.workspace = true +urlencoding.workspace = true utoipa = { workspace = true, optional = true } which.workspace = true futures.workspace = true diff --git a/rust/stackable-cockpit/src/constants.rs b/rust/stackable-cockpit/src/constants.rs index f7ad785d..b0910607 100644 --- a/rust/stackable-cockpit/src/constants.rs +++ b/rust/stackable-cockpit/src/constants.rs @@ -22,6 +22,9 @@ pub const HELM_REPO_NAME_TEST: &str = "stackable-test"; pub const HELM_REPO_NAME_DEV: &str = "stackable-dev"; pub const HELM_REPO_INDEX_FILE: &str = "index.yaml"; +pub const HELM_OCI_BASE: &str = "oci.stackable.tech"; +pub const HELM_OCI_REGISTRY: &str = "oci://oci.stackable.tech/sdp-charts"; + pub const HELM_DEFAULT_CHART_VERSION: &str = ">0.0.0-0"; pub const PRODUCT_NAMES: &[&str] = &[ diff --git a/rust/stackable-cockpit/src/helm.rs b/rust/stackable-cockpit/src/helm.rs index 0e8eb193..25bca566 100644 --- a/rust/stackable-cockpit/src/helm.rs +++ b/rust/stackable-cockpit/src/helm.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::fmt::Display; use serde::{Deserialize, Serialize}; @@ -7,7 +6,10 @@ use tokio::task::block_in_place; use tracing::{debug, error, info, instrument}; use url::Url; -use crate::constants::{HELM_DEFAULT_CHART_VERSION, HELM_REPO_INDEX_FILE}; +use crate::{ + constants::{HELM_DEFAULT_CHART_VERSION, HELM_REPO_INDEX_FILE}, + utils::chartsource::ChartSourceMetadata, +}; #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -35,17 +37,6 @@ pub struct ChartRepo { pub url: String, } -#[derive(Clone, Debug, Deserialize)] -pub struct Repository { - pub entries: HashMap>, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct RepositoryEntry { - pub name: String, - pub version: String, -} - #[derive(Debug, Snafu)] pub enum Error { #[snafu(display("failed to parse URL"))] @@ -182,20 +173,20 @@ impl Display for UninstallReleaseStatus { } pub struct ChartVersion<'a> { - pub repo_name: &'a str, + pub chart_source: &'a str, pub chart_name: &'a str, pub chart_version: Option<&'a str>, } -/// Installs a Helm release from a repo. +/// Installs a Helm release from a repo or registry. /// /// This function expects the fully qualified Helm release name. In case of our /// operators this is: `-operator`. #[instrument] -pub fn install_release_from_repo( +pub fn install_release_from_repo_or_registry( release_name: &str, ChartVersion { - repo_name, + chart_source, chart_name, chart_version, }: ChartVersion, @@ -244,7 +235,7 @@ pub fn install_release_from_repo( } } - let full_chart_name = format!("{repo_name}/{chart_name}"); + let full_chart_name = format!("{chart_source}/{chart_name}"); let chart_version = chart_version.unwrap_or(HELM_DEFAULT_CHART_VERSION); debug!( @@ -398,7 +389,7 @@ pub fn add_repo(repository_name: &str, repository_url: &str) -> Result<(), Error /// Retrieves the Helm index file from the repository URL. #[instrument] -pub async fn get_helm_index(repo_url: T) -> Result +pub async fn get_helm_index(repo_url: T) -> Result where T: AsRef + std::fmt::Debug, { diff --git a/rust/stackable-cockpit/src/lib.rs b/rust/stackable-cockpit/src/lib.rs index 14220780..e572b560 100644 --- a/rust/stackable-cockpit/src/lib.rs +++ b/rust/stackable-cockpit/src/lib.rs @@ -2,6 +2,7 @@ pub mod common; pub mod constants; pub mod engine; pub mod helm; +pub mod oci; pub mod platform; pub mod utils; pub mod xfer; diff --git a/rust/stackable-cockpit/src/oci.rs b/rust/stackable-cockpit/src/oci.rs new file mode 100644 index 00000000..c6248a5b --- /dev/null +++ b/rust/stackable-cockpit/src/oci.rs @@ -0,0 +1,173 @@ +use std::collections::HashMap; + +use serde::Deserialize; +use snafu::{OptionExt, ResultExt, Snafu}; +use tracing::debug; +use urlencoding::encode; + +use crate::{ + constants::{HELM_OCI_BASE, HELM_REPO_NAME_DEV, HELM_REPO_NAME_STABLE, HELM_REPO_NAME_TEST}, + utils::chartsource::{ChartSourceEntry, ChartSourceMetadata}, +}; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("cannot get repositories"))] + GetRepositories { source: reqwest::Error }, + + #[snafu(display("cannot parse repositories"))] + ParseRepositories { source: reqwest::Error }, + + #[snafu(display("cannot get artifacts"))] + GetArtifacts { source: reqwest::Error }, + + #[snafu(display("cannot parse artifacts"))] + ParseArtifacts { source: reqwest::Error }, + + #[snafu(display("unexpected OCI repository name"))] + UnexpectedOciRepositoryName, +} + +#[derive(Deserialize, Debug)] +pub struct OciRepository { + pub name: String, +} + +#[derive(Deserialize, Debug)] +pub struct Tag { + pub name: String, +} + +#[derive(Deserialize, Debug)] +pub struct Artifact { + pub digest: String, + pub tags: Option>, +} + +pub async fn get_oci_index<'a>() -> Result, Error> { + let mut source_index_files: HashMap<&str, ChartSourceMetadata> = HashMap::new(); + + // initialize map + for repo_name in [ + HELM_REPO_NAME_STABLE, + HELM_REPO_NAME_TEST, + HELM_REPO_NAME_DEV, + ] { + source_index_files.insert( + repo_name, + ChartSourceMetadata { + entries: HashMap::new(), + }, + ); + } + let base_url = format!("https://{}/api/v2.0", HELM_OCI_BASE); + + // fetch all operators + let url = format!( + "{}/repositories?page_size={}&q=name=~sdp-charts/", + base_url, 100 + ); + + // reuse connections + let client = reqwest::Client::new(); + + let repositories: Vec = client + .get(&url) + .send() + .await + .context(GetRepositoriesSnafu)? + .json() + .await + .context(ParseRepositoriesSnafu)?; + + debug!("OCI repos {:?}", repositories); + + for repository in &repositories { + // fetch all artifacts pro operator + let (project_name, repository_name) = repository + .name + .split_once('/') + .context(UnexpectedOciRepositoryNameSnafu)?; + + debug!("OCI repo parts {} and {}", project_name, repository_name); + + let mut artifacts = Vec::new(); + let mut page = 1; + let page_size = 20; + + while let Ok(artifacts_page) = client + .get(format!( + "{}/projects/{}/repositories/{}/artifacts?page_size={}&page={}", + base_url, + encode(project_name), + encode(repository_name), + page_size, + page + )) + .send() + .await + .context(GetArtifactsSnafu)? + .json::>() + .await + .context(ParseArtifactsSnafu) + { + let count = artifacts_page.len(); + artifacts.extend(artifacts_page); + if count < page_size { + break; + } + page += 1; + } + + for artifact in &artifacts { + if let Some(release_artifact) = + artifact.tags.as_ref().and_then(|tags| tags.iter().next()) + { + let release_version = release_artifact + .name + .replace("-arm64", "") + .replace("-amd64", ""); + + debug!( + "OCI resolved artifact {}, {}, {}", + release_version.to_string(), + repository_name.to_string(), + release_artifact.name.to_string() + ); + + let entry = ChartSourceEntry { + name: repository_name.to_string(), + version: release_version.to_string(), + }; + + match release_version.as_str() { + "0.0.0-dev" => { + if let Some(repo) = source_index_files.get_mut(HELM_REPO_NAME_DEV) { + repo.entries + .entry(repository_name.to_string()) + .or_default() + .push(entry) + } + } + version if version.contains("-pr") => { + if let Some(repo) = source_index_files.get_mut(HELM_REPO_NAME_TEST) { + repo.entries + .entry(repository_name.to_string()) + .or_default() + .push(entry) + } + } + _ => { + if let Some(repo) = source_index_files.get_mut(HELM_REPO_NAME_STABLE) { + repo.entries + .entry(repository_name.to_string()) + .or_default() + .push(entry) + } + } + } + } + } + } + Ok(source_index_files) +} diff --git a/rust/stackable-cockpit/src/platform/demo/params.rs b/rust/stackable-cockpit/src/platform/demo/params.rs index f2bfb111..b5f613fa 100644 --- a/rust/stackable-cockpit/src/platform/demo/params.rs +++ b/rust/stackable-cockpit/src/platform/demo/params.rs @@ -1,5 +1,7 @@ use stackable_operator::kvp::Labels; +use crate::platform::operator::ChartSourceType; + pub struct DemoInstallParameters { pub operator_namespace: String, pub product_namespace: String, @@ -10,4 +12,5 @@ pub struct DemoInstallParameters { pub stack_labels: Labels, pub labels: Labels, + pub chart_source: ChartSourceType, } diff --git a/rust/stackable-cockpit/src/platform/demo/spec.rs b/rust/stackable-cockpit/src/platform/demo/spec.rs index 68741e87..59aaa8d9 100644 --- a/rust/stackable-cockpit/src/platform/demo/spec.rs +++ b/rust/stackable-cockpit/src/platform/demo/spec.rs @@ -159,6 +159,7 @@ impl DemoSpec { skip_release: install_parameters.skip_release, stack_name: self.stack.clone(), demo_name: None, + chart_source: install_parameters.chart_source.clone(), }; stack diff --git a/rust/stackable-cockpit/src/platform/manifests.rs b/rust/stackable-cockpit/src/platform/manifests.rs index 882533e0..86b604f4 100644 --- a/rust/stackable-cockpit/src/platform/manifests.rs +++ b/rust/stackable-cockpit/src/platform/manifests.rs @@ -94,6 +94,7 @@ pub trait InstallManifestsExt { helm_chart.name, helm_chart.version ); + // Assumption: that all manifest helm charts refer to repos not registries helm::add_repo(&helm_chart.repo.name, &helm_chart.repo.url).context( AddHelmRepositorySnafu { repo_name: helm_chart.repo.name.clone(), @@ -105,10 +106,10 @@ pub trait InstallManifestsExt { .context(SerializeOptionsSnafu)?; // Install the Helm chart using the Helm wrapper - helm::install_release_from_repo( + helm::install_release_from_repo_or_registry( &helm_chart.release_name, helm::ChartVersion { - repo_name: &helm_chart.repo.name, + chart_source: &helm_chart.repo.name, chart_name: &helm_chart.name, chart_version: Some(&helm_chart.version), }, diff --git a/rust/stackable-cockpit/src/platform/operator/mod.rs b/rust/stackable-cockpit/src/platform/operator/mod.rs index ca0d4d64..e32ebc42 100644 --- a/rust/stackable-cockpit/src/platform/operator/mod.rs +++ b/rust/stackable-cockpit/src/platform/operator/mod.rs @@ -1,11 +1,14 @@ use std::{fmt::Display, str::FromStr}; use semver::Version; +use serde::Serialize; use snafu::{ensure, ResultExt, Snafu}; use tracing::{info, instrument}; use crate::{ - constants::{HELM_REPO_NAME_DEV, HELM_REPO_NAME_STABLE, HELM_REPO_NAME_TEST}, + constants::{ + HELM_OCI_REGISTRY, HELM_REPO_NAME_DEV, HELM_REPO_NAME_STABLE, HELM_REPO_NAME_TEST, + }, helm, utils::operator_chart_name, }; @@ -176,20 +179,30 @@ impl OperatorSpec { /// Installs the operator using Helm. #[instrument(skip_all)] - pub fn install(&self, namespace: &str) -> Result<(), helm::Error> { + pub fn install( + &self, + namespace: &str, + chart_source: &ChartSourceType, + ) -> Result<(), helm::Error> { info!("Installing operator {}", self); let version = self.version.as_ref().map(|v| v.to_string()); - let helm_repo = self.helm_repo_name(); let helm_name = self.helm_name(); + // we can't resolve this any earlier as, for the repository case, + // this will be dependent on the operator version. + let chart_source = match chart_source { + ChartSourceType::OCI => HELM_OCI_REGISTRY.to_string(), + ChartSourceType::Repo => self.helm_repo_name(), + }; + // Install using Helm - helm::install_release_from_repo( + helm::install_release_from_repo_or_registry( &helm_name, helm::ChartVersion { chart_version: version.as_deref(), chart_name: &helm_name, - repo_name: &helm_repo, + chart_source: &chart_source, }, None, namespace, @@ -215,6 +228,16 @@ impl OperatorSpec { } } +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum ChartSourceType { + /// OCI registry + OCI, + + /// Nexus repositories: resolution (dev, test, stable) is based on the version and thus may be operator-specific + Repo, +} + #[cfg(test)] mod test { use rstest::rstest; diff --git a/rust/stackable-cockpit/src/platform/release/spec.rs b/rust/stackable-cockpit/src/platform/release/spec.rs index ec0199a4..cf4bb8cd 100644 --- a/rust/stackable-cockpit/src/platform/release/spec.rs +++ b/rust/stackable-cockpit/src/platform/release/spec.rs @@ -11,7 +11,7 @@ use utoipa::ToSchema; use crate::{ helm, platform::{ - operator::{self, OperatorSpec}, + operator::{self, ChartSourceType, OperatorSpec}, product, }, }; @@ -56,6 +56,7 @@ impl ReleaseSpec { include_products: &[String], exclude_products: &[String], namespace: &str, + chart_source: &ChartSourceType, ) -> Result<()> { info!("Installing release"); @@ -63,6 +64,7 @@ impl ReleaseSpec { futures::stream::iter(self.filter_products(include_products, exclude_products)) .map(|(product_name, product)| { let namespace = namespace.clone(); + let chart_source = chart_source.clone(); // Helm installs currently `block_in_place`, so we need to spawn each job onto a separate task to // get useful parallelism. tokio::spawn(async move { @@ -73,7 +75,9 @@ impl ReleaseSpec { .context(OperatorSpecParseSnafu)?; // Install operator - operator.install(&namespace).context(HelmInstallSnafu)?; + operator + .install(&namespace, &chart_source) + .context(HelmInstallSnafu)?; info!("Installed {product_name}-operator"); diff --git a/rust/stackable-cockpit/src/platform/stack/params.rs b/rust/stackable-cockpit/src/platform/stack/params.rs index 27197fbc..9268409d 100644 --- a/rust/stackable-cockpit/src/platform/stack/params.rs +++ b/rust/stackable-cockpit/src/platform/stack/params.rs @@ -1,5 +1,7 @@ use stackable_operator::kvp::Labels; +use crate::platform::operator::ChartSourceType; + #[derive(Debug)] pub struct StackInstallParameters { pub demo_name: Option, @@ -11,4 +13,5 @@ pub struct StackInstallParameters { pub parameters: Vec, pub skip_release: bool, pub labels: Labels, + pub chart_source: ChartSourceType, } diff --git a/rust/stackable-cockpit/src/platform/stack/spec.rs b/rust/stackable-cockpit/src/platform/stack/spec.rs index 499c08b0..2a20ba5d 100644 --- a/rust/stackable-cockpit/src/platform/stack/spec.rs +++ b/rust/stackable-cockpit/src/platform/stack/spec.rs @@ -10,7 +10,9 @@ use crate::{ platform::{ cluster::{ResourceRequests, ResourceRequestsError}, manifests::{self, InstallManifestsExt}, - namespace, release, + namespace, + operator::ChartSourceType, + release, stack::StackInstallParameters, }, utils::{ @@ -173,6 +175,7 @@ impl StackSpec { release_list, &install_parameters.operator_namespace, &install_parameters.product_namespace, + &install_parameters.chart_source, ) .await?; } @@ -195,6 +198,7 @@ impl StackSpec { release_list: release::ReleaseList, operator_namespace: &str, product_namespace: &str, + chart_source: &ChartSourceType, ) -> Result<(), Error> { info!("Trying to install release {}", self.release); @@ -207,7 +211,7 @@ impl StackSpec { // Install the release release - .install(&self.operators, &[], operator_namespace) + .install(&self.operators, &[], operator_namespace, chart_source) .await .context(InstallReleaseSnafu) } diff --git a/rust/stackable-cockpit/src/utils/chartsource.rs b/rust/stackable-cockpit/src/utils/chartsource.rs new file mode 100644 index 00000000..5a829e04 --- /dev/null +++ b/rust/stackable-cockpit/src/utils/chartsource.rs @@ -0,0 +1,14 @@ +use std::collections::HashMap; + +use serde::Deserialize; + +#[derive(Clone, Debug, Deserialize)] +pub struct ChartSourceMetadata { + pub entries: HashMap>, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct ChartSourceEntry { + pub name: String, + pub version: String, +} diff --git a/rust/stackable-cockpit/src/utils/mod.rs b/rust/stackable-cockpit/src/utils/mod.rs index cad8fa40..a42bcc0d 100644 --- a/rust/stackable-cockpit/src/utils/mod.rs +++ b/rust/stackable-cockpit/src/utils/mod.rs @@ -1,3 +1,4 @@ +pub mod chartsource; pub mod check; pub mod k8s; pub mod params; diff --git a/rust/stackable-cockpit/src/xfer/processor.rs b/rust/stackable-cockpit/src/xfer/processor.rs index e6b0252e..c2f705ba 100644 --- a/rust/stackable-cockpit/src/xfer/processor.rs +++ b/rust/stackable-cockpit/src/xfer/processor.rs @@ -91,7 +91,7 @@ impl Yaml { #[derive(Debug)] pub struct Template<'a>(&'a HashMap); -impl<'a> Processor for Template<'a> { +impl Processor for Template<'_> { type Input = String; type Output = String; diff --git a/rust/stackablectl/CHANGELOG.md b/rust/stackablectl/CHANGELOG.md index 720507fc..8b0001c6 100644 --- a/rust/stackablectl/CHANGELOG.md +++ b/rust/stackablectl/CHANGELOG.md @@ -7,12 +7,14 @@ All notable changes to this project will be documented in this file. ### Added - Add new argument `--release` that allows installing a specific version of a demo or stack ([#340]). +- Add new argument `--chart-source` so that operator charts can be pulled either from an OCI registry (the default) or from a Nexus repository ([#344]). ### Removed - Remove argument `--offline` that was not implemented yet ([#340]). [#340]: https://github.com/stackabletech/stackable-cockpit/pull/340 +[#344]: https://github.com/stackabletech/stackable-cockpit/pull/344 ## [24.11.1] - 2024-11-20 diff --git a/rust/stackablectl/Cargo.toml b/rust/stackablectl/Cargo.toml index c635ceaa..279d93a5 100644 --- a/rust/stackablectl/Cargo.toml +++ b/rust/stackablectl/Cargo.toml @@ -34,4 +34,5 @@ tracing-subscriber.workspace = true tracing.workspace = true futures.workspace = true termion.workspace = true +urlencoding.workspace = true libc.workspace = true diff --git a/rust/stackablectl/src/cli/mod.rs b/rust/stackablectl/src/cli/mod.rs index 1f9ec1ef..103d04b6 100644 --- a/rust/stackablectl/src/cli/mod.rs +++ b/rust/stackablectl/src/cli/mod.rs @@ -8,6 +8,7 @@ use tracing::{debug, instrument, Level}; use stackable_cockpit::{ constants::{HELM_REPO_NAME_DEV, HELM_REPO_NAME_STABLE, HELM_REPO_NAME_TEST}, helm, + platform::operator::ChartSourceType, utils::path::{ IntoPathOrUrl, IntoPathsOrUrls, ParsePathsOrUrls, PathOrUrl, PathOrUrlParseError, }, @@ -290,3 +291,27 @@ fn get_files(default_file: &str, env_key: &str) -> Result, PathOr Ok(files) } + +#[derive(Clone, Debug, Default, ValueEnum)] +pub enum ChartSourceTypeArg { + /// OCI registry + #[default] + OCI, + + /// Nexus repositories: resolution (dev, test, stable) is based on the version and thus will be operator-specific + Repo, +} + +impl From for ChartSourceType { + /// Resolves the enum used by clap/arg-resolution to the core type used in + /// stackable-cockpit. For the (Nexus-)repo case this core type cannot be + /// decorated with meaningful information as that would be operator-specific + /// i.e. we cannot resolve *which* Nexus repo to use until we have inspected + /// the operator version. Hence just a simple mapping. + fn from(cli_enum: ChartSourceTypeArg) -> Self { + match cli_enum { + ChartSourceTypeArg::OCI => ChartSourceType::OCI, + ChartSourceTypeArg::Repo => ChartSourceType::Repo, + } + } +} diff --git a/rust/stackablectl/src/cmds/demo.rs b/rust/stackablectl/src/cmds/demo.rs index 0fbb2b76..452671fb 100644 --- a/rust/stackablectl/src/cmds/demo.rs +++ b/rust/stackablectl/src/cmds/demo.rs @@ -12,6 +12,7 @@ use stackable_cockpit::{ constants::{DEFAULT_OPERATOR_NAMESPACE, DEFAULT_PRODUCT_NAMESPACE}, platform::{ demo::{self, DemoInstallParameters}, + operator::ChartSourceType, release, stack, }, utils::{ @@ -23,7 +24,7 @@ use stackable_cockpit::{ use crate::{ args::{CommonClusterArgs, CommonClusterArgsError, CommonNamespaceArgs}, - cli::{Cli, OutputType}, + cli::{ChartSourceTypeArg, Cli, OutputType}, }; #[derive(Debug, Args)] @@ -111,6 +112,13 @@ to specify operator versions." #[command(flatten)] namespaces: CommonNamespaceArgs, + + #[arg( + long, + long_help = "Source the charts from either a OCI registry or from Nexus repositories.", + value_enum, default_value_t = Default::default() + )] + chart_source: ChartSourceTypeArg, } #[derive(Debug, Args)] @@ -285,7 +293,11 @@ async fn describe_cmd( .add_row(vec!["DESCRIPTION", &demo.description]) .add_row_if( |_, _| demo.documentation.is_some(), - vec!["DOCUMENTATION", demo.documentation.as_ref().unwrap()], + // The simple unwrap() may be called despite the condition, if add_row_if evaluates its arguments eagerly, so make sure to avoid a panic + demo.documentation + .as_ref() + .map(|doc| vec!["DOCUMENTATION", doc]) + .unwrap_or_else(Vec::new), ) .add_row(vec!["STACK", &demo.stack]) .add_row(vec!["LABELS", &demo.labels.join(", ")]); @@ -370,6 +382,7 @@ async fn install_cmd( skip_release: args.skip_release, stack_labels, labels, + chart_source: ChartSourceType::from(args.chart_source.clone()), }; demo.install( diff --git a/rust/stackablectl/src/cmds/operator.rs b/rust/stackablectl/src/cmds/operator.rs index 7e955b10..76010d45 100644 --- a/rust/stackablectl/src/cmds/operator.rs +++ b/rust/stackablectl/src/cmds/operator.rs @@ -15,17 +15,22 @@ use stackable_cockpit::{ constants::{ DEFAULT_OPERATOR_NAMESPACE, HELM_REPO_NAME_DEV, HELM_REPO_NAME_STABLE, HELM_REPO_NAME_TEST, }, - helm::{self, Release, Repository}, - platform::{namespace, operator}, + helm::{self, Release}, + oci, + platform::{ + namespace, + operator::{self, ChartSourceType}, + }, utils::{ self, + chartsource::ChartSourceMetadata, k8s::{self, Client}, }, }; use crate::{ args::{CommonClusterArgs, CommonClusterArgsError}, - cli::{Cli, OutputType}, + cli::{ChartSourceTypeArg, Cli, OutputType}, utils::{helm_repo_name_to_repo_url, InvalidRepoNameError}, }; @@ -65,6 +70,13 @@ pub enum OperatorCommands { pub struct OperatorListArgs { #[arg(short, long = "output", value_enum, default_value_t = Default::default())] output_type: OutputType, + + #[arg( + long, + long_help = "Source the charts from either a OCI registry or from Nexus repositories.", + value_enum, default_value_t = Default::default() + )] + chart_source: ChartSourceTypeArg, } #[derive(Debug, Args)] @@ -75,6 +87,12 @@ pub struct OperatorDescribeArgs { #[arg(short, long = "output", value_enum, default_value_t = Default::default())] output_type: OutputType, + + #[arg( + long, + long_help = "Source the charts from either a OCI registry or from Nexus repositories.", value_enum, default_value_t = Default::default() + )] + chart_source: ChartSourceTypeArg, } #[derive(Debug, Args)] @@ -102,6 +120,13 @@ Use \"stackablectl operator describe \" to get available versions for #[command(flatten)] local_cluster: CommonClusterArgs, + + #[arg( + long, + long_help = "Source the charts from either a OCI registry or from Nexus repositories.", + value_enum, default_value_t = Default::default() + )] + chart_source: ChartSourceTypeArg, } #[derive(Debug, Args)] @@ -159,6 +184,9 @@ pub enum CmdError { source: namespace::Error, namespace: String, }, + + #[snafu(display("OCI error"))] + OciError { source: oci::Error }, } /// This list contains a list of operator version grouped by stable, test and @@ -183,12 +211,13 @@ impl OperatorArgs { async fn list_cmd(args: &OperatorListArgs, cli: &Cli) -> Result { debug!("Listing operators"); - // Build map which maps Helm repo name to Helm repo URL - let helm_index_files = build_helm_index_file_list().await?; + // Build map which maps artifacts to a chart source + let source_index_files = + build_source_index_file_list(&ChartSourceType::from(args.chart_source.clone())).await?; // Iterate over all valid operators and create a list of versions grouped // by stable, test and dev lines - let versions_list = build_versions_list(&helm_index_files)?; + let versions_list = build_versions_list(&source_index_files)?; match args.output_type { OutputType::Plain | OutputType::Table => { @@ -239,11 +268,12 @@ async fn list_cmd(args: &OperatorListArgs, cli: &Cli) -> Result Result { debug!("Describing operator {}", args.operator_name); - // Build map which maps Helm repo name to Helm repo URL - let helm_index_files = build_helm_index_file_list().await?; + // Build map which maps artifacts to a chart source + let source_index_files = + build_source_index_file_list(&ChartSourceType::from(args.chart_source.clone())).await?; // Create a list of versions for this operator - let versions_list = build_versions_list_for_operator(&args.operator_name, &helm_index_files)?; + let versions_list = build_versions_list_for_operator(&args.operator_name, &source_index_files)?; match args.output_type { OutputType::Plain | OutputType::Table => { @@ -312,7 +342,10 @@ async fn install_cmd(args: &OperatorInstallArgs, cli: &Cli) -> Result Result() -> Result, CmdError> { - debug!("Building Helm index file list"); - - let mut helm_index_files = HashMap::new(); - - for helm_repo_name in [ - HELM_REPO_NAME_STABLE, - HELM_REPO_NAME_TEST, - HELM_REPO_NAME_DEV, - ] { - let helm_repo_url = - helm_repo_name_to_repo_url(helm_repo_name).context(InvalidRepoNameSnafu)?; - - helm_index_files.insert( - helm_repo_name, - helm::get_helm_index(helm_repo_url) - .await - .context(HelmSnafu)?, - ); - } +async fn build_source_index_file_list<'a>( + chart_source: &ChartSourceType, +) -> Result, CmdError> { + debug!("Building source index file list"); + + let mut source_index_files: HashMap<&str, ChartSourceMetadata> = HashMap::new(); + + match chart_source { + ChartSourceType::OCI => { + source_index_files = oci::get_oci_index().await.context(OciSnafu)?; + + debug!("OCI Repository entries: {:?}", source_index_files); + } + ChartSourceType::Repo => { + for helm_repo_name in [ + HELM_REPO_NAME_STABLE, + HELM_REPO_NAME_TEST, + HELM_REPO_NAME_DEV, + ] { + let helm_repo_url = + helm_repo_name_to_repo_url(helm_repo_name).context(InvalidRepoNameSnafu)?; + + source_index_files.insert( + helm_repo_name, + helm::get_helm_index(helm_repo_url) + .await + .context(HelmSnafu)?, + ); + + debug!("Helm Repository entries: {:?}", source_index_files); + } + } + }; - Ok(helm_index_files) + Ok(source_index_files) } /// Iterates over all valid operators and creates a list of versions grouped /// by stable, test and dev lines based on the list of Helm repo index files. #[instrument] fn build_versions_list( - helm_index_files: &HashMap<&str, Repository>, + helm_index_files: &HashMap<&str, ChartSourceMetadata>, ) -> Result, CmdError> { debug!("Building versions list"); @@ -492,7 +538,7 @@ fn build_versions_list( #[instrument] fn build_versions_list_for_operator( operator_name: T, - helm_index_files: &HashMap<&str, Repository>, + helm_index_files: &HashMap<&str, ChartSourceMetadata>, ) -> Result where T: AsRef + std::fmt::Debug, @@ -517,7 +563,7 @@ where #[instrument] fn list_operator_versions_from_repo( operator_name: T, - helm_repo: &Repository, + helm_repo: &ChartSourceMetadata, ) -> Result, CmdError> where T: AsRef + std::fmt::Debug, diff --git a/rust/stackablectl/src/cmds/release.rs b/rust/stackablectl/src/cmds/release.rs index 5a22bfc6..a935d086 100644 --- a/rust/stackablectl/src/cmds/release.rs +++ b/rust/stackablectl/src/cmds/release.rs @@ -9,7 +9,7 @@ use tracing::{debug, info, instrument}; use stackable_cockpit::{ common::list, constants::DEFAULT_OPERATOR_NAMESPACE, - platform::{namespace, release}, + platform::{namespace, operator::ChartSourceType, release}, utils::{ k8s::{self, Client}, path::PathOrUrlParseError, @@ -19,7 +19,7 @@ use stackable_cockpit::{ use crate::{ args::{CommonClusterArgs, CommonClusterArgsError}, - cli::{Cli, OutputType}, + cli::{ChartSourceTypeArg, Cli, OutputType}, }; #[derive(Debug, Args)] @@ -82,6 +82,13 @@ pub struct ReleaseInstallArgs { #[command(flatten)] local_cluster: CommonClusterArgs, + + #[arg( + long, + long_help = "Source the charts from either a OCI registry or from Nexus repositories.", + value_enum, default_value_t = Default::default() + )] + chart_source: ChartSourceTypeArg, } #[derive(Debug, Args)] @@ -296,6 +303,7 @@ async fn install_cmd( &args.included_products, &args.excluded_products, &args.operator_namespace, + &ChartSourceType::from(args.chart_source.clone()), ) .await .context(ReleaseInstallSnafu)?; diff --git a/rust/stackablectl/src/cmds/stack.rs b/rust/stackablectl/src/cmds/stack.rs index a2ca9423..3dd0c9f5 100644 --- a/rust/stackablectl/src/cmds/stack.rs +++ b/rust/stackablectl/src/cmds/stack.rs @@ -11,6 +11,7 @@ use stackable_cockpit::{ common::list, constants::{DEFAULT_OPERATOR_NAMESPACE, DEFAULT_PRODUCT_NAMESPACE}, platform::{ + operator::ChartSourceType, release, stack::{self, StackInstallParameters}, }, @@ -23,7 +24,7 @@ use stackable_cockpit::{ use crate::{ args::{CommonClusterArgs, CommonClusterArgsError, CommonNamespaceArgs}, - cli::{Cli, OutputType}, + cli::{ChartSourceTypeArg, Cli, OutputType}, }; #[derive(Debug, Args)] @@ -107,6 +108,13 @@ Use \"stackablectl stack describe \" to list available parameters for eac #[command(flatten)] namespaces: CommonNamespaceArgs, + + #[arg( + long, + long_help = "Source the charts from either a OCI registry or from Nexus repositories.", + value_enum, default_value_t = Default::default() + )] + chart_source: ChartSourceTypeArg, } #[derive(Debug, Snafu)] @@ -347,6 +355,7 @@ async fn install_cmd( skip_release: args.skip_release, demo_name: None, labels, + chart_source: ChartSourceType::from(args.chart_source.clone()), }; stack_spec