diff --git a/.github/workflows/build-and-publish-docker.yml b/.github/workflows/build-and-publish-docker.yml index 502c103d1..74bbcae53 100644 --- a/.github/workflows/build-and-publish-docker.yml +++ b/.github/workflows/build-and-publish-docker.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -89,7 +89,7 @@ jobs: - name: Log in to Docker Hub if: inputs.publish - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6a5df116d..308059877 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,10 +16,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod @@ -53,12 +53,12 @@ jobs: HAS_SECRETS: ${{ secrets.TEMPORAL_CLIENT_CERT != '' && secrets.TEMPORAL_CLIENT_KEY != '' }} steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: recursive - name: Setup Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod @@ -69,7 +69,13 @@ jobs: run: mkdir junit-xml - name: Test - run: gotestsum --junitfile junit-xml/${{matrix.os}}.xml -- ./... + shell: bash + run: | + gotestsum --junitfile junit-xml/${{matrix.os}}.xml -- ./... + ( + cd cliext + gotestsum --junitfile ../junit-xml/${{matrix.os}}-cliext.xml -- ./... + ) - name: Upload junit-xml artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/govulncheck.yml b/.github/workflows/govulncheck.yml index 7c49fd9df..0abb20f2a 100644 --- a/.github/workflows/govulncheck.yml +++ b/.github/workflows/govulncheck.yml @@ -11,8 +11,8 @@ jobs: name: Govulncheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: go.mod - uses: temporalio/public-actions/golang/govulncheck@main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec10831b4..6d1bbb486 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,12 +15,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version-file: "go.mod" check-latest: true diff --git a/.github/workflows/validate-dependabot.yml b/.github/workflows/validate-dependabot.yml index d9010aa50..18054169f 100644 --- a/.github/workflows/validate-dependabot.yml +++ b/.github/workflows/validate-dependabot.yml @@ -17,9 +17,9 @@ jobs: name: Validate Dependabot Config runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.12' diff --git a/Makefile b/Makefile index 56e79a663..c92c37ed8 100644 --- a/Makefile +++ b/Makefile @@ -23,3 +23,7 @@ gen-docs: internal/temporalcli/commands.yaml cliext/option-sets.yaml build: go build ./cmd/temporal + +test: + (cd cliext && go test ./...) + go test ./... diff --git a/cliext/flags.gen.go b/cliext/flags.gen.go index dafd27121..922e34eb6 100644 --- a/cliext/flags.gen.go +++ b/cliext/flags.gen.go @@ -30,14 +30,18 @@ type CommonOptions struct { FlagSet *pflag.FlagSet } +func (v *CommonOptions) Description() string { + return "These options apply to every command. They control output formatting,\nlogging, and which configuration profile and environment to use.\nOptions that accept an environment variable can be set instead of\npassing the flag each time.\n" +} + func (v *CommonOptions) BuildFlags(f *pflag.FlagSet) { v.FlagSet = f - f.StringVar(&v.Env, "env", "default", "Active environment name (`ENV`).") - f.StringVar(&v.EnvFile, "env-file", "", "Path to environment settings file. Defaults to `$HOME/.config/temporalio/temporal.yaml`.") - f.StringVar(&v.ConfigFile, "config-file", "", "File path to read TOML config from, defaults to `$CONFIG_PATH/temporalio/temporal.toml` where `$CONFIG_PATH` is defined as `$HOME/.config` on Unix, `$HOME/Library/Application Support` on macOS, and `%AppData%` on Windows.") - f.StringVar(&v.Profile, "profile", "", "Profile to use for config file.") - f.BoolVar(&v.DisableConfigFile, "disable-config-file", false, "If set, disables loading environment config from config file.") - f.BoolVar(&v.DisableConfigEnv, "disable-config-env", false, "If set, disables loading environment config from environment variables.") + f.StringVar(&v.Env, "env", "default", "Active environment name (`ENV`). Env: TEMPORAL_ENV.") + f.StringVar(&v.EnvFile, "env-file", "", "Path to environment settings file. Env: TEMPORAL_ENV_FILE.") + f.StringVar(&v.ConfigFile, "config-file", "", "TOML config file path. Env: TEMPORAL_CONFIG_FILE.") + f.StringVar(&v.Profile, "profile", "", "Profile to use for config file. Env: TEMPORAL_PROFILE.") + f.BoolVar(&v.DisableConfigFile, "disable-config-file", false, "Disable loading config from file.") + f.BoolVar(&v.DisableConfigEnv, "disable-config-env", false, "Disable loading config from environment variables.") v.LogLevel = NewFlagStringEnum([]string{"debug", "info", "warn", "error", "never"}, "never") f.Var(&v.LogLevel, "log-level", "Log level. Default is \"never\" for most commands and \"warn\" for \"server start-dev\". Accepted values: debug, info, warn, error, never.") v.LogFormat = NewFlagStringEnum([]string{"text", "json", "pretty"}, "text") @@ -50,9 +54,9 @@ func (v *CommonOptions) BuildFlags(f *pflag.FlagSet) { f.Var(&v.Color, "color", "Output coloring. Accepted values: always, never, auto.") f.BoolVar(&v.NoJsonShorthandPayloads, "no-json-shorthand-payloads", false, "Raw payload output, even if the JSON option was used.") v.CommandTimeout = 0 - f.Var(&v.CommandTimeout, "command-timeout", "The command execution timeout. 0s means no timeout.") + f.Var(&v.CommandTimeout, "command-timeout", "Command execution timeout.") v.ClientConnectTimeout = 0 - f.Var(&v.ClientConnectTimeout, "client-connect-timeout", "The client connection timeout. 0s means no timeout.") + f.Var(&v.ClientConnectTimeout, "client-connect-timeout", "Client connection timeout.") } type ClientOptions struct { @@ -77,24 +81,52 @@ type ClientOptions struct { FlagSet *pflag.FlagSet } +func (v *ClientOptions) Description() string { + return "These options apply to commands that connect to a Temporal Service\n(workflow, activity, schedule, etc). They specify the server address,\nnamespace, authentication, and TLS settings. Values are resolved in\norder: CLI flag > environment variable > config file.\n\nTo persist these settings, use:\n temporal config set --prop KEY --value VALUE\n" +} + func (v *ClientOptions) BuildFlags(f *pflag.FlagSet) { v.FlagSet = f - f.StringVar(&v.Address, "address", "localhost:7233", "Temporal Service gRPC endpoint.") + f.StringVar(&v.Address, "address", "localhost:7233", "Temporal Service gRPC endpoint. Env: TEMPORAL_ADDRESS. Config: address.") f.StringVar(&v.ClientAuthority, "client-authority", "", "Temporal gRPC client :authority pseudoheader.") - f.StringVarP(&v.Namespace, "namespace", "n", "default", "Temporal Service Namespace.") - f.StringVar(&v.ApiKey, "api-key", "", "API key for request.") - f.StringArrayVar(&v.GrpcMeta, "grpc-meta", nil, "HTTP headers for requests. Format as a `KEY=VALUE` pair. May be passed multiple times to set multiple headers. Can also be made available via environment variable as `TEMPORAL_GRPC_META_[name]`.") - f.BoolVar(&v.Tls, "tls", false, "Enable base TLS encryption. Does not have additional options like mTLS or client certs. This is defaulted to true if api-key or any other TLS options are present. Use --tls=false to explicitly disable.") - f.StringVar(&v.TlsCertPath, "tls-cert-path", "", "Path to x509 certificate. Can't be used with --tls-cert-data.") - f.StringVar(&v.TlsCertData, "tls-cert-data", "", "Data for x509 certificate. Can't be used with --tls-cert-path.") - f.StringVar(&v.TlsKeyPath, "tls-key-path", "", "Path to x509 private key. Can't be used with --tls-key-data.") - f.StringVar(&v.TlsKeyData, "tls-key-data", "", "Private certificate key data. Can't be used with --tls-key-path.") - f.StringVar(&v.TlsCaPath, "tls-ca-path", "", "Path to server CA certificate. Can't be used with --tls-ca-data.") - f.StringVar(&v.TlsCaData, "tls-ca-data", "", "Data for server CA certificate. Can't be used with --tls-ca-path.") - f.BoolVar(&v.TlsDisableHostVerification, "tls-disable-host-verification", false, "Disable TLS host-name verification.") - f.StringVar(&v.TlsServerName, "tls-server-name", "", "Override target TLS server name.") - f.StringVar(&v.CodecEndpoint, "codec-endpoint", "", "Remote Codec Server endpoint.") - f.StringVar(&v.CodecAuth, "codec-auth", "", "Authorization header for Codec Server requests.") - f.StringArrayVar(&v.CodecHeader, "codec-header", nil, "HTTP headers for requests to codec server. Format as a `KEY=VALUE` pair. May be passed multiple times to set multiple headers.") - f.StringVar(&v.Identity, "identity", "", "The identity of the user or client submitting this request. Defaults to \"temporal-cli:$USER@$HOST\".") + f.StringVarP(&v.Namespace, "namespace", "n", "default", "Temporal Service Namespace. Env: TEMPORAL_NAMESPACE. Config: namespace.") + f.StringVar(&v.ApiKey, "api-key", "", "API key for request. Env: TEMPORAL_API_KEY. Config: api_key.") + f.StringArrayVar(&v.GrpcMeta, "grpc-meta", nil, "HTTP headers for requests (KEY=VALUE, repeatable). Config: grpc_meta..") + f.BoolVar(&v.Tls, "tls", false, "Enable base TLS encryption. Auto-enabled when api-key or TLS options are set. Env: TEMPORAL_TLS. Config: tls.") + f.StringVar(&v.TlsCertPath, "tls-cert-path", "", "Path to x509 certificate. Env: TEMPORAL_TLS_CLIENT_CERT_PATH. Config: tls.client_cert_path.") + f.StringVar(&v.TlsCertData, "tls-cert-data", "", "Inline x509 certificate data. Env: TEMPORAL_TLS_CLIENT_CERT_DATA. Config: tls.client_cert_data.") + f.StringVar(&v.TlsKeyPath, "tls-key-path", "", "Path to x509 private key. Env: TEMPORAL_TLS_CLIENT_KEY_PATH. Config: tls.client_key_path.") + f.StringVar(&v.TlsKeyData, "tls-key-data", "", "Inline x509 private key data. Env: TEMPORAL_TLS_CLIENT_KEY_DATA. Config: tls.client_key_data.") + f.StringVar(&v.TlsCaPath, "tls-ca-path", "", "Path to server CA certificate. Env: TEMPORAL_TLS_SERVER_CA_CERT_PATH. Config: tls.server_ca_cert_path.") + f.StringVar(&v.TlsCaData, "tls-ca-data", "", "Inline server CA certificate data. Env: TEMPORAL_TLS_SERVER_CA_CERT_DATA. Config: tls.server_ca_cert_data.") + f.BoolVar(&v.TlsDisableHostVerification, "tls-disable-host-verification", false, "Disable TLS host-name verification. Env: TEMPORAL_TLS_DISABLE_HOST_VERIFICATION. Config: tls.disable_host_verification.") + f.StringVar(&v.TlsServerName, "tls-server-name", "", "Override target TLS server name. Env: TEMPORAL_TLS_SERVER_NAME. Config: tls.server_name.") + f.StringVar(&v.CodecEndpoint, "codec-endpoint", "", "Remote Codec Server endpoint. Env: TEMPORAL_CODEC_ENDPOINT. Config: codec.endpoint.") + f.StringVar(&v.CodecAuth, "codec-auth", "", "Authorization header for Codec Server requests. Env: TEMPORAL_CODEC_AUTH. Config: codec.auth.") + f.StringArrayVar(&v.CodecHeader, "codec-header", nil, "HTTP headers for codec server (KEY=VALUE, repeatable).") + f.StringVar(&v.Identity, "identity", "", "Identity of the client submitting requests.") +} + +func (v *ClientOptions) HideFlags() { + if v.FlagSet == nil { + return + } + v.FlagSet.Lookup("address").Hidden = true + v.FlagSet.Lookup("client-authority").Hidden = true + v.FlagSet.Lookup("namespace").Hidden = true + v.FlagSet.Lookup("api-key").Hidden = true + v.FlagSet.Lookup("grpc-meta").Hidden = true + v.FlagSet.Lookup("tls").Hidden = true + v.FlagSet.Lookup("tls-cert-path").Hidden = true + v.FlagSet.Lookup("tls-cert-data").Hidden = true + v.FlagSet.Lookup("tls-key-path").Hidden = true + v.FlagSet.Lookup("tls-key-data").Hidden = true + v.FlagSet.Lookup("tls-ca-path").Hidden = true + v.FlagSet.Lookup("tls-ca-data").Hidden = true + v.FlagSet.Lookup("tls-disable-host-verification").Hidden = true + v.FlagSet.Lookup("tls-server-name").Hidden = true + v.FlagSet.Lookup("codec-endpoint").Hidden = true + v.FlagSet.Lookup("codec-auth").Hidden = true + v.FlagSet.Lookup("codec-header").Hidden = true + v.FlagSet.Lookup("identity").Hidden = true } diff --git a/cliext/go.mod b/cliext/go.mod index 650a65840..10fd66d8f 100644 --- a/cliext/go.mod +++ b/cliext/go.mod @@ -1,38 +1,40 @@ module github.com/temporalio/cli/cliext -go 1.25.0 +go 1.26.4 require ( github.com/BurntSushi/toml v1.4.0 - github.com/mattn/go-isatty v0.0.20 + github.com/mattn/go-isatty v0.0.23 github.com/spf13/pflag v1.0.10 - github.com/stretchr/testify v1.10.0 - go.temporal.io/sdk v1.41.0 + github.com/stretchr/testify v1.11.1 + go.temporal.io/sdk v1.41.1 go.temporal.io/sdk/contrib/envconfig v1.0.0 - golang.org/x/oauth2 v0.34.0 - google.golang.org/grpc v1.79.3 + golang.org/x/oauth2 v0.36.0 + google.golang.org/grpc v1.82.1 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/mock v1.6.0 // indirect + github.com/golang/mock v1.7.0-rc.1 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect github.com/nexus-rpc/sdk-go v0.6.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/robfig/cron v1.2.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect - go.temporal.io/api v1.62.2 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.32.0 // indirect - golang.org/x/time v0.3.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/protobuf v1.36.10 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/stretchr/objx v0.5.3 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.temporal.io/api v1.62.9 // indirect + golang.org/x/net v0.57.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect + golang.org/x/time v0.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/cliext/go.sum b/cliext/go.sum index c29dfcadc..1e2b4d6e7 100644 --- a/cliext/go.sum +++ b/cliext/go.sum @@ -2,8 +2,8 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0 github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -12,59 +12,59 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= +github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/nexus-rpc/sdk-go v0.6.0 h1:QRgnP2zTbxEbiyWG/aXH8uSC5LV/Mg1fqb19jb4DBlo= github.com/nexus-rpc/sdk-go v0.6.0/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= -go.temporal.io/api v1.62.2 h1:jFhIzlqNyJsJZTiCRQmTIMv6OTQ5BZ57z8gbgLGMaoo= -go.temporal.io/api v1.62.2/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= -go.temporal.io/sdk v1.41.0 h1:c9tayCQJDM5ZQdrqjGmjqk5ejxUtsEScJGF94sAVYpM= -go.temporal.io/sdk v1.41.0/go.mod h1:/InXQT5guZ6AizYzpmzr5avQ/GMgq1ZObcKlKE2AhTc= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.temporal.io/api v1.62.9 h1:AUHbS+MPwHpF/TIf1UAv23xHzko4fzORlhfqzSHSPoM= +go.temporal.io/api v1.62.9/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/sdk v1.41.1 h1:yOpvsHyDD1lNuwlGBv/SUodCPhjv9nDeC9lLHW/fJUA= +go.temporal.io/sdk v1.41.1/go.mod h1:/InXQT5guZ6AizYzpmzr5avQ/GMgq1ZObcKlKE2AhTc= go.temporal.io/sdk/contrib/envconfig v1.0.0 h1:1Q/swVgB4EW/p3k7rI9/4hpU4/DC57FSRbU90+UisXw= go.temporal.io/sdk/contrib/envconfig v1.0.0/go.mod h1:Pj4N1lwUEvxap6quBm8GrVMSUMJhSZkVtxjt3AYnPPg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -72,57 +72,58 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529 h1:zUWMZsvo/IJcD1t6MNCPO/azZTwz0TvwCBqr5aifoVY= +google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529/go.mod h1:a5OGAgyRr4lqco7AG9hQM9Fwh0N2ZV4grR0eXFEsXQg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/cliext/option-sets.yaml b/cliext/option-sets.yaml index 1ebad77af..eced3e905 100644 --- a/cliext/option-sets.yaml +++ b/cliext/option-sets.yaml @@ -4,6 +4,11 @@ option-sets: - name: common + description: | + These options apply to every command. They control output formatting, + logging, and which configuration profile and environment to use. + Options that accept an environment variable can be set instead of + passing the flag each time. options: - name: env type: string @@ -12,17 +17,11 @@ option-sets: implied-env: TEMPORAL_ENV - name: env-file type: string - description: | - Path to environment settings file. - Defaults to `$HOME/.config/temporalio/temporal.yaml`. + description: Path to environment settings file. implied-env: TEMPORAL_ENV_FILE - name: config-file type: string - description: | - File path to read TOML config from, defaults to - `$CONFIG_PATH/temporalio/temporal.toml` where `$CONFIG_PATH` is defined - as `$HOME/.config` on Unix, `$HOME/Library/Application Support` on - macOS, and `%AppData%` on Windows. + description: TOML config file path. implied-env: TEMPORAL_CONFIG_FILE - name: profile type: string @@ -30,13 +29,10 @@ option-sets: implied-env: TEMPORAL_PROFILE - name: disable-config-file type: bool - description: | - If set, disables loading environment config from config file. + description: Disable loading config from file. - name: disable-config-env type: bool - description: | - If set, disables loading environment config from environment - variables. + description: Disable loading config from environment variables. - name: log-level type: string-enum enum-values: @@ -89,20 +85,28 @@ option-sets: description: Raw payload output, even if the JSON option was used. - name: command-timeout type: duration - description: | - The command execution timeout. 0s means no timeout. + description: Command execution timeout. - name: client-connect-timeout type: duration - description: | - The client connection timeout. 0s means no timeout. + description: Client connection timeout. - name: client + hide-from-help: true + description: | + These options apply to commands that connect to a Temporal Service + (workflow, activity, schedule, etc). They specify the server address, + namespace, authentication, and TLS settings. Values are resolved in + order: CLI flag > environment variable > config file. + + To persist these settings, use: + temporal config set --prop KEY --value VALUE options: - name: address type: string description: Temporal Service gRPC endpoint. default: localhost:7233 implied-env: TEMPORAL_ADDRESS + config-key: address - name: client-authority type: string description: Temporal gRPC client :authority pseudoheader. @@ -112,83 +116,74 @@ option-sets: description: Temporal Service Namespace. default: default implied-env: TEMPORAL_NAMESPACE + config-key: namespace - name: api-key type: string description: API key for request. implied-env: TEMPORAL_API_KEY + config-key: api_key - name: grpc-meta type: string[] - description: | - HTTP headers for requests. - Format as a `KEY=VALUE` pair. - May be passed multiple times to set multiple headers. - Can also be made available via environment variable as - `TEMPORAL_GRPC_META_[name]`. + description: HTTP headers for requests (KEY=VALUE, repeatable). + config-key: grpc_meta. - name: tls type: bool - description: | - Enable base TLS encryption. Does not have additional options like mTLS - or client certs. This is defaulted to true if api-key or any other TLS - options are present. Use --tls=false to explicitly disable. + description: Enable base TLS encryption. Auto-enabled when api-key or TLS options are set. implied-env: TEMPORAL_TLS + config-key: tls - name: tls-cert-path type: string - description: | - Path to x509 certificate. - Can't be used with --tls-cert-data. + description: Path to x509 certificate. implied-env: TEMPORAL_TLS_CLIENT_CERT_PATH + config-key: tls.client_cert_path - name: tls-cert-data type: string - description: | - Data for x509 certificate. - Can't be used with --tls-cert-path. + description: Inline x509 certificate data. implied-env: TEMPORAL_TLS_CLIENT_CERT_DATA + config-key: tls.client_cert_data - name: tls-key-path type: string - description: | - Path to x509 private key. - Can't be used with --tls-key-data. + description: Path to x509 private key. implied-env: TEMPORAL_TLS_CLIENT_KEY_PATH + config-key: tls.client_key_path - name: tls-key-data type: string - description: | - Private certificate key data. - Can't be used with --tls-key-path. + description: Inline x509 private key data. implied-env: TEMPORAL_TLS_CLIENT_KEY_DATA + config-key: tls.client_key_data - name: tls-ca-path type: string - description: | - Path to server CA certificate. - Can't be used with --tls-ca-data. + description: Path to server CA certificate. implied-env: TEMPORAL_TLS_SERVER_CA_CERT_PATH + config-key: tls.server_ca_cert_path - name: tls-ca-data type: string - description: | - Data for server CA certificate. - Can't be used with --tls-ca-path. + description: Inline server CA certificate data. implied-env: TEMPORAL_TLS_SERVER_CA_CERT_DATA + config-key: tls.server_ca_cert_data - name: tls-disable-host-verification type: bool description: Disable TLS host-name verification. implied-env: TEMPORAL_TLS_DISABLE_HOST_VERIFICATION + config-key: tls.disable_host_verification - name: tls-server-name type: string description: Override target TLS server name. implied-env: TEMPORAL_TLS_SERVER_NAME + config-key: tls.server_name - name: codec-endpoint type: string description: Remote Codec Server endpoint. implied-env: TEMPORAL_CODEC_ENDPOINT + config-key: codec.endpoint - name: codec-auth type: string description: Authorization header for Codec Server requests. implied-env: TEMPORAL_CODEC_AUTH + config-key: codec.auth - name: codec-header type: string[] - description: | - HTTP headers for requests to codec server. - Format as a `KEY=VALUE` pair. - May be passed multiple times to set multiple headers. + description: HTTP headers for codec server (KEY=VALUE, repeatable). - name: identity type: string - description: The identity of the user or client submitting this request. Defaults to "temporal-cli:$USER@$HOST". + description: Identity of the client submitting requests. diff --git a/go.mod b/go.mod index 403b1a09f..2ba9223a9 100644 --- a/go.mod +++ b/go.mod @@ -9,24 +9,24 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.18.0 github.com/google/uuid v1.6.0 - github.com/mattn/go-isatty v0.0.20 + github.com/mattn/go-isatty v0.0.23 github.com/nexus-rpc/sdk-go v0.6.0 github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 - github.com/stretchr/testify v1.11.1 + github.com/stretchr/testify v1.12.0 github.com/temporalio/cli/cliext v0.0.0 github.com/temporalio/ui-server/v2 v2.50.1 - go.temporal.io/api v1.62.8 + go.temporal.io/api v1.62.9 go.temporal.io/sdk v1.41.1 go.temporal.io/sdk/contrib/envconfig v1.0.0 go.temporal.io/server v1.31.2 golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 - golang.org/x/mod v0.35.0 - golang.org/x/term v0.43.0 - golang.org/x/tools v0.44.0 - google.golang.org/grpc v1.79.3 - google.golang.org/protobuf v1.36.10 + golang.org/x/mod v0.38.0 + golang.org/x/term v0.45.0 + golang.org/x/tools v0.48.0 + google.golang.org/grpc v1.82.1 + google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.44.3 ) @@ -46,7 +46,7 @@ require ( cloud.google.com/go/storage v1.56.0 // indirect dario.cat/mergo v1.0.1 // indirect filippo.io/edwards25519 v1.1.1 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect @@ -81,13 +81,13 @@ require ( github.com/cactus/go-statsd-client/v5 v5.1.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect + github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect github.com/coreos/go-oidc/v3 v3.13.0 // indirect github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect @@ -110,8 +110,8 @@ require ( github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect @@ -124,8 +124,8 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/labstack/echo/v4 v4.13.4 // indirect - github.com/labstack/gommon v0.4.2 // indirect + github.com/labstack/echo/v4 v4.15.3 // indirect + github.com/labstack/gommon v0.5.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect @@ -141,7 +141,6 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.21.1 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.63.0 // indirect @@ -156,7 +155,7 @@ require ( github.com/sony/gobreaker v1.0.0 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/objx v0.5.3 // indirect github.com/temporalio/ringpop-go v0.0.0-20250130211428-b97329e994f7 // indirect github.com/temporalio/sqlparser v0.0.0-20231115171017-f4060bcfa6cb // indirect github.com/temporalio/tchannel-go v1.22.1-0.20240528171429-1db37fdea938 // indirect @@ -168,19 +167,19 @@ require ( github.com/valyala/fasttemplate v1.2.2 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.57.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/otel/sdk v1.43.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect - go.opentelemetry.io/proto/otlp v1.7.1 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.temporal.io/auto-scaled-workers v0.0.0-20260407181057-edd947d743d2 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.19.0 // indirect @@ -190,17 +189,17 @@ require ( go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.52.0 // indirect - golang.org/x/net v0.55.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.37.0 // indirect - golang.org/x/time v0.14.0 // indirect + golang.org/x/crypto v0.54.0 // indirect + golang.org/x/net v0.57.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect + golang.org/x/time v0.15.0 // indirect google.golang.org/api v0.256.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/validator.v2 v2.0.1 // indirect diff --git a/go.sum b/go.sum index 1d71eeae3..eeea0ed9b 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg= @@ -118,8 +118,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8= github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -140,12 +140,12 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= -github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= -github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= +github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= +github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= -github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= -github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= @@ -224,10 +224,10 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -271,10 +271,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= -github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= -github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= -github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/labstack/echo/v4 v4.15.3 h1:lIdG4kK5RdMyhwCwSc4AmSQsLBb3AVwok6S8PX/9kwQ= +github.com/labstack/echo/v4 v4.15.3/go.mod h1:Xzp1Ns1RA2c9fY7nSgUJkpkUZGNbEIVHZbtbOMPktBI= +github.com/labstack/gommon v0.5.0 h1:6VSQ2NOzsnEJ5W6+84E0RbcaDDmgB6NIAzWCczTEe6c= +github.com/labstack/gommon v0.5.0/go.mod h1:Rzlg7HHy1maLfzBYGg9NZcVuz1sA68HHhLjhcEllYE0= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -282,8 +282,8 @@ github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4 github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= +github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -375,8 +375,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -385,8 +385,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= +github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= github.com/temporalio/ringpop-go v0.0.0-20250130211428-b97329e994f7 h1:lEebX/hZss+TSH3EBwhztnBavJVj7pWGJOH8UgKHS0w= github.com/temporalio/ringpop-go v0.0.0-20250130211428-b97329e994f7/go.mod h1:RE+CHmY+kOZQk47AQaVzwrGmxpflnLgTd6EOK0853j4= github.com/temporalio/sqlparser v0.0.0-20231115171017-f4060bcfa6cb h1:YzHH/U/dN7vMP+glybzcXRTczTrgfdRisNTzAj7La04= @@ -424,14 +424,14 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/collector/pdata v1.34.0 h1:2vwYftckXe7pWxI9mfSo+tw3wqdGNrYpMbDx/5q6rw8= go.opentelemetry.io/collector/pdata v1.34.0/go.mod h1:StPHMFkhLBellRWrULq0DNjv4znCDJZP6La4UuC+JHI= -go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= -go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= +go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU= +go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= @@ -442,18 +442,18 @@ go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25 go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= -go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= -go.temporal.io/api v1.62.8 h1:g8RAZmdebYODoNa2GLA4M4TsXNe1096WV3n26C4+fdw= -go.temporal.io/api v1.62.8/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.temporal.io/api v1.62.9 h1:AUHbS+MPwHpF/TIf1UAv23xHzko4fzORlhfqzSHSPoM= +go.temporal.io/api v1.62.9/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/auto-scaled-workers v0.0.0-20260407181057-edd947d743d2 h1:1hKeH3GyR6YD6LKMHGCZ76t6h1Sgha0hXVQBxWi3dlQ= go.temporal.io/auto-scaled-workers v0.0.0-20260407181057-edd947d743d2/go.mod h1:T8dnzVPeO+gaUTj9eDgm/lT2lZH4+JXNvrGaQGyVi50= go.temporal.io/sdk v1.41.1 h1:yOpvsHyDD1lNuwlGBv/SUodCPhjv9nDeC9lLHW/fJUA= @@ -492,8 +492,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -513,8 +513,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -526,18 +526,18 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -553,18 +553,17 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -572,10 +571,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -590,30 +589,30 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529 h1:zUWMZsvo/IJcD1t6MNCPO/azZTwz0TvwCBqr5aifoVY= +google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529/go.mod h1:a5OGAgyRr4lqco7AG9hQM9Fwh0N2ZV4grR0eXFEsXQg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/commandsgen/code.go b/internal/commandsgen/code.go index 84cff3bbc..4af7a0add 100644 --- a/internal/commandsgen/code.go +++ b/internal/commandsgen/code.go @@ -143,6 +143,12 @@ func (o *OptionSets) writeCode(w *codeWriter) error { w.writeLinef("FlagSet *%v.FlagSet", w.importPflag()) w.writeLinef("}\n") + // write description if present + if o.Description != "" { + w.writeLinef("func (v *%v) Description() string { return %q }", o.setStructName(), o.Description) + w.writeLinef("") + } + // write flags w.writeLinef("func (v *%v) BuildFlags(f *%v.FlagSet) {", o.setStructName(), w.importPflag()) @@ -150,6 +156,16 @@ func (o *OptionSets) writeCode(w *codeWriter) error { o.writeFlagBuilding("v", "f", w) w.writeLinef("}\n") + // write HideFlags if hide-from-help is set + if o.HideFromHelp { + w.writeLinef("func (v *%v) HideFlags() {", o.setStructName()) + w.writeLinef("if v.FlagSet == nil { return }") + for _, opt := range o.Options { + w.writeLinef("v.FlagSet.Lookup(%q).Hidden = true", opt.Name) + } + w.writeLinef("}\n") + } + return nil } @@ -285,9 +301,15 @@ func (c *Command) writeCode(w *codeWriter) error { if optSet != nil && optSet.ExternalPackage != "" { // External option-set: use type name with Options suffix w.writeLinef("s.%vOptions.BuildFlags(%v)", namify(include, true), flagVar) + if optSet.HideFromHelp { + w.writeLinef("s.%vOptions.HideFlags()", namify(include, true)) + } } else { // Internal option-set: use struct name w.writeLinef("s.%v.BuildFlags(%v)", setStructName(include), flagVar) + if optSet != nil && optSet.HideFromHelp { + w.writeLinef("s.%v.HideFlags()", setStructName(include)) + } } } @@ -432,8 +454,14 @@ func (o *Option) writeFlagBuilding(selfVar, flagVar string, w *codeWriter) error return fmt.Errorf("unrecognized data type %v", o.Type) } - // If there are enums, append to desc + // If there is an implied env var or config key, append to desc desc := o.Description + if o.ImpliedEnv != "" { + desc += fmt.Sprintf(" Env: %s.", o.ImpliedEnv) + } + if o.ConfigKey != "" { + desc += fmt.Sprintf(" Config: %s.", o.ConfigKey) + } if len(o.EnumValues) > 0 { desc += fmt.Sprintf(" Accepted values: %s.", strings.Join(o.EnumValues, ", ")) } diff --git a/internal/commandsgen/parse.go b/internal/commandsgen/parse.go index 831b86902..f3f6efd18 100644 --- a/internal/commandsgen/parse.go +++ b/internal/commandsgen/parse.go @@ -23,6 +23,7 @@ type ( Short string `yaml:"short,omitempty"` Default string `yaml:"default,omitempty"` ImpliedEnv string `yaml:"implied-env,omitempty"` + ConfigKey string `yaml:"config-key,omitempty"` Required bool `yaml:"required,omitempty"` Aliases []string `yaml:"aliases,omitempty"` EnumValues []string `yaml:"enum-values,omitempty"` @@ -64,6 +65,7 @@ type ( Description string `yaml:"description"` Options []Option `yaml:"options"` ExternalPackage string `yaml:"external-package"` + HideFromHelp bool `yaml:"hide-from-help,omitempty"` } // Commands represents the top-level structure holding commands and option sets. diff --git a/internal/temporalcli/commands.activity.go b/internal/temporalcli/commands.activity.go index 582f5d622..19d19d169 100644 --- a/internal/temporalcli/commands.activity.go +++ b/internal/temporalcli/commands.activity.go @@ -643,7 +643,7 @@ func (c *TemporalActivityUpdateOptionsCommand) run(cctx *CommandContext, args [] if c.Command.Flags().Changed("task-queue") { activityOptions.TaskQueue = &taskqueuepb.TaskQueue{Name: c.TaskQueue} - updatePath = append(updatePath, "task_queue_name") + updatePath = append(updatePath, "task_queue.name") } if c.Command.Flags().Changed("schedule-to-close-timeout") { @@ -924,7 +924,6 @@ func (c *TemporalActivityResetCommand) run(cctx *CommandContext, args []string) } else { // batch operation resetActivitiesOperation := &batch.BatchOperationResetActivities{ Identity: c.Parent.Identity, - ResetAttempts: c.ResetAttempts, ResetHeartbeat: c.ResetHeartbeats, KeepPaused: c.KeepPaused, Jitter: durationpb.New(c.Jitter.Duration()), diff --git a/internal/temporalcli/commands.activity_test.go b/internal/temporalcli/commands.activity_test.go index ec6b50a4f..21e693908 100644 --- a/internal/temporalcli/commands.activity_test.go +++ b/internal/temporalcli/commands.activity_test.go @@ -14,6 +14,7 @@ import ( "go.temporal.io/api/history/v1" "go.temporal.io/api/serviceerror" "go.temporal.io/api/workflowservice/v1" + "go.temporal.io/sdk/activity" "go.temporal.io/sdk/client" "go.temporal.io/sdk/converter" "go.temporal.io/sdk/temporal" @@ -1293,36 +1294,116 @@ func (s *SharedServerSuite) TestActivity_SearchAttributes_InvalidKeywordList() { } func (s *SharedServerSuite) TestActivity_List_Pagination() { + runningActivityStarted := make(chan struct{}, 2) + releaseRunningActivities := make(chan struct{}) + var releaseRunningActivitiesOnce sync.Once + releaseRunning := func() { + releaseRunningActivitiesOnce.Do(func() { close(releaseRunningActivities) }) + } + defer releaseRunning() + s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { + if strings.Contains(activity.GetInfo(ctx).ActivityID, "-running-") { + runningActivityStarted <- struct{}{} + select { + case <-releaseRunningActivities: + case <-ctx.Done(): + return nil, ctx.Err() + } + } return "paginated", nil }) uniqueKW := "page-" + uuid.NewString()[:8] - for i := 0; i < 5; i++ { - s.startActivity(fmt.Sprintf("page-test-%d", i), + query := fmt.Sprintf(`CustomKeywordField = "%s"`, uniqueKW) + completedActivityIDs := []string{ + "page-test-completed-0", + "page-test-completed-1", + "page-test-completed-2", + } + for _, activityID := range completedActivityIDs { + started := s.startActivity(activityID, "--search-attribute", fmt.Sprintf(`CustomKeywordField="%s"`, uniqueKW), ) + handle := s.Client.GetActivityHandle(client.GetActivityHandleOptions{ + ActivityID: activityID, + RunID: started["runId"].(string), + }) + s.NoError(handle.Get(s.Context, nil)) } - // Wait for all 5 to be visible + runningActivityIDs := []string{ + "page-test-running-0", + "page-test-running-1", + } + runningActivityHandles := make([]client.ActivityHandle, 0, len(runningActivityIDs)) + for _, activityID := range runningActivityIDs { + started := s.startActivity(activityID, + "--search-attribute", fmt.Sprintf(`CustomKeywordField="%s"`, uniqueKW), + ) + runningActivityHandles = append(runningActivityHandles, s.Client.GetActivityHandle(client.GetActivityHandleOptions{ + ActivityID: activityID, + RunID: started["runId"].(string), + })) + } + runningActivityStartTimeout := time.After(5 * time.Second) + for range runningActivityIDs { + select { + case <-runningActivityStarted: + case <-runningActivityStartTimeout: + s.Fail("running activities did not start within timeout") + return + } + } + + // Wait for visibility to contain the stable mix that pagination will traverse. + visibilityReady := false s.Eventually(func() bool { res := s.Execute( "activity", "list", + "-o", "json", "--address", s.Address(), - "--query", fmt.Sprintf(`CustomKeywordField = "%s"`, uniqueKW), + "--query", query, ) - return res.Err == nil && strings.Count(res.Stdout.String(), "page-test-") >= 5 - }, 5*time.Second, 200*time.Millisecond) + if res.Err != nil { + return false + } + var activities []struct { + Status string `json:"status"` + } + if err := json.Unmarshal(res.Stdout.Bytes(), &activities); err != nil { + return false + } + statusCounts := make(map[string]int) + for _, activity := range activities { + statusCounts[activity.Status]++ + } + visibilityReady = len(activities) == 5 && + statusCounts["ACTIVITY_EXECUTION_STATUS_COMPLETED"] == len(completedActivityIDs) && + statusCounts["ACTIVITY_EXECUTION_STATUS_RUNNING"] == len(runningActivityIDs) + return visibilityReady + }, 10*time.Second, 200*time.Millisecond) + if !visibilityReady { + return + } - // Small page size forces multi-page fetching; verify all 5 appear + // Small page size forces multi-page fetching across both running and completed activities. res := s.Execute( "activity", "list", "--page-size", "2", "--address", s.Address(), - "--query", fmt.Sprintf(`CustomKeywordField = "%s"`, uniqueKW), + "--query", query, ) s.NoError(res.Err) - s.Equal(5, strings.Count(res.Stdout.String(), "page-test-")) + out := res.Stdout.String() + for _, activityID := range completedActivityIDs { + s.ContainsOnSameLine(out, "Completed", activityID) + s.Equal(1, strings.Count(out, activityID)) + } + for _, activityID := range runningActivityIDs { + s.ContainsOnSameLine(out, "Running", activityID) + s.Equal(1, strings.Count(out, activityID)) + } // --limit 3 with page-size 2 should return exactly 3 res = s.Execute( @@ -1330,10 +1411,15 @@ func (s *SharedServerSuite) TestActivity_List_Pagination() { "--page-size", "2", "--limit", "3", "--address", s.Address(), - "--query", fmt.Sprintf(`CustomKeywordField = "%s"`, uniqueKW), + "--query", query, ) s.NoError(res.Err) s.Equal(3, strings.Count(res.Stdout.String(), "page-test-")) + + releaseRunning() + for _, handle := range runningActivityHandles { + s.NoError(handle.Get(s.Context, nil)) + } } func (s *SharedServerSuite) TestActivity_Terminate_DefaultReason_NoUnknownUser() { diff --git a/internal/temporalcli/commands.extension.go b/internal/temporalcli/commands.extension.go index b00900810..702f2f6ac 100644 --- a/internal/temporalcli/commands.extension.go +++ b/internal/temporalcli/commands.extension.go @@ -36,8 +36,20 @@ func (err ExtensionNonZeroExit) Unwrap() error { // tryExecuteExtension tries to execute an extension command if the command is not a built-in command. // It returns an error if the extension command fails, and a boolean indicating whether an extension was executed. func tryExecuteExtension(cctx *CommandContext, tcmd *TemporalCommand) (error, bool) { + // Special commands like "help" and "__complete" should be set aside and delegated to an extension command that matches + // the rest of the given arguments. "temporal help my-extension" should be rewritten to "temporal-my-extension help" + // Some of these commands, like "__complete" used for shell completion, don't actually get registered by Cobra until + // just before they're invoked, so we need to split out the delegatable commands before trying to Find() a matching + // subcommand. + delegatableCommands, nonDelegatedArgs := splitDelegatedCommands(cctx.Options.Args) + + // If a completion command (used for generating the script that invokes "__complete") already exists or has been + // explicitly disabled, this will do nothing, but if neither of those cases are true, we want to make sure + // this command is registered so extensions can't shadow it. + tcmd.Command.InitDefaultCompletionCmd() + // Find the deepest matching built-in command and remaining args. - foundCmd, remainingArgs, findErr := tcmd.Command.Find(cctx.Options.Args) + foundCmd, remainingArgs, findErr := tcmd.Command.Find(nonDelegatedArgs) // Cobra normally adds --help/-h before parsing, but extension dispatch // pre-parses flags before Cobra's execution path runs. We Initialize it so that @@ -58,6 +70,7 @@ func tryExecuteExtension(cctx *CommandContext, tcmd *TemporalCommand) (error, bo // Search for an extension executable. cmdPrefix := strings.Fields(foundCmd.CommandPath()) + extPath, extArgs := lookupExtension(cmdPrefix, extArgs) // Parse CLI args that need validation. @@ -71,6 +84,16 @@ func tryExecuteExtension(cctx *CommandContext, tcmd *TemporalCommand) (error, bo return nil, false } + if len(delegatableCommands) > 0 && isCompletionCommand(delegatableCommands[0]) && len(extArgs) == 0 { + // __complete always expects at least one argument, the last of which is the current subcommand + // or argument to expand, with an empty string matching all possibilities. + // ["temporal", "__complete", "activity"] means this cli should return any subcommands and extensions that + // match "activity", whereas ["temporal", "__complete", "activity", ""] means we should show what's available + // on the activity subcommand. The same logic applies to extension commands, so even if we matched an extension, + // if there are no further args, it's still this cli's responsibility to respond to the completion request. + return nil, false + } + // Apply --command-timeout if set. ctx := cctx.Context if timeout := tcmd.CommandTimeout.Duration(); timeout > 0 { @@ -79,7 +102,9 @@ func tryExecuteExtension(cctx *CommandContext, tcmd *TemporalCommand) (error, bo defer cancel() } - cmd := exec.CommandContext(ctx, extPath, append(cliPassArgs, extArgs...)...) + rebuiltArgs := slices.Concat(delegatableCommands, cliPassArgs, extArgs) + + cmd := exec.CommandContext(ctx, extPath, rebuiltArgs...) cmd.Stdin, cmd.Stdout, cmd.Stderr = cctx.Options.Stdin, cctx.Options.Stdout, cctx.Options.Stderr if err := cmd.Run(); err != nil { if ctx.Err() != nil { @@ -94,6 +119,38 @@ func tryExecuteExtension(cctx *CommandContext, tcmd *TemporalCommand) (error, bo return nil, true } +// splitDelegatedCommands separates out commands that should be delegated to an extension +// from the rest of the args given. These commands are inherently position-dependent, so they're +// only treated specially when they're at the start of the list of arguments. +// +// The resulting slices should be treated as read-only. Do not append to or otherwise mutate them. +func splitDelegatedCommands(args []string) ([]string, []string) { + if len(args) == 0 { + return args, args + } + + if args[0] == "help" { + // "help __complete" never delegates, whatever comes after, so we can just mark "help" as delegatable and see what matches + return args[:1], args[1:] + } + + if isCompletionCommand(args[0]) { + if len(args) > 1 && args[1] == "help" { + // "__complete help" is what happens when a user types "temporal help", so it should delegate both. This allows + // shell completion to display the available help topics available from an extension + return args[:2], args[2:] + } + + return args[:1], args[1:] + } + + return []string{}, args +} + +func isCompletionCommand(arg string) bool { + return arg == cobra.ShellCompRequestCmd || arg == cobra.ShellCompNoDescRequestCmd +} + func groupArgs(foundCmd *cobra.Command, args []string) (cliParseArgs, cliPassArgs, extArgs []string) { seenPos := false for i := 0; i < len(args); i++ { @@ -199,10 +256,9 @@ func lookupExtension(cmdPrefix, extArgs []string) (string, []string) { } // discoverExtensions scans the PATH for executables with the "temporal-" prefix -// and returns their command parts (without the prefix). -func discoverExtensions() [][]string { - var extensions [][]string - seen := make(map[string]bool) +// and returns their commands (without the prefix) mapped to the executable path +func discoverExtensions() map[string]string { + extensions := make(map[string]string) for _, dir := range filepath.SplitList(os.Getenv("PATH")) { if dir == "" { @@ -230,12 +286,11 @@ func discoverExtensions() [][]string { path := extensionBinaryToCommandPath(baseName) key := strings.Join(path, "/") - if seen[key] { + if extensions[key] != "" { continue } - seen[key] = true - extensions = append(extensions, path) + extensions[key] = filepath.Join(dir, entry.Name()) } } return extensions diff --git a/internal/temporalcli/commands.extension_test.go b/internal/temporalcli/commands.extension_test.go index 2590be0cc..5af0f1540 100644 --- a/internal/temporalcli/commands.extension_test.go +++ b/internal/temporalcli/commands.extension_test.go @@ -62,6 +62,30 @@ func TestExtension_PrefersMostSpecificExtension(t *testing.T) { assert.Equal(t, "Args: temporal-foo-bar \n", res.Stdout.String()) } +func TestExtension_CompleteShowsExtensions(t *testing.T) { + h := newExtensionHarness(t) + h.createExtension("temporal-foo", codeEchoArgs) + + res := h.Execute("__complete", "") + assert.Regexp(t, `foo\s+An extension command located at .*[\\/]temporal-foo`, res.Stdout.String()) +} + +func TestExtension_CompleteDoesNotDelegateWithoutAdditionalArgs(t *testing.T) { + h := newExtensionHarness(t) + h.createExtension("temporal-foo", codeEchoArgs) + + res := h.Execute("__complete", "foo") + assert.Regexp(t, `foo\s+An extension command located at .*[\\/]temporal-foo`, res.Stdout.String()) +} + +func TestExtension_InvokesComplete(t *testing.T) { + h := newExtensionHarness(t) + h.createExtension("temporal-foo", codeEchoArgs) + + res := h.Execute("__complete", "foo", "") + assert.Equal(t, "Args: temporal-foo __complete \n", res.Stdout.String()) +} + func TestExtension_ConvertsDashToUnderscoreInLookup(t *testing.T) { h := newExtensionHarness(t) h.createExtension("temporal-foo-bar_baz", codeEchoArgs) @@ -79,6 +103,7 @@ func TestExtension_DoesNotOverrideBuiltinCommand(t *testing.T) { h := newExtensionHarness(t) h.createExtension("temporal-workflow", codeEchoArgs) h.createExtension("temporal-workflow-list", codeEchoArgs) + h.createExtension("temporal-completion", codeEchoArgs) t.Run("root command", func(t *testing.T) { res := h.Execute("workflow", "--help") @@ -102,6 +127,16 @@ func TestExtension_DoesNotOverrideBuiltinCommand(t *testing.T) { assert.NoError(t, res.Err) } }) + + t.Run("__complete (shell completion)", func(t *testing.T) { + res := h.Execute("__complete", "") + assert.NotContains(t, res.Stdout.String(), "temporal-workflow") + }) + + t.Run("completion script generation", func(t *testing.T) { + res := h.Execute("completion", "zsh") + assert.NotContains(t, res.Stdout.String(), "temporal-completion") + }) } func TestExtension_Flags(t *testing.T) { diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index f1456dbdb..669914ddb 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -452,9 +452,9 @@ func NewTemporalCommand(cctx *CommandContext) *TemporalCommand { s.Command.Use = "temporal" s.Command.Short = "Temporal command-line interface and development server" if hasHighlighting { - s.Command.Long = "The Temporal CLI manages, monitors, and debugs Temporal apps. It lets you run\na local Temporal Service, start Workflow Executions, pass messages to running\nWorkflows, inspect state, and more.\n\n* Start a local development service:\n \x1b[1mtemporal server start-dev\x1b[0m\n* View help: pass \x1b[1m--help\x1b[0m to any command:\n \x1b[1mtemporal activity complete --help\x1b[0m" + s.Command.Long = "The Temporal CLI manages, monitors, and debugs Temporal apps. It lets you run\na local Temporal Service, start Workflow Executions, pass messages to running\nWorkflows, inspect state, and more.\n\n* Start a local development service:\n \x1b[1mtemporal server start-dev\x1b[0m\n* View help: pass \x1b[1m--help\x1b[0m to any command:\n \x1b[1mtemporal activity complete --help\x1b[0m\n* Return structured output with \x1b[1m-o\x1b[0m:\n \x1b[1mtemporal workflow list -o json\x1b[0m\n* View global and connection options:\n \x1b[1mtemporal options\x1b[0m" } else { - s.Command.Long = "The Temporal CLI manages, monitors, and debugs Temporal apps. It lets you run\na local Temporal Service, start Workflow Executions, pass messages to running\nWorkflows, inspect state, and more.\n\n* Start a local development service:\n `temporal server start-dev`\n* View help: pass `--help` to any command:\n `temporal activity complete --help`" + s.Command.Long = "The Temporal CLI manages, monitors, and debugs Temporal apps. It lets you run\na local Temporal Service, start Workflow Executions, pass messages to running\nWorkflows, inspect state, and more.\n\n* Start a local development service:\n `temporal server start-dev`\n* View help: pass `--help` to any command:\n `temporal activity complete --help`\n* Return structured output with `-o`:\n `temporal workflow list -o json`\n* View global and connection options:\n `temporal options`" } s.Command.Args = cobra.NoArgs s.Command.AddCommand(&NewTemporalActivityCommand(cctx, &s).Command) @@ -500,6 +500,7 @@ func NewTemporalActivityCommand(cctx *CommandContext, parent *TemporalCommand) * s.Command.AddCommand(&NewTemporalActivityUnpauseCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalActivityUpdateOptionsCommand(cctx, &s).Command) s.ClientOptions.BuildFlags(s.Command.PersistentFlags()) + s.ClientOptions.HideFlags() return &s } @@ -758,7 +759,6 @@ type TemporalActivityResetCommand struct { SingleActivityOrBatchOptions ActivityId string KeepPaused bool - ResetAttempts bool ResetHeartbeats bool Jitter cliext.FlagDuration RestoreOriginalOptions bool @@ -771,14 +771,13 @@ func NewTemporalActivityResetCommand(cctx *CommandContext, parent *TemporalActiv s.Command.Use = "reset [flags]" s.Command.Short = "Reset an Activity" if hasHighlighting { - s.Command.Long = "Reset an activity. Not supported for Standalone Activities.\nThis restarts the activity as if it were first being\nscheduled. That is, it will reset both the number of attempts and the\nactivity timeout, as well as, optionally, the\nheartbeat details.\n\nIf the activity may be executing (i.e. it has not yet timed out), the\nreset will take effect the next time it fails, heartbeats, or times out.\nIf is waiting for a retry (i.e. has failed or timed out), the reset\nwill apply immediately.\n\nIf the activity is already paused, it will be unpaused by default.\nYou can specify \x1b[1mkeep_paused\x1b[0m to prevent this.\n\nIf the activity is paused and the \x1b[1mkeep_paused\x1b[0m flag is not provided,\nit will be unpaused. If the activity is paused and \x1b[1mkeep_paused\x1b[0m flag\nis provided - it will stay paused.\n\nEither \x1b[1m--activity-id\x1b[0m (with \x1b[1m--workflow-id\x1b[0m) or \x1b[1m--query\x1b[0m must be specified.\n\n### Resetting activities that heartbeat {#reset-heartbeats}\n\nActivities that heartbeat will receive a Canceled failure\nthe next time they heartbeat after a reset.\n\nIf, in your Activity, you need to do any cleanup when an Activity is\nreset, handle this error and then re-throw it when you've cleaned up.\n\nIf the \x1b[1mreset_heartbeats\x1b[0m flag is set, the heartbeat details will also be cleared.\n\nSpecify the Activity and Workflow IDs:\n\n\x1b[1mtemporal activity reset \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n --keep-paused\n --reset-heartbeats\x1b[0m\n\nActivities can be reset in bulk with a visibility query list filter:\n\n\x1b[1mtemporal activity reset \\\n --query 'WorkflowType=\"YourWorkflow\"'\x1b[0m" + s.Command.Long = "Reset an activity. Not supported for Standalone Activities.\nThis restarts the activity as if it were first being scheduled: the\nattempt count returns to one, its per-attempt timeouts are re-armed, and\nits heartbeat details are cleared.\n\nIf the activity may be executing (i.e. it has not yet timed out), the\nreset will take effect the next time it fails, heartbeats, or times out.\nIf is waiting for a retry (i.e. has failed or timed out), the reset\nwill apply immediately.\n\nIf the activity is already paused, it will be unpaused by default.\nYou can specify \x1b[1mkeep_paused\x1b[0m to prevent this.\n\nIf the activity is paused and the \x1b[1mkeep_paused\x1b[0m flag is not provided,\nit will be unpaused. If the activity is paused and \x1b[1mkeep_paused\x1b[0m flag\nis provided - it will stay paused.\n\nEither \x1b[1m--activity-id\x1b[0m (with \x1b[1m--workflow-id\x1b[0m) or \x1b[1m--query\x1b[0m must be specified.\n\n### Resetting activities that heartbeat {#reset-heartbeats}\n\nActivities that heartbeat will receive a Canceled failure\nthe next time they heartbeat after a reset.\n\nIf, in your Activity, you need to do any cleanup when an Activity is\nreset, handle this error and then re-throw it when you've cleaned up.\n\nIf the \x1b[1mreset_heartbeats\x1b[0m flag is set, the heartbeat details will also be cleared.\n\nSpecify the Activity and Workflow IDs:\n\n\x1b[1mtemporal activity reset \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n --keep-paused\n --reset-heartbeats\x1b[0m\n\nActivities can be reset in bulk with a visibility query list filter:\n\n\x1b[1mtemporal activity reset \\\n --query 'WorkflowType=\"YourWorkflow\"'\x1b[0m" } else { - s.Command.Long = "Reset an activity. Not supported for Standalone Activities.\nThis restarts the activity as if it were first being\nscheduled. That is, it will reset both the number of attempts and the\nactivity timeout, as well as, optionally, the\nheartbeat details.\n\nIf the activity may be executing (i.e. it has not yet timed out), the\nreset will take effect the next time it fails, heartbeats, or times out.\nIf is waiting for a retry (i.e. has failed or timed out), the reset\nwill apply immediately.\n\nIf the activity is already paused, it will be unpaused by default.\nYou can specify `keep_paused` to prevent this.\n\nIf the activity is paused and the `keep_paused` flag is not provided,\nit will be unpaused. If the activity is paused and `keep_paused` flag\nis provided - it will stay paused.\n\nEither `--activity-id` (with `--workflow-id`) or `--query` must be specified.\n\n### Resetting activities that heartbeat {#reset-heartbeats}\n\nActivities that heartbeat will receive a Canceled failure\nthe next time they heartbeat after a reset.\n\nIf, in your Activity, you need to do any cleanup when an Activity is\nreset, handle this error and then re-throw it when you've cleaned up.\n\nIf the `reset_heartbeats` flag is set, the heartbeat details will also be cleared.\n\nSpecify the Activity and Workflow IDs:\n\n```\ntemporal activity reset \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n --keep-paused\n --reset-heartbeats\n```\n\nActivities can be reset in bulk with a visibility query list filter:\n\n```\ntemporal activity reset \\\n --query 'WorkflowType=\"YourWorkflow\"'\n```" + s.Command.Long = "Reset an activity. Not supported for Standalone Activities.\nThis restarts the activity as if it were first being scheduled: the\nattempt count returns to one, its per-attempt timeouts are re-armed, and\nits heartbeat details are cleared.\n\nIf the activity may be executing (i.e. it has not yet timed out), the\nreset will take effect the next time it fails, heartbeats, or times out.\nIf is waiting for a retry (i.e. has failed or timed out), the reset\nwill apply immediately.\n\nIf the activity is already paused, it will be unpaused by default.\nYou can specify `keep_paused` to prevent this.\n\nIf the activity is paused and the `keep_paused` flag is not provided,\nit will be unpaused. If the activity is paused and `keep_paused` flag\nis provided - it will stay paused.\n\nEither `--activity-id` (with `--workflow-id`) or `--query` must be specified.\n\n### Resetting activities that heartbeat {#reset-heartbeats}\n\nActivities that heartbeat will receive a Canceled failure\nthe next time they heartbeat after a reset.\n\nIf, in your Activity, you need to do any cleanup when an Activity is\nreset, handle this error and then re-throw it when you've cleaned up.\n\nIf the `reset_heartbeats` flag is set, the heartbeat details will also be cleared.\n\nSpecify the Activity and Workflow IDs:\n\n```\ntemporal activity reset \\\n --activity-id YourActivityId \\\n --workflow-id YourWorkflowId\n --keep-paused\n --reset-heartbeats\n```\n\nActivities can be reset in bulk with a visibility query list filter:\n\n```\ntemporal activity reset \\\n --query 'WorkflowType=\"YourWorkflow\"'\n```" } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVarP(&s.ActivityId, "activity-id", "a", "", "The Activity ID to reset. Mutually exclusive with `--query`. Requires `--workflow-id` to be specified.") s.Command.Flags().BoolVar(&s.KeepPaused, "keep-paused", false, "If the activity was paused, it will stay paused.") - s.Command.Flags().BoolVar(&s.ResetAttempts, "reset-attempts", false, "Reset the activity attempts.") s.Command.Flags().BoolVar(&s.ResetHeartbeats, "reset-heartbeats", false, "Reset the Activity's heartbeats.") s.Jitter = 0 s.Command.Flags().Var(&s.Jitter, "jitter", "The activity will reset at random a time within the specified duration. Can only be used with --query.") @@ -989,6 +988,7 @@ func NewTemporalBatchCommand(cctx *CommandContext, parent *TemporalCommand) *Tem s.Command.AddCommand(&NewTemporalBatchListCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalBatchTerminateCommand(cctx, &s).Command) s.ClientOptions.BuildFlags(s.Command.PersistentFlags()) + s.ClientOptions.HideFlags() return &s } @@ -1391,6 +1391,7 @@ func NewTemporalOperatorCommand(cctx *CommandContext, parent *TemporalCommand) * s.Command.AddCommand(&NewTemporalOperatorNexusCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalOperatorSearchAttributeCommand(cctx, &s).Command) s.ClientOptions.BuildFlags(s.Command.PersistentFlags()) + s.ClientOptions.HideFlags() return &s } @@ -2108,6 +2109,7 @@ func NewTemporalScheduleCommand(cctx *CommandContext, parent *TemporalCommand) * s.Command.AddCommand(&NewTemporalScheduleTriggerCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalScheduleUpdateCommand(cctx, &s).Command) s.ClientOptions.BuildFlags(s.Command.PersistentFlags()) + s.ClientOptions.HideFlags() return &s } @@ -2508,6 +2510,7 @@ func NewTemporalTaskQueueCommand(cctx *CommandContext, parent *TemporalCommand) s.Command.AddCommand(&NewTemporalTaskQueueUpdateBuildIdsCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalTaskQueueVersioningCommand(cctx, &s).Command) s.ClientOptions.BuildFlags(s.Command.PersistentFlags()) + s.ClientOptions.HideFlags() return &s } @@ -3239,6 +3242,7 @@ func NewTemporalWorkerCommand(cctx *CommandContext, parent *TemporalCommand) *Te s.Command.AddCommand(&NewTemporalWorkerDescribeCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkerListCommand(cctx, &s).Command) s.ClientOptions.BuildFlags(s.Command.PersistentFlags()) + s.ClientOptions.HideFlags() return &s } @@ -3304,17 +3308,23 @@ type TemporalWorkerDeploymentCreateVersionCommand struct { Parent *TemporalWorkerDeploymentCommand Command cobra.Command DeploymentVersionOptions - AwsLambdaFunctionArn string - AwsLambdaAssumeRoleArn string - AwsLambdaAssumeRoleExternalId string - GcpCloudRunProject string - GcpCloudRunRegion string - GcpCloudRunWorkerPool string - GcpCloudRunServiceAccount string - GcpCloudRunMinInstances int - GcpCloudRunMaxInstances int - GcpCloudRunInitialInstances int - GcpCloudRunUtilizationTarget float32 + AwsLambdaFunctionArn string + AwsLambdaAssumeRoleArn string + AwsLambdaAssumeRoleExternalId string + AwsLambdaSkipRoleAndExternalId bool + AwsAgentcoreEndpointArn string + AwsAgentcoreAssumeRoleArn string + AwsAgentcoreAssumeRoleExternalId string + AwsAgentcoreSkipRoleAndExternalId bool + GcpCloudRunProject string + GcpCloudRunRegion string + GcpCloudRunWorkerPool string + GcpCloudRunServiceAccount string + GcpCloudRunMinInstances int + GcpCloudRunMaxInstances int + GcpCloudRunInitialInstances int + GcpCloudRunUtilizationTarget float32 + GcpCloudRunScaleDownStabilizationDuration cliext.FlagDuration } func NewTemporalWorkerDeploymentCreateVersionCommand(cctx *CommandContext, parent *TemporalWorkerDeploymentCommand) *TemporalWorkerDeploymentCreateVersionCommand { @@ -3324,22 +3334,29 @@ func NewTemporalWorkerDeploymentCreateVersionCommand(cctx *CommandContext, paren s.Command.Use = "create-version [flags]" s.Command.Short = "Create a new Worker Deployment Version" if hasHighlighting { - s.Command.Long = "\nCreate a new Worker Deployment Version:\n\n\x1b[1mtemporal worker deployment create-version [options]\x1b[0m\n\nConfigure a Worker Deployment Version's compute configuration as needed.\nFor example, pass compute provider information for an AWS Lambda function\nthat spawns a Worker in the Worker Deployment:\n\n\x1b[1mtemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-lambda-function-arn LambdaFunctionARN \\\n --aws-lambda-assume-role-arn LambdaAssumeRoleARN \\\n --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID\x1b[0m\n\nOr pass compute provider information for a GCP Cloud Run worker pool\nthat spawns a Worker in the Worker Deployment:\n\n\x1b[1mtemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool YourWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75\x1b[0m\n\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\n\nReturns an error if all compute configuration fields are empty.\n\nNote: This is an experimental feature and may change in the future." + s.Command.Long = "\nCreate a new Worker Deployment Version:\n\n\x1b[1mtemporal worker deployment create-version [options]\x1b[0m\n\nConfigure a Worker Deployment Version's compute configuration as needed.\nFor example, pass compute provider information for an AWS Lambda function\nthat spawns a Worker in the Worker Deployment:\n\n\x1b[1mtemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-lambda-function-arn LambdaFunctionARN \\\n --aws-lambda-assume-role-arn LambdaAssumeRoleARN \\\n --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID\x1b[0m\n\nOr pass compute provider information for an AWS Bedrock Agentcore Runtime\nthat spawns a Worker in the Worker Deployment:\n\n\x1b[1mtemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-agentcore-endpoint-arn AgentcoreRuntimeEndpointARN \\\n --aws-agentcore-assume-role-arn AgentcoreAssumeRoleARN \\\n --aws-agentcore-assume-role-external-id AgentcoreAssumeRoleExternalID\x1b[0m\n\nOr pass compute provider information for a GCP Cloud Run worker pool\nthat spawns a Worker in the Worker Deployment:\n\n\x1b[1mtemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool YourWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75 \\\n --gcp-cloud-run-scale-down-stabilization-duration 5m\x1b[0m\n\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\n\nReturns an error if all compute configuration fields are empty.\n\nNote: This is an experimental feature and may change in the future." } else { - s.Command.Long = "\nCreate a new Worker Deployment Version:\n\n```\ntemporal worker deployment create-version [options]\n```\n\nConfigure a Worker Deployment Version's compute configuration as needed.\nFor example, pass compute provider information for an AWS Lambda function\nthat spawns a Worker in the Worker Deployment:\n\n```\ntemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-lambda-function-arn LambdaFunctionARN \\\n --aws-lambda-assume-role-arn LambdaAssumeRoleARN \\\n --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID\n```\n\nOr pass compute provider information for a GCP Cloud Run worker pool\nthat spawns a Worker in the Worker Deployment:\n\n```\ntemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool YourWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75\n```\n\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\n\nReturns an error if all compute configuration fields are empty.\n\nNote: This is an experimental feature and may change in the future." + s.Command.Long = "\nCreate a new Worker Deployment Version:\n\n```\ntemporal worker deployment create-version [options]\n```\n\nConfigure a Worker Deployment Version's compute configuration as needed.\nFor example, pass compute provider information for an AWS Lambda function\nthat spawns a Worker in the Worker Deployment:\n\n```\ntemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-lambda-function-arn LambdaFunctionARN \\\n --aws-lambda-assume-role-arn LambdaAssumeRoleARN \\\n --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID\n```\n\nOr pass compute provider information for an AWS Bedrock Agentcore Runtime\nthat spawns a Worker in the Worker Deployment:\n\n```\ntemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --aws-agentcore-endpoint-arn AgentcoreRuntimeEndpointARN \\\n --aws-agentcore-assume-role-arn AgentcoreAssumeRoleARN \\\n --aws-agentcore-assume-role-external-id AgentcoreAssumeRoleExternalID\n```\n\nOr pass compute provider information for a GCP Cloud Run worker pool\nthat spawns a Worker in the Worker Deployment:\n\n```\ntemporal worker deployment create-version \\\n --namespace YourNamespaceName \\\n --deployment-name YourDeploymentName \\\n --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool YourWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75 \\\n --gcp-cloud-run-scale-down-stabilization-duration 5m\n```\n\nIf a Worker Deployment Version with the supplied BuildID already exists,\nthis command will return an error.\n\nReturns an error if all compute configuration fields are empty.\n\nNote: This is an experimental feature and may change in the future." } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVar(&s.AwsLambdaFunctionArn, "aws-lambda-function-arn", "", "Qualified (contains version suffix) or unqualified AWS Lambda function ARN to invoke when there are no active pollers for task queue targets in the Worker Deployment.") - s.Command.Flags().StringVar(&s.AwsLambdaAssumeRoleArn, "aws-lambda-assume-role-arn", "", "AWS IAM role ARN that the Temporal server will assume when invoking the Lambda function that spawns a new Worker in this Worker Deployment Version. Required when --aws-lambda-function-arn is specified.") - s.Command.Flags().StringVar(&s.AwsLambdaAssumeRoleExternalId, "aws-lambda-assume-role-external-id", "", "Temporal server will enforce that the AWS IAM trust policy associated with the AWS IAM role specified in --aws-lambda-assume-role-arn has an aws:ExternalId condition that matches the supplied value. Required when --aws-lambda-function-arn is specified.") + s.Command.Flags().StringVar(&s.AwsLambdaAssumeRoleArn, "aws-lambda-assume-role-arn", "", "AWS IAM role ARN that the Temporal server will assume when invoking the Lambda function that spawns a new Worker in this Worker Deployment Version. Required when --aws-lambda-function-arn is specified, and must be omitted when --aws-lambda-skip-role-and-external-id is passed.") + s.Command.Flags().StringVar(&s.AwsLambdaAssumeRoleExternalId, "aws-lambda-assume-role-external-id", "", "Temporal server will enforce that the AWS IAM trust policy associated with the AWS IAM role specified in --aws-lambda-assume-role-arn has an aws:ExternalId condition that matches the supplied value. Required when --aws-lambda-function-arn is specified, and must be omitted when --aws-lambda-skip-role-and-external-id is passed.") + s.Command.Flags().BoolVar(&s.AwsLambdaSkipRoleAndExternalId, "aws-lambda-skip-role-and-external-id", false, "When --aws-lambda-function-arn is specified, --aws-lambda-assume-role-arn and --aws-lambda-assume-role-external-id are required unless this flag is passed, in which case both must be omitted.") + s.Command.Flags().StringVar(&s.AwsAgentcoreEndpointArn, "aws-agentcore-endpoint-arn", "", "AWS Bedrock Agentcore Runtime endpoint ARN to invoke when there are no active pollers for task queue targets in the Worker Deployment. The endpoint ARN encodes the runtime, endpoint name, and region.") + s.Command.Flags().StringVar(&s.AwsAgentcoreAssumeRoleArn, "aws-agentcore-assume-role-arn", "", "AWS IAM role ARN that the Temporal server will assume when invoking the Agentcore Runtime that spawns a new Worker in this Worker Deployment Version. Required when --aws-agentcore-endpoint-arn is specified, and must be omitted when --aws-agentcore-skip-role-and-external-id is passed.") + s.Command.Flags().StringVar(&s.AwsAgentcoreAssumeRoleExternalId, "aws-agentcore-assume-role-external-id", "", "Temporal server will enforce that the AWS IAM trust policy associated with the AWS IAM role specified in --aws-agentcore-assume-role-arn has an aws:ExternalId condition that matches the supplied value. Required when --aws-agentcore-endpoint-arn is specified, and must be omitted when --aws-agentcore-skip-role-and-external-id is passed.") + s.Command.Flags().BoolVar(&s.AwsAgentcoreSkipRoleAndExternalId, "aws-agentcore-skip-role-and-external-id", false, "When --aws-agentcore-endpoint-arn is specified, --aws-agentcore-assume-role-arn and --aws-agentcore-assume-role-external-id are required unless this flag is passed, in which case both must be omitted.") s.Command.Flags().StringVar(&s.GcpCloudRunProject, "gcp-cloud-run-project", "", "GCP project ID hosting the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") s.Command.Flags().StringVar(&s.GcpCloudRunRegion, "gcp-cloud-run-region", "", "Region of the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") s.Command.Flags().StringVar(&s.GcpCloudRunWorkerPool, "gcp-cloud-run-worker-pool", "", "GCP Cloud Run worker pool name to scale when there are no active pollers for task queue targets in the Worker Deployment.") s.Command.Flags().StringVar(&s.GcpCloudRunServiceAccount, "gcp-cloud-run-service-account", "", "Customer GCP service account the Temporal server impersonates to manage the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") - s.Command.Flags().IntVar(&s.GcpCloudRunMinInstances, "gcp-cloud-run-min-instances", 0, "Minimum number of Cloud Run worker pool instances the scaler will maintain. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together. Defaults to 0 when unset. Only valid with --gcp-cloud-run-worker-pool.") - s.Command.Flags().IntVar(&s.GcpCloudRunMaxInstances, "gcp-cloud-run-max-instances", 0, "Maximum number of Cloud Run worker pool instances the scaler may scale up to. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together. Defaults to 30 when unset. Only valid with --gcp-cloud-run-worker-pool.") - s.Command.Flags().IntVar(&s.GcpCloudRunInitialInstances, "gcp-cloud-run-initial-instances", 0, "Number of Cloud Run worker pool instances the scaler starts with. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together, and this value must be between the min and max (inclusive). Defaults to 0 when unset. Only valid with --gcp-cloud-run-worker-pool.") - s.Command.Flags().Float32Var(&s.GcpCloudRunUtilizationTarget, "gcp-cloud-run-utilization-target", 0, "Target average worker utilization the scaler aims for, as a fraction in the range (0, 1]. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together. Lower values keep more spare capacity per worker. Defaults to 0.8 when unset. Only valid with --gcp-cloud-run-worker-pool.") + s.Command.Flags().IntVar(&s.GcpCloudRunMinInstances, "gcp-cloud-run-min-instances", 0, "Minimum number of Cloud Run worker pool instances the scaler will maintain. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-stabilization-duration must all be set together. Defaults to 0 when unset. Only valid with --gcp-cloud-run-worker-pool.") + s.Command.Flags().IntVar(&s.GcpCloudRunMaxInstances, "gcp-cloud-run-max-instances", 0, "Maximum number of Cloud Run worker pool instances the scaler may scale up to. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-stabilization-duration must all be set together. Defaults to 30 when unset. Only valid with --gcp-cloud-run-worker-pool.") + s.Command.Flags().IntVar(&s.GcpCloudRunInitialInstances, "gcp-cloud-run-initial-instances", 0, "Number of Cloud Run worker pool instances the scaler starts with. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-stabilization-duration must all be set together, and this value must be between the min and max (inclusive). Defaults to 0 when unset. Only valid with --gcp-cloud-run-worker-pool.") + s.Command.Flags().Float32Var(&s.GcpCloudRunUtilizationTarget, "gcp-cloud-run-utilization-target", 0, "Target average worker utilization the scaler aims for, as a fraction in the range (0, 1]. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-stabilization-duration must all be set together. Lower values keep more spare capacity per worker. Defaults to 0.8 when unset. Only valid with --gcp-cloud-run-worker-pool.") + s.GcpCloudRunScaleDownStabilizationDuration = 0 + s.Command.Flags().Var(&s.GcpCloudRunScaleDownStabilizationDuration, "gcp-cloud-run-scale-down-stabilization-duration", "Duration the scaler waits after it last saw unmet task demand before it may scale the Cloud Run worker pool down. Raise this to keep the pool from scaling down before long-running or bursty activities finish. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-stabilization-duration must all be set together. A value of 0s disables the wait. Defaults to 90s when unset. Only valid with --gcp-cloud-run-worker-pool.") s.DeploymentVersionOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { @@ -3643,18 +3660,24 @@ type TemporalWorkerDeploymentUpdateVersionComputeConfigCommand struct { Parent *TemporalWorkerDeploymentCommand Command cobra.Command DeploymentVersionOptions - AwsLambdaFunctionArn string - AwsLambdaAssumeRoleArn string - AwsLambdaAssumeRoleExternalId string - GcpCloudRunProject string - GcpCloudRunRegion string - GcpCloudRunWorkerPool string - GcpCloudRunServiceAccount string - GcpCloudRunMinInstances int - GcpCloudRunMaxInstances int - GcpCloudRunInitialInstances int - GcpCloudRunUtilizationTarget float32 - Remove bool + AwsLambdaFunctionArn string + AwsLambdaAssumeRoleArn string + AwsLambdaAssumeRoleExternalId string + AwsLambdaSkipRoleAndExternalId bool + AwsAgentcoreEndpointArn string + AwsAgentcoreAssumeRoleArn string + AwsAgentcoreAssumeRoleExternalId string + AwsAgentcoreSkipRoleAndExternalId bool + GcpCloudRunProject string + GcpCloudRunRegion string + GcpCloudRunWorkerPool string + GcpCloudRunServiceAccount string + GcpCloudRunMinInstances int + GcpCloudRunMaxInstances int + GcpCloudRunInitialInstances int + GcpCloudRunUtilizationTarget float32 + GcpCloudRunScaleDownStabilizationDuration cliext.FlagDuration + Remove bool } func NewTemporalWorkerDeploymentUpdateVersionComputeConfigCommand(cctx *CommandContext, parent *TemporalWorkerDeploymentCommand) *TemporalWorkerDeploymentUpdateVersionComputeConfigCommand { @@ -3664,22 +3687,29 @@ func NewTemporalWorkerDeploymentUpdateVersionComputeConfigCommand(cctx *CommandC s.Command.Use = "update-version-compute-config [flags]" s.Command.Short = "Update compute configuration for a Version" if hasHighlighting { - s.Command.Long = "Update compute configuration associated with a Worker Deployment\nVersion.\n\nFor example, to update the AWS Lambda function ARN associated with an\nexisting Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-function-arn UpdatedLambdaFunctionARN\x1b[0m\n\nTo update the AWS IAM role ARN that is assumed by the serverless worker\nmanager associated with an existing Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-assume-role-arn UpdatedRoleARN\x1b[0m\n\nTo update the GCP Cloud Run worker pool associated with an existing\nWorker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool UpdatedWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75\x1b[0m\n\nTo update only the scaling settings on an existing GCP Cloud Run Worker\nDeployment Version, supply the four scaler flags without the provider\nfields (all four must be set together):\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75\x1b[0m\n\nProvider fields are only required when changing the compute provider.\nSwitching the provider resets the scaling settings for the new provider.\n\nIf --remove is specified, the compute configuration for the Worker\nDeployment Version will be removed:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --remove\x1b[0m\n\nIf a Worker Deployment Version with the supplied BuildID does not exist,\nthis command will return an error.\n\nNote: This is an experimental feature and may change in the future." + s.Command.Long = "Update compute configuration associated with a Worker Deployment\nVersion.\n\nFor example, to update the AWS Lambda function ARN associated with an\nexisting Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-function-arn UpdatedLambdaFunctionARN\x1b[0m\n\nTo update the AWS IAM role ARN that is assumed by the serverless worker\nmanager associated with an existing Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-assume-role-arn UpdatedRoleARN\x1b[0m\n\nTo update the AWS Bedrock Agentcore Runtime endpoint associated with an\nexisting Worker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-agentcore-endpoint-arn UpdatedAgentcoreRuntimeEndpointARN \\\n --aws-agentcore-assume-role-arn UpdatedRoleARN \\\n --aws-agentcore-assume-role-external-id UpdatedExternalID\x1b[0m\n\nTo update the GCP Cloud Run worker pool associated with an existing\nWorker Deployment Version:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool UpdatedWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75 \\\n --gcp-cloud-run-scale-down-stabilization-duration 5m\x1b[0m\n\nTo update only the scaling settings on an existing GCP Cloud Run Worker\nDeployment Version, supply the five scaler flags without the provider\nfields (all five must be set together):\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75 \\\n --gcp-cloud-run-scale-down-stabilization-duration 5m\x1b[0m\n\nProvider fields are only required when changing the compute provider.\nSwitching the provider resets the scaling settings for the new provider.\n\nIf --remove is specified, the compute configuration for the Worker\nDeployment Version will be removed:\n\n\x1b[1m temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --remove\x1b[0m\n\nIf a Worker Deployment Version with the supplied BuildID does not exist,\nthis command will return an error.\n\nNote: This is an experimental feature and may change in the future." } else { - s.Command.Long = "Update compute configuration associated with a Worker Deployment\nVersion.\n\nFor example, to update the AWS Lambda function ARN associated with an\nexisting Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-function-arn UpdatedLambdaFunctionARN\n```\n\nTo update the AWS IAM role ARN that is assumed by the serverless worker\nmanager associated with an existing Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-assume-role-arn UpdatedRoleARN\n```\n\nTo update the GCP Cloud Run worker pool associated with an existing\nWorker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool UpdatedWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75\n```\n\nTo update only the scaling settings on an existing GCP Cloud Run Worker\nDeployment Version, supply the four scaler flags without the provider\nfields (all four must be set together):\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75\n```\n\nProvider fields are only required when changing the compute provider.\nSwitching the provider resets the scaling settings for the new provider.\n\nIf --remove is specified, the compute configuration for the Worker\nDeployment Version will be removed:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --remove\n```\n\nIf a Worker Deployment Version with the supplied BuildID does not exist,\nthis command will return an error.\n\nNote: This is an experimental feature and may change in the future." + s.Command.Long = "Update compute configuration associated with a Worker Deployment\nVersion.\n\nFor example, to update the AWS Lambda function ARN associated with an\nexisting Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-function-arn UpdatedLambdaFunctionARN\n```\n\nTo update the AWS IAM role ARN that is assumed by the serverless worker\nmanager associated with an existing Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-lambda-assume-role-arn UpdatedRoleARN\n```\n\nTo update the AWS Bedrock Agentcore Runtime endpoint associated with an\nexisting Worker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --aws-agentcore-endpoint-arn UpdatedAgentcoreRuntimeEndpointARN \\\n --aws-agentcore-assume-role-arn UpdatedRoleARN \\\n --aws-agentcore-assume-role-external-id UpdatedExternalID\n```\n\nTo update the GCP Cloud Run worker pool associated with an existing\nWorker Deployment Version:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-project YourGCPProject \\\n --gcp-cloud-run-region us-central1 \\\n --gcp-cloud-run-worker-pool UpdatedWorkerPool \\\n --gcp-cloud-run-service-account customer-sa@proj.iam.gserviceaccount.com \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75 \\\n --gcp-cloud-run-scale-down-stabilization-duration 5m\n```\n\nTo update only the scaling settings on an existing GCP Cloud Run Worker\nDeployment Version, supply the five scaler flags without the provider\nfields (all five must be set together):\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --gcp-cloud-run-min-instances 1 \\\n --gcp-cloud-run-max-instances 3 \\\n --gcp-cloud-run-initial-instances 1 \\\n --gcp-cloud-run-utilization-target 0.75 \\\n --gcp-cloud-run-scale-down-stabilization-duration 5m\n```\n\nProvider fields are only required when changing the compute provider.\nSwitching the provider resets the scaling settings for the new provider.\n\nIf --remove is specified, the compute configuration for the Worker\nDeployment Version will be removed:\n\n```\n temporal worker deployment update-version-compute-config \\\n --deployment-name YourDeploymentName --build-id YourBuildID \\\n --remove\n```\n\nIf a Worker Deployment Version with the supplied BuildID does not exist,\nthis command will return an error.\n\nNote: This is an experimental feature and may change in the future." } s.Command.Args = cobra.NoArgs s.Command.Flags().StringVar(&s.AwsLambdaFunctionArn, "aws-lambda-function-arn", "", "Qualified (contains version suffix) or unqualified AWS Lambda function ARN to invoke when there are no active pollers for task queue targets in the Worker Deployment.") - s.Command.Flags().StringVar(&s.AwsLambdaAssumeRoleArn, "aws-lambda-assume-role-arn", "", "AWS IAM role ARN that the Temporal server will assume when invoking the Lambda function that spawns a new Worker in this Worker Deployment Version. Required when --aws-lambda-function-arn is specified.") - s.Command.Flags().StringVar(&s.AwsLambdaAssumeRoleExternalId, "aws-lambda-assume-role-external-id", "", "Temporal server will enforce that the AWS IAM trust policy associated with the AWS IAM role specified in --aws-lambda-assume-role-arn has an aws:ExternalId condition that matches the supplied value. Required when --aws-lambda-function-arn is specified.") + s.Command.Flags().StringVar(&s.AwsLambdaAssumeRoleArn, "aws-lambda-assume-role-arn", "", "AWS IAM role ARN that the Temporal server will assume when invoking the Lambda function that spawns a new Worker in this Worker Deployment Version. Required when --aws-lambda-function-arn is specified, and must be omitted when --aws-lambda-skip-role-and-external-id is passed.") + s.Command.Flags().StringVar(&s.AwsLambdaAssumeRoleExternalId, "aws-lambda-assume-role-external-id", "", "Temporal server will enforce that the AWS IAM trust policy associated with the AWS IAM role specified in --aws-lambda-assume-role-arn has an aws:ExternalId condition that matches the supplied value. Required when --aws-lambda-function-arn is specified, and must be omitted when --aws-lambda-skip-role-and-external-id is passed.") + s.Command.Flags().BoolVar(&s.AwsLambdaSkipRoleAndExternalId, "aws-lambda-skip-role-and-external-id", false, "When --aws-lambda-function-arn is specified, --aws-lambda-assume-role-arn and --aws-lambda-assume-role-external-id are required unless this flag is passed, in which case both must be omitted.") + s.Command.Flags().StringVar(&s.AwsAgentcoreEndpointArn, "aws-agentcore-endpoint-arn", "", "AWS Bedrock Agentcore Runtime endpoint ARN to invoke when there are no active pollers for task queue targets in the Worker Deployment. The endpoint ARN encodes the runtime, endpoint name, and region.") + s.Command.Flags().StringVar(&s.AwsAgentcoreAssumeRoleArn, "aws-agentcore-assume-role-arn", "", "AWS IAM role ARN that the Temporal server will assume when invoking the Agentcore Runtime that spawns a new Worker in this Worker Deployment Version. Required when --aws-agentcore-endpoint-arn is specified, and must be omitted when --aws-agentcore-skip-role-and-external-id is passed.") + s.Command.Flags().StringVar(&s.AwsAgentcoreAssumeRoleExternalId, "aws-agentcore-assume-role-external-id", "", "Temporal server will enforce that the AWS IAM trust policy associated with the AWS IAM role specified in --aws-agentcore-assume-role-arn has an aws:ExternalId condition that matches the supplied value. Required when --aws-agentcore-endpoint-arn is specified, and must be omitted when --aws-agentcore-skip-role-and-external-id is passed.") + s.Command.Flags().BoolVar(&s.AwsAgentcoreSkipRoleAndExternalId, "aws-agentcore-skip-role-and-external-id", false, "When --aws-agentcore-endpoint-arn is specified, --aws-agentcore-assume-role-arn and --aws-agentcore-assume-role-external-id are required unless this flag is passed, in which case both must be omitted.") s.Command.Flags().StringVar(&s.GcpCloudRunProject, "gcp-cloud-run-project", "", "GCP project ID hosting the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") s.Command.Flags().StringVar(&s.GcpCloudRunRegion, "gcp-cloud-run-region", "", "Region of the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") s.Command.Flags().StringVar(&s.GcpCloudRunWorkerPool, "gcp-cloud-run-worker-pool", "", "GCP Cloud Run worker pool name to scale when there are no active pollers for task queue targets in the Worker Deployment.") s.Command.Flags().StringVar(&s.GcpCloudRunServiceAccount, "gcp-cloud-run-service-account", "", "Customer GCP service account the Temporal server impersonates to manage the Cloud Run worker pool. Required when --gcp-cloud-run-worker-pool is specified.") - s.Command.Flags().IntVar(&s.GcpCloudRunMinInstances, "gcp-cloud-run-min-instances", 0, "Minimum number of Cloud Run worker pool instances the scaler will maintain. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together. If omitted, the version's existing scaling settings are left unchanged. Only valid with --gcp-cloud-run-worker-pool.") - s.Command.Flags().IntVar(&s.GcpCloudRunMaxInstances, "gcp-cloud-run-max-instances", 0, "Maximum number of Cloud Run worker pool instances the scaler may scale up to. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together. If omitted, the version's existing scaling settings are left unchanged. Only valid with --gcp-cloud-run-worker-pool.") - s.Command.Flags().IntVar(&s.GcpCloudRunInitialInstances, "gcp-cloud-run-initial-instances", 0, "Number of Cloud Run worker pool instances the scaler starts with. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together, and this value must be between the min and max (inclusive). If omitted, the version's existing scaling settings are left unchanged. Only valid with --gcp-cloud-run-worker-pool.") - s.Command.Flags().Float32Var(&s.GcpCloudRunUtilizationTarget, "gcp-cloud-run-utilization-target", 0, "Target average worker utilization the scaler aims for, as a fraction in the range (0, 1]. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must all be set together. Lower values keep more spare capacity per worker. If omitted, the version's existing scaling settings are left unchanged. Only valid with --gcp-cloud-run-worker-pool.") + s.Command.Flags().IntVar(&s.GcpCloudRunMinInstances, "gcp-cloud-run-min-instances", 0, "Minimum number of Cloud Run worker pool instances the scaler will maintain. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-stabilization-duration must all be set together. If omitted, the version's existing scaling settings are left unchanged. Only applies to a GCP Cloud Run worker pool.") + s.Command.Flags().IntVar(&s.GcpCloudRunMaxInstances, "gcp-cloud-run-max-instances", 0, "Maximum number of Cloud Run worker pool instances the scaler may scale up to. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-stabilization-duration must all be set together. If omitted, the version's existing scaling settings are left unchanged. Only applies to a GCP Cloud Run worker pool.") + s.Command.Flags().IntVar(&s.GcpCloudRunInitialInstances, "gcp-cloud-run-initial-instances", 0, "Number of Cloud Run worker pool instances the scaler starts with. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-stabilization-duration must all be set together, and this value must be between the min and max (inclusive). If omitted, the version's existing scaling settings are left unchanged. Only applies to a GCP Cloud Run worker pool.") + s.Command.Flags().Float32Var(&s.GcpCloudRunUtilizationTarget, "gcp-cloud-run-utilization-target", 0, "Target average worker utilization the scaler aims for, as a fraction in the range (0, 1]. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-stabilization-duration must all be set together. Lower values keep more spare capacity per worker. If omitted, the version's existing scaling settings are left unchanged. Only applies to a GCP Cloud Run worker pool.") + s.GcpCloudRunScaleDownStabilizationDuration = 0 + s.Command.Flags().Var(&s.GcpCloudRunScaleDownStabilizationDuration, "gcp-cloud-run-scale-down-stabilization-duration", "Duration the scaler waits after it last saw unmet task demand before it may scale the Cloud Run worker pool down. Raise this to keep the pool from scaling down before long-running or bursty activities finish. Optional, but --gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-stabilization-duration must all be set together. A value of 0s disables the wait. If omitted, the version's existing scaling settings are left unchanged. Only applies to a GCP Cloud Run worker pool.") s.Command.Flags().BoolVar(&s.Remove, "remove", false, "Removes any compute configuration associated with this Worker Deployment Version.") s.DeploymentVersionOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { @@ -3820,6 +3850,7 @@ func NewTemporalWorkflowCommand(cctx *CommandContext, parent *TemporalCommand) * s.Command.AddCommand(&NewTemporalWorkflowUpdateCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalWorkflowUpdateOptionsCommand(cctx, &s).Command) s.ClientOptions.BuildFlags(s.Command.PersistentFlags()) + s.ClientOptions.HideFlags() return &s } diff --git a/internal/temporalcli/commands.go b/internal/temporalcli/commands.go index eaf08dbdf..788c9399f 100644 --- a/internal/temporalcli/commands.go +++ b/internal/temporalcli/commands.go @@ -387,6 +387,12 @@ func Execute(ctx context.Context, options CommandOptions) { return } + if !cctx.ActuallyRanCommand && len(cctx.Options.Args) > 0 && isCompletionCommand(cctx.Options.Args[0]) { + // Completion was requested, but we didn't match an extension and delegate. Register all extension + // commands so things like "temporal cl" will expand to "temporal cloud" + registerExtensionCommands(&cmd.Command) + } + // Run builtin command if no extension handled the command. if !cctx.ActuallyRanCommand { err = cmd.Command.ExecuteContext(cctx) @@ -419,7 +425,10 @@ func Execute(ctx context.Context, options CommandOptions) { } } -// getUsageTemplate returns a custom usage template with proper flag wrapping +// getUsageTemplate returns a custom usage template with proper flag wrapping. +// On the root command, global flags are hidden and a hint to "temporal options" +// is shown instead (similar to kubectl). On subcommands, local flags are shown +// normally and inherited flags are replaced with the same hint. // The default template can be found here: https://github.com/spf13/cobra/blob/v1.9.1/command.go#L1937-L1966 func getUsageTemplate() string { // Get terminal width, default to 80 if unable to determine @@ -449,19 +458,108 @@ Available Commands:{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help") {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}} Additional Commands:{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if and .HasAvailableLocalFlags .HasParent}} Flags: -{{.LocalFlags.FlagUsagesWrapped %d | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} - -Global Flags: -{{.InheritedFlags.FlagUsagesWrapped %d | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} +{{.LocalFlags.FlagUsagesWrapped %d | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} -`, flagWidth, flagWidth) + +Use "{{.Root.Name}} options" for global and connection options. +`, flagWidth) +} + +type flagRow struct { + Flag string `json:"flag"` + Env string `json:"env,omitempty"` + Config string `json:"config,omitempty"` + Description string `json:"description"` +} + +type flagRowNoConfig struct { + Flag string `json:"flag"` + Env string `json:"env,omitempty"` + Description string `json:"description"` +} + +// printFlagTable prints flags in a table using the existing printer package. +func printFlagTable(w io.Writer, flags *pflag.FlagSet) { + p := &printer.Printer{Output: w} + + // Determine which columns are needed + hasConfig := false + flags.VisitAll(func(f *pflag.Flag) { + _, _, config := parseFlagUsage(f.Usage) + if config != "" { + hasConfig = true + } + }) + + // Collect rows + var fullRows []flagRow + var shortRows []flagRowNoConfig + + flags.VisitAll(func(f *pflag.Flag) { + desc, env, config := parseFlagUsage(f.Usage) + + // Build flag name with short and type + flag := "--" + f.Name + if f.Shorthand != "" { + flag += ", -" + f.Shorthand + } + if typ := f.Value.Type(); typ != "bool" { + if typ == "stringArray" { + typ = "string[]" + } + flag += " " + typ + } + + // Add default if non-empty + if f.DefValue != "" && f.DefValue != "false" && f.DefValue != "0" && f.DefValue != "0s" && f.DefValue != "[]" { + desc += " (default " + f.DefValue + ")" + } + + fullRows = append(fullRows, flagRow{Flag: flag, Env: env, Config: config, Description: desc}) + shortRows = append(shortRows, flagRowNoConfig{Flag: flag, Env: env, Description: desc}) + }) + + opts := printer.StructuredOptions{Table: &printer.TableOptions{}} + if hasConfig { + _ = p.PrintStructured(fullRows, opts) + } else { + _ = p.PrintStructured(shortRows, opts) + } +} + +// parseFlagUsage extracts the base description, env var, and config key +// from a flag usage string. It looks for "Env: VALUE." and "Config: VALUE." +// suffixes that were added by the code generator. +// +// Env var values never contain dots, so we split on the first dot. +// Config key values may contain dots (e.g. "tls.server_name"), so we +// split on the last dot. +func parseFlagUsage(usage string) (desc, env, config string) { + desc = usage + // Extract config first (uses last dot) since it may appear after env + if i := strings.Index(desc, " Config: "); i >= 0 { + rest := desc[i+9:] + if j := strings.LastIndex(rest, "."); j >= 0 { + config = rest[:j] + desc = strings.TrimSpace(desc[:i] + rest[j+1:]) + } + } + // Extract env (uses first dot since env vars have no dots) + if i := strings.Index(desc, " Env: "); i >= 0 { + rest := desc[i+6:] + if j := strings.Index(rest, "."); j >= 0 { + env = rest[:j] + desc = strings.TrimSpace(desc[:i] + rest[j+1:]) + } + } + return } func (c *TemporalCommand) initCommand(cctx *CommandContext) { @@ -476,6 +574,31 @@ func (c *TemporalCommand) initCommand(cctx *CommandContext) { // Customize the built-in help command to support --all/-a for listing extensions customizeHelpCommand(&c.Command) + // Add "options" command to list global and connection flags (similar to kubectl options) + c.Command.AddCommand(&cobra.Command{ + Use: "options", + Short: "Print global and connection options inherited by all commands", + Long: "Print the list of global and connection flags available across commands.", + Run: func(cmd *cobra.Command, args []string) { + w := cmd.OutOrStdout() + var commonOpts cliext.CommonOptions + fmt.Fprintln(w, "Global options") + fmt.Fprintln(w) + fmt.Fprint(w, commonOpts.Description()) + fmt.Fprintln(w) + printFlagTable(w, cmd.Root().PersistentFlags()) + fmt.Fprintln(w) + var clientOpts cliext.ClientOptions + fmt.Fprintln(w, "Connection options") + fmt.Fprintln(w) + fmt.Fprint(w, clientOpts.Description()) + fmt.Fprintln(w) + connFlags := pflag.NewFlagSet("connection", pflag.ContinueOnError) + clientOpts.BuildFlags(connFlags) + printFlagTable(w, connFlags) + }, + }) + // Unfortunately color is a global option, so we can set in pre-run but we // must unset in post-run origNoColor := color.NoColor diff --git a/internal/temporalcli/commands.help.go b/internal/temporalcli/commands.help.go index f821473bc..205f98468 100644 --- a/internal/temporalcli/commands.help.go +++ b/internal/temporalcli/commands.help.go @@ -1,10 +1,13 @@ package temporalcli import ( + "cmp" + "fmt" "slices" "strings" "github.com/spf13/cobra" + "golang.org/x/exp/maps" ) // customizeHelpCommand adds the --all/-a flag to Cobra's built-in help command @@ -53,36 +56,64 @@ func customizeHelpCommand(rootCmd *cobra.Command) { } // registerExtensionCommands adds discovered extensions as placeholder commands -// so they appear in the default help output. It filters extensions based on -// the current command's path in the hierarchy. +// so they appear in shell completion and the default help output. It filters extensions +// based on the current command's path in the hierarchy. func registerExtensionCommands(cmd *cobra.Command) { cmdPath := strings.Fields(cmd.CommandPath()) - seen := make(map[string]bool) - for _, ext := range discoverExtensions() { + // When built-in subcommands are nested under other subcommands (e.g. `temporal activity cancel`), + // they're guaranteed to have a defined parent (`temporal activity`, which has the parent `temporal`). + // Extension subcommands can also be nested, but when `temporal foo bar` is created via an executable + // named temporal-foo-bar, there's no guarantee that the `temporal foo` command exists. For the full + // command to show up in help and completion, placeholders must exist at every level. + extensionsAndExecutables := discoverExtensions() + + extensionKeys := maps.Keys(extensionsAndExecutables) + + // Shorter command paths first ensures the paths shown in the short description of placeholder commands + // point to the closest match + slices.SortFunc(extensionKeys, func(a, b string) int { + return cmp.Compare(strings.Count(a, "/"), strings.Count(b, "/")) + }) + for _, extKey := range extensionKeys { + ext := strings.Split(extKey, "/") // Extension must be deeper than current command and share the same prefix if len(ext) <= len(cmdPath) || !slices.Equal(ext[:len(cmdPath)], cmdPath) { continue } - // Get the next level command name - nextPart := ext[len(cmdPath)] + extPath := ext[len(cmdPath):] - // Skip if already added - if seen[nextPart] { - continue - } + parent := cmd + executablePath := extensionsAndExecutables[extKey] - // Skip if a built-in command exists - if found, _, _ := cmd.Find([]string{nextPart}); found != cmd { - continue + for i, nextPart := range extPath { + if found, _, _ := parent.Find([]string{nextPart}); found != parent { + // Because we order extensions by depth, we can trust that any command that already exists already + // has the most correct definition for its depth. + parent = found + continue + } + + var short string + + if i == len(extPath)-1 { + short = fmt.Sprintf("An extension command located at %s", executablePath) + } else { + short = fmt.Sprintf("Extension commands under %s", strings.Join(ext[:len(cmdPath)+i+1], " ")) + } + + newCmd := &cobra.Command{ + Use: nextPart, + // Short descriptions must be unique at a given level because otherwise shell completion will + // group all commands with the same description on the same line + Short: short, + DisableFlagParsing: true, + Run: func(*cobra.Command, []string) {}, + } + parent.AddCommand(newCmd) + parent = newCmd } - seen[nextPart] = true - cmd.AddCommand(&cobra.Command{ - Use: nextPart, - DisableFlagParsing: true, - Run: func(*cobra.Command, []string) {}, - }) } } diff --git a/internal/temporalcli/commands.help_test.go b/internal/temporalcli/commands.help_test.go index b3cd10c00..54f04e9d9 100644 --- a/internal/temporalcli/commands.help_test.go +++ b/internal/temporalcli/commands.help_test.go @@ -17,6 +17,8 @@ func TestHelp_Root(t *testing.T) { assert.Contains(t, res.Stdout.String(), "Available Commands:") assert.Contains(t, res.Stdout.String(), "workflow") + assert.Contains(t, res.Stdout.String(), "temporal workflow list -o json") + assert.Contains(t, res.Stdout.String(), "temporal options") assert.NoError(t, res.Err) } @@ -29,6 +31,15 @@ func TestHelp_Subcommand(t *testing.T) { assert.NoError(t, res.Err) } +func TestOptions_LogLevelDescribesStartDevDefault(t *testing.T) { + h := NewCommandHarness(t) + + res := h.Execute("options") + + assert.Contains(t, res.Stdout.String(), `Default is "never" for most commands and "warn" for "server start-dev".`) + assert.NoError(t, res.Err) +} + func TestHelp_WithValueFlag(t *testing.T) { h := NewCommandHarness(t) @@ -70,12 +81,10 @@ func TestHelp_AllFlag_ShowsExtensions(t *testing.T) { assert.Contains(t, out, "foo") // shown now! assert.NotContains(t, out, "bar-baz") // is under workflow - // Verify foo appears in Available Commands section (between "Available Commands:" and "Flags:") + // Verify foo appears in Available Commands section availableIdx := strings.Index(out, "Available Commands:") fooIdx := strings.Index(out, "foo") - flagsIdx := strings.Index(out, "Flags:") assert.Greater(t, fooIdx, availableIdx, "foo should appear after Available Commands:") - assert.Less(t, fooIdx, flagsIdx, "foo should appear before Flags:") assert.NoError(t, res.Err) // Non-executable extensions are skipped @@ -84,8 +93,8 @@ func TestHelp_AllFlag_ShowsExtensions(t *testing.T) { require.NoError(t, os.Rename(fooPath, fooPath+".bak")) require.NoError(t, os.Rename(fooBarPath, fooBarPath+".bak")) } else { - require.NoError(t, os.Chmod(fooPath, 0644)) - require.NoError(t, os.Chmod(fooBarPath, 0644)) + require.NoError(t, os.Chmod(fooPath, 0o644)) + require.NoError(t, os.Chmod(fooBarPath, 0o644)) } res = h.Execute("help", "--all") assert.NotContains(t, res.Stdout.String(), "foo") @@ -119,3 +128,32 @@ func TestHelp_AllFlag_FirstInPathWins(t *testing.T) { assert.Equal(t, "first\n", res.Stdout.String()) assert.NoError(t, res.Err) } + +func TestHelp_AllFlag_ShorterCommandPathWinsi(t *testing.T) { + // bin1/temporal-foo + // bin2/temporal-foo-bar + // bin1/something-nested + // bin2/something + // bin1/uncommon-prefix + h := newExtensionHarness(t) + binDir1 := h.binDir + binDir2 := t.TempDir() + + // Set PATH with binDir1 before binDir2 + oldPath := os.Getenv("PATH") + os.Setenv("PATH", binDir1+string(os.PathListSeparator)+binDir2+string(os.PathListSeparator)+oldPath) + t.Cleanup(func() { os.Setenv("PATH", oldPath) }) + + h.createExtension("temporal-foo", codeEchoArgs) + h.createExtension("temporal-something-nested", codeEchoArgs) + h.createExtension("temporal-sharedprefix-one", codeEchoArgs) + h.binDir = binDir2 + h.createExtension("temporal-foo-bar", codeEchoArgs) + h.createExtension("temporal-something", codeEchoArgs) + h.createExtension("temporal-sharedprefix-two", codeEchoArgs) + + res := h.Execute("help", "--all") + assert.Regexp(t, `foo\s+An extension command located at .*[\\/]temporal-foo`, res.Stdout.String()) + assert.Regexp(t, `something\s+An extension command located at .*[\\/]temporal-something`, res.Stdout.String()) + assert.Contains(t, res.Stdout.String(), "Extension commands under temporal sharedprefix") +} diff --git a/internal/temporalcli/commands.server.go b/internal/temporalcli/commands.server.go index d0aa7d97a..ad335d6cf 100644 --- a/internal/temporalcli/commands.server.go +++ b/internal/temporalcli/commands.server.go @@ -53,7 +53,7 @@ func (t *TemporalServerStartDevCommand) run(cctx *CommandContext, args []string) // Set the log level value of the server to the overall log level given to the // CLI. But if it is "never" we have to do a special value, and if it was // never changed, we have to use the default of "warn" instead of the CLI - // default of "info" since server is noisier. + // default of "never" since server is noisier. logLevel := t.Parent.Parent.LogLevel.Value if !t.Parent.Parent.LogLevel.ChangedFromDefault { logLevel = "warn" diff --git a/internal/temporalcli/commands.server_test.go b/internal/temporalcli/commands.server_test.go index 26b92cf60..f00501844 100644 --- a/internal/temporalcli/commands.server_test.go +++ b/internal/temporalcli/commands.server_test.go @@ -2,6 +2,7 @@ package temporalcli_test import ( "context" + "fmt" "net" "os" "path/filepath" @@ -139,58 +140,105 @@ func startDevServerAndRunSimpleTest(t *testing.T, args []string, dialAddress str } func TestServer_StartDev_ConcurrentStarts(t *testing.T) { - startOne := func() { - h := NewCommandHarness(t) - defer h.Close() + h := NewCommandHarness(t) + defer h.Close() + + startOne := func() error { + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() // Start in background, then wait for client to be able to connect port := strconv.Itoa(devserver.MustGetFreePort("127.0.0.1")) httpPort := strconv.Itoa(devserver.MustGetFreePort("127.0.0.1")) resCh := make(chan *CommandResult, 1) go func() { - resCh <- h.Execute("server", "start-dev", "-p", port, "--http-port", httpPort, "--headless", "--log-level", "never") + resCh <- h.ExecuteWithContext(ctx, "server", "start-dev", "-p", port, "--http-port", httpPort, "--headless", "--log-level", "never") }() // Try to connect for a bit while checking for error var cl client.Client - h.EventuallyWithT(func(t *assert.CollectT) { + var lastDialErr error + ticker := time.NewTicker(200 * time.Millisecond) + defer ticker.Stop() + timeout := time.NewTimer(3 * time.Second) + defer timeout.Stop() + + waitForServer: + for { + select { + case res := <-resCh: + return concurrentStartCommandResultError("got early server result", res) + case <-ticker.C: + var err error + cl, err = client.Dial(client.Options{HostPort: "127.0.0.1:" + port, Logger: testLogger{t: t}}) + if err == nil { + break waitForServer + } + lastDialErr = err + case <-timeout.C: + break waitForServer + } + } + if cl == nil { + cancel() select { + case <-time.After(20 * time.Second): + return fmt.Errorf("server was not reachable after 3 seconds: %w; also did not clean up within 20 seconds", lastDialErr) case res := <-resCh: - require.NoError(t, res.Err) - require.Fail(t, "got early server result") - default: + if res.Err != nil { + return fmt.Errorf( + "server was not reachable after 3 seconds: %w; cleanup failed: %w", + lastDialErr, + res.Err, + ) + } + return fmt.Errorf("server was not reachable after 3 seconds: %w", lastDialErr) } - var err error - cl, err = client.Dial(client.Options{HostPort: "127.0.0.1:" + port, Logger: testLogger{t: h.t}}) - assert.NoError(t, err) - }, 3*time.Second, 200*time.Millisecond) + } defer cl.Close() // Send an interrupt by cancelling context - h.CancelContext() + cancel() select { case <-time.After(20 * time.Second): - h.Fail("didn't cleanup after 20 seconds") + return fmt.Errorf("didn't cleanup after 20 seconds") case res := <-resCh: - h.NoError(res.Err) + if res.Err != nil { + return concurrentStartCommandResultError("server returned error", res) + } } + return nil } - // Start 40 dev server instances, with 8 concurrent executions + // Start 40 dev server instances, with 6 concurrent executions. instanceCounter := atomic.Int32{} instanceCounter.Store(40) + errCh := make(chan error, 40) wg := &sync.WaitGroup{} for i := 0; i < 6; i++ { wg.Add(1) go func() { + defer wg.Done() for instanceCounter.Add(-1) >= 0 { - startOne() + if err := startOne(); err != nil { + errCh <- err + } } - wg.Done() }() } wg.Wait() + close(errCh) + for err := range errCh { + require.NoError(t, err) + } +} + +func concurrentStartCommandResultError(msg string, res *CommandResult) error { + if res.Err != nil { + return fmt.Errorf("%s: %w (stdout: %q, stderr: %q)", msg, res.Err, res.Stdout.String(), res.Stderr.String()) + } + return fmt.Errorf("%s (stdout: %q, stderr: %q)", msg, res.Stdout.String(), res.Stderr.String()) } func TestServer_StartDev_WithSearchAttributes(t *testing.T) { diff --git a/internal/temporalcli/commands.worker.deployment.go b/internal/temporalcli/commands.worker.deployment.go index 2f788ae2f..6dece0b35 100644 --- a/internal/temporalcli/commands.worker.deployment.go +++ b/internal/temporalcli/commands.worker.deployment.go @@ -136,11 +136,12 @@ type formattedComputeConfigProvider struct { } type formattedComputeConfigScaler struct { - Type string `json:"type"` - MinInstances *int64 `json:"minInstances,omitempty"` - MaxInstances *int64 `json:"maxInstances,omitempty"` - InitialInstances *int64 `json:"initialInstances,omitempty"` - UtilizationTarget *float64 `json:"utilizationTarget,omitempty"` + Type string `json:"type"` + MinInstances *int64 `json:"minInstances,omitempty"` + MaxInstances *int64 `json:"maxInstances,omitempty"` + InitialInstances *int64 `json:"initialInstances,omitempty"` + UtilizationTarget *float64 `json:"utilizationTarget,omitempty"` + ScaleDownStabilization string `json:"scaleDownStabilization,omitempty"` } func drainageStatusToStr(drainage client.WorkerDeploymentVersionDrainageStatus) (string, error) { @@ -393,6 +394,7 @@ const ( scalerKeyMaxCount = "max_count" scalerKeyInitialCount = "initial_count" scalerKeyUtilizationTarget = "utilization_target" + scalerKeyNoSyncQuietMs = "no_sync_quiet_ms" ) // scalerCountFromMap reads an integer worker-count value from a decoded scaler @@ -427,10 +429,11 @@ func scalerFloatFromMap(m map[string]any, key string) (float64, bool) { // scalerSettings holds the rate-based scaler settings surfaced for display. Each // field is nil when the corresponding key is absent from the scaler details. type scalerSettings struct { - minInstances *int64 - maxInstances *int64 - initialInstances *int64 - utilizationTarget *float64 + minInstances *int64 + maxInstances *int64 + initialInstances *int64 + utilizationTarget *float64 + scaleDownStabilization *time.Duration } // decodeScalerSettings extracts the rate-based scaler settings from a @@ -459,6 +462,12 @@ func decodeScalerSettings(s *computepb.ComputeScaler) scalerSettings { if v, ok := scalerFloatFromMap(m, scalerKeyUtilizationTarget); ok { out.utilizationTarget = &v } + if v, ok := scalerCountFromMap(m, scalerKeyNoSyncQuietMs); ok { + // The WCI key is stored in milliseconds; surface it as a duration to match + // how the CLI renders other duration fields (e.g. ApproximateBacklogAge). + d := time.Duration(v) * time.Millisecond + out.scaleDownStabilization = &d + } return out } @@ -490,6 +499,9 @@ func formatComputeConfigProto(cc *computepb.ComputeConfig) *formattedComputeConf fs.MaxInstances = set.maxInstances fs.InitialInstances = set.initialInstances fs.UtilizationTarget = set.utilizationTarget + if set.scaleDownStabilization != nil { + fs.ScaleDownStabilization = formatDuration(*set.scaleDownStabilization) + } sg.Scaler = fs } sgs[name] = sg @@ -540,7 +552,8 @@ func computeConfigSummaryStr(cc *computepb.ComputeConfig) string { } summary := p.GetType() // Append whichever scaler settings are present so the one-line summary - // reflects the configured limits (ordered min, initial, max, utilization). + // reflects the configured limits (ordered min, initial, max, utilization, + // scale-down-stabilization). set := decodeScalerSettings(sg.GetScaler()) parts := []string{} if set.minInstances != nil { @@ -555,6 +568,9 @@ func computeConfigSummaryStr(cc *computepb.ComputeConfig) string { if set.utilizationTarget != nil { parts = append(parts, fmt.Sprintf("utilization %g", *set.utilizationTarget)) } + if set.scaleDownStabilization != nil { + parts = append(parts, fmt.Sprintf("scale-down-stabilization %s", formatDuration(*set.scaleDownStabilization))) + } if len(parts) > 0 { summary = fmt.Sprintf("%s (%s)", summary, strings.Join(parts, ", ")) } @@ -989,8 +1005,19 @@ func (c *TemporalWorkerDeploymentManagerIdentityUnsetCommand) run(cctx *CommandC return nil } -func validateAWSLambdaProviderDetails(details map[string]any) error { - for _, key := range []string{"arn", "role", "role_external_id"} { +func validateAWSLambdaProviderDetails(details map[string]any, skipRoleAndExternalID bool) error { + if _, ok := details["arn"]; !ok { + return fmt.Errorf("missing required AWS Lambda provider detail: arn") + } + if skipRoleAndExternalID { + for _, key := range []string{"role", "role_external_id"} { + if _, ok := details[key]; ok { + return fmt.Errorf("AWS Lambda provider detail %q must not be set when --aws-lambda-skip-role-and-external-id is passed", key) + } + } + return nil + } + for _, key := range []string{"role", "role_external_id"} { if _, ok := details[key]; !ok { return fmt.Errorf("missing required AWS Lambda provider detail: %s", key) } @@ -1004,11 +1031,58 @@ func awsLambdaProviderDetailsPayload( functionARN string, assumeRoleARN string, assumeRoleExternalID string, + skipRoleAndExternalID bool, ) (*commonpb.Payload, error) { // Map keys from temporal-auto-scaled-workers: // https://github.com/temporalio/temporal-auto-scaled-workers/blob/c4a7e69b6504365d7e5326b0b8e6cd95e3293f96/wci/workflow/compute_provider/aws_lambda.go#L16-L20 - providerDetails := map[string]any{ - "arn": functionARN, + providerDetails := map[string]any{} + if functionARN != "" { + providerDetails["arn"] = functionARN + } + if assumeRoleARN != "" { + providerDetails["role"] = assumeRoleARN + } + if assumeRoleExternalID != "" { + providerDetails["role_external_id"] = assumeRoleExternalID + } + err := validateAWSLambdaProviderDetails(providerDetails, skipRoleAndExternalID) + if err != nil { + return nil, err + } + dc := converter.GetDefaultDataConverter() + return dc.ToPayload(&providerDetails) +} + +func validateAWSAgentcoreProviderDetails(details map[string]any, skipRoleAndExternalID bool) error { + if v, ok := details["endpoint_arn"].(string); !ok || v == "" { + return fmt.Errorf("missing required AWS Agentcore provider detail: endpoint_arn") + } + if skipRoleAndExternalID { + for _, key := range []string{"role", "role_external_id"} { + if _, ok := details[key]; ok { + return fmt.Errorf("AWS Agentcore provider detail %q must not be set when --aws-agentcore-skip-role-and-external-id is passed", key) + } + } + return nil + } + for _, key := range []string{"role", "role_external_id"} { + if v, ok := details[key].(string); !ok || v == "" { + return fmt.Errorf("missing required AWS Agentcore provider detail: %s", key) + } + } + return nil +} + +// awsAgentcoreProviderDetailsPayload validates the AWS AgentCore inputs and returns encoded payload +func awsAgentcoreProviderDetailsPayload( + endpointARN string, + assumeRoleARN string, + assumeRoleExternalID string, + skipRoleAndExternalID bool, +) (*commonpb.Payload, error) { + providerDetails := map[string]any{} + if endpointARN != "" { + providerDetails["endpoint_arn"] = endpointARN } if assumeRoleARN != "" { providerDetails["role"] = assumeRoleARN @@ -1016,7 +1090,7 @@ func awsLambdaProviderDetailsPayload( if assumeRoleExternalID != "" { providerDetails["role_external_id"] = assumeRoleExternalID } - err := validateAWSLambdaProviderDetails(providerDetails) + err := validateAWSAgentcoreProviderDetails(providerDetails, skipRoleAndExternalID) if err != nil { return nil, err } @@ -1059,41 +1133,87 @@ func gcpCloudRunProviderDetailsPayload( return dc.ToPayload(&providerDetails) } +// ComputeConfigArgs holds ComputeConfig specific args along with helpers for distinguishing between desired compute +// provider +type ComputeConfigArgs struct { + awsLambdaFunctionArn string + awsLambdaAssumeRoleArn string + awsLambdaAssumeRoleExternalId string + awsLambdaSkipRoleAndExternalId bool + awsAgentcoreEndpointArn string + awsAgentcoreAssumeRoleArn string + awsAgentcoreAssumeRoleExternalId string + awsAgentcoreSkipRoleAndExternalId bool + gcpCloudRunProject string + gcpCloudRunRegion string + gcpCloudRunWorkerPool string + gcpCloudRunServiceAccount string +} + +func (c *ComputeConfigArgs) hasAwsLambdaArgs() bool { + return c.awsLambdaFunctionArn != "" || + c.awsLambdaAssumeRoleArn != "" || + c.awsLambdaAssumeRoleExternalId != "" || + c.awsLambdaSkipRoleAndExternalId +} + +func (c *ComputeConfigArgs) hasAwsAgentcoreArgs() bool { + return c.awsAgentcoreEndpointArn != "" || + c.awsAgentcoreAssumeRoleArn != "" || + c.awsAgentcoreAssumeRoleExternalId != "" || + c.awsAgentcoreSkipRoleAndExternalId +} + +func (c *ComputeConfigArgs) hasGcpCloudRunArgs() bool { + return c.gcpCloudRunProject != "" || + c.gcpCloudRunRegion != "" || + c.gcpCloudRunWorkerPool != "" || + c.gcpCloudRunServiceAccount != "" +} + // computeProviderConfig selects the single compute provider for a Worker // Deployment Version's "default" scaling group from the command's flags. It -// enforces that AWS Lambda and GCP Cloud Run flags are not mixed, then -// dispatches on the trigger flag (--aws-lambda-function-arn / +// enforces that flags for different providers are not mixed, then dispatches on +// the trigger flag (--aws-lambda-function-arn / --aws-agentcore-endpoint-arn / // --gcp-cloud-run-worker-pool). Returns an empty providerType when no provider // flags are set, leaving the "no configuration" decision to the caller. -func computeProviderConfig( - awsLambdaFunctionARN string, - awsLambdaAssumeRoleARN string, - awsLambdaAssumeRoleExternalID string, - gcpCloudRunProject string, - gcpCloudRunRegion string, - gcpCloudRunWorkerPool string, - gcpCloudRunServiceAccount string, -) (providerType string, detailsPayload *commonpb.Payload, err error) { - awsSet := awsLambdaFunctionARN != "" || awsLambdaAssumeRoleARN != "" || awsLambdaAssumeRoleExternalID != "" - gcpSet := gcpCloudRunProject != "" || gcpCloudRunRegion != "" || gcpCloudRunWorkerPool != "" || gcpCloudRunServiceAccount != "" - if awsSet && gcpSet { - return "", nil, fmt.Errorf("cannot combine --aws-lambda-* and --gcp-cloud-run-* flags; a Worker Deployment Version supports a single compute provider") +func computeProviderConfig(c *ComputeConfigArgs) (providerType string, detailsPayload *commonpb.Payload, err error) { + awsLambdaSet := c.hasAwsLambdaArgs() + awsAgentcoreSet := c.hasAwsAgentcoreArgs() + gcpSet := c.hasGcpCloudRunArgs() + setCount := 0 + for _, set := range []bool{awsLambdaSet, awsAgentcoreSet, gcpSet} { + if set { + setCount++ + } + } + if setCount > 1 { + return "", nil, fmt.Errorf("cannot combine --aws-lambda-*, --aws-agentcore-*, and --gcp-cloud-run-* flags; a Worker Deployment Version supports a single compute provider") } switch { - case awsLambdaFunctionARN != "": + case c.hasAwsLambdaArgs(): p, err := awsLambdaProviderDetailsPayload( - awsLambdaFunctionARN, - awsLambdaAssumeRoleARN, - awsLambdaAssumeRoleExternalID, + c.awsLambdaFunctionArn, + c.awsLambdaAssumeRoleArn, + c.awsLambdaAssumeRoleExternalId, + c.awsLambdaSkipRoleAndExternalId, ) return "aws-lambda", p, err - case gcpCloudRunWorkerPool != "": + case c.hasAwsAgentcoreArgs(): + p, err := awsAgentcoreProviderDetailsPayload( + c.awsAgentcoreEndpointArn, + c.awsAgentcoreAssumeRoleArn, + c.awsAgentcoreAssumeRoleExternalId, + c.awsAgentcoreSkipRoleAndExternalId, + ) + return "aws-agentcore", p, err + case c.hasGcpCloudRunArgs(): p, err := gcpCloudRunProviderDetailsPayload( - gcpCloudRunProject, - gcpCloudRunRegion, - gcpCloudRunWorkerPool, - gcpCloudRunServiceAccount, + c.gcpCloudRunProject, + c.gcpCloudRunRegion, + c.gcpCloudRunWorkerPool, + c.gcpCloudRunServiceAccount, ) return "gcp-cloud-run", p, err default: @@ -1107,6 +1227,7 @@ func computeProviderConfig( // WCI rejects an incompatible pairing at CreateWorkerDeploymentVersion. var scalerTypeByProvider = map[string]string{ "aws-lambda": "no-sync", + "aws-agentcore": "no-sync", "gcp-cloud-run": "rate-based", } @@ -1120,50 +1241,41 @@ func scalerTypeForProvider(providerType string) (string, error) { return "", fmt.Errorf("no scaler mapping for compute provider %q", providerType) } -// gcpCloudRunScalerDetails builds the ComputeScaler.Details payload from the GCP -// Cloud Run scaling flags. It carries two independent groups of rate-based scaler -// settings: -// - the instance-count group (min_count/max_count/initial_count), which is -// all-or-none and must satisfy min <= initial <= max, and -// - utilization_target, a standalone fraction in (0, 1]. -// -// The *Set booleans come from cobra's Flags().Changed, so an omitted flag stays -// distinct from an explicit 0. Returns a nil payload when nothing is set, leaving -// WCI's defaults (min 0, max 30, initial 0, utilization_target 0.8) in effect. -// Every setting is GCP Cloud Run only; any use with another provider is rejected. -// Config keys mirror the WCI rate-based scaler: -// https://github.com/temporalio/temporal-auto-scaled-workers/blob/main/wci/workflow/scaling_algorithm/rate_based.go // gcpScalerFlags holds the GCP Cloud Run scaling flag values together with // whether each was actually set (from cobra's Flags().Changed). Pairing each // value with its Set bool keeps an omitted flag distinct from an explicit 0 and // removes the positional-argument risk of passing the raw values around. type gcpScalerFlags struct { - min int - minSet bool - max int - maxSet bool - initial int - initialSet bool - utilization float32 - utilizationSet bool + min int + minSet bool + max int + maxSet bool + initial int + initialSet bool + utilization float32 + utilizationSet bool + scaleDownStabilization time.Duration + scaleDownStabilizationSet bool } func (f gcpScalerFlags) anySet() bool { - return f.minSet || f.maxSet || f.initialSet || f.utilizationSet + return f.minSet || f.maxSet || f.initialSet || f.utilizationSet || f.scaleDownStabilizationSet } func (f gcpScalerFlags) allSet() bool { - return f.minSet && f.maxSet && f.initialSet && f.utilizationSet + return f.minSet && f.maxSet && f.initialSet && f.utilizationSet && f.scaleDownStabilizationSet } // gcpCloudRunScalerDetails builds the ComputeScaler.Details payload from the GCP -// Cloud Run scaling flags (min/max/initial instance counts and utilization -// target). The four form a single all-or-none group: setting any one requires -// all four. That keeps the min<=initial<=max relationship self-contained and -// avoids comparing an explicit value against WCI's default for an unset sibling. -// Returns a nil payload when nothing is set, leaving WCI's defaults (min 0, -// max 30, initial 0, utilization_target 0.8) in effect. Every setting is GCP -// Cloud Run only; any use with another provider is rejected. +// Cloud Run scaling flags (min/max/initial instance counts, utilization target, +// and the scale-down stabilization duration). The five form a single all-or-none group: +// setting any one requires all five. That keeps the min<=initial<=max +// relationship self-contained, always writes the full scaler config so an update +// never leaves a stale sibling behind, and avoids comparing an explicit value +// against WCI's default for an unset sibling. Returns a nil payload when nothing +// is set, leaving WCI's defaults (min 0, max 30, initial 0, utilization_target +// 0.8, no_sync_quiet_ms 90000) in effect. Every setting is GCP Cloud Run only; +// any use with another provider is rejected. func gcpCloudRunScalerDetails(providerType string, f gcpScalerFlags) (*commonpb.Payload, error) { if !f.anySet() { return nil, nil @@ -1174,7 +1286,7 @@ func gcpCloudRunScalerDetails(providerType string, f gcpScalerFlags) (*commonpb. return nil, fmt.Errorf("the Cloud Run scaling flags are only valid with --gcp-cloud-run-worker-pool") } if !f.allSet() { - return nil, fmt.Errorf("--gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and --gcp-cloud-run-utilization-target must be set together") + return nil, fmt.Errorf("--gcp-cloud-run-min-instances, --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, --gcp-cloud-run-utilization-target, and --gcp-cloud-run-scale-down-stabilization-duration must be set together") } if f.min < 0 { return nil, fmt.Errorf("--gcp-cloud-run-min-instances cannot be negative") @@ -1193,11 +1305,21 @@ func gcpCloudRunScalerDetails(providerType string, f gcpScalerFlags) (*commonpb. if f.utilization <= 0 || f.utilization > 1 { return nil, fmt.Errorf("--gcp-cloud-run-utilization-target must be greater than 0 and at most 1") } + // Validate the raw duration before converting: Milliseconds() truncates + // toward zero, which would hide a negative sub-millisecond value (turning it + // into 0, disabling the wait) or silently round a fractional millisecond. + if f.scaleDownStabilization < 0 { + return nil, fmt.Errorf("--gcp-cloud-run-scale-down-stabilization-duration cannot be negative") + } + if f.scaleDownStabilization%time.Millisecond != 0 { + return nil, fmt.Errorf("--gcp-cloud-run-scale-down-stabilization-duration must be a whole number of milliseconds") + } details := map[string]any{ scalerKeyMinCount: f.min, scalerKeyMaxCount: f.max, scalerKeyInitialCount: f.initial, scalerKeyUtilizationTarget: f.utilization, + scalerKeyNoSyncQuietMs: f.scaleDownStabilization.Milliseconds(), } dc := converter.GetDefaultDataConverter() return dc.ToPayload(&details) @@ -1210,6 +1332,7 @@ func (c *TemporalWorkerDeploymentCreateVersionCommand) gcpScalerFlags() gcpScale max: c.GcpCloudRunMaxInstances, maxSet: f.Changed("gcp-cloud-run-max-instances"), initial: c.GcpCloudRunInitialInstances, initialSet: f.Changed("gcp-cloud-run-initial-instances"), utilization: c.GcpCloudRunUtilizationTarget, utilizationSet: f.Changed("gcp-cloud-run-utilization-target"), + scaleDownStabilization: c.GcpCloudRunScaleDownStabilizationDuration.Duration(), scaleDownStabilizationSet: f.Changed("gcp-cloud-run-scale-down-stabilization-duration"), } } @@ -1226,15 +1349,20 @@ func (c *TemporalWorkerDeploymentCreateVersionCommand) run(cctx *CommandContext, deploymentName := c.DeploymentName requestID := uuid.NewString() - providerType, detailsPayload, err := computeProviderConfig( - c.AwsLambdaFunctionArn, - c.AwsLambdaAssumeRoleArn, - c.AwsLambdaAssumeRoleExternalId, - c.GcpCloudRunProject, - c.GcpCloudRunRegion, - c.GcpCloudRunWorkerPool, - c.GcpCloudRunServiceAccount, - ) + providerType, detailsPayload, err := computeProviderConfig(&ComputeConfigArgs{ + awsLambdaFunctionArn: c.AwsLambdaFunctionArn, + awsLambdaAssumeRoleArn: c.AwsLambdaAssumeRoleArn, + awsLambdaAssumeRoleExternalId: c.AwsLambdaAssumeRoleExternalId, + awsLambdaSkipRoleAndExternalId: c.AwsLambdaSkipRoleAndExternalId, + awsAgentcoreEndpointArn: c.AwsAgentcoreEndpointArn, + awsAgentcoreAssumeRoleArn: c.AwsAgentcoreAssumeRoleArn, + awsAgentcoreAssumeRoleExternalId: c.AwsAgentcoreAssumeRoleExternalId, + awsAgentcoreSkipRoleAndExternalId: c.AwsAgentcoreSkipRoleAndExternalId, + gcpCloudRunProject: c.GcpCloudRunProject, + gcpCloudRunRegion: c.GcpCloudRunRegion, + gcpCloudRunWorkerPool: c.GcpCloudRunWorkerPool, + gcpCloudRunServiceAccount: c.GcpCloudRunServiceAccount, + }) if err != nil { return err } @@ -1291,6 +1419,7 @@ func (c *TemporalWorkerDeploymentUpdateVersionComputeConfigCommand) gcpScalerFla max: c.GcpCloudRunMaxInstances, maxSet: f.Changed("gcp-cloud-run-max-instances"), initial: c.GcpCloudRunInitialInstances, initialSet: f.Changed("gcp-cloud-run-initial-instances"), utilization: c.GcpCloudRunUtilizationTarget, utilizationSet: f.Changed("gcp-cloud-run-utilization-target"), + scaleDownStabilization: c.GcpCloudRunScaleDownStabilizationDuration.Duration(), scaleDownStabilizationSet: f.Changed("gcp-cloud-run-scale-down-stabilization-duration"), } } @@ -1317,23 +1446,29 @@ func (c *TemporalWorkerDeploymentUpdateVersionComputeConfigCommand) run(cctx *Co RequestId: requestID, } + computeConfigArgs := &ComputeConfigArgs{ + awsLambdaFunctionArn: c.AwsLambdaFunctionArn, + awsLambdaAssumeRoleArn: c.AwsLambdaAssumeRoleArn, + awsLambdaAssumeRoleExternalId: c.AwsLambdaAssumeRoleExternalId, + awsLambdaSkipRoleAndExternalId: c.AwsLambdaSkipRoleAndExternalId, + awsAgentcoreEndpointArn: c.AwsAgentcoreEndpointArn, + awsAgentcoreAssumeRoleArn: c.AwsAgentcoreAssumeRoleArn, + awsAgentcoreAssumeRoleExternalId: c.AwsAgentcoreAssumeRoleExternalId, + awsAgentcoreSkipRoleAndExternalId: c.AwsAgentcoreSkipRoleAndExternalId, + gcpCloudRunProject: c.GcpCloudRunProject, + gcpCloudRunRegion: c.GcpCloudRunRegion, + gcpCloudRunWorkerPool: c.GcpCloudRunWorkerPool, + gcpCloudRunServiceAccount: c.GcpCloudRunServiceAccount, + } + if c.Remove { - if c.AwsLambdaFunctionArn != "" || c.AwsLambdaAssumeRoleArn != "" || c.AwsLambdaAssumeRoleExternalId != "" || - c.GcpCloudRunProject != "" || c.GcpCloudRunRegion != "" || c.GcpCloudRunWorkerPool != "" || c.GcpCloudRunServiceAccount != "" || + if computeConfigArgs.hasAwsLambdaArgs() || computeConfigArgs.hasAwsAgentcoreArgs() || computeConfigArgs.hasGcpCloudRunArgs() || c.gcpScalerFlags().anySet() { - return fmt.Errorf("--remove cannot be combined with --aws-lambda-* or --gcp-cloud-run-* flags") + return fmt.Errorf("--remove cannot be combined with --aws-lambda-*, --aws-agentcore-*, or --gcp-cloud-run-* flags") } request.RemoveComputeConfigScalingGroups = []string{"default"} } else { - providerType, detailsPayload, err := computeProviderConfig( - c.AwsLambdaFunctionArn, - c.AwsLambdaAssumeRoleArn, - c.AwsLambdaAssumeRoleExternalId, - c.GcpCloudRunProject, - c.GcpCloudRunRegion, - c.GcpCloudRunWorkerPool, - c.GcpCloudRunServiceAccount, - ) + providerType, detailsPayload, err := computeProviderConfig(computeConfigArgs) if err != nil { return err } diff --git a/internal/temporalcli/commands.worker.deployment.internal_test.go b/internal/temporalcli/commands.worker.deployment.internal_test.go index 9db39ad77..f25603819 100644 --- a/internal/temporalcli/commands.worker.deployment.internal_test.go +++ b/internal/temporalcli/commands.worker.deployment.internal_test.go @@ -2,6 +2,7 @@ package temporalcli import ( "testing" + "time" "github.com/stretchr/testify/require" computepb "go.temporal.io/api/compute/v1" @@ -16,6 +17,7 @@ func TestScalerTypeForProvider(t *testing.T) { expectErr bool }{ {"aws-lambda is invoke-based -> no-sync", "aws-lambda", "no-sync", false}, + {"aws-agentcore is invoke-based -> no-sync", "aws-agentcore", "no-sync", false}, {"gcp-cloud-run is worker-set-based -> rate-based", "gcp-cloud-run", "rate-based", false}, {"unknown provider errors", "azure-container-apps", "", true}, {"empty provider errors", "", "", true}, @@ -37,53 +39,108 @@ func TestScalerTypeForProvider(t *testing.T) { // scaler mapping; a missing entry makes scalerTypeForProvider error before the // request is sent, so this guards against forgetting to map a newly-added provider. func TestScalerTypeByProviderCoversAllProviders(t *testing.T) { - for _, providerType := range []string{"aws-lambda", "gcp-cloud-run"} { + for _, providerType := range []string{"aws-lambda", "aws-agentcore", "gcp-cloud-run"} { _, ok := scalerTypeByProvider[providerType] require.Truef(t, ok, "provider %q has no scaler mapping", providerType) } } func TestGCPCloudRunScalerDetails(t *testing.T) { + // A fully-set, valid group; each case clones this and overrides one field so + // the all-or-none check passes and the case isolates a single value check. + valid := func() gcpScalerFlags { + return gcpScalerFlags{ + min: 1, minSet: true, + max: 10, maxSet: true, + initial: 5, initialSet: true, + utilization: 0.5, utilizationSet: true, + scaleDownStabilization: 90 * time.Second, scaleDownStabilizationSet: true, + } + } + // Nothing set -> nil payload so WCI defaults apply (min 0, max 30, - // initial 0, utilization_target 0.8). + // initial 0, utilization_target 0.8, no_sync_quiet_ms 90000). p, err := gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{}) require.NoError(t, err) require.Nil(t, p) - // Any scaler flag alongside a non-GCP provider is rejected. Covers both an - // instance-count flag and the utilization flag. + // Any scaler flag alongside a non-GCP provider is rejected. Covers an + // instance-count flag, the utilization flag, and the no-sync flag. _, err = gcpCloudRunScalerDetails("aws-lambda", gcpScalerFlags{minSet: true}) require.ErrorContains(t, err, "only valid with --gcp-cloud-run-worker-pool") _, err = gcpCloudRunScalerDetails("aws-lambda", gcpScalerFlags{utilization: 0.5, utilizationSet: true}) require.ErrorContains(t, err, "only valid with --gcp-cloud-run-worker-pool") + _, err = gcpCloudRunScalerDetails("aws-lambda", gcpScalerFlags{scaleDownStabilization: time.Second, scaleDownStabilizationSet: true}) + require.ErrorContains(t, err, "only valid with --gcp-cloud-run-worker-pool") - // All four settings are one all-or-none group: any partial set is rejected. + // All five settings are one all-or-none group: any partial set is rejected. _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 5, minSet: true}) require.ErrorContains(t, err, "must be set together") _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{utilization: 0.5, utilizationSet: true}) // utilization alone require.ErrorContains(t, err, "must be set together") - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{ // trio set, utilization missing - min: 1, minSet: true, max: 3, maxSet: true, initial: 2, initialSet: true, - }) + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{scaleDownStabilization: time.Second, scaleDownStabilizationSet: true}) // scale-down-stabilization-duration alone require.ErrorContains(t, err, "must be set together") + // The four instance/utilization flags without scale-down-stabilization-duration are also + // rejected: scale-down-stabilization-duration is part of the same all-or-none group. + missingStabilization := valid() + missingStabilization.scaleDownStabilization, missingStabilization.scaleDownStabilizationSet = 0, false + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", missingStabilization) + require.ErrorContains(t, err, "must be set together") + + // Value checks, with the whole group set so the group check passes first. + neg := valid() + neg.min, neg.initial = -1, 0 + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", neg) + require.ErrorContains(t, err, "--gcp-cloud-run-min-instances cannot be negative") - // Value checks, with all four set so the group check passes first. - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: -1, minSet: true, max: 3, maxSet: true, initial: 0, initialSet: true, utilization: 0.5, utilizationSet: true}) - require.ErrorContains(t, err, "cannot be negative") - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 0, minSet: true, max: 0, maxSet: true, initial: 0, initialSet: true, utilization: 0.5, utilizationSet: true}) + maxTooLow := valid() + maxTooLow.min, maxTooLow.max, maxTooLow.initial = 0, 0, 0 + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", maxTooLow) require.ErrorContains(t, err, "--gcp-cloud-run-max-instances must be at least 1") - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 5, minSet: true, max: 3, maxSet: true, initial: 4, initialSet: true, utilization: 0.5, utilizationSet: true}) + + minGtMax := valid() + minGtMax.min, minGtMax.max, minGtMax.initial = 5, 3, 4 + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", minGtMax) require.ErrorContains(t, err, "cannot exceed") - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 2, minSet: true, max: 10, maxSet: true, initial: 15, initialSet: true, utilization: 0.5, utilizationSet: true}) + + initialOOR := valid() + initialOOR.min, initialOOR.max, initialOOR.initial = 2, 10, 15 + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", initialOOR) require.ErrorContains(t, err, "must be between") - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 0, minSet: true, max: 10, maxSet: true, initial: 5, initialSet: true, utilization: 0, utilizationSet: true}) + + utilZero := valid() + utilZero.utilization = 0 + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", utilZero) require.ErrorContains(t, err, "must be greater than 0 and at most 1") - _, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 0, minSet: true, max: 10, maxSet: true, initial: 5, initialSet: true, utilization: 1.5, utilizationSet: true}) + + utilHigh := valid() + utilHigh.utilization = 1.5 + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", utilHigh) require.ErrorContains(t, err, "must be greater than 0 and at most 1") - // All four set and valid -> payload decodes to the WCI rate-based keys. + negStabilization := valid() + negStabilization.scaleDownStabilization = -time.Second + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", negStabilization) + require.ErrorContains(t, err, "--gcp-cloud-run-scale-down-stabilization-duration cannot be negative") + + // A negative sub-millisecond value must be caught before Milliseconds() + // truncates it toward zero (which would send 0 and silently disable the wait). + negSubMs := valid() + negSubMs.scaleDownStabilization = -time.Microsecond + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", negSubMs) + require.ErrorContains(t, err, "--gcp-cloud-run-scale-down-stabilization-duration cannot be negative") + + // A positive sub-millisecond value is rejected rather than silently rounded. + subMs := valid() + subMs.scaleDownStabilization = 500 * time.Microsecond + _, err = gcpCloudRunScalerDetails("gcp-cloud-run", subMs) + require.ErrorContains(t, err, "--gcp-cloud-run-scale-down-stabilization-duration must be a whole number of milliseconds") + + // Whole group set and valid -> payload decodes to the WCI rate-based keys. // JSON round-trips numbers as float64; WCI handles that on read. - p, err = gcpCloudRunScalerDetails("gcp-cloud-run", gcpScalerFlags{min: 1, minSet: true, max: 10, maxSet: true, initial: 5, initialSet: true, utilization: 0.5, utilizationSet: true}) + ok := valid() + ok.scaleDownStabilization = 120 * time.Second + p, err = gcpCloudRunScalerDetails("gcp-cloud-run", ok) require.NoError(t, err) require.NotNil(t, p) var details map[string]any @@ -92,6 +149,7 @@ func TestGCPCloudRunScalerDetails(t *testing.T) { require.Equal(t, float64(10), details[scalerKeyMaxCount]) require.Equal(t, float64(5), details[scalerKeyInitialCount]) require.Equal(t, float64(0.5), details[scalerKeyUtilizationTarget]) + require.Equal(t, float64(120000), details[scalerKeyNoSyncQuietMs]) } func TestFormatComputeConfigProto_ScalerBounds(t *testing.T) { @@ -101,6 +159,7 @@ func TestFormatComputeConfigProto_ScalerBounds(t *testing.T) { max: 10, maxSet: true, initial: 5, initialSet: true, utilization: 0.75, utilizationSet: true, + scaleDownStabilization: 120 * time.Second, scaleDownStabilizationSet: true, }) require.NoError(t, err) require.NotNil(t, scalerDetails) @@ -114,7 +173,7 @@ func TestFormatComputeConfigProto_ScalerBounds(t *testing.T) { }, } - // JSON/structured path surfaces min, max, initial, and utilization. + // JSON/structured path surfaces min, max, initial, utilization, and scale-down-stabilization. formatted := formatComputeConfigProto(cc) require.NotNil(t, formatted) sg, ok := formatted.ScalingGroups["default"] @@ -129,9 +188,10 @@ func TestFormatComputeConfigProto_ScalerBounds(t *testing.T) { require.Equal(t, int64(10), *sg.Scaler.MaxInstances) require.Equal(t, int64(5), *sg.Scaler.InitialInstances) require.Equal(t, float64(0.75), *sg.Scaler.UtilizationTarget) + require.Equal(t, "2m 0s", sg.Scaler.ScaleDownStabilization) - // Human-readable summary reflects the settings (min, initial, max, utilization). - require.Equal(t, "gcp-cloud-run (min 0, initial 5, max 10, utilization 0.75)", computeConfigSummaryStr(cc)) + // Human-readable summary reflects the settings (min, initial, max, utilization, scale-down-stabilization). + require.Equal(t, "gcp-cloud-run (min 0, initial 5, max 10, utilization 0.75, scale-down-stabilization 2m 0s)", computeConfigSummaryStr(cc)) // Without scaler details, the settings are nil and the summary is just the // provider (guards against printing zeroed-out values). @@ -150,5 +210,6 @@ func TestFormatComputeConfigProto_ScalerBounds(t *testing.T) { require.Nil(t, sg.Scaler.MaxInstances) require.Nil(t, sg.Scaler.InitialInstances) require.Nil(t, sg.Scaler.UtilizationTarget) + require.Empty(t, sg.Scaler.ScaleDownStabilization) require.Equal(t, "gcp-cloud-run", computeConfigSummaryStr(ccNoBounds)) } diff --git a/internal/temporalcli/commands.worker.deployment_test.go b/internal/temporalcli/commands.worker.deployment_test.go index e1a7115b4..db9c62661 100644 --- a/internal/temporalcli/commands.worker.deployment_test.go +++ b/internal/temporalcli/commands.worker.deployment_test.go @@ -1315,6 +1315,108 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { s.Error(res.Err) s.ErrorContains(res.Err, "missing required AWS Lambda provider detail: role") + // --aws-lambda-skip-role-and-external-id bypasses the client-side check, so + // the request reaches the server, which enforces its own + // require_role_and_external_id policy (enabled by default here). + skipRoleAndIDBuildID := uuid.NewString() + + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", skipRoleAndIDBuildID, + "--aws-lambda-function-arn", invokeARN, + "--aws-lambda-skip-role-and-external-id", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, `AWS Lambda compute provider requires "role" to be configured`) + + // --aws-lambda-skip-role-and-external-id and the role/external-id flags are + // mutually exclusive: passing both is rejected client-side. + skipWithRoleBuildID := uuid.NewString() + + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", skipWithRoleBuildID, + "--aws-lambda-function-arn", invokeARN, + "--aws-lambda-assume-role-arn", assumeRoleARN, + "--aws-lambda-skip-role-and-external-id", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "--aws-lambda-skip-role-and-external-id") + + // AWS Agentcore: a single endpoint ARN drives the provider; role and external + // id are required (unless skipped) + agentcoreEndpointARN := "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/my-runtime-abc123/runtime-endpoint/DEFAULT" + + agentcoreMissingExternalIDBuildID := uuid.NewString() + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", agentcoreMissingExternalIDBuildID, + "--aws-agentcore-endpoint-arn", agentcoreEndpointARN, + "--aws-agentcore-assume-role-arn", assumeRoleARN, + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "missing required AWS Agentcore provider detail: role_external_id") + + agentcoreMissingRoleBuildID := uuid.NewString() + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", agentcoreMissingRoleBuildID, + "--aws-agentcore-endpoint-arn", agentcoreEndpointARN, + "--aws-agentcore-assume-role-external-id", assumeRoleExternalID, + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "missing required AWS Agentcore provider detail: role") + + // --aws-agentcore-skip-role-and-external-id and the role/external-id flags are + // mutually exclusive: passing both is rejected client-side. + agentcoreSkipWithRoleBuildID := uuid.NewString() + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", agentcoreSkipWithRoleBuildID, + "--aws-agentcore-endpoint-arn", agentcoreEndpointARN, + "--aws-agentcore-assume-role-arn", assumeRoleARN, + "--aws-agentcore-assume-role-external-id", assumeRoleExternalID, + "--aws-agentcore-skip-role-and-external-id", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "--aws-agentcore-skip-role-and-external-id") + + // AWS Agentcore and GCP Cloud Run providers are mutually exclusive on create. + agentcoreMixedProvidersBuildID := uuid.NewString() + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", agentcoreMixedProvidersBuildID, + "--aws-agentcore-endpoint-arn", agentcoreEndpointARN, + "--gcp-cloud-run-worker-pool", "my-worker-pool", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "cannot combine --aws-lambda-*, --aws-agentcore-*, and --gcp-cloud-run-* flags") + + // AWS Agentcore and Lambda providers are mutually exclusive on create. + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", agentcoreMixedProvidersBuildID, + "--aws-agentcore-endpoint-arn", agentcoreEndpointARN, + "--aws-lambda-assume-role-arn", assumeRoleARN, + "--aws-lambda-assume-role-external-id", assumeRoleExternalID, + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "cannot combine --aws-lambda-*, --aws-agentcore-*, and --gcp-cloud-run-* flags") + // --gcp-cloud-run-worker-pool requires project, region, and // service-account; the first missing detail key is reported. missingGCPProjectBuildID := uuid.NewString() @@ -1356,7 +1458,7 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { "--gcp-cloud-run-worker-pool", "my-worker-pool", ) s.Error(res.Err) - s.ErrorContains(res.Err, "cannot combine --aws-lambda-* and --gcp-cloud-run-* flags") + s.ErrorContains(res.Err, "cannot combine --aws-lambda-*, --aws-agentcore-*, and --gcp-cloud-run-* flags") // Attempting to update the compute config for a non-existent WDV // should fail. @@ -1396,7 +1498,7 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { "--gcp-cloud-run-worker-pool", "my-worker-pool", ) s.Error(res.Err) - s.ErrorContains(res.Err, "cannot combine --aws-lambda-* and --gcp-cloud-run-* flags") + s.ErrorContains(res.Err, "cannot combine --aws-lambda-*, --aws-agentcore-*, and --gcp-cloud-run-* flags") // --remove cannot be combined with GCP Cloud Run flags. res = s.Execute( @@ -1438,7 +1540,7 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { s.Error(res.Err) s.ErrorContains(res.Err, "only valid with --gcp-cloud-run-worker-pool") - // A lone flag is rejected: all four scaling settings must be set together. + // A lone flag is rejected: all five scaling settings must be set together. res = s.Execute( "worker", "deployment", "create-version", "--address", s.Address(), @@ -1471,7 +1573,86 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { s.Error(res.Err) s.ErrorContains(res.Err, "must be set together") - // min cannot exceed max (all four set so the group check passes first). + // The instance counts and utilization-target without scale-down-stabilization-duration are + // also rejected: scale-down-stabilization-duration is part of the same all-or-none group. + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", uuid.NewString(), + "--gcp-cloud-run-project", "my-gcp-project", + "--gcp-cloud-run-region", "us-central1", + "--gcp-cloud-run-worker-pool", "my-worker-pool", + "--gcp-cloud-run-service-account", "customer-sa@my-gcp-project.iam.gserviceaccount.com", + "--gcp-cloud-run-min-instances", "1", + "--gcp-cloud-run-max-instances", "3", + "--gcp-cloud-run-initial-instances", "2", + "--gcp-cloud-run-utilization-target", "0.5", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "must be set together") + + // scale-down-stabilization-duration cannot be negative (all five set so the group check + // passes first). + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", uuid.NewString(), + "--gcp-cloud-run-project", "my-gcp-project", + "--gcp-cloud-run-region", "us-central1", + "--gcp-cloud-run-worker-pool", "my-worker-pool", + "--gcp-cloud-run-service-account", "customer-sa@my-gcp-project.iam.gserviceaccount.com", + "--gcp-cloud-run-min-instances", "0", + "--gcp-cloud-run-max-instances", "10", + "--gcp-cloud-run-initial-instances", "5", + "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-stabilization-duration=-1s", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "--gcp-cloud-run-scale-down-stabilization-duration cannot be negative") + + // A negative sub-millisecond value is also rejected: it must be caught before + // the duration is truncated to whole milliseconds (which would send 0 and + // silently disable the wait instead). + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", uuid.NewString(), + "--gcp-cloud-run-project", "my-gcp-project", + "--gcp-cloud-run-region", "us-central1", + "--gcp-cloud-run-worker-pool", "my-worker-pool", + "--gcp-cloud-run-service-account", "customer-sa@my-gcp-project.iam.gserviceaccount.com", + "--gcp-cloud-run-min-instances", "0", + "--gcp-cloud-run-max-instances", "10", + "--gcp-cloud-run-initial-instances", "5", + "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-stabilization-duration=-1us", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "--gcp-cloud-run-scale-down-stabilization-duration cannot be negative") + + // A positive sub-millisecond value is rejected rather than silently rounded. + res = s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", uuid.NewString(), + "--gcp-cloud-run-project", "my-gcp-project", + "--gcp-cloud-run-region", "us-central1", + "--gcp-cloud-run-worker-pool", "my-worker-pool", + "--gcp-cloud-run-service-account", "customer-sa@my-gcp-project.iam.gserviceaccount.com", + "--gcp-cloud-run-min-instances", "0", + "--gcp-cloud-run-max-instances", "10", + "--gcp-cloud-run-initial-instances", "5", + "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-stabilization-duration", "500us", + ) + s.Error(res.Err) + s.ErrorContains(res.Err, "--gcp-cloud-run-scale-down-stabilization-duration must be a whole number of milliseconds") + + // min cannot exceed max (all five set so the group check passes first). res = s.Execute( "worker", "deployment", "create-version", "--address", s.Address(), @@ -1485,6 +1666,7 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { "--gcp-cloud-run-max-instances", "3", "--gcp-cloud-run-initial-instances", "4", "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-stabilization-duration", "90s", ) s.Error(res.Err) s.ErrorContains(res.Err, "cannot exceed") @@ -1503,6 +1685,7 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { "--gcp-cloud-run-max-instances", "0", "--gcp-cloud-run-initial-instances", "0", "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-stabilization-duration", "90s", ) s.Error(res.Err) s.ErrorContains(res.Err, "--gcp-cloud-run-max-instances must be at least 1") @@ -1521,6 +1704,7 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { "--gcp-cloud-run-max-instances", "10", "--gcp-cloud-run-initial-instances", "15", "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-stabilization-duration", "90s", ) s.Error(res.Err) s.ErrorContains(res.Err, "must be between") @@ -1540,6 +1724,7 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_Errors() { "--gcp-cloud-run-max-instances", "10", "--gcp-cloud-run-initial-instances", "5", "--gcp-cloud-run-utilization-target", "1.5", + "--gcp-cloud-run-scale-down-stabilization-duration", "90s", ) s.Error(res.Err) s.ErrorContains(res.Err, "must be greater than 0 and at most 1") @@ -1608,7 +1793,7 @@ func (s *SharedServerSuite) TestUpdateWorkerDeploymentVersionComputeConfig_Updat serviceAccount := "customer-sa@my-gcp-project.iam.gserviceaccount.com" // Scaler-only update (no provider flags): the mask is just scaler.details, - // no provider is sent, and all four settings are carried. + // no provider is sent, and all five settings are carried. res := s.Execute( "worker", "deployment", "update-version-compute-config", "--address", s.Address(), @@ -1617,6 +1802,7 @@ func (s *SharedServerSuite) TestUpdateWorkerDeploymentVersionComputeConfig_Updat "--gcp-cloud-run-max-instances", "10", "--gcp-cloud-run-initial-instances", "5", "--gcp-cloud-run-utilization-target", "0.5", + "--gcp-cloud-run-scale-down-stabilization-duration", "2m", ) s.NoError(res.Err) req := takeCaptured() @@ -1631,6 +1817,7 @@ func (s *SharedServerSuite) TestUpdateWorkerDeploymentVersionComputeConfig_Updat s.Equal(float64(10), details["max_count"]) s.Equal(float64(5), details["initial_count"]) s.Equal(float64(0.5), details["utilization_target"]) + s.Equal(float64(120000), details["no_sync_quiet_ms"]) // Switching to AWS Lambda clears the (rate-based) scaler.details so they // don't linger under the no-sync scaler. @@ -1668,7 +1855,7 @@ func (s *SharedServerSuite) TestUpdateWorkerDeploymentVersionComputeConfig_Updat s.NotContains(sg.GetUpdateMask().GetPaths(), "scaler.details") s.Equal("gcp-cloud-run", sg.GetScalingGroup().GetProvider().GetType()) - // A scaler-only update still requires all four flags together. + // A scaler-only update still requires all five flags together. res = s.Execute( "worker", "deployment", "update-version-compute-config", "--address", s.Address(), @@ -1806,6 +1993,124 @@ func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_LambdaComputeConfi s.Contains(res.Stdout.String(), "Successfully removed worker deployment version compute config") } +// TODO(jaypipes): Enable this test when we have a way of ensuring AWS resource +// fixtures since the CLI test harness uses a real Temporal Server and a real +// Temporal Server validates any supplied AWS Lambda Function and Assume Role +// ARNs are good... +func (s *SharedServerSuite) TestCreateWorkerDeploymentVersion_AgentCoreComputeConfig() { + s.T().Skip("AWS AgentCore Runtime, Endpoint and Assume Role fixtures needed.") + deploymentName := uuid.NewString() + taskQueue := uuid.NewString() + + lazyCreatedBuildID := uuid.NewString() + lazyCreatedVer := worker.WorkerDeploymentVersion{ + DeploymentName: deploymentName, + BuildID: lazyCreatedBuildID, + } + + // Create worker with explicit versioning. This will end up creating a + // WorkerDeployment with the specified name. We will then manually create a + // worker deployment version using the `temporal worker deployment + // create-version` command. + w1 := worker.New(s.Client, taskQueue, worker.Options{ + DeploymentOptions: worker.DeploymentOptions{ + UseVersioning: true, + Version: lazyCreatedVer, + }, + }) + + // Register a workflow with explicit Pinned versioning behavior to trigger + // creation of the worker deployment. + w1.RegisterWorkflowWithOptions( + func(ctx workflow.Context, input any) (any, error) { + workflow.GetSignalChannel(ctx, "complete-signal").Receive(ctx, nil) + return nil, nil + }, + workflow.RegisterOptions{ + Name: "TestCreateWorkerDeploymentVersion_AgentCoreComputeConfig", + VersioningBehavior: workflow.VersioningBehaviorPinned, + }, + ) + + s.NoError(w1.Start()) + + // Now that we know the worker deployment exists (because the above + // lazily-created worker deployment version ended up creating it), we will + // manually create a new worker deployment version using the `temporal + // worker deployment create-version` CLI command. + // + // Create a WDV with a valid Compute Config specified and verify that the + // compute config provider is displayed in the output of `temporal worker + // deployment describe-version` + computeConfigBuildID := uuid.NewString() + + endpointARN := "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/my-runtime-abc123/runtime-endpoint/myEndpoint" + assumeRoleARN := "arn:aws:iam::123456789012:role/MyServiceRole" + assumeRoleExternalID := "external-id" + + res := s.Execute( + "worker", "deployment", "create-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", computeConfigBuildID, + "--aws-agentcore-endpoint-arn", endpointARN, + "--aws-agentcore-assume-role-arn", assumeRoleARN, + "--aws-agentcore-assume-role-external-id", assumeRoleExternalID, + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Successfully created worker deployment version") + + // Wait for the deployment version to appear + s.EventuallyWithT(func(t *assert.CollectT) { + res := s.Execute( + "worker", "deployment", "describe-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", computeConfigBuildID, + ) + assert.NoError(t, res.Err) + }, 30*time.Second, 100*time.Millisecond) + + // Check that there is a compute config returned for this WDV + res = s.Execute( + "worker", "deployment", "describe-version", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", computeConfigBuildID, + "--output", "json", + ) + s.NoError(res.Err) + jsonOut := jsonDeploymentVersionInfoType{} + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &jsonOut)) + s.NotNil(jsonOut.ComputeConfig, "ComputeConfig should not be nil.") + + // We should be able to update the compute config. + endpointARN2 := "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/my-runtime-abc123/runtime-endpoint/myEndpoint2" + assumeRoleARN2 := "arn:aws:iam::123456789012:role/MyServiceRole2" + res = s.Execute( + "worker", "deployment", "update-version-compute-config", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", computeConfigBuildID, + "--aws-agentcore-endpoint-arn", endpointARN2, + "--aws-agentcore-assume-role-arn", assumeRoleARN2, + "--aws-agentcore-assume-role-external-id", assumeRoleExternalID, + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Successfully updated worker deployment version compute config") + + // As well as remove the compute config. + res = s.Execute( + "worker", "deployment", "update-version-compute-config", + "--address", s.Address(), + "--deployment-name", deploymentName, + "--build-id", computeConfigBuildID, + "--remove", + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Successfully removed worker deployment version compute config") +} + // TODO(jaypipes): Enable this test when we have a way of ensuring GCP resource // fixtures since the CLI test harness uses a real Temporal Server and a real // Temporal Server validates that any supplied GCP Cloud Run worker pool and diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 7ca3f075f..4365e5f50 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -141,6 +141,10 @@ commands: `temporal server start-dev` * View help: pass `--help` to any command: `temporal activity complete --help` + * Return structured output with `-o`: + `temporal workflow list -o json` + * View global and connection options: + `temporal options` has-init: true option-sets: - common @@ -561,10 +565,9 @@ commands: summary: Reset an Activity description: | Reset an activity. Not supported for Standalone Activities. - This restarts the activity as if it were first being - scheduled. That is, it will reset both the number of attempts and the - activity timeout, as well as, optionally, the - [heartbeat details](#reset-heartbeats). + This restarts the activity as if it were first being scheduled: the + attempt count returns to one, its per-attempt timeouts are re-armed, and + its [heartbeat details](#reset-heartbeats) are cleared. If the activity may be executing (i.e. it has not yet timed out), the reset will take effect the next time it fails, heartbeats, or times out. @@ -614,9 +617,6 @@ commands: - name: keep-paused type: bool description: If the activity was paused, it will stay paused. - - name: reset-attempts - type: bool - description: Reset the activity attempts. - name: reset-heartbeats type: bool description: Reset the Activity's heartbeats. @@ -1093,6 +1093,19 @@ commands: --aws-lambda-assume-role-external-id LambdaAssumeRoleExternalID ``` + Or pass compute provider information for an AWS Bedrock Agentcore Runtime + that spawns a Worker in the Worker Deployment: + + ``` + temporal worker deployment create-version \ + --namespace YourNamespaceName \ + --deployment-name YourDeploymentName \ + --build-id YourBuildID \ + --aws-agentcore-endpoint-arn AgentcoreRuntimeEndpointARN \ + --aws-agentcore-assume-role-arn AgentcoreAssumeRoleARN \ + --aws-agentcore-assume-role-external-id AgentcoreAssumeRoleExternalID + ``` + Or pass compute provider information for a GCP Cloud Run worker pool that spawns a Worker in the Worker Deployment: @@ -1108,7 +1121,8 @@ commands: --gcp-cloud-run-min-instances 1 \ --gcp-cloud-run-max-instances 3 \ --gcp-cloud-run-initial-instances 1 \ - --gcp-cloud-run-utilization-target 0.75 + --gcp-cloud-run-utilization-target 0.75 \ + --gcp-cloud-run-scale-down-stabilization-duration 5m ``` If a Worker Deployment Version with the supplied BuildID already exists, @@ -1132,14 +1146,51 @@ commands: AWS IAM role ARN that the Temporal server will assume when invoking the Lambda function that spawns a new Worker in this Worker Deployment Version. Required when --aws-lambda-function-arn is - specified. + specified, and must be omitted when + --aws-lambda-skip-role-and-external-id is passed. - name: aws-lambda-assume-role-external-id type: string description: | Temporal server will enforce that the AWS IAM trust policy associated with the AWS IAM role specified in --aws-lambda-assume-role-arn has an aws:ExternalId condition that matches the supplied value. Required - when --aws-lambda-function-arn is specified. + when --aws-lambda-function-arn is specified, and must be omitted when + --aws-lambda-skip-role-and-external-id is passed. + - name: aws-lambda-skip-role-and-external-id + type: bool + description: | + When --aws-lambda-function-arn is specified, + --aws-lambda-assume-role-arn and --aws-lambda-assume-role-external-id + are required unless this flag is passed, in which case both must be + omitted. + - name: aws-agentcore-endpoint-arn + type: string + description: | + AWS Bedrock Agentcore Runtime endpoint ARN to invoke when there are + no active pollers for task queue targets in the Worker Deployment. + The endpoint ARN encodes the runtime, endpoint name, and region. + - name: aws-agentcore-assume-role-arn + type: string + description: | + AWS IAM role ARN that the Temporal server will assume when invoking + the Agentcore Runtime that spawns a new Worker in this Worker + Deployment Version. Required when --aws-agentcore-endpoint-arn is + specified, and must be omitted when + --aws-agentcore-skip-role-and-external-id is passed. + - name: aws-agentcore-assume-role-external-id + type: string + description: | + Temporal server will enforce that the AWS IAM trust policy associated + with the AWS IAM role specified in --aws-agentcore-assume-role-arn has an + aws:ExternalId condition that matches the supplied value. Required + when --aws-agentcore-endpoint-arn is specified, and must be omitted + when --aws-agentcore-skip-role-and-external-id is passed. + - name: aws-agentcore-skip-role-and-external-id + type: bool + description: | + When --aws-agentcore-endpoint-arn is specified, --aws-agentcore-assume-role-arn + and --aws-agentcore-assume-role-external-id are required unless this + flag is passed, in which case both must be omitted. - name: gcp-cloud-run-project type: string description: | @@ -1166,24 +1217,27 @@ commands: description: | Minimum number of Cloud Run worker pool instances the scaler will maintain. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together. Defaults + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-stabilization-duration must all be set together. Defaults to 0 when unset. Only valid with --gcp-cloud-run-worker-pool. - name: gcp-cloud-run-max-instances type: int description: | Maximum number of Cloud Run worker pool instances the scaler may scale up to. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together. Defaults + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-stabilization-duration must all be set together. Defaults to 30 when unset. Only valid with --gcp-cloud-run-worker-pool. - name: gcp-cloud-run-initial-instances type: int description: | Number of Cloud Run worker pool instances the scaler starts with. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together, and this + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-stabilization-duration must all be set together, and this value must be between the min and max (inclusive). Defaults to 0 when unset. Only valid with --gcp-cloud-run-worker-pool. - name: gcp-cloud-run-utilization-target @@ -1191,10 +1245,23 @@ commands: description: | Target average worker utilization the scaler aims for, as a fraction in the range (0, 1]. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together. Lower + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-stabilization-duration must all be set together. Lower values keep more spare capacity per worker. Defaults to 0.8 when unset. Only valid with --gcp-cloud-run-worker-pool. + - name: gcp-cloud-run-scale-down-stabilization-duration + type: duration + description: | + Duration the scaler waits after it last saw unmet task demand + before it may scale the Cloud Run worker pool down. Raise this to + keep the pool from scaling down before long-running or bursty + activities finish. Optional, but --gcp-cloud-run-min-instances, + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-stabilization-duration must all be set together. A + value of 0s disables the wait. Defaults to 90s when unset. Only + valid with --gcp-cloud-run-worker-pool. - name: temporal worker deployment describe-version summary: Show properties of a Worker Deployment Version @@ -1423,6 +1490,17 @@ commands: --aws-lambda-assume-role-arn UpdatedRoleARN ``` + To update the AWS Bedrock Agentcore Runtime endpoint associated with an + existing Worker Deployment Version: + + ``` + temporal worker deployment update-version-compute-config \ + --deployment-name YourDeploymentName --build-id YourBuildID \ + --aws-agentcore-endpoint-arn UpdatedAgentcoreRuntimeEndpointARN \ + --aws-agentcore-assume-role-arn UpdatedRoleARN \ + --aws-agentcore-assume-role-external-id UpdatedExternalID + ``` + To update the GCP Cloud Run worker pool associated with an existing Worker Deployment Version: @@ -1436,12 +1514,13 @@ commands: --gcp-cloud-run-min-instances 1 \ --gcp-cloud-run-max-instances 3 \ --gcp-cloud-run-initial-instances 1 \ - --gcp-cloud-run-utilization-target 0.75 + --gcp-cloud-run-utilization-target 0.75 \ + --gcp-cloud-run-scale-down-stabilization-duration 5m ``` To update only the scaling settings on an existing GCP Cloud Run Worker - Deployment Version, supply the four scaler flags without the provider - fields (all four must be set together): + Deployment Version, supply the five scaler flags without the provider + fields (all five must be set together): ``` temporal worker deployment update-version-compute-config \ @@ -1449,7 +1528,8 @@ commands: --gcp-cloud-run-min-instances 1 \ --gcp-cloud-run-max-instances 3 \ --gcp-cloud-run-initial-instances 1 \ - --gcp-cloud-run-utilization-target 0.75 + --gcp-cloud-run-utilization-target 0.75 \ + --gcp-cloud-run-scale-down-stabilization-duration 5m ``` Provider fields are only required when changing the compute provider. @@ -1483,14 +1563,51 @@ commands: AWS IAM role ARN that the Temporal server will assume when invoking the Lambda function that spawns a new Worker in this Worker Deployment Version. Required when --aws-lambda-function-arn is - specified. + specified, and must be omitted when + --aws-lambda-skip-role-and-external-id is passed. - name: aws-lambda-assume-role-external-id type: string description: | Temporal server will enforce that the AWS IAM trust policy associated with the AWS IAM role specified in --aws-lambda-assume-role-arn has an aws:ExternalId condition that matches the supplied value. Required - when --aws-lambda-function-arn is specified. + when --aws-lambda-function-arn is specified, and must be omitted when + --aws-lambda-skip-role-and-external-id is passed. + - name: aws-lambda-skip-role-and-external-id + type: bool + description: | + When --aws-lambda-function-arn is specified, + --aws-lambda-assume-role-arn and --aws-lambda-assume-role-external-id + are required unless this flag is passed, in which case both must be + omitted. + - name: aws-agentcore-endpoint-arn + type: string + description: | + AWS Bedrock Agentcore Runtime endpoint ARN to invoke when there are + no active pollers for task queue targets in the Worker Deployment. + The endpoint ARN encodes the runtime, endpoint name, and region. + - name: aws-agentcore-assume-role-arn + type: string + description: | + AWS IAM role ARN that the Temporal server will assume when invoking + the Agentcore Runtime that spawns a new Worker in this Worker + Deployment Version. Required when --aws-agentcore-endpoint-arn is + specified, and must be omitted when + --aws-agentcore-skip-role-and-external-id is passed. + - name: aws-agentcore-assume-role-external-id + type: string + description: | + Temporal server will enforce that the AWS IAM trust policy associated + with the AWS IAM role specified in --aws-agentcore-assume-role-arn has an + aws:ExternalId condition that matches the supplied value. Required + when --aws-agentcore-endpoint-arn is specified, and must be omitted + when --aws-agentcore-skip-role-and-external-id is passed. + - name: aws-agentcore-skip-role-and-external-id + type: bool + description: | + When --aws-agentcore-endpoint-arn is specified, --aws-agentcore-assume-role-arn + and --aws-agentcore-assume-role-external-id are required unless this + flag is passed, in which case both must be omitted. - name: gcp-cloud-run-project type: string description: | @@ -1517,39 +1634,56 @@ commands: description: | Minimum number of Cloud Run worker pool instances the scaler will maintain. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together. If + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-stabilization-duration must all be set together. If omitted, the version's existing scaling settings are left unchanged. - Only valid with --gcp-cloud-run-worker-pool. + Only applies to a GCP Cloud Run worker pool. - name: gcp-cloud-run-max-instances type: int description: | Maximum number of Cloud Run worker pool instances the scaler may scale up to. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together. If + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-stabilization-duration must all be set together. If omitted, the version's existing scaling settings are left unchanged. - Only valid with --gcp-cloud-run-worker-pool. + Only applies to a GCP Cloud Run worker pool. - name: gcp-cloud-run-initial-instances type: int description: | Number of Cloud Run worker pool instances the scaler starts with. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together, and this + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-stabilization-duration must all be set together, and this value must be between the min and max (inclusive). If omitted, the - version's existing scaling settings are left unchanged. Only valid - with --gcp-cloud-run-worker-pool. + version's existing scaling settings are left unchanged. Only applies + to a GCP Cloud Run worker pool. - name: gcp-cloud-run-utilization-target type: float description: | Target average worker utilization the scaler aims for, as a fraction in the range (0, 1]. Optional, but --gcp-cloud-run-min-instances, - --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, and - --gcp-cloud-run-utilization-target must all be set together. Lower + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-stabilization-duration must all be set together. Lower values keep more spare capacity per worker. If omitted, the version's - existing scaling settings are left unchanged. Only valid with - --gcp-cloud-run-worker-pool. + existing scaling settings are left unchanged. Only applies to a + GCP Cloud Run worker pool. + - name: gcp-cloud-run-scale-down-stabilization-duration + type: duration + description: | + Duration the scaler waits after it last saw unmet task demand + before it may scale the Cloud Run worker pool down. Raise this to + keep the pool from scaling down before long-running or bursty + activities finish. Optional, but --gcp-cloud-run-min-instances, + --gcp-cloud-run-max-instances, --gcp-cloud-run-initial-instances, + --gcp-cloud-run-utilization-target, and + --gcp-cloud-run-scale-down-stabilization-duration must all be set together. A + value of 0s disables the wait. If omitted, the version's existing + scaling settings are left unchanged. Only applies to a + GCP Cloud Run worker pool. - name: remove type: bool description: | @@ -4748,6 +4882,7 @@ option-sets: external-package: github.com/temporalio/cli/cliext - name: client external-package: github.com/temporalio/cli/cliext + hide-from-help: true - name: overlap-policy options: diff --git a/internal/temporalcli/commands_test.go b/internal/temporalcli/commands_test.go index b77732bcb..9feb1c9c6 100644 --- a/internal/temporalcli/commands_test.go +++ b/internal/temporalcli/commands_test.go @@ -151,11 +151,23 @@ type CommandResult struct { } func (h *CommandHarness) Execute(args ...string) *CommandResult { + ctx, cancel := context.WithCancel(h.Context) + h.t.Cleanup(cancel) + defer cancel() + return h.execute(ctx, &h.Stdin, args...) +} + +func (h *CommandHarness) ExecuteWithContext(ctx context.Context, args ...string) *CommandResult { + var stdin bytes.Buffer + return h.execute(ctx, &stdin, args...) +} + +func (h *CommandHarness) execute(ctx context.Context, stdin io.Reader, args ...string) *CommandResult { // Copy options, update as needed res := &CommandResult{} options := h.Options // Set stdio - options.Stdin = &h.Stdin + options.Stdin = stdin options.Stdout = &res.Stdout options.Stderr = &res.Stderr // Set args @@ -175,10 +187,6 @@ func (h *CommandHarness) Execute(args ...string) *CommandResult { res.Err = err } - // Run - ctx, cancel := context.WithCancel(h.Context) - h.t.Cleanup(cancel) - defer cancel() h.t.Logf("Calling: %v", strings.Join(args, " ")) temporalcli.Execute(ctx, options) if res.Stdout.Len() > 0 {